Skip to content

Commit

Permalink
feat: make suggestion providers accept CommandInput
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret committed Dec 25, 2023
1 parent 033de86 commit 0aafdfa
Show file tree
Hide file tree
Showing 45 changed files with 191 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import cloud.commandframework.arguments.suggestion.Suggestion;
import cloud.commandframework.arguments.suggestion.SuggestionProvider;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandInput;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
Expand All @@ -47,6 +48,7 @@
public final class MethodSuggestionProvider<C> implements SuggestionProvider<C> {

private final MethodHandle methodHandle;
private final boolean passString;

/**
* Create a new provider
Expand All @@ -60,6 +62,7 @@ public MethodSuggestionProvider(
) {
try {
this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance);
this.passString = method.getParameterTypes()[1] == String.class;
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
Expand All @@ -68,10 +71,15 @@ public MethodSuggestionProvider(
@Override
public @NonNull CompletableFuture<Iterable<@NonNull Suggestion>> suggestionsFuture(
final @NonNull CommandContext<C> context,
final @NonNull String input
final @NonNull CommandInput input
) {
try {
final Object output = this.methodHandle.invokeWithArguments(context, input);
final Object output;
if (this.passString) {
output = this.methodHandle.invokeWithArguments(context, input.lastRemainingToken());
} else {
output = this.methodHandle.invokeWithArguments(context, input);
}
return mapSuggestions(output);
} catch (final Throwable t) {
throw new RuntimeException(t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ void testAnnotatedSuggestionProviders() {
this.manager.parserRegistry().getSuggestionProvider("cows").orElse(null);

assertThat(suggestionProvider).isNotNull();
assertThat(suggestionProvider.suggestionsFuture(new CommandContext<>(new TestCommandSender(), manager), "").join())
.contains(Suggestion.simple("Stella"));
assertThat(suggestionProvider.suggestionsFuture(new CommandContext<>(new TestCommandSender(), manager),
CommandInput.empty()).join()).contains(Suggestion.simple("Stella"));
}

@Test
Expand All @@ -177,7 +177,8 @@ void testAnnotatedArgumentParser() {
);
assertThat(parser.parse(context, CommandInput.empty()).parsedValue().orElse(new CustomType("")).toString())
.isEqualTo("yay");
assertThat(parser.suggestionProvider().suggestionsFuture(context, "").join()).contains(Suggestion.simple("Stella"));
assertThat(parser.suggestionProvider().suggestionsFuture(context, CommandInput.empty()).join())
.contains(Suggestion.simple("Stella"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import cloud.commandframework.arguments.suggestion.Suggestion;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandContextFactory;
import cloud.commandframework.context.CommandInput;
import cloud.commandframework.context.StandardCommandContextFactory;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -74,7 +75,7 @@ void testSuggestions(final @NonNull Object instance) {
this.commandManager.parserRegistry()
.getSuggestionProvider("suggestions")
.orElseThrow(NullPointerException::new)
.suggestionsFuture(context, "")
.suggestionsFuture(context, CommandInput.empty())
.join();

// Assert
Expand Down
41 changes: 18 additions & 23 deletions cloud-core/src/main/java/cloud/commandframework/CommandTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,9 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
// Calculate suggestions for the literal arguments
CompletableFuture<SuggestionContext<C>> suggestionFuture = CompletableFuture.completedFuture(context);
if (commandInput.remainingTokens() <= 1) {
final String literalValue = commandInput.peekString().replace(" ", "");
for (final CommandNode<C> node : staticArguments) {
suggestionFuture = suggestionFuture
.thenCompose(ctx -> this.addSuggestionsForLiteralArgument(context, node, literalValue));
.thenCompose(ctx -> this.addSuggestionsForLiteralArgument(context, node, commandInput));
}
}

Expand All @@ -712,7 +711,7 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument(
final @NonNull SuggestionContext<C> context,
final @NonNull CommandNode<C> node,
final @NonNull String input
final @NonNull CommandInput input
) {
if (this.findMissingPermission(context.commandContext().sender(), node) != null) {
return CompletableFuture.completedFuture(context);
Expand All @@ -722,8 +721,9 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
return component.suggestionProvider()
.suggestionsFuture(context.commandContext(), input)
.thenApply(suggestionsToAdd -> {
final String string = input.peekString();
for (Suggestion suggestion : suggestionsToAdd) {
if (suggestion.suggestion().equals(input) || !suggestion.suggestion().startsWith(input)) {
if (suggestion.suggestion().equals(string) || !suggestion.suggestion().startsWith(string)) {
continue;
}
context.addSuggestion(suggestion);
Expand Down Expand Up @@ -764,10 +764,9 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
return future.thenCompose(ignored -> {
if (commandInput.isEmpty()) {
return CompletableFuture.completedFuture(context);
} else if (commandInput.remainingTokens() == 1) {
return this.addArgumentSuggestions(context, child, commandInput.peekString());
} else if (child.isLeaf() && child.component().parser() instanceof AggregateCommandParser) {
return this.addArgumentSuggestions(context, child, commandInput.lastRemainingToken());
} else if (commandInput.remainingTokens() == 1
|| (child.isLeaf() && child.component().parser() instanceof AggregateCommandParser)) {
return this.addArgumentSuggestions(context, child, commandInput);
}

// Store original input command queue before the parsers below modify it
Expand Down Expand Up @@ -799,11 +798,7 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument

if (result.failure().isPresent()) {
commandInput.cursor(preParseInput.cursor());

while (commandInput.remainingTokens() > 1) {
commandInput.readString();
}
return this.addArgumentSuggestions(context, child, commandInput.lastRemainingToken());
return this.addArgumentSuggestions(context, child, commandInput);
}

if (child.isLeaf()) {
Expand All @@ -813,7 +808,7 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument

// Greedy parser took all the input, we can restore and just ask for suggestions
commandInput.cursor(commandInputOriginal.cursor());
this.addArgumentSuggestions(context, child, commandInput.remainingInput());
this.addArgumentSuggestions(context, child, commandInput);
}

if (parseSuccess && !commandInput.isEmpty()) {
Expand Down Expand Up @@ -848,7 +843,7 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
return CompletableFuture.completedFuture(context);
}

return this.addArgumentSuggestions(context, child, commandInput.peekString());
return this.addArgumentSuggestions(context, child, commandInput);
});
});
}
Expand Down Expand Up @@ -902,20 +897,20 @@ private CompletableFuture<Void> popRequiredArguments(
*
* @param context the suggestion context
* @param node the node containing the argument to get suggestions from
* @param text the input from the sender
* @param input the input from the sender
* @return the context
*/
private @NonNull CompletableFuture<SuggestionContext<C>> addArgumentSuggestions(
final @NonNull SuggestionContext<C> context,
final @NonNull CommandNode<C> node,
final @NonNull String text
final @NonNull CommandInput input
) {
final CommandComponent<C> component = Objects.requireNonNull(node.component());
return this.addArgumentSuggestions(context, component, text).thenCompose(ctx -> {
return this.addArgumentSuggestions(context, component, input).thenCompose(ctx -> {
// When suggesting a flag, potentially suggest following nodes too
final boolean isParsingFlag = component.type() == CommandComponent.ComponentType.FLAG
&& !node.children().isEmpty() // Has children
&& !text.startsWith("-") // Not a flag
&& !(input.hasRemainingInput() && input.peek() == '-') // Not a flag
&& !context.commandContext().optional(CommandFlagParser.FLAG_META_KEY).isPresent();

if (!isParsingFlag) {
Expand All @@ -925,7 +920,7 @@ private CompletableFuture<Void> popRequiredArguments(
return CompletableFuture.allOf(
node.children()
.stream()
.map(child -> this.addArgumentSuggestions(context, Objects.requireNonNull(child.component()), text))
.map(child -> this.addArgumentSuggestions(context, Objects.requireNonNull(child.component()), input))
.toArray(CompletableFuture[]::new)
).thenApply(v -> ctx);
});
Expand All @@ -936,17 +931,17 @@ private CompletableFuture<Void> popRequiredArguments(
*
* @param context the suggestion context
* @param component the component to get suggestions from
* @param text the input from the sender
* @param input the input from the sender
* @return future that completes with the context
*/
private CompletableFuture<SuggestionContext<C>> addArgumentSuggestions(
final @NonNull SuggestionContext<C> context,
final @NonNull CommandComponent<C> component,
final @NonNull String text
final @NonNull CommandInput input
) {
context.commandContext().currentComponent(component);
return component.suggestionProvider()
.suggestionsFuture(context.commandContext(), text)
.suggestionsFuture(context.commandContext(), input)
.thenAccept(context::addSuggestions)
.thenApply(in -> context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;

public final class LiteralParser<C> implements ArgumentParser<C, String>, BlockingSuggestionProvider.Strings<C> {
public final class LiteralParser<C> implements ArgumentParser<C, String>, BlockingSuggestionProvider.ConstantStrings<C> {

/**
* Creates a new literal parser that accepts the given {@code name} and {@code aliases}.
Expand Down Expand Up @@ -90,10 +90,7 @@ private LiteralParser(final @NonNull String name, final @NonNull String... alias
}

@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
public @NonNull Iterable<@NonNull String> stringSuggestions() {
return Collections.singletonList(this.name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public interface AggregateCommandParser<C, O> extends ArgumentParser.FutureArgum
@Override
default @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture(
final @NonNull CommandContext<C> context,
final @NonNull String input
final @NonNull CommandInput input
) {
return this.components()
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag<?>> flag
@SuppressWarnings({"unchecked", "rawtypes"})
public @NonNull CompletableFuture<Iterable<@NonNull Suggestion>> suggestionsFuture(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
final @NonNull CommandInput input
) {
/* Check if we have a last flag stored */
final String lastArg = Objects.requireNonNull(commandContext.getOrDefault(FLAG_META_KEY, ""));
Expand Down Expand Up @@ -193,7 +193,8 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag<?>> flag
suggestions.add(Suggestion.simple(String.format("--%s", flag.getName())));
}
/* Recommend aliases */
final boolean suggestCombined = input.length() > 1 && input.charAt(0) == '-' && input.charAt(1) != '-';
final String nextToken = input.peekString();
final boolean suggestCombined = nextToken.length() > 1 && nextToken.startsWith("-") && !nextToken.startsWith("--");
for (final CommandFlag<?> flag : this.flags) {
if (usedFlags.contains(flag) && flag.mode() != CommandFlag.FlagMode.REPEATABLE) {
continue;
Expand All @@ -204,15 +205,15 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag<?>> flag

for (final String alias : flag.getAliases()) {
if (suggestCombined && flag.commandComponent() == null) {
suggestions.add(Suggestion.simple(String.format("%s%s", input, alias)));
suggestions.add(Suggestion.simple(String.format("%s%s", input.peekString(), alias)));
} else {
suggestions.add(Suggestion.simple(String.format("-%s", alias)));
}
}
}
/* If we are suggesting the combined flag, then also suggest the current input */
if (suggestCombined) {
suggestions.add(Suggestion.simple(input));
suggestions.add(Suggestion.simple(input.peekString()));
}
return CompletableFuture.completedFuture(suggestions);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import org.checkerframework.checker.nullness.qual.NonNull;

@API(status = API.Status.STABLE)
public final class BooleanParser<C> implements ArgumentParser<C, Boolean>, BlockingSuggestionProvider.Strings<C> {
public final class BooleanParser<C> implements ArgumentParser<C, Boolean>, BlockingSuggestionProvider.ConstantStrings<C> {

private static final List<String> STRICT_LOWER = CommandInput.BOOLEAN_STRICT
.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList());
Expand Down Expand Up @@ -113,10 +113,7 @@ public BooleanParser(final boolean liberal) {
}

@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
public @NonNull Iterable<@NonNull String> stringSuggestions() {
if (!this.liberal) {
return STRICT_LOWER;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public boolean hasMin() {
@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
final @NonNull CommandInput input
) {
return IntegerParser.getSuggestions(this.min, this.max, input);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import cloud.commandframework.exceptions.parsing.ParserException;
import java.time.Duration;
import java.util.Collections;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -133,28 +132,25 @@ public final class DurationParser<C> implements ArgumentParser<C, Duration>, Blo
@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
final @NonNull CommandInput input
) {
char[] chars = input.toLowerCase(Locale.ROOT).toCharArray();

if (chars.length == 0) {
if (input.isEmpty(true)) {
return IntStream.range(1, 10).boxed()
.sorted()
.map(String::valueOf)
.collect(Collectors.toList());
}

char last = chars[chars.length - 1];

// 1d_, 5d4m_, etc
if (Character.isLetter(last)) {
if (Character.isLetter(input.lastRemainingCharacter())) {
return Collections.emptyList();
}

// 1d5_, 5d4m2_, etc
final String string = input.readString();
return Stream.of("d", "h", "m", "s")
.filter(unit -> !input.contains(unit))
.map(unit -> input + unit)
.filter(unit -> !string.contains(unit))
.map(unit -> string + unit)
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

@API(status = API.Status.STABLE)
public final class EnumParser<C, E extends Enum<E>> implements ArgumentParser<C, E>,
BlockingSuggestionProvider.Strings<C> {
BlockingSuggestionProvider.ConstantStrings<C> {

/**
* Creates a new enum parser.
Expand Down Expand Up @@ -112,10 +112,7 @@ public EnumParser(final @NonNull Class<E> enumClass) {
}

@Override
public @NonNull Iterable<@NonNull String> stringSuggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
public @NonNull Iterable<@NonNull String> stringSuggestions() {
return EnumSet.allOf(this.enumClass).stream().map(e -> e.name().toLowerCase(Locale.ROOT)).collect(Collectors.toList());
}

Expand Down
Loading

0 comments on commit 0aafdfa

Please sign in to comment.