@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.executable.ExecutableValidator;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Base class implementing a shell loop.
|
||||
*
|
||||
* <p>Given some textual input, locate the {@link MethodTarget} to invoke and {@link ResultHandler#handleResult(Object) handle}
|
||||
* the result.</p>
|
||||
*
|
||||
* <p>Also provides hooks for code completion</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public abstract class AbstractShell implements Shell {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("main")
|
||||
ResultHandler resultHandler;
|
||||
|
||||
@Autowired
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
protected Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
|
||||
@Autowired
|
||||
protected List<ParameterResolver> parameterResolvers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
|
||||
*/
|
||||
protected static final Object UNRESOLVED = new Object();
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> listCommands() {
|
||||
return methodTargets;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void gatherMethodTargets() throws Exception {
|
||||
for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
|
||||
methodTargets.putAll(resolver.resolve());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main program loop: acquire input, try to match it to a command and evaluate. Repeat until a
|
||||
* {@link ResultHandler} causes the process to exit.
|
||||
*/
|
||||
public void run() throws IOException {
|
||||
while (true) {
|
||||
Input input = readInput();
|
||||
if (input.words().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
String line = input.rawText();
|
||||
List<String> words = input.words();
|
||||
|
||||
String command = findLongestCommand(line);
|
||||
|
||||
if (command != null) {
|
||||
int wordsUsedForCommandKey = command.split(" ").length;
|
||||
MethodTarget methodTarget = methodTargets.get(command);
|
||||
List<String> wordsForArgs = words.subList(wordsUsedForCommandKey, words.size());
|
||||
Method method = methodTarget.getMethod();
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
Object[] args = resolveArgs(method, wordsForArgs);
|
||||
validateArgs(args, methodTarget);
|
||||
result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
}
|
||||
|
||||
resultHandler.handleResult(result);
|
||||
|
||||
}
|
||||
else {
|
||||
System.out.println("No command found for " + words);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return text entered by user to invoke commands.
|
||||
*/
|
||||
protected abstract Input readInput();
|
||||
|
||||
/**
|
||||
* 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 subclasses.
|
||||
*/
|
||||
public List<CompletionProposal> complete(CompletionContext context) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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, context).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<CompletionProposal> commandsStartingWith(String prefix) {
|
||||
return methodTargets.entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(prefix))
|
||||
.map(e -> toCompletionProposal(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private CompletionProposal toCompletionProposal(String command, MethodTarget methodTarget) {
|
||||
return new CompletionProposal(command)
|
||||
.category("Available commands")
|
||||
.description(methodTarget.getHelp());
|
||||
}
|
||||
|
||||
private void validateArgs(Object[] args, MethodTarget methodTarget) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == UNRESOLVED) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
|
||||
throw new IllegalStateException("Could not resolve " + methodParameter);
|
||||
}
|
||||
}
|
||||
ExecutableValidator executableValidator = Validation
|
||||
.buildDefaultValidatorFactory().getValidator().forExecutables();
|
||||
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
|
||||
methodTarget.getMethod(),
|
||||
args);
|
||||
if (constraintViolations.size() > 0) {
|
||||
System.out.println(constraintViolations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use all known {@link ParameterResolver}s to try to compute a value for each parameter of the method to
|
||||
* invoke.
|
||||
* @param method the method for which parameters should be computed
|
||||
* @param wordsForArgs the list of 'words' that should be converted to parameter values.
|
||||
* May include markers for passing parameters 'by name'
|
||||
* @return an array containing resolved parameter values, or {@link #UNRESOLVED} for parameters that could not be
|
||||
* resolved
|
||||
*/
|
||||
private Object[] resolveArgs(Method method, List<String> wordsForArgs) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
Arrays.fill(args, UNRESOLVED);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(method, i);
|
||||
args[i] = findResolver(methodParameter).resolve(methodParameter, wordsForArgs);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
protected ParameterResolver findResolver(MethodParameter parameter) {
|
||||
return parameterResolvers.stream()
|
||||
.filter(resolver -> resolver.supports(parameter))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("resolver not found"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the longest command that can be matched as first word(s) in the given buffer.
|
||||
*
|
||||
* @return a valid command name, or {@literal null} if none matched
|
||||
*/
|
||||
protected String findLongestCommand(String prefix) {
|
||||
String result = methodTargets.keySet().stream()
|
||||
.filter(prefix::startsWith)
|
||||
.reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
|
||||
return "".equals(result) ? null : result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -59,7 +59,10 @@ public class CompletionContext {
|
||||
public String upToCursor() {
|
||||
String start = words.subList(0, wordIndex).stream().collect(Collectors.joining(" "));
|
||||
if (wordIndex < words.size()) {
|
||||
start += " " + currentWord().substring(0, position);
|
||||
if (!start.isEmpty()) {
|
||||
start += " ";
|
||||
}
|
||||
start += currentWord().substring(0, position);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
@@ -51,32 +51,36 @@ public class CompletionProposal {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void value(String value) {
|
||||
public CompletionProposal value(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String displayText() {
|
||||
return displayText;
|
||||
}
|
||||
|
||||
public void displayText(String displayText) {
|
||||
public CompletionProposal displayText(String displayText) {
|
||||
this.displayText = displayText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void description(String description) {
|
||||
public CompletionProposal description(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String category() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void category(String category) {
|
||||
public CompletionProposal category(String category) {
|
||||
this.category = category;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the input buffer to the shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface Input {
|
||||
|
||||
Input EMPTY = new Input() {
|
||||
@Override
|
||||
public String rawText() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> words() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the input as entered by the user.
|
||||
*/
|
||||
String rawText();
|
||||
|
||||
/**
|
||||
* Return the input as a list of parsed "words", having split the raw input according
|
||||
* to parsing rules (for example, handling quoted portions of the readInput as a single
|
||||
* "word")
|
||||
*/
|
||||
List<String> words();
|
||||
}
|
||||
@@ -16,22 +16,10 @@
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.executable.ExecutableValidator;
|
||||
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.Completer;
|
||||
@@ -40,63 +28,30 @@ import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Main component implementing a REPL using JLine.
|
||||
*
|
||||
* <p>Discovers {@link MethodTarget}s at startup and hands off execution of commands according
|
||||
* to the parsed JLine buffer.</p>
|
||||
* Shell implementation using JLine to capture input and trigger completions.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Component
|
||||
public class JLineShell implements Shell {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("main")
|
||||
ResultHandler resultHandler;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
public class JLineShell extends AbstractShell {
|
||||
|
||||
LineReader lineReader;
|
||||
|
||||
@Autowired
|
||||
private Terminal terminal;
|
||||
|
||||
@Autowired
|
||||
private List<ParameterResolver> parameterResolvers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
|
||||
*/
|
||||
private static final Object UNRESOLVED = new Object();
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> listCommands() {
|
||||
return methodTargets;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
|
||||
methodTargets.putAll(resolver.resolve());
|
||||
}
|
||||
|
||||
ExtendedDefaultParser parser = new ExtendedDefaultParser();
|
||||
parser.setEofOnUnclosedQuote(true);
|
||||
parser.setEofOnEscapedNewLine(true);
|
||||
@@ -132,53 +87,26 @@ public class JLineShell implements Shell {
|
||||
}
|
||||
|
||||
|
||||
public void run() throws IOException {
|
||||
while (true) {
|
||||
try {
|
||||
lineReader.readLine(new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)).toAnsi(terminal));
|
||||
}
|
||||
catch (UserInterruptException e) {
|
||||
if (e.getPartialLine().isEmpty()) {
|
||||
resultHandler.handleResult(new ExitRequest(1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String line = lineReader.getParsedLine().line();
|
||||
String command = findLongestCommand(line);
|
||||
|
||||
List<String> words = lineReader.getParsedLine().words();
|
||||
if (command != null) {
|
||||
int wordsUsedForCommandKey = command.split(" ").length;
|
||||
MethodTarget methodTarget = methodTargets.get(command);
|
||||
List<String> wordsForArgs = sanitizeInput(words.subList(wordsUsedForCommandKey, words.size()));
|
||||
Method method = methodTarget.getMethod();
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
Object[] args = resolveArgs(method, wordsForArgs);
|
||||
validateArgs(args, methodTarget);
|
||||
result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
}
|
||||
|
||||
resultHandler.handleResult(result);
|
||||
|
||||
}
|
||||
else {
|
||||
System.out.println("No command found for " + sanitizeInput(words));
|
||||
@Override
|
||||
protected Input readInput() {
|
||||
try {
|
||||
lineReader.readLine(new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)).toAnsi(terminal));
|
||||
}
|
||||
catch (UserInterruptException e) {
|
||||
if (e.getPartialLine().isEmpty()) {
|
||||
resultHandler.handleResult(new ExitRequest(1));
|
||||
} else {
|
||||
return Input.EMPTY;
|
||||
}
|
||||
}
|
||||
return new JLineInput(lineReader.getParsedLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the buffer input given the customizations applied to the JLine parser (<em>e.g.</em> support for
|
||||
* line continuations, <em>etc.</em>)
|
||||
*/
|
||||
private List<String> sanitizeInput(List<String> words) {
|
||||
static private List<String> sanitizeInput(List<String> words) {
|
||||
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
|
||||
@@ -187,48 +115,12 @@ public class JLineShell implements Shell {
|
||||
return words;
|
||||
}
|
||||
|
||||
private void validateArgs(Object[] args, MethodTarget methodTarget) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == UNRESOLVED) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
|
||||
throw new IllegalStateException("Could not resolve " + methodParameter);
|
||||
}
|
||||
}
|
||||
ExecutableValidator executableValidator = Validation
|
||||
.buildDefaultValidatorFactory().getValidator().forExecutables();
|
||||
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
|
||||
methodTarget.getMethod(),
|
||||
args);
|
||||
if (constraintViolations.size() > 0) {
|
||||
System.out.println(constraintViolations);
|
||||
}
|
||||
}
|
||||
// Overridden so it can be called from CompleterAdapter
|
||||
|
||||
/**
|
||||
* Use all known {@link ParameterResolver}s to try to compute a value for each parameter of the method to
|
||||
* invoke.
|
||||
* @param method the method for which parameters should be computed
|
||||
* @param wordsForArgs the list of 'words' that should be converted to parameter values.
|
||||
* May include markers for passing parameters 'by name'
|
||||
* @return an array containing resolved parameter values, or {@link #UNRESOLVED} for parameters that could not be
|
||||
* resolved
|
||||
*/
|
||||
private Object[] resolveArgs(Method method, List<String> wordsForArgs) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
Arrays.fill(args, UNRESOLVED);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(method, i);
|
||||
args[i] = findResolver(methodParameter).resolve(methodParameter, wordsForArgs);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private ParameterResolver findResolver(MethodParameter parameter) {
|
||||
return parameterResolvers.stream()
|
||||
.filter(resolver -> resolver.supports(parameter))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("resolver not found"));
|
||||
@Override
|
||||
public List<CompletionProposal> complete(CompletionContext context) {
|
||||
return super.complete(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,72 +131,43 @@ public class JLineShell implements Shell {
|
||||
|
||||
@Override
|
||||
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
|
||||
String prefix = reader.getBuffer().upToCursor();
|
||||
|
||||
// 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;
|
||||
} // 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;
|
||||
}
|
||||
|
||||
CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t;
|
||||
|
||||
// Try to complete arguments
|
||||
MethodTarget methodTarget = methodTargets.get(best);
|
||||
List<String> words = line.words();
|
||||
int noOfWordsInCommand = best.split(" ").length;
|
||||
List<String> rest = words.subList(noOfWordsInCommand, words.size())
|
||||
.stream()
|
||||
.filter(w -> !w.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
CompletionContext context = new CompletionContext(rest, line.wordIndex() - noOfWordsInCommand, line.wordCursor());
|
||||
Method method = methodTarget.getMethod();
|
||||
for (int i = 0; i < method.getParameterCount(); i++) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(method, i);
|
||||
ParameterResolver resolver = findResolver(methodParameter);
|
||||
resolver.complete(methodParameter, context)
|
||||
.stream()
|
||||
.map(completion -> new Candidate(
|
||||
cpl.emit(completion.value()).toString(),
|
||||
completion.displayText(),
|
||||
"Value for parameter " + resolver.describe(methodParameter).toString(),
|
||||
resolver.describe(methodParameter).help(),
|
||||
null, null, true)
|
||||
)
|
||||
.forEach(candidates::add);
|
||||
}
|
||||
}
|
||||
CompletionContext context = new CompletionContext(sanitizeInput(line.words()), line.wordIndex(), line.wordCursor());
|
||||
|
||||
private List<Candidate> commandsStartingWith(String prefix) {
|
||||
return methodTargets.entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(prefix)) // find commands that start with our buffer prefix
|
||||
.map(e -> toCandidate(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Candidate toCandidate(String command, MethodTarget methodTarget) {
|
||||
return new Candidate(command, command, "Available commands", methodTarget.getHelp(), null, null, true);
|
||||
List<CompletionProposal> proposals = JLineShell.this.complete(context);
|
||||
proposals.stream()
|
||||
.map(p -> new Candidate(
|
||||
cpl.emit(p.value()).toString(),
|
||||
p.displayText(),
|
||||
p.category(),
|
||||
p.description(),
|
||||
null,
|
||||
null,
|
||||
true)
|
||||
)
|
||||
.forEach(candidates::add);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the longest command that can be matched as first word(s) in the given buffer.
|
||||
*
|
||||
* @return a valid command name, or {@literal null} if none matched
|
||||
*/
|
||||
private String findLongestCommand(String prefix) {
|
||||
String result = methodTargets.keySet().stream()
|
||||
.filter(prefix::startsWith)
|
||||
.reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
|
||||
return "".equals(result) ? null : result;
|
||||
private static class JLineInput implements Input {
|
||||
|
||||
private final ParsedLine parsedLine;
|
||||
|
||||
JLineInput(ParsedLine parsedLine) {
|
||||
this.parsedLine = parsedLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rawText() {
|
||||
return parsedLine.line();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> words() {
|
||||
return sanitizeInput(parsedLine.words());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user