8157200: jshell tool: Add /retain command to persist settings
8156910: jshell tool: crash when code with syntax error contains format specifier
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ArgTokenizer.java Fri May 20 11:55:46 2016 -0700
@@ -93,6 +93,13 @@
return isQuoted;
}
+ boolean isIdentifier() {
+ if (isQuoted) {
+ return false;
+ }
+ return sval.codePoints().allMatch(cp -> Character.isJavaIdentifierPart(cp));
+ }
+
String whole() {
return prefix + str;
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java Fri May 20 11:55:46 2016 -0700
@@ -30,9 +30,11 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.joining;
@@ -50,12 +52,18 @@
// Internal field name for truncation length
private static final String TRUNCATION_FIELD = "<truncation>";
+ // For encoding to Properties String
+ private static final String RECORD_SEPARATOR = "\u241E";
+
// Current mode
private Mode mode = new Mode("", false); // initial value placeholder during start-up
- // Mapping of mode names to mode modes
+ // Mapping of mode name to mode
private final Map<String, Mode> modeMap = new HashMap<>();
+ // Mapping of mode names to encoded retained mode
+ private final Map<String, String> retainedMap = new HashMap<>();
+
// Mapping selector enum names to enums
private final Map<String, Selector<?>> selectorMap = new HashMap<>();
@@ -118,6 +126,23 @@
return new Setter(messageHandler, at).setPrompt();
}
+ public String retainFeedback(MessageHandler messageHandler, ArgTokenizer at) {
+ return new Setter(messageHandler, at).retainFeedback();
+ }
+
+ public String retainMode(MessageHandler messageHandler, ArgTokenizer at) {
+ return new Setter(messageHandler, at).retainMode();
+ }
+
+ public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) {
+ return new Setter(messageHandler, new ArgTokenizer("")).restoreEncodedModes(encoded);
+ }
+
+ public void markModesReadOnly() {
+ modeMap.values().stream()
+ .forEach(m -> m.readOnly = true);
+ }
+
{
for (FormatCase e : EnumSet.allOf(FormatCase.class))
selectorMap.put(e.name().toLowerCase(Locale.US), e);
@@ -147,6 +172,8 @@
// Event cases: class, method, expression, ...
final Map<String, List<Setting>> cases;
+ boolean readOnly = false;
+
String prompt = "\n-> ";
String continuationPrompt = ">> ";
@@ -204,6 +231,57 @@
this.continuationPrompt = m.continuationPrompt;
}
+ /**
+ * Set up a mode reconstituted from a preferences string.
+ *
+ * @param it the encoded Mode broken into String chunks, may contain
+ * subsequent encoded modes
+ */
+ Mode(Iterator<String> it) {
+ this.name = it.next();
+ this.commandFluff = Boolean.parseBoolean(it.next());
+ this.prompt = it.next();
+ this.continuationPrompt = it.next();
+ cases = new HashMap<>();
+ String field;
+ while (!(field = it.next()).equals("***")) {
+ String open = it.next();
+ assert open.equals("(");
+ List<Setting> settings = new ArrayList<>();
+ String bits;
+ while (!(bits = it.next()).equals(")")) {
+ String format = it.next();
+ Setting ing = new Setting(Long.parseLong(bits), format);
+ settings.add(ing);
+ }
+ cases.put(field, settings);
+ }
+ }
+
+ /**
+ * Encodes the mode into a String so it can be saved in Preferences.
+ *
+ * @return the string representation
+ */
+ String encode() {
+ List<String> el = new ArrayList<>();
+ el.add(name);
+ el.add(String.valueOf(commandFluff));
+ el.add(prompt);
+ el.add(continuationPrompt);
+ for (Entry<String, List<Setting>> es : cases.entrySet()) {
+ el.add(es.getKey());
+ el.add("(");
+ for (Setting ing : es.getValue()) {
+ el.add(String.valueOf(ing.enumBits));
+ el.add(ing.format);
+ }
+ el.add(")");
+ }
+ el.add("***");
+ return String.join(RECORD_SEPARATOR, el);
+ }
+
private boolean add(String field, Setting ing) {
List<Setting> settings = cases.computeIfAbsent(field, k -> new ArrayList<>());
if (settings == null) {
@@ -590,8 +668,12 @@
// For /set prompt <mode> "<prompt>" "<continuation-prompt>"
boolean setPrompt() {
Mode m = nextMode();
- String prompt = nextFormat();
- String continuationPrompt = nextFormat();
+ if (valid && m.readOnly) {
+ errorat("jshell.err.not.valid.with.predefined.mode", m.name);
+ valid = false;
+ }
+ String prompt = valid ? nextFormat() : null;
+ String continuationPrompt = valid ? nextFormat() : null;
if (valid) {
m.setPrompts(prompt, continuationPrompt);
} else {
@@ -603,7 +685,7 @@
// For /set newmode <new-mode> [-command|-quiet [<old-mode>]]
boolean setNewMode() {
String umode = at.next();
- if (umode == null) {
+ if (umode == null || !at.isIdentifier()) {
errorat("jshell.err.feedback.expected.new.feedback.mode");
valid = false;
}
@@ -637,7 +719,7 @@
// For /set feedback <mode>
boolean setFeedback() {
Mode m = nextMode();
- if (valid && m != null) {
+ if (valid) {
mode = m;
fluffmsg("jshell.msg.feedback.mode", mode.name);
} else {
@@ -650,8 +732,12 @@
// For /set format <mode> "<format>" <selector>...
boolean setFormat() {
Mode m = nextMode();
+ if (valid && m.readOnly) {
+ errorat("jshell.err.not.valid.with.predefined.mode", m.name);
+ valid = false;
+ }
String field = at.next();
- if (field == null || at.isQuoted()) {
+ if (field == null || !at.isIdentifier()) {
errorat("jshell.err.feedback.expected.field");
valid = false;
}
@@ -662,6 +748,10 @@
// For /set truncation <mode> <length> <selector>...
boolean setTruncation() {
Mode m = nextMode();
+ if (valid && m.readOnly) {
+ errorat("jshell.err.not.valid.with.predefined.mode", m.name);
+ valid = false;
+ }
String length = at.next();
if (length == null) {
errorat("jshell.err.truncation.expected.length");
@@ -679,6 +769,54 @@
return installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation");
}
+ String retainFeedback() {
+ String umode = at.next();
+ if (umode != null) {
+ Mode m = toMode(umode);
+ if (valid && !m.readOnly && !retainedMap.containsKey(m.name)) {
+ errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined");
+ valid = false;
+ }
+ if (valid) {
+ mode = m;
+ fluffmsg("jshell.msg.feedback.mode", mode.name);
+ } else {
+ fluffmsg("jshell.msg.see", "/help /retain feedback");
+ return null;
+ }
+ }
+ return mode.name;
+ }
+
+ String retainMode() {
+ Mode m = nextMode();
+ if (valid && m.readOnly) {
+ errorat("jshell.err.not.valid.with.predefined.mode", m.name);
+ valid = false;
+ }
+ if (valid) {
+ retainedMap.put(m.name, m.encode());
+ return String.join(RECORD_SEPARATOR, retainedMap.values());
+ } else {
+ fluffmsg("jshell.msg.see", "/help /retain mode");
+ return null;
+ }
+ }
+
+ boolean restoreEncodedModes(String allEncoded) {
+ // Iterate over each record in each encoded mode
+ String[] ms = allEncoded.split(RECORD_SEPARATOR);
+ Iterator<String> itr = Arrays.asList(ms).iterator();
+ while (itr.hasNext()) {
+ // Reconstruct the encoded mode
+ Mode m = new Mode(itr);
+ modeMap.put(m.name, m);
+ // Continue to retain it a new retains occur
+ retainedMap.put(m.name, m.encode());
+ }
+ return true;
+ }
+
// install the format of a field under parsed selectors
boolean installFormat(Mode m, String field, String format, String help) {
String slRaw;
@@ -712,7 +850,7 @@
}
Mode toMode(String umode) {
- if (umode == null) {
+ if (umode == null || !at.isIdentifier()) {
errorat("jshell.err.feedback.expected.mode");
valid = false;
return null;
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Fri May 20 11:55:46 2016 -0700
@@ -166,7 +166,7 @@
private boolean regenerateOnDeath = true;
private boolean live = false;
private boolean feedbackInitialized = false;
- private String initialMode = null;
+ private String commandLineFeedbackMode = null;
private List<String> remoteVMOptions = new ArrayList<>();
SourceCodeAnalysis analysis;
@@ -176,14 +176,17 @@
private boolean debug = false;
public boolean testPrompt = false;
private String cmdlineClasspath = null;
- private String cmdlineStartup = null;
+ private String startup = null;
private String[] editor = null;
// Commands and snippets which should be replayed
private List<String> replayableHistory;
private List<String> replayableHistoryPrevious;
- static final String STARTUP_KEY = "STARTUP";
+ static final String STARTUP_KEY = "STARTUP";
+ static final String EDITOR_KEY = "EDITOR";
+ static final String FEEDBACK_KEY = "FEEDBACK";
+ static final String MODE_KEY = "MODE";
static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE";
static final String DEFAULT_STARTUP =
@@ -454,6 +457,12 @@
}
private void start(IOContext in, List<String> loadList) {
+ // Read retained editor setting (if any)
+ String editorString = prefs.get(EDITOR_KEY, null);
+ if (editorString != null) {
+ editor = editorString.split(RECORD_SEPARATOR);
+ }
+
resetState(); // Initialize
// Read replay history from last jshell session into previous history
@@ -519,37 +528,37 @@
return null;
case "-feedback":
if (ai.hasNext()) {
- initialMode = ai.next();
+ commandLineFeedbackMode = ai.next();
} else {
startmsg("jshell.err.opt.feedback.arg");
return null;
}
break;
case "-q":
- initialMode = "concise";
+ commandLineFeedbackMode = "concise";
break;
case "-qq":
- initialMode = "silent";
+ commandLineFeedbackMode = "silent";
break;
case "-v":
- initialMode = "verbose";
+ commandLineFeedbackMode = "verbose";
break;
case "-startup":
- if (cmdlineStartup != null) {
+ if (startup != null) {
startmsg("jshell.err.opt.startup.conflict");
return null;
}
- cmdlineStartup = readFile(ai.hasNext()? ai.next() : null, "'-startup'");
- if (cmdlineStartup == null) {
+ startup = readFile(ai.hasNext()? ai.next() : null, "'-startup'");
+ if (startup == null) {
return null;
}
break;
case "-nostartup":
- if (cmdlineStartup != null && !cmdlineStartup.isEmpty()) {
+ if (startup != null && !startup.isEmpty()) {
startmsg("jshell.err.opt.startup.conflict");
return null;
}
- cmdlineStartup = "";
+ startup = "";
break;
default:
if (arg.startsWith("-R")) {
@@ -571,6 +580,27 @@
cmdout.print(getResourceString("help.usage"));
}
+ /**
+ * Message handler to use during initial start-up.
+ */
+ private class InitMessageHandler implements MessageHandler {
+
+ @Override
+ public void fluff(String format, Object... args) {
+ //ignore
+ }
+
+ @Override
+ public void fluffmsg(String messageKey, Object... args) {
+ //ignore
+ }
+
+ @Override
+ public void errormsg(String messageKey, Object... args) {
+ startmsg(messageKey, args);
+ }
+ }
+
private void resetState() {
closeState();
@@ -604,8 +634,9 @@
analysis = state.sourceCodeAnalysis();
live = true;
if (!feedbackInitialized) {
- startUpRun(getResourceString("startup.feedback"));
+ // One time per run feedback initialization
feedbackInitialized = true;
+ initFeedback();
}
if (cmdlineClasspath != null) {
@@ -613,38 +644,46 @@
}
String start;
- if (cmdlineStartup == null) {
- start = prefs.get(STARTUP_KEY, "<nada>");
- if (start.equals("<nada>")) {
+ if (startup == null) {
+ start = startup = prefs.get(STARTUP_KEY, null);
+ if (start == null) {
start = DEFAULT_STARTUP;
- prefs.put(STARTUP_KEY, DEFAULT_STARTUP);
}
} else {
- start = cmdlineStartup;
+ start = startup;
}
startUpRun(start);
- if (initialMode != null) {
- MessageHandler mh = new MessageHandler() {
- @Override
- public void fluff(String format, Object... args) {
- }
-
- @Override
- public void fluffmsg(String messageKey, Object... args) {
- }
-
- @Override
- public void errormsg(String messageKey, Object... args) {
- startmsg(messageKey, args);
- }
- };
- if (!feedback.setFeedback(mh, new ArgTokenizer("-feedback ", initialMode))) {
+ currentNameSpace = mainNamespace;
+ }
+ //where -- one-time per run initialization of feedback modes
+ private void initFeedback() {
+ // No fluff, no prefix, for init failures
+ MessageHandler initmh = new InitMessageHandler();
+ // Execute the feedback initialization code in the resource file
+ startUpRun(getResourceString("startup.feedback"));
+ // These predefined modes are read-only
+ feedback.markModesReadOnly();
+ // Restore user defined modes retained on previous run with /retain mode
+ String encoded = prefs.get(MODE_KEY, null);
+ if (encoded != null) {
+ feedback.restoreEncodedModes(initmh, encoded);
+ }
+ if (commandLineFeedbackMode != null) {
+ // The feedback mode to use was specified on the command line, use it
+ if (!feedback.setFeedback(initmh, new ArgTokenizer("-feedback ", commandLineFeedbackMode))) {
regenerateOnDeath = false;
}
- initialMode = null;
+ commandLineFeedbackMode = null;
+ } else {
+ String fb = prefs.get(FEEDBACK_KEY, null);
+ if (fb != null) {
+ // Restore the feedback mode to use that was retained
+ // on a previous run with /retain feedback
+ feedback.setFeedback(initmh, new ArgTokenizer("/retain feedback ", fb));
+ }
}
- currentNameSpace = mainNamespace;
}
+
//where
private void startUpRun(String start) {
try (IOContext suin = new FileScannerIOContext(new StringReader(start))) {
@@ -1052,6 +1091,9 @@
registerCommand(new Command("/set",
arg -> cmdSet(arg),
new FixedCompletionProvider(SET_SUBCOMMANDS)));
+ registerCommand(new Command("/retain",
+ arg -> cmdRetain(arg),
+ new FixedCompletionProvider(RETAIN_SUBCOMMANDS)));
registerCommand(new Command("/?",
"help.quest",
arg -> cmdHelp(arg),
@@ -1128,9 +1170,13 @@
private static final String[] SET_SUBCOMMANDS = new String[]{
"format", "truncation", "feedback", "newmode", "prompt", "editor", "start"};
+ private static final String[] RETAIN_SUBCOMMANDS = new String[]{
+ "feedback", "mode", "editor", "start"};
+
final boolean cmdSet(String arg) {
- ArgTokenizer at = new ArgTokenizer("/set ", arg.trim());
- String which = setSubCommand(at);
+ String cmd = "/set";
+ ArgTokenizer at = new ArgTokenizer(cmd +" ", arg.trim());
+ String which = subCommand(cmd, at, SET_SUBCOMMANDS);
if (which == null) {
return false;
}
@@ -1151,56 +1197,106 @@
errormsg("jshell.err.set.editor.arg");
return false;
} else {
- List<String> ed = new ArrayList<>();
- ed.add(prog);
- String n;
- while ((n = at.next()) != null) {
- ed.add(n);
- }
- editor = ed.toArray(new String[ed.size()]);
- fluffmsg("jshell.msg.set.editor.set", prog);
- return true;
+ return setEditor(cmd, prog, at);
}
}
case "start": {
- String init = readFile(at.next(), "/set start");
- if (init == null) {
- return false;
- } else {
- prefs.put(STARTUP_KEY, init);
- return true;
- }
+ return setStart(cmd, at.next());
}
default:
- errormsg("jshell.err.arg", "/set", at.val());
+ errormsg("jshell.err.arg", cmd, at.val());
return false;
}
}
- boolean printSetHelp(ArgTokenizer at) {
- String which = setSubCommand(at);
+ final boolean cmdRetain(String arg) {
+ String cmd = "/retain";
+ ArgTokenizer at = new ArgTokenizer(cmd +" ", arg.trim());
+ String which = subCommand(cmd, at, RETAIN_SUBCOMMANDS);
if (which == null) {
return false;
}
- hardrb("help.set." + which);
+ switch (which) {
+ case "feedback": {
+ String fb = feedback.retainFeedback(this, at);
+ if (fb != null) {
+ // If a feedback mode has been set now, or in the past, retain it
+ prefs.put(FEEDBACK_KEY, fb);
+ return true;
+ }
+ return false;
+ }
+ case "mode":
+ String retained = feedback.retainMode(this, at);
+ if (retained != null) {
+ // Retain this mode and all previously retained modes
+ prefs.put(MODE_KEY, retained);
+ return true;
+ }
+ return false;
+ case "editor": {
+ String prog = at.next();
+ if (prog != null) {
+ // If the editor is specified, first run /set editor ...
+ if(!setEditor(cmd, prog, at)) {
+ return false;
+ }
+ }
+ if (editor != null) {
+ // If an editor has been set now, or in the past, retain it
+ prefs.put(EDITOR_KEY, String.join(RECORD_SEPARATOR, editor));
+ return true;
+ }
+ return false;
+ }
+ case "start": {
+ String fn = at.next();
+ if (fn != null) {
+ if (!setStart(cmd, fn)) {
+ return false;
+ }
+ }
+ if (startup != null) {
+ prefs.put(STARTUP_KEY, startup);
+ return true;
+ }
+ return false;
+ }
+ default:
+ errormsg("jshell.err.arg", cmd, at.val());
+ return false;
+ }
+ }
+
+ // Print the help doc for the specified sub-command
+ boolean printSubCommandHelp(String cmd, ArgTokenizer at, String helpPrefix, String[] subs) {
+ String which = subCommand(cmd, at, subs);
+ if (which == null) {
+ return false;
+ }
+ hardrb(helpPrefix + which);
return true;
}
- String setSubCommand(ArgTokenizer at) {
- String[] matches = at.next(SET_SUBCOMMANDS);
+ // Find which, if any, sub-command matches
+ String subCommand(String cmd, ArgTokenizer at, String[] subs) {
+ String[] matches = at.next(subs);
if (matches == null) {
- errormsg("jshell.err.set.arg");
+ // No sub-command was given
+ errormsg("jshell.err.sub.arg", cmd);
return null;
}
if (matches.length == 0) {
- errormsg("jshell.err.arg", "/set", at.val());
- fluffmsg("jshell.msg.use.one.of", Arrays.stream(SET_SUBCOMMANDS)
+ // There are no matching sub-commands
+ errormsg("jshell.err.arg", cmd, at.val());
+ fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs)
.collect(Collectors.joining(", "))
);
return null;
}
if (matches.length > 1) {
- errormsg("jshell.err.set.ambiguous", at.val());
+ // More than one sub-command matches the initial characters provided
+ errormsg("jshell.err.sub.ambiguous", cmd, at.val());
fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches)
.collect(Collectors.joining(", "))
);
@@ -1209,6 +1305,31 @@
return matches[0];
}
+ // The sub-command: /set editor <editor-command-line>>
+ boolean setEditor(String cmd, String prog, ArgTokenizer at) {
+ List<String> ed = new ArrayList<>();
+ ed.add(prog);
+ String n;
+ while ((n = at.next()) != null) {
+ ed.add(n);
+ }
+ editor = ed.toArray(new String[ed.size()]);
+ fluffmsg("jshell.msg.set.editor.set", prog);
+ return true;
+ }
+
+ // The sub-command: /set start <start-file>
+ boolean setStart(String cmd, String fn) {
+ String init = readFile(fn, cmd + " start");
+ if (init == null) {
+ return false;
+ } else {
+ startup = init;
+ //prefs.put(STARTUP_KEY, init);
+ return true;
+ }
+ }
+
boolean cmdClasspath(String arg) {
if (arg.isEmpty()) {
errormsg("jshell.err.classpath.arg");
@@ -1301,9 +1422,16 @@
.toArray(size -> new Command[size]);
at.mark();
String sub = at.next();
- if (sub != null && matches.length == 1 && matches[0].command.equals("/set")) {
- at.rewind();
- return printSetHelp(at);
+ if (sub != null && matches.length == 1) {
+ String cmd = matches[0].command;
+ switch (cmd) {
+ case "/set":
+ at.rewind();
+ return printSubCommandHelp(cmd, at, "help.set.", SET_SUBCOMMANDS);
+ case "/retain":
+ at.rewind();
+ return printSubCommandHelp(cmd, at, "help.retain.", RETAIN_SUBCOMMANDS);
+ }
}
if (matches.length > 0) {
for (Command c : matches) {
@@ -1964,7 +2092,7 @@
List<String> disp = new ArrayList<>();
displayDiagnostics(source, d, disp);
disp.stream()
- .forEach(l -> hard(l));
+ .forEach(l -> hard("%s", l));
}
if (ste.status() != Status.REJECTED) {
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Fri May 20 09:47:00 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Fri May 20 11:55:46 2016 -0700
@@ -74,8 +74,8 @@
jshell.msg.error = Error:
jshell.msg.warning = Warning:
-jshell.err.set.arg = The ''/set'' command requires a sub-command and arguments. See: ''/help /set''
-jshell.err.set.ambiguous = Ambiguous sub-command argument to ''/set'': {0}
+jshell.err.sub.arg = The ''{0}'' command requires a sub-command. See: ''/help {0}''
+jshell.err.sub.ambiguous = Ambiguous sub-command argument to ''{0}'': {1}
jshell.err.classpath.arg = The /classpath command requires a path argument.
jshell.msg.classpath = Path ''{0}'' added to classpath
@@ -120,6 +120,10 @@
jshell.err.truncation.expected.length = Expected truncation length -- {0}
jshell.err.truncation.length.not.integer = Truncation length must be an integer: {0} -- {1}
+jshell.err.not.valid.with.predefined.mode = Not valid with predefined modes: {0} -- {1}
+jshell.err.retained.feedback.mode.must.be.retained.or.predefined = \
+''/retain feedback <mode>'' requires that <mode> is predefined or has been retained with ''/retain mode'' -- {0}
+
jshell.console.see.more = <press tab to see more>
jshell.console.do.nothing = Do nothing
jshell.console.choice = Choice: \
@@ -355,12 +359,31 @@
/set prompt <mode> "<prompt>" "<continuation-prompt>"\n\t\
Set the displayed prompts for a given feedback mode.\n\n\
/set truncation <mode> <length> <selector>...\n\t\
- Set the maximum length of a displayed value\n\t\
+ Set the maximum length of a displayed value\n\
/set format <mode> <field> "<format>" <selector>...\n\t\
Configure a feedback mode by setting the format of a field when the selector matchs.\n\n\
To get more information about one of these forms, use /help with the form specified.\n\
For example: /help /set format
+help.retain.summary = retain jshell configuration information for subsequent sessions
+help.retain.args = editor|start|feedback|mode
+help.retain =\
+Retain jshell configuration information for future invocations of the jshell tool,\n\
+including: the external editor to use, the start-up definitions to use, the\n\
+configuration of a feedback mode, or the feedback mode to use.\n\
+\n\
+/retain editor [<command> <optional-arg>...]\n\t\
+ Specify the command to launch for the /edit command.\n\t\
+ The <command> is an operating system dependent string.\n\n\
+/retain start [<file>]\n\t\
+ The contents of the specified <file> become the default start-up snippets and commands.\n\n\
+/retain feedback [<mode>]\n\t\
+ Set the feedback mode describing displayed feedback for entered snippets and commands.\n\n\
+/retain mode <mode>\n\t\
+ Create a user-defined feedback mode, optionally copying from an existing mode.\n\n\
+To get more information about one of these forms, use /help with the form specified.\n\
+For example: /help /retain feedback
+
help.quest.summary = get information about jshell
help.quest.args = [<command>|<subject>]
help.quest =\
@@ -568,10 +591,10 @@
help.set.editor =\
Specify the command to launch for the /edit command.\n\
\n\t\
-/set editor <command> <optional-arg>...\n\
+/set editor <command>\n\
\n\
The <command> is an operating system dependent string.\n\
-The <command> may include space-separated arguments (such as flags) -- <optional-arg>....\n\
+The <command> may include space-separated arguments (such as flags)\n\
When /edit is used, the temporary file to edit will be appended as the last argument.
help.set.start =\
@@ -579,8 +602,54 @@
\n\t\
/set start <file>\n\
\n\
+The contents of the specified <file> become the start-up snippets and commands used\n\
+when the /reset or /reload commands are used in this session.\n\
+This command is good for testing the start-up settings. To retain them for future\n\
+runs of the jshell tool use the command:\n\t\
+/retain start\n
+
+help.retain.feedback = \
+Retain which feedback mode to use for displayed feedback for entered snippets and commands.\n\
+This feedback mode will be used in this and future sessions of the jshell tool.\n\
+\n\t\
+/retain feedback [<mode>]\n\
+\n\
+Where <mode> is the name of a previously defined feedback mode.\n\
+You may use just enough letters to make it unique.\n\
+If the <mode> is not specified, this command retains the current mode (as set\n\
+with the most recent /set feedback or /retain feedback command.)\n\
+
+help.retain.mode = \
+Retain the existence and configuration of a user-defined feedback mode.\n\
+This mode will be available in this and future sessions of the jshell tool.
+\n\t\
+/retain mode <mode>\n\
+\n\
+Where <mode> is the name of a mode you wish to retain.\n\
+The <mode> must previously have been created with /set newmode and\n\
+configured as desired with /set prompt, /set format, and /set truncation.\n
+
+help.retain.editor =\
+Retain the command to launch for the /edit command. This command will be invoked when\n\
+the /edit command is used in this and future sessions of the jshell tool.\n\
+\n\t\
+/retain editor [<command>]\n\
+\n\
+The <command> is an operating system dependent string.\n\
+The <command> may include space-separated arguments (such as flags)\n\
+When /edit is used, the temporary file to edit will be appended as the last argument.\n\
+If <command> is not specified, the command last specified in a /set editor or\n\
+/retain editor command will be retained.\n
+
+help.retain.start =\
+Retain the start-up configuration -- a sequence of snippets and commands read at start-up.\n\
+\n\t\
+/retain start [<file>]\n\
+\n\
The contents of the specified <file> become the default start-up snippets and commands --\n\
-which are run when the jshell tool is started or reset.
+which are run when the jshell tool is started or reset.\n\
+If <file> is not specified, the start-up last specified in a /set start or\n\
+/retain start command will be retained.\n
startup.feedback = \
/set newmode verbose -command \n\
--- a/langtools/test/jdk/jshell/CommandCompletionTest.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java Fri May 20 11:55:46 2016 -0700
@@ -53,7 +53,7 @@
public void testCommand() {
assertCompletion("/deb|", false);
- assertCompletion("/re|", false, "/reload ", "/reset ");
+ assertCompletion("/re|", false, "/reload ", "/reset ", "/retain ");
assertCompletion("/h|", false, "/help ", "/history ");
}
--- a/langtools/test/jdk/jshell/ToolBasicTest.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/test/jdk/jshell/ToolBasicTest.java Fri May 20 11:55:46 2016 -0700
@@ -569,4 +569,17 @@
return ex.getMessage();
}
}
+
+ public void testHeadlessEditPad() {
+ String prevHeadless = System.getProperty("java.awt.headless");
+ try {
+ System.setProperty("java.awt.headless", "true");
+ test(
+ (a) -> assertCommandOutputStartsWith(a, "/edit printf", "| Cannot launch editor -- unexpected exception:")
+ );
+ } finally {
+ System.setProperty("java.awt.headless", prevHeadless==null? "false" : prevHeadless);
+ }
+ }
+
}
--- a/langtools/test/jdk/jshell/ToolFormatTest.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/test/jdk/jshell/ToolFormatTest.java Fri May 20 11:55:46 2016 -0700
@@ -220,7 +220,7 @@
(a) -> assertCommandOutputStartsWith(a, "/set feedback te",
""),
(a) -> assertCommandOutputStartsWith(a, "/set ",
- "ERROR: The '/set' command requires a sub-command and arguments"),
+ "ERROR: The '/set' command requires a sub-command"),
(a) -> assertCommandOutputStartsWith(a, "/set xyz",
"ERROR: Invalid '/set' argument: xyz"),
(a) -> assertCommandOutputStartsWith(a, "/set f",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolRetainTest.java Fri May 20 11:55:46 2016 -0700
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8157200
+ * @summary Tests of what information is retained across jshell tool runs
+ * @modules jdk.jshell/jdk.internal.jshell.tool
+ * @build ToolRetainTest ReplToolTesting
+ * @run testng ToolRetainTest
+ */
+
+import org.testng.annotations.Test;
+
+@Test
+public class ToolRetainTest extends ReplToolTesting {
+
+ public void testRetainMode() {
+ test(
+ (a) -> assertCommand(a, "/set newmode trm -quiet", "| Created new feedback mode: trm"),
+ (a) -> assertCommand(a, "/set feedback trm", ""),
+ (a) -> assertCommand(a, "/set format trm display '{name}:{value}'", ""),
+ (a) -> assertCommand(a, "int x = 45", "x:45"),
+ (a) -> assertCommand(a, "/retain mode trm", ""),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommand(a, "/set feedback trm", ""),
+ (a) -> assertCommand(a, "int x = 45", "x:45")
+ );
+ }
+
+ public void testRetain2Mode() {
+ test(
+ (a) -> assertCommand(a, "/set newmode trm1 -quiet", "| Created new feedback mode: trm1"),
+ (a) -> assertCommand(a, "/retain mode trm1", ""),
+ (a) -> assertCommand(a, "/retain feedback trm1", ""),
+ (a) -> assertCommand(a, "/set format trm1 display '{name}:{value}'", ""),
+ (a) -> assertCommand(a, "int x = 66", "x:66"),
+ (a) -> assertCommand(a, "/retain mode trm1", ""),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommand(a, "/set newmode trm2 -quiet", ""),
+ (a) -> assertCommand(a, "/set format trm2 display '{name}={value}'", ""),
+ (a) -> assertCommand(a, "int x = 45", "x:45"),
+ (a) -> assertCommand(a, "/retain mode trm2", ""),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommand(a, "int x = 99", "x:99"),
+ (a) -> assertCommand(a, "/set feedback trm2", ""),
+ (a) -> assertCommand(a, "int z = 77", "z=77")
+ );
+ }
+
+ public void testRetainFeedback() {
+ test(
+ (a) -> assertCommand(a, "/retain feedback verbose", "| Feedback mode: verbose"),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommandOutputContains(a, "int h =8", "| created variable h : int")
+ );
+ }
+
+ public void testRetainFeedbackBlank() {
+ test(
+ (a) -> assertCommand(a, "/set feedback verbose", "| Feedback mode: verbose"),
+ (a) -> assertCommand(a, "/retain feedback", ""),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommandOutputContains(a, "int qw = 5", "| created variable qw : int")
+ );
+ }
+
+ public void testRetainEditor() {
+ test(
+ (a) -> assertCommand(a, "/retain editor nonexistent", "| Editor set to: nonexistent"),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommandOutputContains(a, "int h =8", ""),
+ (a) -> assertCommandOutputContains(a, "/edit h", "Edit Error:")
+ );
+ }
+
+ public void testRetainEditorBlank() {
+ test(
+ (a) -> assertCommand(a, "/set editor nonexistent", "| Editor set to: nonexistent"),
+ (a) -> assertCommand(a, "/retain editor", ""),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommandOutputContains(a, "int h =8", ""),
+ (a) -> assertCommandOutputContains(a, "/edit h", "Edit Error:")
+ );
+ }
+
+ public void testRetainModeNeg() {
+ test(
+ (a) -> assertCommandOutputStartsWith(a, "/retain mode verbose",
+ "| Not valid with predefined mode"),
+ (a) -> assertCommandOutputStartsWith(a, "/retain mode ????",
+ "| Expected a feedback mode")
+ );
+ }
+
+ public void testRetainFeedbackNeg() {
+ test(
+ (a) -> assertCommandOutputStartsWith(a, "/retain feedback babble1",
+ "| Does not match any current feedback mode"),
+ (a) -> assertCommand(a, "/set newmode trfn",
+ "| Created new feedback mode: trfn"),
+ (a) -> assertCommandOutputContains(a, "/retain feedback trfn",
+ "is predefined or has been retained"),
+ (a) -> assertCommandOutputStartsWith(a, "/retain feedback !!!!",
+ "| Expected a feedback mode")
+ );
+ }
+
+ public void testNoRetainMode() {
+ test(
+ (a) -> assertCommand(a, "/set newmode trm -quiet", "| Created new feedback mode: trm"),
+ (a) -> assertCommand(a, "/set feedback trm", ""),
+ (a) -> assertCommand(a, "/set format trm display '{name}:{value}'", ""),
+ (a) -> assertCommand(a, "int x = 45", "x:45"),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommandOutputStartsWith(a, "/set feedback trm",
+ "| Does not match any current feedback mode"),
+ (a) -> assertCommandOutputContains(a, "int x = 45", "==> 45")
+ );
+ }
+
+ public void testNoRetainFeedback() {
+ test(
+ (a) -> assertCommand(a, "/set feedback verbose", "| Feedback mode: verbose"),
+ (a) -> assertCommand(a, "/exit", "")
+ );
+ test(
+ (a) -> assertCommand(a, "int h =8", "h ==> 8")
+ );
+ }
+
+}
--- a/langtools/test/jdk/jshell/ToolSimpleTest.java Fri May 20 09:47:00 2016 -0700
+++ b/langtools/test/jdk/jshell/ToolSimpleTest.java Fri May 20 11:55:46 2016 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8153716 8143955 8151754 8150382 8153920
+ * @bug 8153716 8143955 8151754 8150382 8153920 8156910
* @summary Simple jshell tool tests
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@@ -449,18 +449,6 @@
assertStartsWith("| '/save' requires a filename argument.")));
}
- public void testHeadlessEditPad() {
- String prevHeadless = System.getProperty("java.awt.headless");
- try {
- System.setProperty("java.awt.headless", "true");
- test(
- (a) -> assertCommandOutputStartsWith(a, "/edit printf", "| Cannot launch editor -- unexpected exception:")
- );
- } finally {
- System.setProperty("java.awt.headless", prevHeadless==null? "false" : prevHeadless);
- }
- }
-
public void testOptionQ() {
test(new String[]{"-q", "-nostartup"},
(a) -> assertCommand(a, "1+1", "$1 ==> 2"),
@@ -495,4 +483,11 @@
"$1 ==> \"blorp\"")
);
}
+
+ public void test8156910() {
+ test(
+ (a) -> assertCommandOutputContains(a, "System.out.println(\"%5d\", 10);", "%5d"),
+ (a) -> assertCommandOutputContains(a, "1234", "==> 1234")
+ );
+ }
}