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
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java Thu May 18 14:16:25 2017 -0700
@@ -121,6 +121,17 @@
}
/**
+ * 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.
*
* @param opt the option to check
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Thu May 18 14:16:25 2017 -0700
@@ -90,7 +90,6 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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) {
+ badSnippetErrormsg(arg);
+ return null;
+ } else {
+ return st;
+ }
+ }
+
+ /**
+ * 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());
}
- return null;
- }
- if (result == null) {
- result = st;
} else {
- result = Stream.concat(result, st);
+ errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command",
+ arg, est.findFirst().get().source());
}
}
- 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)
- );
+
+ /**
+ * 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 @@
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 @@
}
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 @@
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) {
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Thu May 18 14:16:25 2017 -0700
@@ -50,7 +50,7 @@
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 @@
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 @@
/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 @@
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 <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 -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 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 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 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 @@
/? <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 @@
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 @@
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\
--- a/langtools/test/jdk/jshell/CommandCompletionTest.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java Thu May 18 14:16:25 2017 -0700
@@ -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 @@
"/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,
--- a/langtools/test/jdk/jshell/EditorTestBase.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/EditorTestBase.java Thu May 18 14:16:25 2017 -0700
@@ -73,7 +73,7 @@
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")
);
--- a/langtools/test/jdk/jshell/MergedTabShiftTabCommandTest.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/MergedTabShiftTabCommandTest.java Thu May 18 14:16:25 2017 -0700
@@ -66,17 +66,17 @@
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" +
--- a/langtools/test/jdk/jshell/ToolBasicTest.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/ToolBasicTest.java Thu May 18 14:16:25 2017 -0700
@@ -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 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 @@
);
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 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 @@
(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 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");
--- a/langtools/test/jdk/jshell/ToolLocaleMessageTest.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/ToolLocaleMessageTest.java Thu May 18 14:16:25 2017 -0700
@@ -117,7 +117,6 @@
(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"),
--- a/langtools/test/jdk/jshell/ToolSimpleTest.java Wed Jul 05 23:27:00 2017 +0200
+++ b/langtools/test/jdk/jshell/ToolSimpleTest.java Thu May 18 14:16:25 2017 -0700
@@ -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.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 @@
@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."));
}
@@ -275,9 +276,25 @@
}
@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 @@
@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 @@
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 @@
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 @@
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,