@@ -97,21 +97,19 @@ public class Shell implements CommandRegistry {
|
||||
resultHandler.handleResult(e);
|
||||
continue;
|
||||
}
|
||||
if (input.words().isEmpty()) {
|
||||
if (noInput(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
String line = input.words().stream().collect(Collectors.joining(" ")).trim();
|
||||
List<String> words = input.words();
|
||||
|
||||
String command = findLongestCommand(line);
|
||||
|
||||
List<String> words = input.words();
|
||||
Object result;
|
||||
if (command != null) {
|
||||
int wordsUsedForCommandKey = command.split(" ").length;
|
||||
MethodTarget methodTarget = methodTargets.get(command);
|
||||
List<String> wordsForArgs = words.subList(wordsUsedForCommandKey, words.size());
|
||||
List<String> wordsForArgs = wordsForArguments(command, words);
|
||||
Method method = methodTarget.getMethod();
|
||||
|
||||
try {
|
||||
@@ -130,6 +128,29 @@ public class Shell implements CommandRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the parsed input ends up being empty (<em>e.g.</em> hitting ENTER on an empty line or blank space)
|
||||
*/
|
||||
private boolean noInput(Input input) {
|
||||
return input.words().isEmpty()
|
||||
|| (input.words().size() == 1 && input.words().get(0).trim().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of words to be considered for argument resolving. Drops the first N words used for the
|
||||
* command, as well as an optional empty word at the end of the list (which may be present if user added spaces
|
||||
* before submitting the buffer)
|
||||
*/
|
||||
private List<String> wordsForArguments(String command, List<String> words) {
|
||||
int wordsUsedForCommandKey = command.split(" ").length;
|
||||
List<String> args = words.subList(wordsUsedForCommandKey, words.size());
|
||||
int last = args.size() - 1;
|
||||
if (last >= 0 && "".equals(args.get(last))) {
|
||||
args.remove(last);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather completion proposals given some (incomplete) input the user has already typed in.
|
||||
* When and how this method is invoked is implementation specific and decided by the actual user interface.
|
||||
@@ -139,39 +160,30 @@ public class Shell implements CommandRegistry {
|
||||
String prefix = context.upToCursor();
|
||||
|
||||
List<CompletionProposal> candidates = new ArrayList<>();
|
||||
// Find the longest match for a command name with words in the buffer
|
||||
String best = findLongestCommand(prefix);
|
||||
if (best == null) { // no command found
|
||||
candidates.addAll(commandsStartingWith(prefix));
|
||||
return candidates;
|
||||
} // if we're here, we're either trying to complete args for command <best> (will fall through)
|
||||
// or trying to complete command whose name starts with <best> (which also happens to be a command)
|
||||
else if (prefix.equals(best)) {
|
||||
candidates.addAll(commandsStartingWith(best));
|
||||
} // valid command (<best>) followed by a suffix (but not necessarily [<space> args*])
|
||||
else if (!prefix.startsWith(best + " ")) {
|
||||
// must be an invalid command, can't do anything
|
||||
return candidates;
|
||||
}
|
||||
candidates.addAll(commandsStartingWith(prefix));
|
||||
|
||||
CompletionContext argsContext = context.drop(best.split(" ").length);
|
||||
// Try to complete arguments
|
||||
MethodTarget methodTarget = methodTargets.get(best);
|
||||
Method method = methodTarget.getMethod();
|
||||
return Arrays.stream(method.getParameters())
|
||||
.map(Utils::createMethodParameter)
|
||||
.flatMap(mp -> findResolver(mp).complete(mp, argsContext).stream())
|
||||
.collect(Collectors.toList());
|
||||
String best = findLongestCommand(prefix);
|
||||
if (best != null) {
|
||||
CompletionContext argsContext = context.drop(best.split(" ").length);
|
||||
// Try to complete arguments
|
||||
MethodTarget methodTarget = methodTargets.get(best);
|
||||
Method method = methodTarget.getMethod();
|
||||
Arrays.stream(method.getParameters())
|
||||
.map(Utils::createMethodParameter)
|
||||
.flatMap(mp -> findResolver(mp).complete(mp, argsContext).stream())
|
||||
.forEach(candidates::add);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private List<CompletionProposal> commandsStartingWith(String prefix) {
|
||||
return methodTargets.entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(prefix))
|
||||
.map(e -> toCompletionProposal(e.getKey(), e.getValue()))
|
||||
.map(e -> toCommandProposal(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private CompletionProposal toCompletionProposal(String command, MethodTarget methodTarget) {
|
||||
private CompletionProposal toCommandProposal(String command, MethodTarget methodTarget) {
|
||||
return new CompletionProposal(command)
|
||||
.dontQuote(true)
|
||||
.category("Available commands")
|
||||
|
||||
@@ -132,7 +132,6 @@ public class JLineShell {
|
||||
words = words.stream()
|
||||
.map(s -> s.replaceAll("^\\n+|\\n+$", "")) // CR at beginning/end of line introduced by backslash continuation
|
||||
.map(s -> s.replaceAll("\\n+", " ")) // CR in middle of word introduced by return inside a quoted string
|
||||
.filter(w -> w.length() > 0) // Empty word introduced when using quotes, no idea why...
|
||||
.collect(Collectors.toList());
|
||||
return words;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.springframework.stereotype.Component;
|
||||
@ShellComponent("")
|
||||
public class Commands {
|
||||
|
||||
@ShellMethod(help = "a command whose name looks the same as another one")
|
||||
@ShellMethod(help = "a command whose name looks the same as another one", value = "help me out")
|
||||
public void helpMeOut() {
|
||||
System.out.println("You can go");
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public class StandardParameterResolver implements ParameterResolver {
|
||||
|
||||
if (!set) {
|
||||
if (unfinished == null) { // case 1 above
|
||||
return commandsThatStartWithContextPrefix(methodParameter, context);
|
||||
return argumentKeysThatStartWithContextPrefix(methodParameter, context);
|
||||
} // case 2
|
||||
else {
|
||||
return valueCompletions(methodParameter, context);
|
||||
@@ -306,9 +306,6 @@ public class StandardParameterResolver implements ParameterResolver {
|
||||
else {
|
||||
List<CompletionProposal> result = new ArrayList<>();
|
||||
|
||||
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
|
||||
// TODO: should not look at last word only, but everything after what was used for key
|
||||
|
||||
Object value = convertRawValue(parameterRawValue, methodParameter);
|
||||
if (value instanceof Collection && ((Collection) value).size() == arity
|
||||
|| (ObjectUtils.isArray(value) && Array.getLength(value) == arity)) {
|
||||
@@ -320,7 +317,7 @@ public class StandardParameterResolver implements ParameterResolver {
|
||||
|
||||
if (parameterRawValue.positional()) {
|
||||
// Case 4.1: There exists "--command foo" and user has typed "--comm" which (wrongly) got resolved as a positional param
|
||||
result.addAll(commandsThatStartWithContextPrefix(methodParameter, context));
|
||||
result.addAll(argumentKeysThatStartWithContextPrefix(methodParameter, context));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -333,7 +330,7 @@ public class StandardParameterResolver implements ParameterResolver {
|
||||
.findFirst().orElseGet(() -> Collections.emptyList());
|
||||
}
|
||||
|
||||
private List<CompletionProposal> commandsThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
|
||||
private List<CompletionProposal> argumentKeysThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
|
||||
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
|
||||
return describe(methodParameter).keys().stream()
|
||||
.filter(k -> k.startsWith(prefix))
|
||||
|
||||
Reference in New Issue
Block a user