8153402: jshell tool: completion provider for /help
authorrfield
Fri, 18 Nov 2016 09:41:51 -0800
changeset 42265 b36ad5a64e75
parent 42264 64435520b580
child 42266 512d46c824db
8153402: jshell tool: completion provider for /help 8169818: jshell tool: completion provider for /vars /methods /types gives -history Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/test/jdk/jshell/CommandCompletionTest.java
langtools/test/jdk/jshell/ReplToolTesting.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Thu Nov 17 22:18:50 2016 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Fri Nov 18 09:41:51 2016 -0800
@@ -45,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -1051,8 +1052,12 @@
     }
 
     static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
-    private static final CompletionProvider KEYWORD_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history ");
-    private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore", "-quiet");
+    private static final CompletionProvider SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start ", "-history");
+    private static final CompletionProvider SAVE_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history ");
+    private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " );
+    private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore ", "-quiet ");
+    private static final CompletionProvider RESTORE_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore");
+    private static final CompletionProvider QUIET_COMPLETION_PROVIDER = new FixedCompletionProvider("-quiet");
     private static final CompletionProvider SET_MODE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-command", "-quiet", "-delete");
     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
     private final Map<String, Command> commands = new LinkedHashMap<>();
@@ -1106,24 +1111,62 @@
                                     p.getFileName().toString().endsWith(".jar"));
     }
 
+    // Completion based on snippet supplier
     private CompletionProvider snippetCompletion(Supplier<Stream<? extends Snippet>> snippetsSupplier) {
         return (prefix, cursor, anchor) -> {
             anchor[0] = 0;
+            int space = prefix.lastIndexOf(' ');
+            Set<String> prior = new HashSet<>(Arrays.asList(prefix.split(" ")));
+            if (prior.contains("-all") || prior.contains("-history")) {
+                return Collections.emptyList();
+            }
+            String argPrefix = prefix.substring(space + 1);
             return snippetsSupplier.get()
+                        .filter(k -> !prior.contains(String.valueOf(k.id()))
+                                && (!(k instanceof DeclarationSnippet)
+                                     || !prior.contains(((DeclarationSnippet) k).name())))
                         .flatMap(k -> (k instanceof DeclarationSnippet)
-                                ? Stream.of(String.valueOf(k.id()), ((DeclarationSnippet) k).name())
-                                : Stream.of(String.valueOf(k.id())))
-                        .filter(k -> k.startsWith(prefix))
+                                ? Stream.of(String.valueOf(k.id()) + " ", ((DeclarationSnippet) k).name() + " ")
+                                : Stream.of(String.valueOf(k.id()) + " "))
+                        .filter(k -> k.startsWith(argPrefix))
                         .map(k -> new ArgSuggestion(k))
                         .collect(Collectors.toList());
         };
     }
 
-    private CompletionProvider snippetKeywordCompletion(Supplier<Stream<? extends Snippet>> snippetsSupplier) {
+    // Completion based on snippet supplier with -all -start (and sometimes -history) options
+    private CompletionProvider snippetWithOptionCompletion(CompletionProvider optionProvider,
+            Supplier<Stream<? extends Snippet>> snippetsSupplier) {
         return (code, cursor, anchor) -> {
             List<Suggestion> result = new ArrayList<>();
-            result.addAll(KEYWORD_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor));
+            int pastSpace = code.lastIndexOf(' ') + 1; // zero if no space
+            if (pastSpace == 0) {
+                result.addAll(optionProvider.completionSuggestions(code, cursor, anchor));
+            }
             result.addAll(snippetCompletion(snippetsSupplier).completionSuggestions(code, cursor, anchor));
+            anchor[0] += pastSpace;
+            return result;
+        };
+    }
+
+    // Completion of help, commands and subjects
+    private CompletionProvider helpCompletion() {
+        return (code, cursor, anchor) -> {
+            List<Suggestion> result;
+            int pastSpace = code.indexOf(' ') + 1; // zero if no space
+            if (pastSpace == 0) {
+                result = new FixedCompletionProvider(commands.values().stream()
+                        .filter(cmd -> cmd.kind.showInHelp || cmd.kind == CommandKind.HELP_SUBJECT)
+                        .map(c -> c.command + " ")
+                        .toArray(size -> new String[size]))
+                        .completionSuggestions(code, cursor, anchor);
+            } else if (code.startsWith("/se")) {
+                result = new FixedCompletionProvider(SET_SUBCOMMANDS)
+                        .completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor);
+            } else {
+                result = Collections.emptyList();
+            }
+            anchor[0] += pastSpace;
             return result;
         };
     }
@@ -1133,7 +1176,7 @@
             List<Suggestion> result = new ArrayList<>();
             int space = code.indexOf(' ');
             if (space == (-1)) {
-                result.addAll(KEYWORD_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor));
+                result.addAll(SAVE_OPTION_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor));
             }
             result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor));
             anchor[0] += space + 1;
@@ -1143,9 +1186,25 @@
 
     private static CompletionProvider reloadCompletion() {
         return (code, cursor, anchor) -> {
-            List<Suggestion> result = new ArrayList<>();
+            CompletionProvider provider;
             int pastSpace = code.indexOf(' ') + 1; // zero if no space
-            result.addAll(RELOAD_OPTIONS_COMPLETION_PROVIDER.completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor));
+            if (pastSpace == 0) {
+                provider = RELOAD_OPTIONS_COMPLETION_PROVIDER;
+            } else {
+                switch (code.substring(0, pastSpace - 1)) {
+                    case "-quiet":
+                        provider = RESTORE_COMPLETION_PROVIDER;
+                        break;
+                    case "-restore":
+                        provider = QUIET_COMPLETION_PROVIDER;
+                        break;
+                    default:
+                        provider = EMPTY_COMPLETION_PROVIDER;
+                        break;
+                }
+            }
+            List<Suggestion> result = provider.completionSuggestions(
+                    code.substring(pastSpace), cursor - pastSpace, anchor);
             anchor[0] += pastSpace;
             return result;
         };
@@ -1210,10 +1269,12 @@
     {
         registerCommand(new Command("/list",
                 arg -> cmdList(arg),
-                snippetKeywordCompletion(this::allSnippets)));
+                snippetWithOptionCompletion(SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER,
+                        this::allSnippets)));
         registerCommand(new Command("/edit",
                 arg -> cmdEdit(arg),
-                snippetCompletion(this::allSnippets)));
+                snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER,
+                        this::allSnippets)));
         registerCommand(new Command("/drop",
                 arg -> cmdDrop(arg),
                 snippetCompletion(this::dropableSnippets),
@@ -1226,13 +1287,16 @@
                 FILE_COMPLETION_PROVIDER));
         registerCommand(new Command("/vars",
                 arg -> cmdVars(arg),
-                snippetKeywordCompletion(this::allVarSnippets)));
+                snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER,
+                        this::allVarSnippets)));
         registerCommand(new Command("/methods",
                 arg -> cmdMethods(arg),
-                snippetKeywordCompletion(this::allMethodSnippets)));
+                snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER,
+                        this::allMethodSnippets)));
         registerCommand(new Command("/types",
                 arg -> cmdTypes(arg),
-                snippetKeywordCompletion(this::allTypeSnippets)));
+                snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER,
+                        this::allTypeSnippets)));
         registerCommand(new Command("/imports",
                 arg -> cmdImports(),
                 EMPTY_COMPLETION_PROVIDER));
@@ -1258,7 +1322,7 @@
                 CommandKind.HIDDEN));
         registerCommand(new Command("/help",
                 arg -> cmdHelp(arg),
-                EMPTY_COMPLETION_PROVIDER));
+                helpCompletion()));
         registerCommand(new Command("/set",
                 arg -> cmdSet(arg),
                 new ContinuousCompletionProvider(Map.of(
@@ -1276,7 +1340,7 @@
         registerCommand(new Command("/?",
                 "help.quest",
                 arg -> cmdHelp(arg),
-                EMPTY_COMPLETION_PROVIDER,
+                helpCompletion(),
                 CommandKind.NORMAL));
         registerCommand(new Command("/!",
                 "help.bang",
--- a/langtools/test/jdk/jshell/CommandCompletionTest.java	Thu Nov 17 22:18:50 2016 +0000
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java	Fri Nov 18 09:41:51 2016 -0800
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8144095 8164825
+ * @bug 8144095 8164825 8169818 8153402
  * @summary Test Command Completion
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
@@ -61,12 +61,13 @@
     public void testList() {
         test(false, new String[] {"--no-startup"},
                 a -> assertCompletion(a, "/l|", false, "/list "),
-                a -> assertCompletion(a, "/list |", false, "-all ", "-history ", "-start "),
-                a -> assertCompletion(a, "/list -h|", false, "-history "),
+                a -> assertCompletion(a, "/list |", false, "-all", "-history", "-start "),
+                a -> assertCompletion(a, "/list -h|", false, "-history"),
                 a -> assertCompletion(a, "/list q|", false),
                 a -> assertVariable(a, "int", "xray"),
-                a -> assertCompletion(a, "/list |", false, "-all ", "-history ", "-start ", "1", "xray"),
-                a -> assertCompletion(a, "/list x|", false, "xray")
+                a -> assertCompletion(a, "/list |", false, "-all", "-history", "-start ", "1 ", "xray "),
+                a -> assertCompletion(a, "/list x|", false, "xray "),
+                a -> assertCompletion(a, "/list xray |", false)
         );
     }
 
@@ -76,8 +77,8 @@
                 a -> assertClass(a, "class cTest {}", "class", "cTest"),
                 a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
                 a -> assertVariable(a, "int", "fTest"),
-                a -> assertCompletion(a, "/drop |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
-                a -> assertCompletion(a, "/drop f|", false, "fTest")
+                a -> assertCompletion(a, "/drop |", false, "1 ", "2 ", "3 ", "cTest ", "fTest ", "mTest "),
+                a -> assertCompletion(a, "/drop f|", false, "fTest ")
         );
     }
 
@@ -88,8 +89,54 @@
                 a -> assertClass(a, "class cTest {}", "class", "cTest"),
                 a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
                 a -> assertVariable(a, "int", "fTest"),
-                a -> assertCompletion(a, "/edit |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
-                a -> assertCompletion(a, "/edit f|", false, "fTest")
+                a -> assertCompletion(a, "/edit |", false,
+                        "-all" , "-start " , "1 ", "2 ", "3 ", "cTest ", "fTest ", "mTest "),
+                a -> assertCompletion(a, "/edit cTest |", false,
+                        "2 ", "3 ", "fTest ", "mTest "),
+                a -> assertCompletion(a, "/edit 1 fTest |", false,
+                        "2 ", "mTest "),
+                a -> assertCompletion(a, "/edit f|", false, "fTest "),
+                a -> assertCompletion(a, "/edit mTest f|", false, "fTest ")
+        );
+    }
+
+    public void testHelp() {
+        assertCompletion("/help |", false,
+                "/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
+                "/edit ", "/exit ", "/help ", "/history ", "/imports ",
+                "/list ", "/methods ", "/open ", "/reload ", "/reset ",
+                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
+        assertCompletion("/? |", false,
+                "/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
+                "/edit ", "/exit ", "/help ", "/history ", "/imports ",
+                "/list ", "/methods ", "/open ", "/reload ", "/reset ",
+                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
+        assertCompletion("/help /s|", false,
+                "/save ", "/set ");
+        assertCompletion("/help /set |", false,
+                "editor", "feedback", "format", "mode", "prompt", "start", "truncation");
+        assertCompletion("/help /edit |", false);
+    }
+
+    public void testReload() {
+        assertCompletion("/reload |", false, "-quiet ", "-restore ");
+        assertCompletion("/reload -restore |", false, "-quiet");
+        assertCompletion("/reload -quiet |", false, "-restore");
+        assertCompletion("/reload -restore -quiet |", false);
+    }
+
+    public void testVarsMethodsTypes() {
+        test(false, new String[]{"--no-startup"},
+                a -> assertCompletion(a, "/v|", false, "/vars "),
+                a -> assertCompletion(a, "/m|", false, "/methods "),
+                a -> assertCompletion(a, "/t|", false, "/types "),
+                a -> assertClass(a, "class cTest {}", "class", "cTest"),
+                a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
+                a -> assertVariable(a, "int", "fTest"),
+                a -> assertCompletion(a, "/vars |", false, "-all", "-start ", "3 ", "fTest "),
+                a -> assertCompletion(a, "/meth |", false, "-all", "-start ", "2 ", "mTest "),
+                a -> assertCompletion(a, "/typ |", false, "-all", "-start ", "1 ", "cTest "),
+                a -> assertCompletion(a, "/var f|", false, "fTest ")
         );
     }
 
--- a/langtools/test/jdk/jshell/ReplToolTesting.java	Thu Nov 17 22:18:50 2016 +0000
+++ b/langtools/test/jdk/jshell/ReplToolTesting.java	Fri Nov 18 09:41:51 2016 -0800
@@ -476,7 +476,7 @@
         code = code.replace("|", "");
         assertTrue(cursor > -1, "'|' not found: " + code);
         List<Suggestion> completions =
-                js.commandCompletionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
+                js.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
         return completions.stream()
                           .filter(s -> isSmart == s.matchesType())
                           .map(s -> s.continuation())