8182270: JShell API: Tools need snippet information without evaluating snippet
authorrfield
Wed, 16 Aug 2017 18:42:11 -0700
changeset 46185 f4c981fc7818
parent 46184 f1325703ea85
child 46186 5e941736c260
8182270: JShell API: Tools need snippet information without evaluating snippet 8166334: jshell tool: shortcut: expression/statement to method Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/resources/l10n.properties
langtools/test/jdk/jshell/AnalyzeSnippetTest.java
langtools/test/jdk/jshell/MergedTabShiftTabCommandTest.java
langtools/test/jdk/jshell/MergedTabShiftTabExpressionTest.java
langtools/test/jdk/jshell/ToolShiftTabTest.java
langtools/test/jdk/jshell/ToolTabCommandTest.java
langtools/test/jdk/jshell/ToolTabSnippetTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Wed Aug 16 18:42:11 2017 -0700
@@ -64,6 +64,11 @@
 import jdk.internal.jshell.tool.StopDetectingInputStream.State;
 import jdk.internal.misc.Signal;
 import jdk.internal.misc.Signal.Handler;
+import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.Snippet;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import jdk.jshell.VarSnippet;
 
 class ConsoleIOContext extends IOContext {
 
@@ -916,6 +921,111 @@
                 return new FixResult(fixes, null);
             }
         },
+        new FixComputer('m', false) { //compute "Introduce method" Fix:
+            private void performToMethod(ConsoleReader in, String type, String code) throws IOException {
+                in.redrawLine();
+                if (!code.trim().endsWith(";")) {
+                    in.putString(";");
+                }
+                in.putString(" }");
+                in.setCursorPosition(0);
+                String afterCursor = type.equals("void")
+                        ? "() { "
+                        : "() { return ";
+                in.putString(type + " " + afterCursor);
+                // position the cursor where the method name should be entered (before parens)
+                in.setCursorPosition(in.getCursorBuffer().cursor - afterCursor.length());
+                in.flush();
+            }
+
+            private FixResult reject(JShellTool repl, String messageKey) {
+                return new FixResult(Collections.emptyList(), repl.messageFormat(messageKey));
+            }
+
+            @Override
+            public FixResult compute(JShellTool repl, String code, int cursor) {
+                final String codeToCursor = code.substring(0, cursor);
+                final String type;
+                final CompletionInfo ci = repl.analysis.analyzeCompletion(codeToCursor);
+                if (!ci.remaining().isEmpty()) {
+                    return reject(repl, "jshell.console.exprstmt");
+                }
+                switch (ci.completeness()) {
+                    case COMPLETE:
+                    case COMPLETE_WITH_SEMI:
+                    case CONSIDERED_INCOMPLETE:
+                        break;
+                    case EMPTY:
+                        return reject(repl, "jshell.console.empty");
+                    case DEFINITELY_INCOMPLETE:
+                    case UNKNOWN:
+                    default:
+                        return reject(repl, "jshell.console.erroneous");
+                }
+                List<Snippet> snl = repl.analysis.sourceToSnippets(ci.source());
+                if (snl.size() != 1) {
+                    return reject(repl, "jshell.console.erroneous");
+                }
+                Snippet sn = snl.get(0);
+                switch (sn.kind()) {
+                    case EXPRESSION:
+                        type = ((ExpressionSnippet) sn).typeName();
+                        break;
+                    case STATEMENT:
+                        type = "void";
+                        break;
+                    case VAR:
+                        if (sn.subKind() != SubKind.TEMP_VAR_EXPRESSION_SUBKIND) {
+                            // only valid var is an expression turned into a temp var
+                            return reject(repl, "jshell.console.exprstmt");
+                        }
+                        type = ((VarSnippet) sn).typeName();
+                        break;
+                    case IMPORT:
+                    case METHOD:
+                    case TYPE_DECL:
+                        return reject(repl, "jshell.console.exprstmt");
+                    case ERRONEOUS:
+                    default:
+                        return reject(repl, "jshell.console.erroneous");
+                }
+                List<Fix> fixes = new ArrayList<>();
+                fixes.add(new Fix() {
+                    @Override
+                    public String displayName() {
+                        return repl.messageFormat("jshell.console.create.method");
+                    }
+
+                    @Override
+                    public void perform(ConsoleReader in) throws IOException {
+                        performToMethod(in, type, codeToCursor);
+                    }
+                });
+                int idx = type.lastIndexOf(".");
+                if (idx > 0) {
+                    String stype = type.substring(idx + 1);
+                    QualifiedNames res = repl.analysis.listQualifiedNames(stype, stype.length());
+                    if (res.isUpToDate() && res.getNames().contains(type)
+                            && !res.isResolvable()) {
+                        fixes.add(new Fix() {
+                            @Override
+                            public String displayName() {
+                                return "import: " + type + ". " +
+                                        repl.messageFormat("jshell.console.create.method");
+                            }
+
+                            @Override
+                            public void perform(ConsoleReader in) throws IOException {
+                                repl.processCompleteSource("import " + type + ";");
+                                in.println("Imported: " + type);
+                                performToMethod(in, stype, codeToCursor);
+                            }
+                        });
+                    }
+                }
+                return new FixResult(fixes, null);
+            }
+        },
         new FixComputer('i', true) { //compute "Add import" Fixes:
             @Override
             public FixResult compute(JShellTool repl, String code, int cursor) {
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties	Wed Aug 16 18:42:11 2017 -0700
@@ -174,10 +174,19 @@
 jshell.console.choice = Choice: \
 
 jshell.console.create.variable = Create variable
+jshell.console.create.method = Create method
 jshell.console.resolvable = \nThe identifier is resolvable in this context.
 jshell.console.no.candidate = \nNo candidate fully qualified names found to import.
 jshell.console.incomplete = \nResults may be incomplete; try again later for complete results.
+jshell.console.erroneous = \nIncomplete or erroneous. A single valid expression or statement must proceed Shift-<tab> m.
+jshell.console.exprstmt = \nA single valid expression or statement must proceed Shift-<tab> m.
+jshell.console.empty = \nEmpty entry. A single valid expression or statement must proceed Shift-<tab> m..
 
+jshell.fix.wrong.shortcut =\
+Unexpected character after Shift-Tab.\n\
+Use "i" for auto-import, "v" for variable creation, or "m" for method creation.\n\
+For more information see:\n\
+   /help shortcuts
 
 help.usage = \
 Usage:   jshell <options> <load files>\n\
@@ -549,6 +558,11 @@
         After a complete expression, hold down <shift> while pressing <tab>,\n\t\t\
         then release and press "v", the expression will be converted to\n\t\t\
         a variable declaration whose type is based on the type of the expression.\n\n\
+Shift-<tab> m\n\t\t\
+        After a complete expression or statement, hold down <shift> while pressing <tab>,\n\t\t\
+        then release and press "m", the expression or statement will be converted to\n\t\t\
+        a method declaration. If an expression, the return type is based on the type\n\t\t\
+        of the expression.\n\n\
 Shift-<tab> i\n\t\t\
         After an unresolvable identifier, hold down <shift> while pressing <tab>,\n\t\t\
         then release and press "i", and jshell will propose possible imports\n\t\t\
@@ -1021,7 +1035,3 @@
 /set format silent errorpre '|  '    \n\
 /set format silent errorpost '%n'    \n\
 /set format silent display ''    \n
-
-jshell.fix.wrong.shortcut =\
-Unexpected character after Shift-Tab.  Use "i" for auto-import or "v" for variable creation.  For more information see:\n\
-   /help shortcuts
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Wed Aug 16 18:42:11 2017 -0700
@@ -91,6 +91,9 @@
 
     private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\p{javaWhitespace}+(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))");
 
+    // for uses that should not change state -- non-evaluations
+    private boolean preserveState = false;
+
     private int varNumber = 0;
 
     private final JShell state;
@@ -145,6 +148,23 @@
     /**
      * Converts the user source of a snippet into a Snippet object (or list of
      * objects in the case of: int x, y, z;).  Does not install the Snippets
+     * or execute them.  Does not change any state.
+     *
+     * @param userSource the source of the snippet
+     * @return usually a singleton list of Snippet, but may be empty or multiple
+     */
+    List<Snippet> toScratchSnippets(String userSource) {
+        try {
+            preserveState = true;
+            return sourceToSnippets(userSource);
+        } finally {
+            preserveState = false;
+        }
+    }
+
+    /**
+     * Converts the user source of a snippet into a Snippet object (or list of
+     * objects in the case of: int x, y, z;).  Does not install the Snippets
      * or execute them.
      *
      * @param userSource the source of the snippet
@@ -316,11 +336,15 @@
                 subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
             }
             if (shouldGenTempVar(subkind)) {
-                if (state.tempVariableNameGenerator != null) {
-                    name = state.tempVariableNameGenerator.get();
-                }
-                while (name == null || state.keyMap.doesVariableNameExist(name)) {
-                    name = "$" + ++varNumber;
+                if (preserveState) {
+                    name = "$$";
+                } else {
+                    if (state.tempVariableNameGenerator != null) {
+                        name = state.tempVariableNameGenerator.get();
+                    }
+                    while (name == null || state.keyMap.doesVariableNameExist(name)) {
+                        name = "$" + ++varNumber;
+                    }
                 }
                 guts = Wrap.tempVarWrap(compileSource, typeName, name);
                 Collection<String> declareReferences = null; //TODO
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Wed Aug 16 18:42:11 2017 -0700
@@ -862,7 +862,7 @@
      * Check if this JShell has been closed
      * @throws IllegalStateException if it is closed
      */
-    private void checkIfAlive()  throws IllegalStateException {
+    void checkIfAlive()  throws IllegalStateException {
         if (closed) {
             throw new IllegalStateException(messageFormat("jshell.exc.closed", this));
         }
@@ -879,8 +879,8 @@
         if (sn == null) {
             throw new NullPointerException(messageFormat("jshell.exc.null"));
         } else {
-            if (sn.key().state() != this) {
-                throw new IllegalArgumentException(messageFormat("jshell.exc.alien"));
+            if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) {
+                throw new IllegalArgumentException(messageFormat("jshell.exc.alien", sn.toString()));
             }
             return sn;
         }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java	Wed Aug 16 18:42:11 2017 -0700
@@ -552,6 +552,8 @@
         }
     }
 
+    static final String UNASSOCIATED_ID = "*UNASSOCIATED*";
+
     private final Key key;
     private final String source;
     private final Wrap guts;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java	Wed Aug 16 18:42:11 2017 -0700
@@ -133,6 +133,34 @@
     public abstract List<SnippetWrapper> wrappers(String input);
 
     /**
+     * Converts the source code of a snippet into a {@link Snippet} object (or
+     * list of {@code Snippet} objects in the case of some var declarations,
+     * e.g.: int x, y, z;).
+     * Does not install the snippets: declarations are not
+     * accessible by other snippets; imports are not added.
+     * Does not execute the snippets.
+     * <p>
+     * Queries may be done on the {@code Snippet} object. The {@link Snippet#id()}
+     * will be {@code "*UNASSOCIATED*"}.
+     * The returned snippets are not associated with the
+     * {@link JShell} instance, so attempts to pass them to {@code JShell}
+     * methods will throw an {@code IllegalArgumentException}.
+     * They will not appear in queries for snippets --
+     * for example, {@link JShell#snippets() }.
+     * <p>
+     * Restrictions on the input are as in {@link JShell#eval}.
+     * <p>
+     * Only preliminary compilation is performed, sufficient to build the
+     * {@code Snippet}.  Snippets known to be erroneous, are returned as
+     * {@link ErroneousSnippet}, other snippets may or may not be in error.
+     * <p>
+     * @param input The input String to convert
+     * @return usually a singleton list of Snippet, but may be empty or multiple
+     * @throws IllegalStateException if the {@code JShell} instance is closed.
+     */
+    public abstract List<Snippet> sourceToSnippets(String input);
+
+    /**
      * Returns a collection of {@code Snippet}s which might need updating if the
      * given {@code Snippet} is updated. The returned collection is designed to
      * be inclusive and may include many false positives.
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Wed Aug 16 18:42:11 2017 -0700
@@ -533,6 +533,16 @@
     }
 
     @Override
+    public List<Snippet> sourceToSnippets(String input) {
+        proc.checkIfAlive();
+        List<Snippet> snl = proc.eval.toScratchSnippets(input);
+        for (Snippet sn : snl) {
+            sn.setId(Snippet.UNASSOCIATED_ID);
+        }
+        return snl;
+    }
+
+    @Override
     public Collection<Snippet> dependents(Snippet snippet) {
         return proc.maps.getDependents(snippet);
     }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/resources/l10n.properties	Tue Aug 15 13:16:32 2017 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/resources/l10n.properties	Wed Aug 16 18:42:11 2017 -0700
@@ -29,6 +29,6 @@
 jshell.diag.modifier.single.ignore = Modifier {0} not permitted in top-level declarations, ignored
 
 jshell.exc.null = Snippet must not be null
-jshell.exc.alien = Snippet not from this JShell
+jshell.exc.alien = Snippet not from this JShell: {0}
 jshell.exc.closed = JShell ({0}) has been closed.
 jshell.exc.var.not.valid = Snippet parameter of varValue() {0} must be VALID, it is: {1}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/AnalyzeSnippetTest.java	Wed Aug 16 18:42:11 2017 -0700
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2017, 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 8182270
+ * @summary test non-eval Snippet analysis
+ * @build KullaTesting TestingInputStream
+ * @run testng AnalyzeSnippetTest
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.List;
+import jdk.jshell.Snippet;
+import jdk.jshell.DeclarationSnippet;
+import org.testng.annotations.Test;
+
+import jdk.jshell.JShell;
+import jdk.jshell.MethodSnippet;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.ErroneousSnippet;
+import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.ImportSnippet;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.SourceCodeAnalysis;
+import jdk.jshell.StatementSnippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import static jdk.jshell.Snippet.SubKind.*;
+
+@Test
+public class AnalyzeSnippetTest {
+
+    JShell state;
+    SourceCodeAnalysis sca;
+
+    @BeforeMethod
+    public void setUp() {
+        state = JShell.builder()
+                .out(new PrintStream(new ByteArrayOutputStream()))
+                .err(new PrintStream(new ByteArrayOutputStream()))
+                .build();
+        sca = state.sourceCodeAnalysis();
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        if (state != null) {
+            state.close();
+        }
+        state = null;
+        sca = null;
+    }
+
+    public void testImport() {
+        ImportSnippet sn = (ImportSnippet) assertSnippet("import java.util.List;",
+                SubKind.SINGLE_TYPE_IMPORT_SUBKIND);
+        assertEquals(sn.name(), "List");
+        sn = (ImportSnippet) assertSnippet("import static java.nio.file.StandardOpenOption.CREATE;",
+                SubKind.SINGLE_STATIC_IMPORT_SUBKIND);
+        assertTrue(sn.isStatic());
+    }
+
+    public void testClass() {
+        TypeDeclSnippet sn = (TypeDeclSnippet) assertSnippet("class C {}",
+                SubKind.CLASS_SUBKIND);
+        assertEquals(sn.name(), "C");
+        sn = (TypeDeclSnippet) assertSnippet("enum EE {A, B , C}",
+                SubKind.ENUM_SUBKIND);
+    }
+
+    public void testMethod() {
+        MethodSnippet sn = (MethodSnippet) assertSnippet("int m(int x) { return x + x; }",
+                SubKind.METHOD_SUBKIND);
+        assertEquals(sn.name(), "m");
+        assertEquals(sn.signature(), "(int)int");
+    }
+
+    public void testVar() {
+        VarSnippet sn = (VarSnippet) assertSnippet("int i;",
+                SubKind.VAR_DECLARATION_SUBKIND);
+        assertEquals(sn.name(), "i");
+        assertEquals(sn.typeName(), "int");
+        sn = (VarSnippet) assertSnippet("int jj = 6;",
+                SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND);
+        sn = (VarSnippet) assertSnippet("2 + 2",
+                SubKind.TEMP_VAR_EXPRESSION_SUBKIND);
+    }
+
+    public void testExpression() {
+        state.eval("int aa = 10;");
+        ExpressionSnippet sn = (ExpressionSnippet) assertSnippet("aa",
+                SubKind.VAR_VALUE_SUBKIND);
+        assertEquals(sn.name(), "aa");
+        assertEquals(sn.typeName(), "int");
+        sn = (ExpressionSnippet) assertSnippet("aa;",
+                SubKind.VAR_VALUE_SUBKIND);
+        assertEquals(sn.name(), "aa");
+        assertEquals(sn.typeName(), "int");
+        sn = (ExpressionSnippet) assertSnippet("aa = 99",
+                SubKind.ASSIGNMENT_SUBKIND);
+    }
+
+    public void testStatement() {
+        StatementSnippet sn = (StatementSnippet) assertSnippet("System.out.println(33)",
+                SubKind.STATEMENT_SUBKIND);
+        sn = (StatementSnippet) assertSnippet("if (true) System.out.println(33);",
+                SubKind.STATEMENT_SUBKIND);
+    }
+
+    public void testErroneous() {
+        ErroneousSnippet sn = (ErroneousSnippet) assertSnippet("+++",
+                SubKind.UNKNOWN_SUBKIND);
+        sn = (ErroneousSnippet) assertSnippet("abc",
+                SubKind.UNKNOWN_SUBKIND);
+    }
+
+    public void testNoStateChange() {
+        assertSnippet("int a = 5;", SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND);
+        assertSnippet("a", SubKind.UNKNOWN_SUBKIND);
+        VarSnippet vsn = (VarSnippet) state.eval("int aa = 10;").get(0).snippet();
+        assertSnippet("++aa;", SubKind.TEMP_VAR_EXPRESSION_SUBKIND);
+        assertEquals(state.varValue(vsn), "10");
+        assertSnippet("class CC {}", SubKind.CLASS_SUBKIND);
+        assertSnippet("new CC();", SubKind.UNKNOWN_SUBKIND);
+    }
+
+    private Snippet assertSnippet(String input, SubKind sk) {
+        List<Snippet> sns = sca.sourceToSnippets(input);
+        assertEquals(sns.size(), 1, "snippet count");
+        Snippet sn = sns.get(0);
+        assertEquals(sn.id(), "*UNASSOCIATED*");
+        assertEquals(sn.subKind(), sk);
+        return sn;
+    }
+}
--- a/langtools/test/jdk/jshell/MergedTabShiftTabCommandTest.java	Tue Aug 15 13:16:32 2017 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- * Copyright (c) 2017, 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 8177076
- * @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
- * @library /tools/lib
- * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
- * @build Compiler UITesting
- * @build MergedTabShiftTabCommandTest
- * @run testng MergedTabShiftTabCommandTest
- */
-
-import java.util.regex.Pattern;
-
-import org.testng.annotations.Test;
-
-@Test
-public class MergedTabShiftTabCommandTest extends UITesting {
-
-    public void testCommand() throws Exception {
-        // set terminal height so that help output won't hit page breaks
-        System.setProperty("test.terminal.height", "1000000");
-
-        doRunTest((inputSink, out) -> {
-            inputSink.write("1\n");
-            waitOutput(out, "\u0005");
-            inputSink.write("/\011");
-            waitOutput(out, ".*/edit.*/list.*\n\n" + Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n\r\u0005/");
-            inputSink.write("\011");
-            waitOutput(out,   ".*\n/edit\n" + Pattern.quote(getResource("help.edit.summary")) +
-                            "\n.*\n/list\n" + Pattern.quote(getResource("help.list.summary")) +
-                            ".*\n\n" + Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/");
-            inputSink.write("\011");
-            waitOutput(out,  "/!\n" +
-                            Pattern.quote(getResource("help.bang")) + "\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.command.doc")) + "\n" +
-                            "\r\u0005/");
-            inputSink.write("\011");
-            waitOutput(out,  "/-<n>\n" +
-                            Pattern.quote(getResource("help.previous")) + "\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.command.doc")) + "\n" +
-                            "\r\u0005/");
-
-            inputSink.write("ed\011");
-            waitOutput(out, "edit $");
-
-            inputSink.write("\011");
-            waitOutput(out, ".*-all.*" +
-                            "\n\n" + Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n\r\u0005/");
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.edit.summary")) + "\n\n" +
-                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/edit ");
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.edit").replaceAll("\t", "    ")));
-
-            inputSink.write("\u0003/env \011");
-            waitOutput(out, "\u0005/env -\n" +
-                            "-add-exports    -add-modules    -class-path     -module-path    \n" +
-                            "\r\u0005/env -");
-
-            inputSink.write("\011");
-            waitOutput(out, "-add-exports    -add-modules    -class-path     -module-path    \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n" +
-                            "\r\u0005/env -");
-
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.env.summary")) + "\n\n" +
-                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n" +
-                            "\r\u0005/env -");
-
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.env").replaceAll("\t", "    ")) + "\n" +
-                            "\r\u0005/env -");
-
-            inputSink.write("\011");
-            waitOutput(out, "-add-exports    -add-modules    -class-path     -module-path    \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n" +
-                            "\r\u0005/env -");
-
-            inputSink.write("\u0003/exit \011");
-            waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
-                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.exit")) + "\n" +
-                            "\r\u0005/exit ");
-            inputSink.write("\011");
-            waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
-                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
-            inputSink.write("\u0003/doesnotexist\011");
-            waitOutput(out, "\u0005/doesnotexist\n" +
-                            Pattern.quote(getResource("jshell.console.no.such.command")) + "\n" +
-                            "\n" +
-                            "\r\u0005/doesnotexist");
-        });
-    }
-
-}
--- a/langtools/test/jdk/jshell/MergedTabShiftTabExpressionTest.java	Tue Aug 15 13:16:32 2017 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-/*
- * Copyright (c) 2017, 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 8177076
- * @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
- * @library /tools/lib
- * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
- * @build Compiler UITesting
- * @build MergedTabShiftTabExpressionTest
- * @run testng/timeout=300 MergedTabShiftTabExpressionTest
- */
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.regex.Pattern;
-
-import org.testng.annotations.Test;
-
-@Test
-public class MergedTabShiftTabExpressionTest extends UITesting {
-
-    public void testExpression() throws Exception {
-        Path classes = prepareZip();
-        doRunTest((inputSink, out) -> {
-            inputSink.write("/env -class-path " + classes.toString() + "\n");
-            waitOutput(out, Pattern.quote(getResource("jshell.msg.set.restore")) + "\n\u0005");
-            inputSink.write("import jshelltest.*;\n");
-            waitOutput(out, "\n\u0005");
-
-            //-> <tab>
-            inputSink.write("\011");
-            waitOutput(out, getMessage("jshell.console.completion.all.completions.number", "[0-9]+"));
-            inputSink.write("\011");
-            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005");
-
-            //new JShellTes<tab>
-            inputSink.write("new JShellTes\011");
-            waitOutput(out, "t\nJShellTest\\(      JShellTestAux\\(   \n\r\u0005new JShellTest");
-
-            //new JShellTest<tab>
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
-                            "jshelltest.JShellTest\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
-                            "\r\u0005new JShellTest");
-            inputSink.write("\011");
-            waitOutput(out, "jshelltest.JShellTest\n" +
-                            "JShellTest 0\n" +
-                            "\r\u0005new JShellTest");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
-                            "jshelltest.JShellTest\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
-                            "\r\u0005new JShellTest");
-
-            //new JShellTest(<tab>
-            inputSink.write("(\011");
-            waitOutput(out, "\\(\n" +
-                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
-                            "JShellTest\\(String str\\)\n" +
-                            "JShellTest\\(String str, int i\\)\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(String str\\)\n" +
-                            "JShellTest 1\n" +
-                            "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.page")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.javadoc")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(String str, int i\\)\n" +
-                            "JShellTest 2\n" +
-                            "\n" +
-                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005new JShellTest\\(");
-
-            inputSink.write("\u0003String str = \"\";\nnew JShellTest(");
-            waitOutput(out, "\u0005new JShellTest\\(");
-
-            inputSink.write("\011");
-            waitOutput(out, "\n" +
-                            "str   \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
-                            "JShellTest\\(String str\\)\n" +
-                            "JShellTest\\(String str, int i\\)\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(String str\\)\n" +
-                            "JShellTest 1\n" +
-                            "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.page")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.next.javadoc")) + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(String str, int i\\)\n" +
-                            "JShellTest 2\n" +
-                            "\n" +
-                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
-                            "\r\u0005new JShellTest\\(");
-            inputSink.write("\011");
-            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005new JShellTest\\(");
-
-            inputSink.write("\u0003JShellTest t = new JShellTest\011");
-            waitOutput(out, "\u0005JShellTest t = new JShellTest\n" +
-                            "JShellTest\\(   \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
-                            "jshelltest.JShellTest\n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.completion.all.completions")) + "\n" +
-                            "\r\u0005JShellTest t = new JShellTest");
-            inputSink.write("\011");
-            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
-                            "\n" +
-                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
-                            "\r\u0005JShellTest t = new JShellTest");
-
-            inputSink.write("\u0003JShellTest t = new \011");
-            waitOutput(out, "\u0005JShellTest t = new \n" +
-                            "JShellTest\\(   \n" +
-                            "\n" +
-                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
-                            "\r\u0005JShellTest t = new ");
-            inputSink.write("\011");
-            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005JShellTest t = new ");
-
-            inputSink.write("\u0003class JShelX{}\n");
-            inputSink.write("new JShel\011");
-            waitOutput(out, "\u0005new JShel\n" +
-                            "JShelX\\(\\)         JShellTest\\(      JShellTestAux\\(   \n" +
-                            "\r\u0005new JShel");
-
-            //no crash:
-            inputSink.write("\u0003new Stringbuil\011");
-            waitOutput(out, "\u0005new Stringbuil\u0007");
-        });
-    }
-
-    private Path prepareZip() {
-        String clazz1 =
-                "package jshelltest;\n" +
-                "/**JShellTest 0" +
-                " */\n" +
-                "public class JShellTest {\n" +
-                "    /**JShellTest 1\n" +
-                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
-                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
-                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
-                "     */\n" +
-                "    public JShellTest(String str) {}\n" +
-                "    /**JShellTest 2" +
-                "     */\n" +
-                "    public JShellTest(String str, int i) {}\n" +
-                "}\n";
-
-        String clazz2 =
-                "package jshelltest;\n" +
-                "/**JShellTestAux 0" +
-                " */\n" +
-                "public class JShellTestAux {\n" +
-                "    /**JShellTest 1" +
-                "     */\n" +
-                "    public JShellTestAux(String str) { }\n" +
-                "    /**JShellTest 2" +
-                "     */\n" +
-                "    public JShellTestAux(String str, int i) { }\n" +
-                "}\n";
-
-        Path srcZip = Paths.get("src.zip");
-
-        try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
-            out.putNextEntry(new JarEntry("jshelltest/JShellTest.java"));
-            out.write(clazz1.getBytes());
-            out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java"));
-            out.write(clazz2.getBytes());
-        } catch (IOException ex) {
-            throw new IllegalStateException(ex);
-        }
-
-        compiler.compile(clazz1, clazz2);
-
-        try {
-            Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("availableSourcesOverride");
-            availableSources.setAccessible(true);
-            availableSources.set(null, Arrays.asList(srcZip));
-        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) {
-            throw new IllegalStateException(ex);
-        }
-
-        return compiler.getClassDir();
-    }
-    //where:
-        private final Compiler compiler = new Compiler();
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolShiftTabTest.java	Wed Aug 16 18:42:11 2017 -0700
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2017, 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 8166334
+ * @summary test shift-tab shortcuts "fixes"
+ * @modules
+ *     jdk.jshell/jdk.internal.jshell.tool.resources:open
+ *     jdk.jshell/jdk.jshell:open
+ * @build UITesting
+ * @build ToolShiftTabTest
+ * @run testng/timeout=300 ToolShiftTabTest
+ */
+
+import java.util.regex.Pattern;
+import org.testng.annotations.Test;
+
+@Test
+public class ToolShiftTabTest extends UITesting {
+
+    // Shift-tab as escape sequence
+    private String FIX = "\033\133\132";
+
+    public void testFixVariable() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("3+4");
+            inputSink.write(FIX + "v");
+            inputSink.write("jj\n");
+            waitOutput(out, "jj ==> 7");
+            inputSink.write("jj\n");
+            waitOutput(out, "jj ==> 7");
+        });
+    }
+
+    public void testFixMethod() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("5.5 >= 3.1415926535");
+            inputSink.write(FIX + "m");
+            waitOutput(out, "boolean ");
+            inputSink.write("mm\n");
+            waitOutput(out, "|  created method mm()");
+            inputSink.write("mm()\n");
+            waitOutput(out, "==> true");
+            inputSink.write("/method\n");
+            waitOutput(out, "boolean mm()");
+        });
+    }
+
+    public void testFixMethodVoid() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("System.out.println(\"Testing\")");
+            inputSink.write(FIX + "m");
+            inputSink.write("p\n");
+            waitOutput(out, "|  created method p()");
+            inputSink.write("p()\n");
+            waitOutput(out, "Testing");
+            inputSink.write("/method\n");
+            waitOutput(out, "void p()");
+        });
+    }
+
+    public void testFixMethodNoLeaks() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("4");
+            inputSink.write(FIX + "m");
+            inputSink.write("\u0003 55");
+            inputSink.write(FIX + "m");
+            inputSink.write("\u0003 55");
+            inputSink.write(FIX + "m");
+            inputSink.write("\u0003 55");
+            inputSink.write(FIX + "m");
+            inputSink.write("\u0003 55");
+            inputSink.write(FIX + "m");
+            inputSink.write("\u0003'X'");
+            inputSink.write(FIX + "m");
+            inputSink.write("nl\n");
+            waitOutput(out, "|  created method nl()");
+            inputSink.write("/list\n");
+            waitOutput(out, Pattern.quote("1 : char nl() { return 'X'; }"));
+            inputSink.write("true\n");
+            waitOutput(out, Pattern.quote("$2 ==> true"));
+            inputSink.write("/list\n");
+            waitOutput(out, "2 : true");
+        });
+    }
+
+    public void testFixImport() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("Frame");
+            inputSink.write(FIX + "i");
+            inputSink.write("1");
+            inputSink.write(".WIDTH\n");
+            waitOutput(out, "==> 1");
+            inputSink.write("/import\n");
+            waitOutput(out, "|    import java.awt.Frame");
+
+            inputSink.write("Object");
+            inputSink.write(FIX + "i");
+            waitOutput(out, "The identifier is resolvable in this context");
+        });
+    }
+
+    public void testFixBad() throws Exception {
+        doRunTest((inputSink, out) -> {
+            inputSink.write("123");
+            inputSink.write(FIX + "z");
+            waitOutput(out, "Unexpected character after Shift-Tab");
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolTabCommandTest.java	Wed Aug 16 18:42:11 2017 -0700
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017, 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 8177076
+ * @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
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @build Compiler UITesting
+ * @build ToolTabCommandTest
+ * @run testng ToolTabCommandTest
+ */
+
+import java.util.regex.Pattern;
+
+import org.testng.annotations.Test;
+
+@Test
+public class ToolTabCommandTest extends UITesting {
+
+    public void testCommand() throws Exception {
+        // set terminal height so that help output won't hit page breaks
+        System.setProperty("test.terminal.height", "1000000");
+
+        doRunTest((inputSink, out) -> {
+            inputSink.write("1\n");
+            waitOutput(out, "\u0005");
+            inputSink.write("/\011");
+            waitOutput(out, ".*/edit.*/list.*\n\n" + Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n\r\u0005/");
+            inputSink.write("\011");
+            waitOutput(out,   ".*\n/edit\n" + Pattern.quote(getResource("help.edit.summary")) +
+                            "\n.*\n/list\n" + Pattern.quote(getResource("help.list.summary")) +
+                            ".*\n\n" + Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/");
+            inputSink.write("\011");
+            waitOutput(out,  "/!\n" +
+                            Pattern.quote(getResource("help.bang")) + "\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.command.doc")) + "\n" +
+                            "\r\u0005/");
+            inputSink.write("\011");
+            waitOutput(out,  "/-<n>\n" +
+                            Pattern.quote(getResource("help.previous")) + "\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.command.doc")) + "\n" +
+                            "\r\u0005/");
+
+            inputSink.write("ed\011");
+            waitOutput(out, "edit $");
+
+            inputSink.write("\011");
+            waitOutput(out, ".*-all.*" +
+                            "\n\n" + Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n\r\u0005/");
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.edit.summary")) + "\n\n" +
+                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/edit ");
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.edit").replaceAll("\t", "    ")));
+
+            inputSink.write("\u0003/env \011");
+            waitOutput(out, "\u0005/env -\n" +
+                            "-add-exports    -add-modules    -class-path     -module-path    \n" +
+                            "\r\u0005/env -");
+
+            inputSink.write("\011");
+            waitOutput(out, "-add-exports    -add-modules    -class-path     -module-path    \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n" +
+                            "\r\u0005/env -");
+
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.env.summary")) + "\n\n" +
+                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n" +
+                            "\r\u0005/env -");
+
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.env").replaceAll("\t", "    ")) + "\n" +
+                            "\r\u0005/env -");
+
+            inputSink.write("\011");
+            waitOutput(out, "-add-exports    -add-modules    -class-path     -module-path    \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.synopsis")) + "\n" +
+                            "\r\u0005/env -");
+
+            inputSink.write("\u0003/exit \011");
+            waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
+                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.exit")) + "\n" +
+                            "\r\u0005/exit ");
+            inputSink.write("\011");
+            waitOutput(out, Pattern.quote(getResource("help.exit.summary")) + "\n\n" +
+                            Pattern.quote(getResource("jshell.console.see.full.documentation")) + "\n\r\u0005/exit ");
+            inputSink.write("\u0003/doesnotexist\011");
+            waitOutput(out, "\u0005/doesnotexist\n" +
+                            Pattern.quote(getResource("jshell.console.no.such.command")) + "\n" +
+                            "\n" +
+                            "\r\u0005/doesnotexist");
+        });
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolTabSnippetTest.java	Wed Aug 16 18:42:11 2017 -0700
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2017, 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 8177076
+ * @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
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @build Compiler UITesting
+ * @build ToolTabSnippetTest
+ * @run testng/timeout=300 ToolTabSnippetTest
+ */
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Pattern;
+
+import org.testng.annotations.Test;
+
+@Test
+public class ToolTabSnippetTest extends UITesting {
+
+    public void testExpression() throws Exception {
+        Path classes = prepareZip();
+        doRunTest((inputSink, out) -> {
+            inputSink.write("/env -class-path " + classes.toString() + "\n");
+            waitOutput(out, Pattern.quote(getResource("jshell.msg.set.restore")) + "\n\u0005");
+            inputSink.write("import jshelltest.*;\n");
+            waitOutput(out, "\n\u0005");
+
+            //-> <tab>
+            inputSink.write("\011");
+            waitOutput(out, getMessage("jshell.console.completion.all.completions.number", "[0-9]+"));
+            inputSink.write("\011");
+            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005");
+
+            //new JShellTes<tab>
+            inputSink.write("new JShellTes\011");
+            waitOutput(out, "t\nJShellTest\\(      JShellTestAux\\(   \n\r\u0005new JShellTest");
+
+            //new JShellTest<tab>
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
+                            "jshelltest.JShellTest\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
+                            "\r\u0005new JShellTest");
+            inputSink.write("\011");
+            waitOutput(out, "jshelltest.JShellTest\n" +
+                            "JShellTest 0\n" +
+                            "\r\u0005new JShellTest");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
+                            "jshelltest.JShellTest\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
+                            "\r\u0005new JShellTest");
+
+            //new JShellTest(<tab>
+            inputSink.write("(\011");
+            waitOutput(out, "\\(\n" +
+                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
+                            "JShellTest\\(String str\\)\n" +
+                            "JShellTest\\(String str, int i\\)\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(String str\\)\n" +
+                            "JShellTest 1\n" +
+                            "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.page")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.javadoc")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(String str, int i\\)\n" +
+                            "JShellTest 2\n" +
+                            "\n" +
+                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005new JShellTest\\(");
+
+            inputSink.write("\u0003String str = \"\";\nnew JShellTest(");
+            waitOutput(out, "\u0005new JShellTest\\(");
+
+            inputSink.write("\011");
+            waitOutput(out, "\n" +
+                            "str   \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
+                            "JShellTest\\(String str\\)\n" +
+                            "JShellTest\\(String str, int i\\)\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(String str\\)\n" +
+                            "JShellTest 1\n" +
+                            "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.page")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.next.javadoc")) + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(String str, int i\\)\n" +
+                            "JShellTest 2\n" +
+                            "\n" +
+                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
+                            "\r\u0005new JShellTest\\(");
+            inputSink.write("\011");
+            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005new JShellTest\\(");
+
+            inputSink.write("\u0003JShellTest t = new JShellTest\011");
+            waitOutput(out, "\u0005JShellTest t = new JShellTest\n" +
+                            "JShellTest\\(   \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.completion.current.signatures")) + "\n" +
+                            "jshelltest.JShellTest\n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.completion.all.completions")) + "\n" +
+                            "\r\u0005JShellTest t = new JShellTest");
+            inputSink.write("\011");
+            waitOutput(out, "JShellTest\\(      JShellTestAux\\(   \n" +
+                            "\n" +
+                            Pattern.quote(getResource("jshell.console.see.documentation")) + "\n" +
+                            "\r\u0005JShellTest t = new JShellTest");
+
+            inputSink.write("\u0003JShellTest t = new \011");
+            waitOutput(out, "\u0005JShellTest t = new \n" +
+                            "JShellTest\\(   \n" +
+                            "\n" +
+                            getMessage("jshell.console.completion.all.completions.number", "[0-9]+") + "\n" +
+                            "\r\u0005JShellTest t = new ");
+            inputSink.write("\011");
+            waitOutput(out, ".*String.*StringBuilder.*\n\r\u0005JShellTest t = new ");
+
+            inputSink.write("\u0003class JShelX{}\n");
+            inputSink.write("new JShel\011");
+            waitOutput(out, "\u0005new JShel\n" +
+                            "JShelX\\(\\)         JShellTest\\(      JShellTestAux\\(   \n" +
+                            "\r\u0005new JShel");
+
+            //no crash:
+            inputSink.write("\u0003new Stringbuil\011");
+            waitOutput(out, "\u0005new Stringbuil\u0007");
+        });
+    }
+
+    private Path prepareZip() {
+        String clazz1 =
+                "package jshelltest;\n" +
+                "/**JShellTest 0" +
+                " */\n" +
+                "public class JShellTest {\n" +
+                "    /**JShellTest 1\n" +
+                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
+                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
+                "     * <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1 <p>1\n" +
+                "     */\n" +
+                "    public JShellTest(String str) {}\n" +
+                "    /**JShellTest 2" +
+                "     */\n" +
+                "    public JShellTest(String str, int i) {}\n" +
+                "}\n";
+
+        String clazz2 =
+                "package jshelltest;\n" +
+                "/**JShellTestAux 0" +
+                " */\n" +
+                "public class JShellTestAux {\n" +
+                "    /**JShellTest 1" +
+                "     */\n" +
+                "    public JShellTestAux(String str) { }\n" +
+                "    /**JShellTest 2" +
+                "     */\n" +
+                "    public JShellTestAux(String str, int i) { }\n" +
+                "}\n";
+
+        Path srcZip = Paths.get("src.zip");
+
+        try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) {
+            out.putNextEntry(new JarEntry("jshelltest/JShellTest.java"));
+            out.write(clazz1.getBytes());
+            out.putNextEntry(new JarEntry("jshelltest/JShellTestAux.java"));
+            out.write(clazz2.getBytes());
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        compiler.compile(clazz1, clazz2);
+
+        try {
+            Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("availableSourcesOverride");
+            availableSources.setAccessible(true);
+            availableSources.set(null, Arrays.asList(srcZip));
+        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        return compiler.getClassDir();
+    }
+    //where:
+        private final Compiler compiler = new Compiler();
+
+}