8000 Fix multi-word flag suggestions by jpenilla · Pull Request #736 · Incendo/cloud · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix multi-word flag suggestions #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions cloud-core/src/main/java/org/incendo/cloud/CommandTree.java
10000
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
continue;
}
suggestionFuture = suggestionFuture
.thenCompose(ctx -> this.addSuggestionsForDynamicArgument(context, commandInput, child, executor));
.thenCompose(ctx -> this.addSuggestionsForDynamicArgument(context, commandInput, child, executor, false));
}

return suggestionFuture;
Expand Down Expand Up @@ -769,28 +769,33 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
final @NonNull SuggestionContext<C, ?> context,
final @NonNull CommandInput commandInput,
final @NonNull CommandNode<C> child,
final @NonNull Executor executor
final @NonNull Executor executor,
final boolean inFlag
) {
final CommandComponent<C> component = child.component();
if (component == null) {
return CompletableFuture.completedFuture(context);
}

if (component.parser() instanceof CommandFlagParser) {
if (!inFlag && component.parser() instanceof CommandFlagParser) {
// Use the flag argument parser to deduce what flag is being suggested right now
// If empty, then no flag value is being typed, and the different flag options should
// be suggested instead.
final CommandFlagParser<C> parser = (CommandFlagParser<C>) component.parser();
final Optional<String> lastFlag = parser.parseCurrentFlag(context.commandContext(), commandInput);
if (lastFlag.isPresent()) {
context.commandContext().store(CommandFlagParser.FLAG_META_KEY, lastFlag.get());
} else {
context.commandContext().remove(CommandFlagParser.FLAG_META_KEY);
}

return parser.parseCurrentFlag(context.commandContext(), commandInput, executor).thenCompose(lastFlag -> {
if (lastFlag.isPresent()) {
context.commandContext().store(CommandFlagParser.FLAG_META_KEY, lastFlag.get());
} else {
context.commandContext().remove(CommandFlagParser.FLAG_META_KEY);
}
return this.addSuggestionsForDynamicArgument(context, commandInput, child, executor, true);
});
}

if (commandInput.isEmpty() || commandInput.remainingTokens() == 1
|| (child.isLeaf() && child.component().parser() instanceof AggregateParser)) {
|| (child.isLeaf() && child.component().parser() instanceof AggregateParser)
|| (child.isLeaf() && child.component().parser() instanceof CommandFlagParser)) {
return this.addArgumentSuggestions(context, child, commandInput, executor);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apiguardian.api.API;
Expand Down Expand Up @@ -63,6 +64,10 @@ public final class CommandFlagParser<C> implements ArgumentParser.FutureArgument
* Metadata for the last argument that was suggested
*/
public static final CloudKey<String> FLAG_META_KEY = CloudKey.of("__last_flag__", TypeToken.get(String.class));
/**
* Metadata for the position of the cursor before parsing the last flag's value
*/
public static final CloudKey<Integer> FLAG_CURSOR_KEY = CloudKey.of("__flag_cursor__", TypeToken.get(Integer.class));
/**
* Metadata for the set of parsed flags, used to detect duplicates.
*/
Expand Down Expand Up @@ -110,35 +115,35 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag<?>> flag
*
* @param commandContext Command context
* @param commandInput The input arguments
* @param completionExecutor The completion executor
* @return current flag being typed, or {@code empty()} if none is
*/
@API(status = API.Status.STABLE)
public @NonNull Optional<String> parseCurrentFlag(
public @NonNull CompletableFuture<Optional<String>> parseCurrentFlag(
final @NonNull CommandContext<@NonNull C> commandContext,
final @NonNull CommandInput commandInput
final @NonNull CommandInput commandInput,
final @NonNull Executor completionExecutor
) {
/* If empty, nothing to do */
if (commandInput.isEmpty()) {
return Optional.empty();
return CompletableFuture.completedFuture(Optional.empty());
}

/* Before parsing, retrieve the last known input of the queue */
final String lastInputValue = commandInput.lastRemainingToken();

/* Parse, but ignore the result of parsing */
final FlagParser parser = new FlagParser();
parser.parse(commandContext, commandInput);

/*
* If the parser parsed the entire queue, restore the last typed
* input obtained earlier.
*/
if (commandInput.isEmpty()) {
final int count = lastInputValue.length();
commandInput.moveCursor(-count);
}

return Optional.ofNullable(parser.lastParsedFlag());
final CompletableFuture<@NonNull ArgumentParseResult<Object>> result = parser.parse(commandContext, commandInput);

return result.thenApplyAsync(parseResult -> {
if (commandContext.contains(FLAG_CURSOR_KEY)) {
commandInput.cursor(commandContext.get(FLAG_CURSOR_KEY));
} else if (parser.lastParsedFlag() == null && commandInput.isEmpty()) {
final int count = lastInputValue.length();
commandInput.moveCursor(-count);
}
return Optional.ofNullable(parser.lastParsedFlag());
}, completionExecutor);
}

@Override
Expand Down Expand Up @@ -485,6 +490,7 @@ private final class FlagParser {

// The flag has no argument, so we're done.
if (flag.commandComponent() == null) {
commandContext.remove(FLAG_CURSOR_KEY);
commandContext.flags().addPresenceFlag(flag);
parsedFlags.add(flag);
return CompletableFuture.completedFuture(null);
Expand Down Expand Up @@ -515,12 +521,17 @@ private final class FlagParser {

// We then attempt to parse the flag.
final CommandFlag parsingFlag = flag;
final CommandInput commandInputCopy = commandInput.copy();
return ((CommandComponent<C>) flag.commandComponent())
.parser()
.parseFuture(
commandContext,
commandInput
).thenApply(parsedValue -> {
if (parsedValue.failure().isPresent() || commandInput.isEmpty() || commandInput.peek() != ' ') {
commandContext.store(FLAG_CURSOR_KEY, commandInputCopy.cursor());
}

// Forward parsing errors.
if (parsedValue.failure().isPresent()) {
return (ArgumentParseResult<Object>) parsedValue;
Expand All @@ -531,8 +542,12 @@ private final class FlagParser {
// At this point we know the flag parsed successfully.
parsedFlags.add(parsingFlag);

// We're no longer parsing a flag.
this.lastParsedFlag = null;
if (!commandInput.isEmpty(false)) {
if (commandInput.peek() == ' ') {
// We're no longer parsing a flag.
this.lastParsedFlag = null;
}
}

return null;
});
Expand All @@ -548,7 +563,7 @@ private final class FlagParser {
}

private @NonNull CompletableFuture<ArgumentParseResult<Object>> fail(final @NonNull Throwable exception) {
return CompletableFuture.completedFuture(ArgumentParseResult.failure(exception));
return ArgumentParseResult.failureFuture(exception);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,11 @@ void testCompoundFlags() {

final String input5 = "flags3 --compound 22 ";
final List<? extends Suggestion> suggestions5 = this.manager.suggestionFactory().suggestImmediately(new TestCommandSender(), input5).list();
Assertions.assertEquals(suggestionList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"), suggestions5);
Assertions.assertEquals(suggestionList("22 0", "22 1", "22 2", "22 3", "22 4", "22 5", "22 6", "22 7", "22 8", "22 9"), suggestions5);

final String input6 = "flags3 --compound 22 1";
final List<? extends Suggestion> suggestions6 = this.manager.suggestionFactory().suggestImmediately(new TestCommandSender(), input6).list();
Assertions.assertEquals(suggestionList("1", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"), suggestions6);
Assertions.assertEquals(suggestionList("22 1", "22 10", "22 11", "22 12", "22 13", "22 14", "22 15", "22 16", "22 17", "22 18", "22 19"), suggestions6);

/* We've typed compound already, so that flag should be omitted from the suggestions */
final String input7 = "flags3 --compound 22 33 44 ";
Expand Down
Loading
0