--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ContinuousCompletionProvider.java Thu Sep 01 11:07:00 2016 +0900
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.internal.jshell.tool;
+
+import java.util.List;
+import static java.util.Comparator.comparing;
+import java.util.Map;
+import java.util.function.BiPredicate;
+import java.util.function.Supplier;
+import static java.util.stream.Collectors.toList;
+import java.util.stream.Stream;
+import jdk.internal.jshell.tool.JShellTool.CompletionProvider;
+import jdk.jshell.SourceCodeAnalysis;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+
+class ContinuousCompletionProvider implements CompletionProvider {
+
+ static final BiPredicate<String, String> STARTSWITH_MATCHER =
+ (word, input) -> word.startsWith(input);
+ static final BiPredicate<String, String> PERFECT_MATCHER =
+ (word, input) -> word.equals(input);
+
+ private final Supplier<Map<String, CompletionProvider>> wordCompletionProviderSupplier;
+ private final BiPredicate<String, String> matcher;
+
+ ContinuousCompletionProvider(
+ Map<String, CompletionProvider> wordCompletionProvider,
+ BiPredicate<String, String> matcher) {
+ this(() -> wordCompletionProvider, matcher);
+ }
+
+ ContinuousCompletionProvider(
+ Supplier<Map<String, CompletionProvider>> wordCompletionProviderSupplier,
+ BiPredicate<String, String> matcher) {
+ this.wordCompletionProviderSupplier = wordCompletionProviderSupplier;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) {
+ String prefix = input.substring(0, cursor);
+ int space = prefix.indexOf(' ');
+
+ Stream<SourceCodeAnalysis.Suggestion> result;
+
+ Map<String, CompletionProvider> wordCompletionProvider = wordCompletionProviderSupplier.get();
+
+ if (space == (-1)) {
+ result = wordCompletionProvider.keySet().stream()
+ .distinct()
+ .filter(key -> key.startsWith(prefix))
+ .map(key -> new JShellTool.ArgSuggestion(key + " "));
+ anchor[0] = 0;
+ } else {
+ String rest = prefix.substring(space + 1);
+ String word = prefix.substring(0, space);
+
+ List<CompletionProvider> candidates = wordCompletionProvider.entrySet().stream()
+ .filter(e -> matcher.test(e.getKey(), word))
+ .map(Map.Entry::getValue)
+ .collect(toList());
+ if (candidates.size() == 1) {
+ result = candidates.get(0).completionSuggestions(rest, cursor - space - 1, anchor).stream();
+ } else {
+ result = Stream.empty();
+ }
+ anchor[0] += space + 1;
+ }
+
+ return result.sorted(comparing(Suggestion::continuation))
+ .collect(toList());
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java Wed Aug 31 10:35:51 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Feedback.java Thu Sep 01 11:07:00 2016 +0900
@@ -35,9 +35,14 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toMap;
+import static jdk.internal.jshell.tool.ContinuousCompletionProvider.PERFECT_MATCHER;
+import jdk.internal.jshell.tool.JShellTool.CompletionProvider;
+import static jdk.internal.jshell.tool.JShellTool.EMPTY_COMPLETION_PROVIDER;
/**
* Feedback customization support
@@ -146,6 +151,17 @@
.forEach(m -> m.readOnly = true);
}
+ JShellTool.CompletionProvider modeCompletions() {
+ return modeCompletions(EMPTY_COMPLETION_PROVIDER);
+ }
+
+ JShellTool.CompletionProvider modeCompletions(CompletionProvider successor) {
+ return new ContinuousCompletionProvider(
+ () -> modeMap.keySet().stream()
+ .collect(toMap(Function.identity(), m -> successor)),
+ PERFECT_MATCHER);
+ }
+
{
for (FormatCase e : FormatCase.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Aug 31 10:35:51 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Thu Sep 01 11:07:00 2016 +0900
@@ -112,6 +112,7 @@
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER;
/**
* Command line REPL tool for Java using the JShell API.
@@ -909,6 +910,7 @@
interface CompletionProvider {
List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
+
}
enum CommandKind {
@@ -953,14 +955,31 @@
}
- private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
+ 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 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<>();
private void registerCommand(Command cmd) {
commands.put(cmd.command, cmd);
}
+
+ private static CompletionProvider skipWordThenCompletion(CompletionProvider completionProvider) {
+ return (input, cursor, anchor) -> {
+ List<Suggestion> result = Collections.emptyList();
+
+ int space = input.indexOf(' ');
+ if (space != -1) {
+ String rest = input.substring(space + 1);
+ result = completionProvider.completionSuggestions(rest, cursor - space - 1, anchor);
+ anchor[0] += space + 1;
+ }
+
+ return result;
+ };
+ }
+
private static CompletionProvider fileCompletions(Predicate<Path> accept) {
return (code, cursor, anchor) -> {
int lastSlash = code.lastIndexOf('/');
@@ -1037,6 +1056,31 @@
};
}
+ private static CompletionProvider orMostSpecificCompletion(
+ CompletionProvider left, CompletionProvider right) {
+ return (code, cursor, anchor) -> {
+ int[] leftAnchor = {-1};
+ int[] rightAnchor = {-1};
+
+ List<Suggestion> leftSuggestions = left.completionSuggestions(code, cursor, leftAnchor);
+ List<Suggestion> rightSuggestions = right.completionSuggestions(code, cursor, rightAnchor);
+
+ List<Suggestion> suggestions = new ArrayList<>();
+
+ if (leftAnchor[0] >= rightAnchor[0]) {
+ anchor[0] = leftAnchor[0];
+ suggestions.addAll(leftSuggestions);
+ }
+
+ if (leftAnchor[0] <= rightAnchor[0]) {
+ anchor[0] = rightAnchor[0];
+ suggestions.addAll(rightSuggestions);
+ }
+
+ return suggestions;
+ };
+ }
+
// Snippet lists
Stream<Snippet> allSnippets() {
@@ -1123,10 +1167,26 @@
EMPTY_COMPLETION_PROVIDER));
registerCommand(new Command("/set",
arg -> cmdSet(arg),
- new FixedCompletionProvider(SET_SUBCOMMANDS)));
+ new ContinuousCompletionProvider(Map.of(
+ // need more completion for format for usability
+ "format", feedback.modeCompletions(),
+ "truncation", feedback.modeCompletions(),
+ "feedback", feedback.modeCompletions(),
+ "mode", skipWordThenCompletion(orMostSpecificCompletion(
+ feedback.modeCompletions(SET_MODE_OPTIONS_COMPLETION_PROVIDER),
+ SET_MODE_OPTIONS_COMPLETION_PROVIDER)),
+ "prompt", feedback.modeCompletions(),
+ "editor", fileCompletions(Files::isExecutable),
+ "start", FILE_COMPLETION_PROVIDER),
+ STARTSWITH_MATCHER)));
registerCommand(new Command("/retain",
arg -> cmdRetain(arg),
- new FixedCompletionProvider(RETAIN_SUBCOMMANDS)));
+ new ContinuousCompletionProvider(Map.of(
+ "feedback", feedback.modeCompletions(),
+ "mode", feedback.modeCompletions(),
+ "editor", fileCompletions(Files::isExecutable),
+ "start", FILE_COMPLETION_PROVIDER),
+ STARTSWITH_MATCHER)));
registerCommand(new Command("/?",
"help.quest",
arg -> cmdHelp(arg),
@@ -1151,36 +1211,18 @@
registerCommand(new Command("shortcuts",
"help.shortcuts",
CommandKind.HELP_SUBJECT));
+
+ commandCompletions = new ContinuousCompletionProvider(
+ commands.values().stream()
+ .filter(c -> c.kind.shouldSuggestCompletions)
+ .collect(toMap(c -> c.command, c -> c.completions)),
+ STARTSWITH_MATCHER);
}
+ private ContinuousCompletionProvider commandCompletions;
+
public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) {
- String prefix = code.substring(0, cursor);
- int space = prefix.indexOf(' ');
- Stream<Suggestion> result;
-
- if (space == (-1)) {
- result = commands.values()
- .stream()
- .distinct()
- .filter(cmd -> cmd.kind.shouldSuggestCompletions)
- .map(cmd -> cmd.command)
- .filter(key -> key.startsWith(prefix))
- .map(key -> new ArgSuggestion(key + " "));
- anchor[0] = 0;
- } else {
- String arg = prefix.substring(space + 1);
- String cmd = prefix.substring(0, space);
- Command[] candidates = findCommand(cmd, c -> true);
- if (candidates.length == 1) {
- result = candidates[0].completions.completionSuggestions(arg, cursor - space, anchor).stream();
- anchor[0] += space + 1;
- } else {
- result = Stream.empty();
- }
- }
-
- return result.sorted((s1, s2) -> s1.continuation().compareTo(s2.continuation()))
- .collect(Collectors.toList());
+ return commandCompletions.completionSuggestions(code, cursor, anchor);
}
public String commandDocumentation(String code, int cursor) {
@@ -2484,7 +2526,7 @@
}
}
- private static class ArgSuggestion implements Suggestion {
+ static class ArgSuggestion implements Suggestion {
private final String continuation;
--- a/langtools/test/jdk/jshell/CommandCompletionTest.java Wed Aug 31 10:35:51 2016 -0700
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java Thu Sep 01 11:07:00 2016 +0900
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8144095
+ * @bug 8144095 8164825
* @summary Test Command Completion
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@@ -40,6 +40,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@@ -148,6 +149,80 @@
assertCompletion("/classpath ~/|", false, completions.toArray(new String[completions.size()]));
}
+ public void testSet() throws IOException {
+ List<String> p1 = listFiles(Paths.get(""));
+ FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
+ Collections.sort(p1);
+
+ String[] modes = {"concise ", "normal ", "silent ", "verbose "};
+ String[] options = {"-command", "-delete", "-quiet"};
+ 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 "),
+
+ // /set editor
+ a -> assertCompletion(a, "/set e|", false, "editor "),
+ a -> assertCompletion(a, "/set editor |", false, p1.toArray(new String[p1.size()])),
+
+ // /set feedback
+ a -> assertCompletion(a, "/set fe|", false, "feedback "),
+ a -> assertCompletion(a, "/set fe |", false, modes),
+
+ // /set format
+ a -> assertCompletion(a, "/set fo|", false, "format "),
+ a -> assertCompletion(a, "/set fo |", false, modes),
+
+ // /set mode
+ a -> assertCompletion(a, "/set mo|", false, "mode "),
+ a -> assertCompletion(a, "/set mo |", false),
+ a -> assertCompletion(a, "/set mo newmode |", false, modesWithOptions),
+ a -> assertCompletion(a, "/set mo newmode -|", false, options),
+ a -> assertCompletion(a, "/set mo newmode -command |", false),
+ a -> assertCompletion(a, "/set mo newmode normal |", false, options),
+
+ // /set prompt
+ a -> assertCompletion(a, "/set pro|", false, "prompt "),
+ a -> assertCompletion(a, "/set pro |", false, modes),
+
+ // /set start
+ a -> assertCompletion(a, "/set st|", false, "start "),
+ a -> assertCompletion(a, "/set st |", false, p1.toArray(new String[p1.size()])),
+
+ // /set truncation
+ a -> assertCompletion(a, "/set tr|", false, "truncation "),
+ a -> assertCompletion(a, "/set tr |", false, modes)
+ );
+ }
+
+ public void testRetain() throws IOException {
+ List<String> p1 = listFiles(Paths.get(""));
+ FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
+ Collections.sort(p1);
+
+ String[] modes = {"concise ", "normal ", "silent ", "verbose "};
+ test(false, new String[] {"--no-startup"},
+ a -> assertCompletion(a, "/ret|", false, "/retain "),
+ a -> assertCompletion(a, "/retain |", false, "editor ", "feedback ", "mode ", "start "),
+
+ // /retain editor
+ a -> assertCompletion(a, "/retain e|", false, "editor "),
+ a -> assertCompletion(a, "/retain editor |", false, p1.toArray(new String[p1.size()])),
+
+ // /retain feedback
+ a -> assertCompletion(a, "/retain fe|", false, "feedback "),
+ a -> assertCompletion(a, "/retain fe |", false, modes),
+
+ // /retain mode
+ a -> assertCompletion(a, "/retain mo|", false, "mode "),
+ a -> assertCompletion(a, "/retain mo |", false, modes),
+
+ // /retain start
+ a -> assertCompletion(a, "/retain st|", false, "start "),
+ a -> assertCompletion(a, "/retain st |", false, p1.toArray(new String[p1.size()]))
+ );
+ }
+
private void createIfNeeded(Path file) throws IOException {
if (!Files.exists(file))
Files.createFile(file);