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:
parent
479ecdbdaf
commit
2a791e9edd
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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\
|
||||
|
@ -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 ");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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",
|
||||
|
168
langtools/test/jdk/jshell/ToolRetainTest.java
Normal file
168
langtools/test/jdk/jshell/ToolRetainTest.java
Normal 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")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user