8167554: jshell tool: re-execute a range and/or sequence of snippets

8180508: jshell tool: support id ranges in all commands with id arguments

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2017-05-18 14:16:25 -07:00
parent 5a0726a47a
commit 08a3a23043
9 changed files with 546 additions and 158 deletions

View File

@ -120,6 +120,17 @@ class ArgTokenizer {
}
}
/**
* Is the specified option allowed.
*
* @param opt the option to check
* @return true if the option is allowed
*/
boolean isAllowedOption(String opt) {
Boolean has = options.get(opt);
return has != null;
}
/**
* Has the specified option been encountered.
*

View File

@ -90,7 +90,6 @@ import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.util.MissingResourceException;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.Spliterators;
@ -126,6 +125,7 @@ import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_M
public class JShellTool implements MessageHandler {
private static final Pattern LINEBREAK = Pattern.compile("\\R");
private static final Pattern ID = Pattern.compile("[se]?\\d+([-\\s].*)?");
static final String RECORD_SEPARATOR = "\u241E";
private static final String RB_NAME_PREFIX = "jdk.internal.jshell.tool.resources";
private static final String VERSION_RB_NAME = RB_NAME_PREFIX + ".version";
@ -1189,36 +1189,54 @@ public class JShellTool implements MessageHandler {
}
}
private void processCommand(String cmd) {
if (cmd.startsWith("/-")) {
/**
* Process a command (as opposed to a snippet) -- things that start with
* slash.
*
* @param input
*/
private void processCommand(String input) {
if (input.startsWith("/-")) {
try {
//handle "/-[number]"
cmdUseHistoryEntry(Integer.parseInt(cmd.substring(1)));
cmdUseHistoryEntry(Integer.parseInt(input.substring(1)));
return ;
} catch (NumberFormatException ex) {
//ignore
}
}
String arg = "";
int idx = cmd.indexOf(' ');
String cmd;
String arg;
int idx = input.indexOf(' ');
if (idx > 0) {
arg = cmd.substring(idx + 1).trim();
cmd = cmd.substring(0, idx);
arg = input.substring(idx + 1).trim();
cmd = input.substring(0, idx);
} else {
cmd = input;
arg = "";
}
// find the command as a "real command", not a pseudo-command or doc subject
Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand);
switch (candidates.length) {
case 0:
if (!rerunHistoryEntryById(cmd.substring(1))) {
errormsg("jshell.err.no.such.command.or.snippet.id", cmd);
// not found, it is either a snippet command or an error
if (ID.matcher(cmd.substring(1)).matches()) {
// it is in the form of a snipppet id, see if it is a valid history reference
rerunHistoryEntriesById(input);
} else {
errormsg("jshell.err.invalid.command", cmd);
fluffmsg("jshell.msg.help.for.help");
} break;
}
break;
case 1:
Command command = candidates[0];
// If comand was successful and is of a replayable kind, add it the replayable history
if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) {
addToReplayHistory((command.command + " " + arg).trim());
} break;
}
break;
default:
// command if too short (ambigous), show the possibly matches
errormsg("jshell.err.command.ambiguous", cmd,
Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
fluffmsg("jshell.msg.help.for.help");
@ -1701,6 +1719,9 @@ public class JShellTool implements MessageHandler {
registerCommand(new Command("context",
"help.context",
CommandKind.HELP_SUBJECT));
registerCommand(new Command("rerun",
"help.rerun",
CommandKind.HELP_SUBJECT));
commandCompletions = new ContinuousCompletionProvider(
commands.values().stream()
@ -2247,6 +2268,20 @@ public class JShellTool implements MessageHandler {
Predicate<Snippet> defFilter, String rawargs, String cmd) {
ArgTokenizer at = new ArgTokenizer(cmd, rawargs.trim());
at.allowedOptions("-all", "-start");
return argsOptionsToSnippets(snippetSupplier, defFilter, at);
}
/**
* Convert user arguments to a Stream of snippets referenced by those
* arguments (or lack of arguments).
*
* @param snippets the base list of possible snippets
* @param defFilter the filter to apply to the arguments if no argument
* @param at the ArgTokenizer, with allowed options set
* @return
*/
private <T extends Snippet> Stream<T> argsOptionsToSnippets(Supplier<Stream<T>> snippetSupplier,
Predicate<Snippet> defFilter, ArgTokenizer at) {
List<String> args = new ArrayList<>();
String s;
while ((s = at.next()) != null) {
@ -2263,11 +2298,11 @@ public class JShellTool implements MessageHandler {
errormsg("jshell.err.conflicting.options", at.whole());
return null;
}
if (at.hasOption("-all")) {
if (at.isAllowedOption("-all") && at.hasOption("-all")) {
// all snippets including start-up, failed, and overwritten
return snippetSupplier.get();
}
if (at.hasOption("-start")) {
if (at.isAllowedOption("-start") && at.hasOption("-start")) {
// start-up snippets
return snippetSupplier.get()
.filter(this::inStartUp);
@ -2277,54 +2312,227 @@ public class JShellTool implements MessageHandler {
return snippetSupplier.get()
.filter(defFilter);
}
return argsToSnippets(snippetSupplier, args);
return new ArgToSnippets<>(snippetSupplier).argsToSnippets(args);
}
/**
* Convert user arguments to a Stream of snippets referenced by those
* arguments.
* Support for converting arguments that are definition names, snippet ids,
* or snippet id ranges into a stream of snippets,
*
* @param snippetSupplier the base list of possible snippets
* @param args the user's argument to the command, maybe be the empty list
* @return a Stream of referenced snippets or null if no matches to specific
* arg
* @param <T> the snipper subtype
*/
private <T extends Snippet> Stream<T> argsToSnippets(Supplier<Stream<T>> snippetSupplier,
List<String> args) {
Stream<T> result = null;
for (String arg : args) {
private class ArgToSnippets<T extends Snippet> {
// the supplier of snippet streams
final Supplier<Stream<T>> snippetSupplier;
// these two are parallel, and lazily filled if a range is encountered
List<T> allSnippets;
String[] allIds = null;
/**
*
* @param snippetSupplier the base list of possible snippets
*/
ArgToSnippets(Supplier<Stream<T>> snippetSupplier) {
this.snippetSupplier = snippetSupplier;
}
/**
* Convert user arguments to a Stream of snippets referenced by those
* arguments.
*
* @param args the user's argument to the command, maybe be the empty
* list
* @return a Stream of referenced snippets or null if no matches to
* specific arg
*/
Stream<T> argsToSnippets(List<String> args) {
Stream<T> result = null;
for (String arg : args) {
// Find the best match
Stream<T> st = argToSnippets(arg);
if (st == null) {
return null;
} else {
result = (result == null)
? st
: Stream.concat(result, st);
}
}
return result;
}
/**
* Convert a user argument to a Stream of snippets referenced by the
* argument.
*
* @param snippetSupplier the base list of possible snippets
* @param arg the user's argument to the command
* @return a Stream of referenced snippets or null if no matches to
* specific arg
*/
Stream<T> argToSnippets(String arg) {
if (arg.contains("-")) {
return range(arg);
}
// Find the best match
Stream<T> st = layeredSnippetSearch(snippetSupplier, arg);
if (st == null) {
Stream<Snippet> est = layeredSnippetSearch(state::snippets, arg);
if (est == null) {
errormsg("jshell.err.no.such.snippets", arg);
} else {
errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command",
arg, est.findFirst().get().source());
}
badSnippetErrormsg(arg);
return null;
}
if (result == null) {
result = st;
} else {
result = Stream.concat(result, st);
return st;
}
}
return result;
}
private <T extends Snippet> Stream<T> layeredSnippetSearch(Supplier<Stream<T>> snippetSupplier, String arg) {
return nonEmptyStream(
// the stream supplier
snippetSupplier,
// look for active user declarations matching the name
sn -> isActive(sn) && matchingDeclaration(sn, arg),
// else, look for any declarations matching the name
sn -> matchingDeclaration(sn, arg),
// else, look for an id of this name
sn -> sn.id().equals(arg)
);
/**
* Look for inappropriate snippets to give best error message
*
* @param arg the bad snippet arg
* @param errKey the not found error key
*/
void badSnippetErrormsg(String arg) {
Stream<Snippet> est = layeredSnippetSearch(state::snippets, arg);
if (est == null) {
if (ID.matcher(arg).matches()) {
errormsg("jshell.err.no.snippet.with.id", arg);
} else {
errormsg("jshell.err.no.such.snippets", arg);
}
} else {
errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command",
arg, est.findFirst().get().source());
}
}
/**
* Search through the snippets for the best match to the id/name.
*
* @param <R> the snippet type
* @param aSnippetSupplier the supplier of snippet streams
* @param arg the arg to match
* @return a Stream of referenced snippets or null if no matches to
* specific arg
*/
<R extends Snippet> Stream<R> layeredSnippetSearch(Supplier<Stream<R>> aSnippetSupplier, String arg) {
return nonEmptyStream(
// the stream supplier
aSnippetSupplier,
// look for active user declarations matching the name
sn -> isActive(sn) && matchingDeclaration(sn, arg),
// else, look for any declarations matching the name
sn -> matchingDeclaration(sn, arg),
// else, look for an id of this name
sn -> sn.id().equals(arg)
);
}
/**
* Given an id1-id2 range specifier, return a stream of snippets within
* our context
*
* @param arg the range arg
* @return a Stream of referenced snippets or null if no matches to
* specific arg
*/
Stream<T> range(String arg) {
int dash = arg.indexOf('-');
String iid = arg.substring(0, dash);
String tid = arg.substring(dash + 1);
int iidx = snippetIndex(iid);
if (iidx < 0) {
return null;
}
int tidx = snippetIndex(tid);
if (tidx < 0) {
return null;
}
if (tidx < iidx) {
errormsg("jshell.err.end.snippet.range.less.than.start", iid, tid);
return null;
}
return allSnippets.subList(iidx, tidx+1).stream();
}
/**
* Lazily initialize the id mapping -- needed only for id ranges.
*/
void initIdMapping() {
if (allIds == null) {
allSnippets = snippetSupplier.get()
.sorted((a, b) -> order(a) - order(b))
.collect(toList());
allIds = allSnippets.stream()
.map(sn -> sn.id())
.toArray(n -> new String[n]);
}
}
/**
* Return all the snippet ids -- within the context, and in order.
*
* @return the snippet ids
*/
String[] allIds() {
initIdMapping();
return allIds;
}
/**
* Establish an order on snippet ids. All startup snippets are first,
* all error snippets are last -- within that is by snippet number.
*
* @param id the id string
* @return an ordering int
*/
int order(String id) {
try {
switch (id.charAt(0)) {
case 's':
return Integer.parseInt(id.substring(1));
case 'e':
return 0x40000000 + Integer.parseInt(id.substring(1));
default:
return 0x20000000 + Integer.parseInt(id);
}
} catch (Exception ex) {
return 0x60000000;
}
}
/**
* Establish an order on snippets, based on its snippet id. All startup
* snippets are first, all error snippets are last -- within that is by
* snippet number.
*
* @param sn the id string
* @return an ordering int
*/
int order(Snippet sn) {
return order(sn.id());
}
/**
* Find the index into the parallel allSnippets and allIds structures.
*
* @param s the snippet id name
* @return the index, or, if not found, report the error and return a
* negative number
*/
int snippetIndex(String s) {
int idx = Arrays.binarySearch(allIds(), 0, allIds().length, s,
(a, b) -> order(a) - order(b));
if (idx < 0) {
// the id is not in the snippet domain, find the right error to report
if (!ID.matcher(s).matches()) {
errormsg("jshell.err.range.requires.id", s);
} else {
badSnippetErrormsg(s);
}
}
return idx;
}
}
private boolean cmdDrop(String rawargs) {
@ -2342,24 +2550,13 @@ public class JShellTool implements MessageHandler {
errormsg("jshell.err.drop.arg");
return false;
}
Stream<Snippet> stream = argsToSnippets(this::dropableSnippets, args);
Stream<Snippet> stream = new ArgToSnippets<>(this::dropableSnippets).argsToSnippets(args);
if (stream == null) {
// Snippet not found. Error already printed
fluffmsg("jshell.msg.see.classes.etc");
return false;
}
List<Snippet> snippets = stream.collect(toList());
if (snippets.size() > args.size()) {
// One of the args references more thean one snippet
errormsg("jshell.err.drop.ambiguous");
fluffmsg("jshell.msg.use.one.of", snippets.stream()
.map(sn -> String.format("\n/drop %-5s : %s", sn.id(), sn.source().replace("\n", "\n ")))
.collect(Collectors.joining(", "))
);
return false;
}
snippets.stream()
.forEach(sn -> state.drop(sn).forEach(this::handleEvent));
stream.forEach(sn -> state.drop(sn).forEach(this::handleEvent));
return true;
}
@ -2690,37 +2887,38 @@ public class JShellTool implements MessageHandler {
}
private boolean cmdSave(String rawargs) {
ArgTokenizer at = new ArgTokenizer("/save", rawargs.trim());
at.allowedOptions("-all", "-start", "-history");
String filename = at.next();
if (filename == null) {
// The filename to save to is the last argument, extract it
String[] args = rawargs.split("\\s");
String filename = args[args.length - 1];
if (filename.isEmpty()) {
errormsg("jshell.err.file.filename", "/save");
return false;
}
if (!checkOptionsAndRemainingInput(at)) {
return false;
}
if (at.optionCount() > 1) {
errormsg("jshell.err.conflicting.options", at.whole());
// All the non-filename arguments are the specifier of what to save
String srcSpec = Arrays.stream(args, 0, args.length - 1)
.collect(Collectors.joining("\n"));
// From the what to save specifier, compute the snippets (as a stream)
ArgTokenizer at = new ArgTokenizer("/save", srcSpec);
at.allowedOptions("-all", "-start", "-history");
Stream<Snippet> snippetStream = argsOptionsToSnippets(state::snippets, this::mainActive, at);
if (snippetStream == null) {
// error occurred, already reported
return false;
}
try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename),
Charset.defaultCharset(),
CREATE, TRUNCATE_EXISTING, WRITE)) {
if (at.hasOption("-history")) {
// they want history (commands and snippets), ignore the snippet stream
for (String s : input.currentSessionHistory()) {
writer.write(s);
writer.write("\n");
}
} else if (at.hasOption("-start")) {
writer.append(startup.toString());
} else {
String sources = (at.hasOption("-all")
? state.snippets()
: state.snippets().filter(this::mainActive))
// write the snippet stream to the file
writer.write(snippetStream
.map(Snippet::source)
.collect(Collectors.joining("\n"));
writer.write(sources);
.collect(Collectors.joining("\n")));
}
} catch (FileNotFoundException e) {
errormsg("jshell.err.file.not.found", "/save", filename, e.getMessage());
@ -2837,14 +3035,21 @@ public class JShellTool implements MessageHandler {
return true;
}
private boolean rerunHistoryEntryById(String id) {
Optional<Snippet> snippet = state.snippets()
.filter(s -> s.id().equals(id))
.findFirst();
return snippet.map(s -> {
rerunSnippet(s);
return true;
}).orElse(false);
/**
* Handle snippet reevaluation commands: {@code /<id>}. These commands are a
* sequence of ids and id ranges (names are permitted, though not in the
* first position. Support for names is purposely not documented).
*
* @param rawargs the whole command including arguments
*/
private void rerunHistoryEntriesById(String rawargs) {
ArgTokenizer at = new ArgTokenizer("/<id>", rawargs.trim().substring(1));
at.allowedOptions();
Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, sn -> true, at);
if (stream != null) {
// successfully parsed, rerun snippets
stream.forEach(sn -> rerunSnippet(sn));
}
}
private void rerunSnippet(Snippet snippet) {

View File

@ -50,7 +50,7 @@ jshell.err.file.filename = ''{0}'' requires a filename argument.
jshell.err.startup.unexpected.exception = Unexpected exception reading start-up: {0}
jshell.err.unexpected.exception = Unexpected exception: {0}
jshell.err.no.such.command.or.snippet.id = No such command or snippet id: {0}
jshell.err.invalid.command = Invalid command: {0}
jshell.err.command.ambiguous = Command: ''{0}'' is ambiguous: {1}
jshell.msg.set.restore = Setting new options and restoring state.
jshell.msg.set.editor.set = Editor set to: {0}
@ -105,10 +105,13 @@ For example ''/help /list'' or ''/help intro''.\n\
Subjects:\n\
\n
jshell.err.no.snippet.with.id = No snippet with id: {0}
jshell.err.end.snippet.range.less.than.start = End of snippet range less than start: {0} - {1}
jshell.err.range.requires.id = Snippet ranges require snippet ids: {0}
jshell.err.drop.arg =\
In the /drop argument, please specify an import, variable, method, or class to drop.\n\
Specify by id or name. Use /list to see ids. Use /reset to reset all state.
jshell.err.drop.ambiguous = The argument references more than one import, variable, method, or class.
jshell.err.failed = Failed.
jshell.msg.native.method = Native Method
jshell.msg.unknown.source = Unknown Source
@ -225,7 +228,11 @@ Show the source of snippets, prefaced with the snippet id.\n\
/list <name>\n\t\
List snippets with the specified name (preference for active snippets)\n\n\
/list <id>\n\t\
List the snippet with the specified snippet id
List the snippet with the specified snippet id\n\n\
/list <id> <id>...\n\t\
List the snippets with the specified snippet ids\n\n\
/list <id>-<id>\n\t\
List the snippets within the range of snippet ids
help.edit.summary = edit a source entry referenced by name or id
help.edit.args = <name or id>
@ -238,6 +245,10 @@ If no editor has been set, a simple editor will be launched.\n\
Edit the snippet or snippets with the specified name (preference for active snippets)\n\n\
/edit <id>\n\t\
Edit the snippet with the specified snippet id\n\n\
/edit <id> <id>...\n\t\
Edit the snippets with the specified snippet ids\n\n\
/edit <id>-<id>\n\t\
Edit the snippets within the range of snippet ids\n\n\
/edit\n\t\
Edit the currently active snippets of code that you typed or read with /open
@ -249,7 +260,11 @@ Drop a snippet -- making it inactive.\n\
/drop <name>\n\t\
Drop the snippet with the specified name\n\n\
/drop <id>\n\t\
Drop the snippet with the specified snippet id
Drop the snippet with the specified snippet id\n\n\
/drop <id> <id>...\n\t\
Drop the snippets with the specified snippet ids\n\n\
/drop <id>-<id>\n\t\
Drop the snippets within the range of snippet ids
help.save.summary = Save snippet source to a file.
help.save.args = [-all|-history|-start] <file>
@ -264,7 +279,13 @@ Save the specified snippets and/or commands to the specified file.\n\
/save -history <file>\n\t\
Save the sequential history of all commands and snippets entered since jshell was launched.\n\n\
/save -start <file>\n\t\
Save the current start-up definitions to the file.
Save the current start-up definitions to the file.\n\n\
/save <id> <file>\n\t\
Save the snippet with the specified snippet id\n\n\
/save <id> <id>... <file>\n\t\
Save the snippets with the specified snippet ids\n\n\
/save <id>-<id> <file>\n\t\
Save the snippets within the range of snippet ids
help.open.summary = open a file as source input
help.open.args = <file>
@ -285,6 +306,10 @@ List the type, name, and value of jshell variables.\n\
List jshell variables with the specified name (preference for active variables)\n\n\
/vars <id>\n\t\
List the jshell variable with the specified snippet id\n\n\
/vars <id> <id>... <file>\n\t\
List the jshell variables with the specified snippet ids\n\n\
/vars <id>-<id> <file>\n\t\
List the jshell variables within the range of snippet ids\n\n\
/vars -start\n\t\
List the automatically added start-up jshell variables\n\n\
/vars -all\n\t\
@ -301,6 +326,10 @@ List the name, parameter types, and return type of jshell methods.\n\
List jshell methods with the specified name (preference for active methods)\n\n\
/methods <id>\n\t\
List the jshell method with the specified snippet id\n\n\
/methods <id> <id>... <file>\n\t\
List jshell methods with the specified snippet ids\n\n\
/methods <id>-<id> <file>\n\t\
List jshell methods within the range of snippet ids\n\n\
/methods -start\n\t\
List the automatically added start-up jshell methods\n\n\
/methods -all\n\t\
@ -317,6 +346,10 @@ List jshell classes, interfaces, and enums.\n\
List jshell types with the specified name (preference for active types)\n\n\
/types <id>\n\t\
List the jshell type with the specified snippet id\n\n\
/types <id> <id>... <file>\n\t\
List jshell types with the specified snippet ids\n\n\
/types <id>-<id> <file>\n\t\
List jshell types within the range of snippet ids\n\n\
/types -start\n\t\
List the automatically added start-up jshell types\n\n\
/types -all\n\t\
@ -461,17 +494,24 @@ Display information about jshell (abbreviation for /help).\n\
/? <subject>\n\t\
Display information about the specified help subject. Example: /? intro
help.bang.summary = re-run last snippet
help.bang.summary = rerun last snippet -- see /help rerun
help.bang.args =
help.bang =\
Reevaluate the most recently entered snippet.
help.id.summary = re-run snippet by id
help.id.summary = rerun snippets by id or id range -- see /help rerun
help.id.args =
help.id =\
Reevaluate the snippet specified by the id.
/<id> <id> <id>\n\
\n\
/<id>-<id>\n\
\n\
Reevaluate the snippets specified by the id or id range.\n\
An id range is represented as a two ids separated by a hyphen, e.g.: 3-17\n\
Start-up and error snippets maybe used, e.g.: s3-s9 or e1-e4\n\
Any number of ids or id ranges may be used, e.g.: /3-7 s4 14-16 e2
help.previous.summary = re-run n-th previous snippet
help.previous.summary = rerun n-th previous snippet -- see /help rerun
help.previous.args =
help.previous =\
Reevaluate the n-th most recently entered snippet.
@ -509,7 +549,7 @@ Shift-<tab> i\n\t\t\
then release and press "i", and jshell will propose possible imports\n\t\t\
which will resolve the identifier based on the content of the specified classpath.
help.context.summary = the evaluation context options for /env /reload and /reset
help.context.summary = a description of the evaluation context options for /env /reload and /reset
help.context =\
These options configure the evaluation context, they can be specified when\n\
jshell is started: on the command-line, or restarted with the commands /env,\n\
@ -540,6 +580,38 @@ They are:\n\t\
On the command-line these options must have two dashes, e.g.: --module-path\n\
On jshell commands they can have one or two dashes, e.g.: -module-path\n\
help.rerun.summary = a description of ways to re-evaluate previously entered snippets
help.rerun =\
There are four ways to re-evaluate previously entered snippets.\n\
The last snippet can be re-evaluated using: /!\n\
The n-th previous snippet can be re-evaluated by slash-minus and the digits of n, e.g.: /-4\n\
For example:\n\
\n\
\tjshell> 2 + 2\n\
\t$1 ==> 4\n\
\n\
\tjshell> /!\n\
\t2 + 2\n\
\t$2 ==> 4\n\
\n\
\tjshell> int z\n\
\tz ==> 0\n\
\n\
\tjshell> /-1\n\
\tint z;\n\
\tz ==> 0\n\
\n\
\tjshell> /-4\n\
\t2 + 2\n\
\t$5 ==> 4\n\
\n\
The snippets to re-evaluate may be specified by snippet id or id range.\n\
An id range is represented as a two ids separated by a hyphen, e.g.: 3-17\n\
Start-up and error snippets maybe used, e.g.: s3-s9 or e1-e4\n\
Any number of ids or id ranges may be used, e.g.: /3-7 s4 14-16 e2\n\
\n\
Finally, you can search backwards through history by entering ctrl-R followed by the string to search for.
help.set._retain = \
The '-retain' option saves a setting so that it is used in future sessions.\n\
The -retain option can be used on the following forms of /set:\n\n\t\

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8144095 8164825 8169818 8153402 8165405 8177079 8178013
* @bug 8144095 8164825 8169818 8153402 8165405 8177079 8178013 8167554
* @summary Test Command Completion
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -162,13 +162,13 @@ public class CommandCompletionTest extends ReplToolTesting {
"/edit ", "/env ", "/exit ",
"/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "),
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "rerun ", "shortcuts "),
a -> assertCompletion(a, "/? |", false,
"/! ", "/-<n> ", "/<id> ", "/? ", "/drop ",
"/edit ", "/env ", "/exit ",
"/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "),
"/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "rerun ", "shortcuts "),
a -> assertCompletion(a, "/help /s|", false,
"/save ", "/set "),
a -> assertCompletion(a, "/help /set |", false,

View File

@ -73,7 +73,7 @@ public abstract class EditorTestBase extends ReplToolTesting {
for (String edit : new String[] {"/ed", "/edit"}) {
test(new String[]{"--no-startup"},
a -> assertCommandOutputStartsWith(a, edit + " 1",
"| No such snippet: 1"),
"| No snippet with id: 1"),
a -> assertCommandOutputStartsWith(a, edit + " unknown",
"| No such snippet: unknown")
);

View File

@ -66,17 +66,17 @@ public class MergedTabShiftTabCommandTest extends UITesting {
Pattern.quote(getResource("jshell.console.see.next.command.doc")) + "\n" +
"\r\u0005/");
inputSink.write("lis\011");
waitOutput(out, "list $");
inputSink.write("ed\011");
waitOutput(out, "edit $");
inputSink.write("\011");
waitOutput(out, ".*-all.*" +
"\n\n" + Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n\r\u0005/");
inputSink.write("\011");
waitOutput(out, Pattern.quote(getResource("help.list.summary")) + "\n\n" +
Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/list ");
waitOutput(out, Pattern.quote(getResource("help.edit.summary")) + "\n\n" +
Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/edit ");
inputSink.write("\011");
waitOutput(out, Pattern.quote(getResource("help.list").replaceAll("\t", " ")));
waitOutput(out, Pattern.quote(getResource("help.edit").replaceAll("\t", " ")));
inputSink.write("\u0003/env \011");
waitOutput(out, "\u0005/env -\n" +

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/*
* @test
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 8165405 8174796 8174797 8175304
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 8165405 8174796 8174797 8175304 8167554 8180508
* @summary Tests for Basic tests for REPL tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -190,8 +190,8 @@ public class ToolBasicTest extends ReplToolTesting {
public void testRerun() {
test(false, new String[] {"--no-startup"},
(a) -> assertCommand(a, "/0", "| No such command or snippet id: /0\n| Type /help for help."),
(a) -> assertCommand(a, "/5", "| No such command or snippet id: /5\n| Type /help for help.")
(a) -> assertCommand(a, "/0", "| No snippet with id: 0"),
(a) -> assertCommand(a, "/5", "| No snippet with id: 5")
);
String[] codes = new String[] {
"int a = 0;", // var
@ -252,9 +252,9 @@ public class ToolBasicTest extends ReplToolTesting {
);
test(false, new String[] {"--no-startup"},
(a) -> assertCommand(a, "/s1", "| No such command or snippet id: /s1\n| Type /help for help."),
(a) -> assertCommand(a, "/1", "| No such command or snippet id: /1\n| Type /help for help."),
(a) -> assertCommand(a, "/e1", "| No such command or snippet id: /e1\n| Type /help for help.")
(a) -> assertCommand(a, "/s1", "| No snippet with id: s1"),
(a) -> assertCommand(a, "/1", "| No snippet with id: 1"),
(a) -> assertCommand(a, "/e1", "| No snippet with id: e1")
);
}
@ -481,17 +481,19 @@ public class ToolBasicTest extends ReplToolTesting {
public void testSave() throws IOException {
Compiler compiler = new Compiler();
Path path = compiler.getPath("testSave.repl");
List<String> list = Arrays.asList(
"int a;",
"class A { public String toString() { return \"A\"; } }"
);
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertCommand(a, "()", null, null, null, "", ""),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommand(a, "/save " + path.toString(), "")
);
assertEquals(Files.readAllLines(path), list);
{
List<String> list = Arrays.asList(
"int a;",
"class A { public String toString() { return \"A\"; } }"
);
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertCommand(a, "()", null, null, null, "", ""),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommand(a, "/save " + path.toString(), "")
);
assertEquals(Files.readAllLines(path), list);
}
{
List<String> output = new ArrayList<>();
test(
@ -499,28 +501,47 @@ public class ToolBasicTest extends ReplToolTesting {
(a) -> assertCommand(a, "()", null, null, null, "", ""),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommandCheckOutput(a, "/list -all", (out) ->
output.addAll(Stream.of(out.split("\n"))
.filter(str -> !str.isEmpty())
.map(str -> str.substring(str.indexOf(':') + 2))
.filter(str -> !str.startsWith("/"))
.collect(Collectors.toList()))),
output.addAll(Stream.of(out.split("\n"))
.filter(str -> !str.isEmpty())
.map(str -> str.substring(str.indexOf(':') + 2))
.filter(str -> !str.startsWith("/"))
.collect(Collectors.toList()))),
(a) -> assertCommand(a, "/save -all " + path.toString(), "")
);
assertEquals(Files.readAllLines(path), output);
}
List<String> output = new ArrayList<>();
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertCommand(a, "()", null, null, null, "", ""),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommandCheckOutput(a, "/history", (out) ->
output.addAll(Stream.of(out.split("\n"))
.filter(str -> !str.isEmpty())
.collect(Collectors.toList()))),
(a) -> assertCommand(a, "/save -history " + path.toString(), "")
);
output.add("/save -history " + path.toString());
assertEquals(Files.readAllLines(path), output);
{
List<String> output = new ArrayList<>();
test(
(a) -> assertCommand(a, "int a;", null),
(a) -> assertCommand(a, "int b;", null),
(a) -> assertCommand(a, "int c;", null),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommandCheckOutput(a, "/list b c a A", (out) ->
output.addAll(Stream.of(out.split("\n"))
.filter(str -> !str.isEmpty())
.map(str -> str.substring(str.indexOf(':') + 2))
.filter(str -> !str.startsWith("/"))
.collect(Collectors.toList()))),
(a) -> assertCommand(a, "/save 2-3 1 4 " + path.toString(), "")
);
assertEquals(Files.readAllLines(path), output);
}
{
List<String> output = new ArrayList<>();
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertCommand(a, "()", null, null, null, "", ""),
(a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
(a) -> assertCommandCheckOutput(a, "/history", (out) ->
output.addAll(Stream.of(out.split("\n"))
.filter(str -> !str.isEmpty())
.collect(Collectors.toList()))),
(a) -> assertCommand(a, "/save -history " + path.toString(), "")
);
output.add("/save -history " + path.toString());
assertEquals(Files.readAllLines(path), output);
}
}
public void testStartRetain() {
@ -652,6 +673,64 @@ public class ToolBasicTest extends ReplToolTesting {
);
}
public void testRerunIdRange() {
Compiler compiler = new Compiler();
Path startup = compiler.getPath("rangeStartup");
String[] startupSources = new String[] {
"boolean go = false",
"void println(String s) { if (go) System.out.println(s); }",
"void println(int i) { if (go) System.out.println(i); }",
"println(\"s4\")",
"println(\"s5\")",
"println(\"s6\")"
};
String[] sources = new String[] {
"frog",
"go = true",
"println(2)",
"println(3)",
"println(4)",
"querty"
};
compiler.writeToFile(startup, startupSources);
test(false, new String[]{"--startup", startup.toString()},
a -> assertCommandOutputStartsWith(a, sources[0], "| Error:"),
a -> assertCommand(a, sources[1], "go ==> true", "", null, "", ""),
a -> assertCommand(a, sources[2], "", "", null, "2\n", ""),
a -> assertCommand(a, sources[3], "", "", null, "3\n", ""),
a -> assertCommand(a, sources[4], "", "", null, "4\n", ""),
a -> assertCommandOutputStartsWith(a, sources[5], "| Error:"),
a -> assertCommand(a, "/3", "println(3)", "", null, "3\n", ""),
a -> assertCommand(a, "/s4", "println(\"s4\")", "", null, "s4\n", ""),
a -> assertCommandOutputStartsWith(a, "/e1", "frog\n| Error:"),
a -> assertCommand(a, "/2-4",
"println(2)\nprintln(3)\nprintln(4)",
"", null, "2\n3\n4\n", ""),
a -> assertCommand(a, "/s4-s6",
startupSources[3] + "\n" +startupSources[4] + "\n" +startupSources[5],
"", null, "s4\ns5\ns6\n", ""),
a -> assertCommand(a, "/s4-4", null,
"", null, "s4\ns5\ns6\n2\n3\n4\n", ""),
a -> assertCommandCheckOutput(a, "/e1-e2",
s -> {
assertTrue(s.trim().startsWith("frog\n| Error:"),
"Output: \'" + s + "' does not start with: " + "| Error:");
assertTrue(s.trim().lastIndexOf("| Error:") > 10,
"Output: \'" + s + "' does not have second: " + "| Error:");
}),
a -> assertCommand(a, "/4 s4 2",
"println(4)\nprintln(\"s4\")\nprintln(2)",
"", null, "4\ns4\n2\n", ""),
a -> assertCommand(a, "/s5 2-4 3",
"println(\"s5\")\nprintln(2)\nprintln(3)\nprintln(4)\nprintln(3)",
"", null, "s5\n2\n3\n4\n3\n", ""),
a -> assertCommand(a, "/2 ff", "| No such snippet: ff"),
a -> assertCommand(a, "/4-2", "| End of snippet range less than start: 4 - 2"),
a -> assertCommand(a, "/s5-s3", "| End of snippet range less than start: s5 - s3"),
a -> assertCommand(a, "/4-s5", "| End of snippet range less than start: 4 - s5")
);
}
@Test(enabled = false) // TODO 8158197
public void testHeadlessEditPad() {
String prevHeadless = System.getProperty("java.awt.headless");

View File

@ -117,7 +117,6 @@ public class ToolLocaleMessageTest extends ReplToolTesting {
(a) -> assertCommandFail(a, "/drop rats"),
(a) -> assertCommandOK(a, "void dup() {}"),
(a) -> assertCommandOK(a, "int dup"),
(a) -> assertCommandFail(a, "/drop dup"),
(a) -> assertCommandFail(a, "/edit zebra", "zebra"),
(a) -> assertCommandFail(a, "/list zebra", "zebra", "No such snippet: zebra"),
(a) -> assertCommandFail(a, "/open", "/open"),

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/*
* @test
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405 8173073 8173848 8174041 8173916 8174028 8174262 8174797 8177079
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405 8173073 8173848 8174041 8173916 8174028 8174262 8174797 8177079 8180508
* @summary Simple jshell tool tests
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -202,7 +203,7 @@ public class ToolSimpleTest extends ReplToolTesting {
@Test
public void testUnknownCommand() {
test((a) -> assertCommand(a, "/unknown",
"| No such command or snippet id: /unknown\n" +
"| Invalid command: /unknown\n" +
"| Type /help for help."));
}
@ -274,10 +275,26 @@ public class ToolSimpleTest extends ReplToolTesting {
);
}
@Test
public void testDropRange() {
test(false, new String[]{"--no-startup"},
a -> assertVariable(a, "int", "a"),
a -> assertMethod(a, "int b() { return 0; }", "()int", "b"),
a -> assertClass(a, "class A {}", "class", "A"),
a -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
a -> assertCommand(a, "for (int i = 0; i < 10; ++i) {}", ""),
a -> assertCommand(a, "/drop 3-5 b 1",
"| dropped class A\n" +
"| dropped method b()\n" +
"| dropped variable a\n"),
a -> assertCommand(a, "/list", "")
);
}
@Test
public void testDropNegative() {
test(false, new String[]{"--no-startup"},
a -> assertCommandOutputStartsWith(a, "/drop 0", "| No such snippet: 0"),
a -> assertCommandOutputStartsWith(a, "/drop 0", "| No snippet with id: 0"),
a -> assertCommandOutputStartsWith(a, "/drop a", "| No such snippet: a"),
a -> assertCommandCheckOutput(a, "/drop",
assertStartsWith("| In the /drop argument, please specify an import, variable, method, or class to drop.")),
@ -292,27 +309,23 @@ public class ToolSimpleTest extends ReplToolTesting {
@Test
public void testAmbiguousDrop() {
Consumer<String> check = s -> {
assertTrue(s.startsWith("| The argument references more than one import, variable, method, or class"), s);
int lines = s.split("\n").length;
assertEquals(lines, 5, "Expected 3 ambiguous keys, but found: " + (lines - 2) + "\n" + s);
};
test(
a -> assertVariable(a, "int", "a"),
a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
a -> assertClass(a, "class a {}", "class", "a"),
a -> assertCommandCheckOutput(a, "/drop a", check),
a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
a -> assertCommandCheckOutput(a, "/methods", assertMethods()),
a -> assertCommandCheckOutput(a, "/types", assertClasses()),
a -> assertCommandCheckOutput(a, "/imports", assertImports())
a -> assertCommand(a, "/drop a",
"| dropped variable a\n" +
"| dropped method a()\n" +
"| dropped class a")
);
test(
a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
a -> assertMethod(a, "double a(int a) { return 0; }", "(int)double", "a"),
a -> assertMethod(a, "double a(double a) { return 0; }", "(double)double", "a"),
a -> assertCommandCheckOutput(a, "/drop a", check),
a -> assertCommandCheckOutput(a, "/methods", assertMethods())
a -> assertCommand(a, "/drop a",
"| dropped method a()\n" +
"| dropped method a(int)\n" +
"| dropped method a(double)\n")
);
}
@ -402,12 +415,14 @@ public class ToolSimpleTest extends ReplToolTesting {
String arg = "qqqq";
List<String> startVarList = new ArrayList<>(START_UP);
startVarList.add("int aardvark");
startVarList.add("int weevil");
test(
a -> assertCommandCheckOutput(a, "/list -all",
s -> checkLineToList(s, START_UP)),
a -> assertCommandOutputStartsWith(a, "/list " + arg,
"| No such snippet: " + arg),
a -> assertVariable(a, "int", "aardvark"),
a -> assertVariable(a, "int", "weevil"),
a -> assertCommandOutputContains(a, "/list aardvark", "aardvark"),
a -> assertCommandCheckOutput(a, "/list -start",
s -> checkLineToList(s, START_UP)),
@ -415,6 +430,11 @@ public class ToolSimpleTest extends ReplToolTesting {
s -> checkLineToList(s, startVarList)),
a -> assertCommandOutputStartsWith(a, "/list s3",
"s3 : import"),
a -> assertCommandCheckOutput(a, "/list 1-2 s3",
s -> {
assertTrue(Pattern.matches(".*aardvark.*\\R.*weevil.*\\R.*s3.*import.*", s.trim()),
"No match: " + s);
}),
a -> assertCommandOutputStartsWith(a, "/list " + arg,
"| No such snippet: " + arg)
);
@ -439,6 +459,8 @@ public class ToolSimpleTest extends ReplToolTesting {
s -> checkLineToList(s, startVarList)),
a -> assertCommandOutputStartsWith(a, "/vars -all",
"| int aardvark = 0\n| int a = "),
a -> assertCommandOutputStartsWith(a, "/vars 1-4",
"| int aardvark = 0\n| int a = "),
a -> assertCommandOutputStartsWith(a, "/vars f",
"| This command does not accept the snippet 'f'"),
a -> assertCommand(a, "/var " + arg,