8241950: JShell could support auto-indent

Reviewed-by: rfield
This commit is contained in:
Jan Lahoda 2020-04-20 12:36:09 +02:00
parent f51088e261
commit f08b5a8006
10 changed files with 205 additions and 16 deletions

View File

@ -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.
*/

View File

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

View File

@ -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;

View File

@ -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\

View File

@ -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 "),

View File

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

View 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 + "\\}");
});
}
}

View File

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

View File

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

View File

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