8241950: JShell could support auto-indent
Reviewed-by: rfield
This commit is contained in:
parent
f51088e261
commit
f08b5a8006
@ -25,7 +25,6 @@
|
||||
|
||||
package jdk.internal.jshell.tool;
|
||||
|
||||
|
||||
import jdk.jshell.SourceCodeAnalysis.Documentation;
|
||||
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
|
||||
import jdk.jshell.SourceCodeAnalysis.Suggestion;
|
||||
@ -146,13 +145,34 @@ class ConsoleIOContext extends IOContext {
|
||||
completionState.actionCount++;
|
||||
return super.readBinding(keys, local);
|
||||
}
|
||||
@Override
|
||||
protected boolean insertCloseParen() {
|
||||
Object oldIndent = getVariable(INDENTATION);
|
||||
try {
|
||||
setVariable(INDENTATION, 0);
|
||||
return super.insertCloseParen();
|
||||
} finally {
|
||||
setVariable(INDENTATION, oldIndent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected boolean insertCloseSquare() {
|
||||
Object oldIndent = getVariable(INDENTATION);
|
||||
try {
|
||||
setVariable(INDENTATION, 0);
|
||||
return super.insertCloseSquare();
|
||||
} finally {
|
||||
setVariable(INDENTATION, oldIndent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.setOpt(Option.DISABLE_EVENT_EXPANSION);
|
||||
|
||||
reader.setParser((line, cursor, context) -> {
|
||||
if (!allowIncompleteInputs && !repl.isComplete(line)) {
|
||||
throw new EOFError(cursor, cursor, line);
|
||||
int pendingBraces = countPendingOpenBraces(line);
|
||||
throw new EOFError(cursor, cursor, line, null, pendingBraces, null);
|
||||
}
|
||||
return new ArgumentLine(line, cursor);
|
||||
});
|
||||
@ -283,6 +303,11 @@ class ConsoleIOContext extends IOContext {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndent(int indent) {
|
||||
in.variable(LineReader.INDENTATION, indent);
|
||||
}
|
||||
|
||||
private static final String FIXES_SHORTCUT = "\033\133\132"; //Shift-TAB
|
||||
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
@ -937,6 +962,25 @@ class ConsoleIOContext extends IOContext {
|
||||
return inputBytes[inputBytesPointer++];
|
||||
}
|
||||
|
||||
private int countPendingOpenBraces(String code) {
|
||||
int pendingBraces = 0;
|
||||
com.sun.tools.javac.util.Context ctx =
|
||||
new com.sun.tools.javac.util.Context();
|
||||
com.sun.tools.javac.parser.ScannerFactory scannerFactory =
|
||||
com.sun.tools.javac.parser.ScannerFactory.instance(ctx);
|
||||
com.sun.tools.javac.parser.Scanner scanner =
|
||||
scannerFactory.newScanner(code, false);
|
||||
|
||||
while (true) {
|
||||
switch (scanner.token().kind) {
|
||||
case LBRACE: pendingBraces++; break;
|
||||
case RBRACE: pendingBraces--; break;
|
||||
case EOF: return pendingBraces;
|
||||
}
|
||||
scanner.nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A possible action which the user can choose to perform.
|
||||
*/
|
||||
|
@ -56,6 +56,8 @@ abstract class IOContext implements AutoCloseable {
|
||||
|
||||
public abstract int readUserInput() throws IOException;
|
||||
|
||||
public void setIndent(int indent) {}
|
||||
|
||||
class InputInterruptedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
@ -230,15 +230,17 @@ public class JShellTool implements MessageHandler {
|
||||
|
||||
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 FEEDBACK_KEY = "FEEDBACK";
|
||||
static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE";
|
||||
public static final String INDENT_KEY = "INDENT";
|
||||
|
||||
static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+");
|
||||
static final String BUILTIN_FILE_PATH_FORMAT = "/jdk/jshell/tool/resources/%s.jsh";
|
||||
static final String INT_PREFIX = "int $$exit$$ = ";
|
||||
|
||||
static final int OUTPUT_WIDTH = 72;
|
||||
static final int DEFAULT_INDENT = 4;
|
||||
|
||||
// match anything followed by whitespace
|
||||
private static final Pattern OPTION_PRE_PATTERN =
|
||||
@ -910,6 +912,12 @@ public class JShellTool implements MessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String indent() {
|
||||
String indentValue = prefs.get(INDENT_KEY);
|
||||
if (indentValue == null) indentValue = Integer.toString(DEFAULT_INDENT);
|
||||
return indentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry point into the JShell tool.
|
||||
*
|
||||
@ -968,6 +976,14 @@ public class JShellTool implements MessageHandler {
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
// execute from user input
|
||||
try (IOContext in = new ConsoleIOContext(this, cmdin, console)) {
|
||||
int indent;
|
||||
try {
|
||||
String indentValue = indent();
|
||||
indent = Integer.parseInt(indentValue);
|
||||
} catch (NumberFormatException ex) {
|
||||
indent = DEFAULT_INDENT;
|
||||
}
|
||||
in.setIndent(indent);
|
||||
while (regenerateOnDeath) {
|
||||
if (!live) {
|
||||
resetState();
|
||||
@ -1830,7 +1846,8 @@ public class JShellTool implements MessageHandler {
|
||||
SET_MODE_OPTIONS_COMPLETION_PROVIDER)),
|
||||
"prompt", feedback.modeCompletions(),
|
||||
"editor", fileCompletions(Files::isExecutable),
|
||||
"start", FILE_COMPLETION_PROVIDER),
|
||||
"start", FILE_COMPLETION_PROVIDER,
|
||||
"indent", EMPTY_COMPLETION_PROVIDER),
|
||||
STARTSWITH_MATCHER)));
|
||||
registerCommand(new Command("/?",
|
||||
"help.quest",
|
||||
@ -1941,7 +1958,7 @@ public class JShellTool implements MessageHandler {
|
||||
// --- Command implementations ---
|
||||
|
||||
private static final String[] SET_SUBCOMMANDS = new String[]{
|
||||
"format", "truncation", "feedback", "mode", "prompt", "editor", "start"};
|
||||
"format", "truncation", "feedback", "mode", "prompt", "editor", "start", "indent"};
|
||||
|
||||
final boolean cmdSet(String arg) {
|
||||
String cmd = "/set";
|
||||
@ -1958,6 +1975,7 @@ public class JShellTool implements MessageHandler {
|
||||
case "_blank": {
|
||||
// show top-level settings
|
||||
new SetEditor().set();
|
||||
showIndent();
|
||||
showSetStart();
|
||||
setFeedback(this, at); // no args so shows feedback setting
|
||||
hardmsg("jshell.msg.set.show.mode.settings");
|
||||
@ -1978,6 +1996,23 @@ public class JShellTool implements MessageHandler {
|
||||
return new SetEditor(at).set();
|
||||
case "start":
|
||||
return setStart(at);
|
||||
case "indent":
|
||||
String value = at.next();
|
||||
if (value != null) {
|
||||
try {
|
||||
int indent = Integer.parseInt(value);
|
||||
String indentValue = Integer.toString(indent);
|
||||
prefs.put(INDENT_KEY, indentValue);
|
||||
input.setIndent(indent);
|
||||
fluffmsg("jshell.msg.set.indent.set", indentValue);
|
||||
} catch (NumberFormatException ex) {
|
||||
errormsg("jshell.err.invalid.indent", value);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
showIndent();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
errormsg("jshell.err.arg", cmd, at.val());
|
||||
return false;
|
||||
@ -2252,6 +2287,10 @@ public class JShellTool implements MessageHandler {
|
||||
hard(sb.toString());
|
||||
}
|
||||
|
||||
private void showIndent() {
|
||||
hard("/set indent %s", indent());
|
||||
}
|
||||
|
||||
boolean cmdDebug(String arg) {
|
||||
if (arg.isEmpty()) {
|
||||
debug = !debug;
|
||||
|
@ -54,6 +54,8 @@ jshell.err.command.ambiguous = Command: ''{0}'' is ambiguous: {1}
|
||||
jshell.msg.set.restore = Setting new options and restoring state.
|
||||
jshell.msg.set.editor.set = Editor set to: {0}
|
||||
jshell.msg.set.editor.retain = Editor setting retained: {0}
|
||||
jshell.msg.set.indent.set = Indent level set to: {0}
|
||||
jshell.err.invalid.indent = Invalid indent level: {0}
|
||||
jshell.err.no.builtin.editor = Built-in editor not available.
|
||||
jshell.err.cant.launch.editor = Cannot launch built-in editor -- unexpected exception: {0}
|
||||
jshell.msg.try.set.editor = See ''/help /set editor'' to use external editor.
|
||||
@ -516,6 +518,8 @@ the command prompt, the feedback mode to use, or the format of output.\n\
|
||||
Set the maximum length of a displayed value\n\n\
|
||||
/set format <mode> <field> "<format>" <selector>...\n\t\
|
||||
Configure a feedback mode by setting the format of a field when the selector matches\n\n\
|
||||
/set indent <number>\n\t\
|
||||
Set the number of spaces that should be used to automatically indent snippets\n\n\
|
||||
/set\n\t\
|
||||
Show editor, start, and feedback settings as /set commands.\n\t\
|
||||
To show the settings of any of the above, omit the set value\n\n\
|
||||
@ -1136,6 +1140,18 @@ More than one <file> may be specified, for example:\n\
|
||||
\n\t\
|
||||
/set start -retain DEFAULT PRINTING
|
||||
|
||||
help.set.indent.summary =\
|
||||
Specify the number of spaces that should be used to indent snippets
|
||||
|
||||
help.set.indent =\
|
||||
Specify the number of spaces that should be used to indent snippets:\n\
|
||||
\n\t\
|
||||
/set indent <number>\n\
|
||||
\n\
|
||||
Show the indent setting:\n\
|
||||
\n\t\
|
||||
/set indent\n\
|
||||
|
||||
startup.feedback = \
|
||||
/set mode verbose -command \n\
|
||||
\n\
|
||||
|
@ -182,9 +182,9 @@ public class CommandCompletionTest extends ReplToolTesting {
|
||||
a -> assertCompletion(a, "/help /s|", false,
|
||||
"/save ", "/set "),
|
||||
a -> assertCompletion(a, "/help /set |", false,
|
||||
"editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
|
||||
"editor", "feedback", "format", "indent", "mode", "prompt", "start", "truncation"),
|
||||
a -> assertCompletion(a, "/help set |", false,
|
||||
"editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
|
||||
"editor", "feedback", "format", "indent", "mode", "prompt", "start", "truncation"),
|
||||
a -> assertCompletion(a, "/help /edit |", false),
|
||||
a -> assertCompletion(a, "/help dr|", false,
|
||||
"drop ")
|
||||
@ -354,7 +354,7 @@ public class CommandCompletionTest extends ReplToolTesting {
|
||||
String[] modesWithOptions = Stream.concat(Arrays.stream(options), Arrays.stream(modes)).sorted().toArray(String[]::new);
|
||||
test(false, new String[] {"--no-startup"},
|
||||
a -> assertCompletion(a, "/se|", false, "/set "),
|
||||
a -> assertCompletion(a, "/set |", false, "editor ", "feedback ", "format ", "mode ", "prompt ", "start ", "truncation "),
|
||||
a -> assertCompletion(a, "/set |", false, "editor ", "feedback ", "format ", "indent ", "mode ", "prompt ", "start ", "truncation "),
|
||||
|
||||
// /set editor
|
||||
a -> assertCompletion(a, "/set e|", false, "editor "),
|
||||
|
@ -54,7 +54,7 @@ public class HistoryUITest extends UITesting {
|
||||
waitOutput(out, PROMPT);
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^void test2\\(\\) \\{\n" +
|
||||
CONTINUATION_PROMPT + "System.err.println\\(2\\);\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(2\\);\n" +
|
||||
CONTINUATION_PROMPT + "\\}");
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^\u001b\\[A");
|
||||
@ -62,13 +62,13 @@ public class HistoryUITest extends UITesting {
|
||||
waitOutput(out, "^\u001b\\[A");
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^\u001b\\[8C1\n" +
|
||||
"\u001b\\[19C1\n\u001b\\[C");
|
||||
"\u001b\\[23C1\n\u001b\\[C");
|
||||
inputSink.write(DOWN);
|
||||
waitOutput(out, "^\u001B\\[2A\u001b\\[8C2\n" +
|
||||
"\u001b\\[19C2\n\u001b\\[C");
|
||||
"\u001b\\[23C2\n\u001b\\[C");
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^\u001b\\[A");
|
||||
for (int i = 0; i < 19; i++) inputSink.write("\033[C");
|
||||
for (int i = 0; i < 23; i++) inputSink.write("\033[C");
|
||||
waitOutput(out, "C");
|
||||
inputSink.write("\u0008\"Modified!\"\n");
|
||||
waitOutput(out, PROMPT);
|
||||
|
76
test/langtools/jdk/jshell/IndentUITest.java
Normal file
76
test/langtools/jdk/jshell/IndentUITest.java
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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 8241950
|
||||
* @summary Check the UI behavior of indentation
|
||||
* @library /tools/lib
|
||||
* @modules
|
||||
* jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.jshell/jdk.internal.jshell.tool.resources:open
|
||||
* jdk.jshell/jdk.jshell:open
|
||||
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
|
||||
* @build Compiler UITesting
|
||||
* @compile IndentUITest.java
|
||||
* @run testng IndentUITest
|
||||
*/
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class IndentUITest extends UITesting {
|
||||
|
||||
public IndentUITest() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
public void testIdent() throws Exception {
|
||||
doRunTest((inputSink, out) -> {
|
||||
inputSink.write("void test1() {\nSystem.err.println(1);\n}\n");
|
||||
waitOutput(out, "void test1\\(\\)\u001B\\[2D\u001B\\[2C \\{\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + " \\}\u001B\\[2A\u001B\\[8C\n\n\u001B\\[K\\}\n" +
|
||||
"\u001B\\[\\?2004l\\| created method test1\\(\\)\n" +
|
||||
"\u001B\\[\\?2004h" + PROMPT);
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^void test1\\(\\) \\{\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\);\n" +
|
||||
CONTINUATION_PROMPT + "\\}");
|
||||
inputSink.write(DOWN);
|
||||
inputSink.write("/set indent 2\n");
|
||||
inputSink.write("void test2() {\nSystem.err.println(1);\n}\n");
|
||||
waitOutput(out, "void test2\\(\\)\u001B\\[2D\u001B\\[2C \\{\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + " \\}\u001B\\[2A\u001B\\[10C\n\n\u001B\\[K\\}\n" +
|
||||
"\u001B\\[\\?2004l\\| created method test2\\(\\)\n" +
|
||||
"\u001B\\[\\?2004h" + PROMPT);
|
||||
inputSink.write(UP);
|
||||
waitOutput(out, "^void test2\\(\\) \\{\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\);\n" +
|
||||
CONTINUATION_PROMPT + "\\}");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -62,12 +62,12 @@ public class PasteAndMeasurementsUITest extends UITesting {
|
||||
inputSink.write("void test1() {\nSystem.err.println(1);\n}\n" + //LOC +
|
||||
"void test2() {\nSystem.err.println(1);\n}\n"/* + LOC + LOC + LOC + LOC + LOC*/);
|
||||
waitOutput(out, "void test1\\(\\)\u001B\\[2D\u001B\\[2C \\{\n" +
|
||||
CONTINUATION_PROMPT + "System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + "\\}\u001B\\[2A\u001B\\[12C\n\n\u001B\\[C\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + " \\}\u001B\\[2A\u001B\\[8C\n\n\u001B\\[K\\}\n" +
|
||||
"\u001B\\[\\?2004l\\| created method test1\\(\\)\n" +
|
||||
"\u001B\\[\\?2004h" + PROMPT + "void test2\\(\\)\u001B\\[2D\u001B\\[2C \\{\n" +
|
||||
CONTINUATION_PROMPT + "System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + "\\}\u001B\\[2A\u001B\\[12C\n\n\u001B\\[C\n" +
|
||||
CONTINUATION_PROMPT + " System.err.println\\(1\\)\u001B\\[3D\u001B\\[3C;\n" +
|
||||
CONTINUATION_PROMPT + " \\}\u001B\\[2A\u001B\\[8C\n\n\u001B\\[K\\}\n" +
|
||||
"\u001B\\[\\?2004l\\| created method test2\\(\\)\n" +
|
||||
"\u001B\\[\\?2004h" + PROMPT);
|
||||
});
|
||||
|
@ -268,6 +268,7 @@ public class ReplToolTesting {
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
prefsMap = new HashMap<>();
|
||||
prefsMap.put("INDENT", "0");
|
||||
envvars = new HashMap<>();
|
||||
System.setProperty("jshell.test.allow.incomplete.inputs", "true");
|
||||
}
|
||||
|
@ -879,4 +879,15 @@ public class ToolBasicTest extends ReplToolTesting {
|
||||
);
|
||||
}
|
||||
|
||||
public void testIndent() { //8223688
|
||||
prefsMap.remove("INDENT");
|
||||
test(false, new String[]{"--no-startup"},
|
||||
a -> assertCommand(a, "/set indent", "| /set indent 4"),
|
||||
a -> assertCommand(a, "/set indent 2", "| Indent level set to: 2"),
|
||||
a -> assertCommand(a, "/set indent", "| /set indent 2"),
|
||||
a -> assertCommand(a, "/set indent broken", "| Invalid indent level: broken"),
|
||||
a -> assertCommandOutputContains(a, "/set", "| /set indent 2")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user