8157200: jshell tool: Add /retain command to persist settings

8156910: jshell tool: crash when code with syntax error contains format specifier

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2016-05-20 11:55:46 -07:00
parent 479ecdbdaf
commit 2a791e9edd
9 changed files with 616 additions and 98 deletions

View File

@ -93,6 +93,13 @@ class ArgTokenizer {
return isQuoted;
}
boolean isIdentifier() {
if (isQuoted) {
return false;
}
return sval.codePoints().allMatch(cp -> Character.isJavaIdentifierPart(cp));
}
String whole() {
return prefix + str;
}

View File

@ -30,9 +30,11 @@ import java.util.Arrays;
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 @@ class Feedback {
// 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 @@ class Feedback {
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 @@ class Feedback {
// Event cases: class, method, expression, ...
final Map<String, List<Setting>> cases;
boolean readOnly = false;
String prompt = "\n-> ";
String continuationPrompt = ">> ";
@ -204,6 +231,57 @@ class Feedback {
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 @@ class Feedback {
// 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 @@ class Feedback {
// 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 @@ class Feedback {
// 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 @@ class Feedback {
// 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 @@ class Feedback {
// 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 @@ class Feedback {
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 @@ class Feedback {
}
Mode toMode(String umode) {
if (umode == null) {
if (umode == null || !at.isIdentifier()) {
errorat("jshell.err.feedback.expected.mode");
valid = false;
return null;

View File

@ -166,7 +166,7 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
}
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
}
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))) {
regenerateOnDeath = false;
}
initialMode = null;
}
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;
}
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));
}
}
}
//where
private void startUpRun(String start) {
try (IOContext suin = new FileScannerIOContext(new StringReader(start))) {
@ -1052,6 +1091,9 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
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 @@ public class JShellTool implements MessageHandler {
.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 @@ public class JShellTool implements MessageHandler {
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) {

View File

@ -74,8 +74,8 @@ jshell.err.out.of.range = Out of range
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.msg.feedback.mode.following = The feedback mode should be one of the foll
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 @@ the command prompt, the feedback mode to use, or the format of output.\n\
/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 @@ The continuation-prompt is used on the second and subsequent lines of a multi-li
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 @@ Set the start-up configuration -- a sequence of snippets and commands read at st
\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\

View File

@ -53,7 +53,7 @@ public class CommandCompletionTest extends ReplToolTesting {
public void testCommand() {
assertCompletion("/deb|", false);
assertCompletion("/re|", false, "/reload ", "/reset ");
assertCompletion("/re|", false, "/reload ", "/reset ", "/retain ");
assertCompletion("/h|", false, "/help ", "/history ");
}

View File

@ -569,4 +569,17 @@ public class ToolBasicTest extends ReplToolTesting {
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);
}
}
}

View File

@ -220,7 +220,7 @@ public class ToolFormatTest extends ReplToolTesting {
(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",

View File

@ -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")
);
}
}

View File

@ -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 @@ public class ToolSimpleTest extends ReplToolTesting {
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 @@ public class ToolSimpleTest extends ReplToolTesting {
"$1 ==> \"blorp\"")
);
}
public void test8156910() {
test(
(a) -> assertCommandOutputContains(a, "System.out.println(\"%5d\", 10);", "%5d"),
(a) -> assertCommandOutputContains(a, "1234", "==> 1234")
);
}
}