diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionProvider.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionProvider.java index 74406a45e..70505e4df 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionProvider.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionProvider.java @@ -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; @@ -47,6 +48,7 @@ public final class MethodSuggestionProvider implements SuggestionProvider { private final MethodHandle methodHandle; + private final boolean passString; /** * Create a new provider @@ -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); } @@ -68,10 +71,15 @@ public MethodSuggestionProvider( @Override public @NonNull CompletableFuture> suggestionsFuture( final @NonNull CommandContext 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); diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java index 1a09c32c8..e917b3ebf 100644 --- a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java @@ -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 @@ -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 diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/MethodSuggestionProviderTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/MethodSuggestionProviderTest.java index 2c308573b..094949fde 100644 --- a/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/MethodSuggestionProviderTest.java +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/MethodSuggestionProviderTest.java @@ -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; @@ -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 diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index 5eaf42745..b43da7646 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -682,10 +682,9 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode> chil // Calculate suggestions for the literal arguments CompletableFuture> suggestionFuture = CompletableFuture.completedFuture(context); if (commandInput.remainingTokens() <= 1) { - final String literalValue = commandInput.peekString().replace(" ", ""); for (final CommandNode node : staticArguments) { suggestionFuture = suggestionFuture - .thenCompose(ctx -> this.addSuggestionsForLiteralArgument(context, node, literalValue)); + .thenCompose(ctx -> this.addSuggestionsForLiteralArgument(context, node, commandInput)); } } @@ -712,7 +711,7 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode> chil private CompletableFuture> addSuggestionsForLiteralArgument( final @NonNull SuggestionContext context, final @NonNull CommandNode node, - final @NonNull String input + final @NonNull CommandInput input ) { if (this.findMissingPermission(context.commandContext().sender(), node) != null) { return CompletableFuture.completedFuture(context); @@ -720,10 +719,11 @@ private CompletableFuture> addSuggestionsForLiteralArgument final CommandComponent component = Objects.requireNonNull(node.component()); context.commandContext().currentComponent(component); return component.suggestionProvider() - .suggestionsFuture(context.commandContext(), input) + .suggestionsFuture(context.commandContext(), input.copy()) .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); @@ -764,10 +764,9 @@ private CompletableFuture> 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 @@ -799,11 +798,7 @@ private CompletableFuture> 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()) { @@ -813,10 +808,13 @@ private CompletableFuture> 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()) { + if (parseSuccess && (!commandInput.isEmpty() || commandInput.input().endsWith(" "))) { + if (commandInput.isEmpty()) { + commandInput.moveCursor(-1); + } // the current argument at the position is parsable and there are more arguments following context.commandContext().store(child.component().name(), parsedValue.get()); return this.getSuggestions(context, commandInput, child); @@ -848,7 +846,7 @@ private CompletableFuture> addSuggestionsForLiteralArgument return CompletableFuture.completedFuture(context); } - return this.addArgumentSuggestions(context, child, commandInput.peekString()); + return this.addArgumentSuggestions(context, child, commandInput); }); }); } @@ -902,20 +900,20 @@ private CompletableFuture 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> addArgumentSuggestions( final @NonNull SuggestionContext context, final @NonNull CommandNode node, - final @NonNull String text + final @NonNull CommandInput input ) { final CommandComponent 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) { @@ -925,7 +923,7 @@ private CompletableFuture 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); }); @@ -936,17 +934,17 @@ private CompletableFuture 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> addArgumentSuggestions( final @NonNull SuggestionContext context, final @NonNull CommandComponent component, - final @NonNull String text + final @NonNull CommandInput input ) { context.commandContext().currentComponent(component); return component.suggestionProvider() - .suggestionsFuture(context.commandContext(), text) + .suggestionsFuture(context.commandContext(), input.copy()) .thenAccept(context::addSuggestions) .thenApply(in -> context); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/LiteralParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/LiteralParser.java index 8e8255020..45f376488 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/LiteralParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/LiteralParser.java @@ -39,7 +39,7 @@ import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; -public final class LiteralParser implements ArgumentParser, BlockingSuggestionProvider.Strings { +public final class LiteralParser implements ArgumentParser, BlockingSuggestionProvider.ConstantStrings { /** * Creates a new literal parser that accepts the given {@code name} and {@code aliases}. @@ -90,10 +90,7 @@ private LiteralParser(final @NonNull String name, final @NonNull String... alias } @Override - public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input - ) { + public @NonNull Iterable<@NonNull String> stringSuggestions() { return Collections.singletonList(this.name); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/aggregate/AggregateCommandParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/aggregate/AggregateCommandParser.java index 501100855..b0f9b8fe5 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/aggregate/AggregateCommandParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/aggregate/AggregateCommandParser.java @@ -122,7 +122,7 @@ public interface AggregateCommandParser extends ArgumentParser.FutureArgum @Override default @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture( final @NonNull CommandContext context, - final @NonNull String input + final @NonNull CommandInput input ) { return this.components() .stream() diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlagParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlagParser.java index eccdfe2be..eae158a51 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlagParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlagParser.java @@ -146,7 +146,7 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag> flag @SuppressWarnings({"unchecked", "rawtypes"}) public @NonNull CompletableFuture> suggestionsFuture( final @NonNull CommandContext 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, "")); @@ -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; @@ -204,7 +205,7 @@ 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))); } @@ -212,7 +213,7 @@ public CommandFlagParser(final @NonNull Collection<@NonNull CommandFlag> flag } /* 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 { diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanParser.java index aee62b963..d39332aeb 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanParser.java @@ -41,7 +41,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @API(status = API.Status.STABLE) -public final class BooleanParser implements ArgumentParser, BlockingSuggestionProvider.Strings { +public final class BooleanParser implements ArgumentParser, BlockingSuggestionProvider.ConstantStrings { private static final List STRICT_LOWER = CommandInput.BOOLEAN_STRICT .stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()); @@ -113,10 +113,7 @@ public BooleanParser(final boolean liberal) { } @Override - public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input - ) { + public @NonNull Iterable<@NonNull String> stringSuggestions() { if (!this.liberal) { return STRICT_LOWER; } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteParser.java index a533ca3ac..7832e35d5 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteParser.java @@ -183,7 +183,7 @@ public boolean hasMin() { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return IntegerParser.getSuggestions(this.min, this.max, input); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DurationParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DurationParser.java index beb66341e..bef941b4f 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DurationParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DurationParser.java @@ -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; @@ -133,28 +132,25 @@ public final class DurationParser implements ArgumentParser, Blo @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext 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()); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumParser.java index 62ff3b586..95a73f7d7 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumParser.java @@ -43,7 +43,7 @@ @API(status = API.Status.STABLE) public final class EnumParser> implements ArgumentParser, - BlockingSuggestionProvider.Strings { + BlockingSuggestionProvider.ConstantStrings { /** * Creates a new enum parser. @@ -112,10 +112,7 @@ public EnumParser(final @NonNull Class enumClass) { } @Override - public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext 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()); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerParser.java index cfda47e22..c1e0cfbc5 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerParser.java @@ -145,12 +145,13 @@ public IntegerParser(final int min, final int max) { public static @NonNull List<@NonNull String> getSuggestions( final long min, final long max, - final @NonNull String input + final @NonNull CommandInput input ) { final Set numbers = new TreeSet<>(); + final String token = input.peekString(); try { - final long inputNum = Long.parseLong(input.equals("-") ? "-0" : input.isEmpty() ? "0" : input); + final long inputNum = Long.parseLong(token.equals("-") ? "-0" : token.isEmpty() ? "0" : token); final long inputNumAbsolute = Math.abs(inputNum); numbers.add(inputNumAbsolute); /* It's a valid number, so we suggest it */ @@ -161,7 +162,7 @@ public IntegerParser(final int min, final int max) { final List suggestions = new LinkedList<>(); for (long number : numbers) { - if (input.startsWith("-")) { + if (token.startsWith("-")) { number = -number; /* Preserve sign */ } if (number < min || number > max) { @@ -234,7 +235,7 @@ public boolean hasMin() { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return getSuggestions(this.min, this.max, input); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongParser.java index 0fa7a231a..244c2cb9a 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongParser.java @@ -183,7 +183,7 @@ public boolean hasMin() { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return IntegerParser.getSuggestions(this.min, this.max, input); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortParser.java index dfcaa5836..2ad41c8af 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortParser.java @@ -183,7 +183,7 @@ public boolean hasMin() { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return IntegerParser.getSuggestions(this.min, this.max, input); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/BlockingSuggestionProvider.java b/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/BlockingSuggestionProvider.java index 6d7ade71d..2595bb2fb 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/BlockingSuggestionProvider.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/BlockingSuggestionProvider.java @@ -24,6 +24,7 @@ package cloud.commandframework.arguments.suggestion; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.context.CommandInput; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -50,12 +51,12 @@ interface BlockingSuggestionProvider extends SuggestionProvider { * @param input the current input * @return the suggestions */ - @NonNull Iterable<@NonNull Suggestion> suggestions(@NonNull CommandContext context, @NonNull String input); + @NonNull Iterable<@NonNull Suggestion> suggestions(@NonNull CommandContext context, @NonNull CommandInput input); @Override default @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture( final @NonNull CommandContext context, - final @NonNull String input + final @NonNull CommandInput input ) { return CompletableFuture.completedFuture(this.suggestions(context, input)); } @@ -64,7 +65,7 @@ interface BlockingSuggestionProvider extends SuggestionProvider { * Specialized variant of {@link cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider} that has {@link String} results * instead of {@link Suggestion} results. * - *

The provided default implementation of {@link #suggestions(CommandContext, String)} + *

The provided default implementation of {@link #suggestions(CommandContext, CommandInput)} * maps the {@link String} results to {@link Suggestion suggestions} using {@link Suggestion#simple(String)}.

* * @param command sender type @@ -85,17 +86,51 @@ interface Strings extends cloud.commandframework.arguments.suggestion.Blockin */ @NonNull Iterable<@NonNull String> stringSuggestions( @NonNull CommandContext commandContext, - @NonNull String input + @NonNull CommandInput input ); @Override default @NonNull Iterable<@NonNull Suggestion> suggestions( final @NonNull CommandContext context, - final @NonNull String input + final @NonNull CommandInput input ) { return StreamSupport.stream(this.stringSuggestions(context, input).spliterator(), false) .map(Suggestion::simple) .collect(Collectors.toList()); } } + + /** + * Specialized variant of {@link cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider} that has {@link String} results + * instead of {@link Suggestion} results. + * + *

The provided default implementation of {@link #suggestions(CommandContext, CommandInput)} + * maps the {@link String} results to {@link Suggestion suggestions} using {@link Suggestion#simple(String)}.

+ * + * @param command sender type + */ + @FunctionalInterface + @API(status = API.Status.STABLE, since = "2.0.0") + interface ConstantStrings extends cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider { + + /** + * Returns a list of suggested arguments that would be correctly parsed by this parser. + * + *

This method is likely to be called for every character provided by the sender and + * so it may be necessary to cache results locally to prevent unnecessary computations

+ * + * @return list of suggestions + */ + @NonNull Iterable<@NonNull String> stringSuggestions(); + + @Override + default @NonNull Iterable<@NonNull Suggestion> suggestions( + final @NonNull CommandContext context, + final @NonNull CommandInput input + ) { + return StreamSupport.stream(this.stringSuggestions().spliterator(), false) + .map(Suggestion::simple) + .collect(Collectors.toList()); + } + } } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/SuggestionProvider.java b/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/SuggestionProvider.java index e34c8f409..3d97ea33d 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/SuggestionProvider.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/suggestion/SuggestionProvider.java @@ -24,6 +24,7 @@ package cloud.commandframework.arguments.suggestion; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.context.CommandInput; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.CompletableFuture; @@ -51,7 +52,7 @@ public interface SuggestionProvider { */ @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture( @NonNull CommandContext context, - @NonNull String input + @NonNull CommandInput input ); /** diff --git a/cloud-core/src/main/java/cloud/commandframework/context/CommandInput.java b/cloud-core/src/main/java/cloud/commandframework/context/CommandInput.java index 6e3bcc72c..0ed9e8ab8 100644 --- a/cloud-core/src/main/java/cloud/commandframework/context/CommandInput.java +++ b/cloud-core/src/main/java/cloud/commandframework/context/CommandInput.java @@ -622,6 +622,20 @@ default boolean readBoolean() { return remainingInput.substring(lastSpace + 1); } + /** + * Returns the last remaining character. + * + * @return the last remaining character + * @throws CursorOutOfBoundsException if {@link #isEmpty()} is {@code true} + */ + default char lastRemainingCharacter() { + final String lastToken = this.lastRemainingToken(); + if (lastToken.isEmpty()) { + throw new CursorOutOfBoundsException(this.cursor(), this.length()); + } + return lastToken.charAt(lastToken.length() - 1); + } + /** * Returns a copy of this instance. * diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/aggregate/AggregateCommandParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/aggregate/AggregateCommandParserTest.java index d28d30649..efd8e266d 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/aggregate/AggregateCommandParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/aggregate/AggregateCommandParserTest.java @@ -135,7 +135,7 @@ void testSuggestionsFirstArgument() { .build(); // Act - final Iterable suggestions = parser.suggestionsFuture(this.commandContext, "").join(); + final Iterable suggestions = parser.suggestionsFuture(this.commandContext, CommandInput.empty()).join(); // Assert assertThat(suggestions).containsExactly( @@ -164,7 +164,7 @@ void testSuggestionsSecondArgument() { when(this.commandContext.contains("number")).thenReturn(true); // Act - final Iterable suggestions = parser.suggestionsFuture(this.commandContext, "").join(); + final Iterable suggestions = parser.suggestionsFuture(this.commandContext, CommandInput.empty()).join(); // Assert assertThat(suggestions).containsExactly( diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/BooleanParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/BooleanParserTest.java index ab9976194..422a19eb6 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/BooleanParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/BooleanParserTest.java @@ -140,7 +140,7 @@ void Suggestions_ExpectedSuggestions(final boolean liberal, final List suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ByteParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ByteParserTest.java index 73e9b5e13..e27a26a53 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ByteParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ByteParserTest.java @@ -155,7 +155,7 @@ void Suggestions_EmptyInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert @@ -178,7 +178,7 @@ void Suggestions_NegativeSignInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "-" + CommandInput.of("-") ); // Assert diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/EnumParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/EnumParserTest.java index a29e808d9..25778cab8 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/EnumParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/EnumParserTest.java @@ -95,7 +95,7 @@ void Suggestions_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/IntegerParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/IntegerParserTest.java index e97a4f237..384cccccd 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/IntegerParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/IntegerParserTest.java @@ -149,7 +149,7 @@ void Suggestions_EmptyInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert @@ -172,7 +172,7 @@ void Suggestions_NegativeSignInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "-" + CommandInput.of("-") ); // Assert diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/LongParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/LongParserTest.java index 248e2d681..33eeffc67 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/LongParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/LongParserTest.java @@ -149,7 +149,7 @@ void Suggestions_EmptyInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert @@ -172,7 +172,7 @@ void Suggestions_NegativeSignInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "-" + CommandInput.of("-") ); // Assert diff --git a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ShortParserTest.java b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ShortParserTest.java index f911d91bb..cae2721c6 100644 --- a/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ShortParserTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/arguments/standard/ShortParserTest.java @@ -149,7 +149,7 @@ void Suggestions_EmptyInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "" + CommandInput.empty() ); // Assert @@ -172,7 +172,7 @@ void Suggestions_NegativeSignInput_ExpectedSuggestions() { // Act final Iterable suggestions = parser.suggestions( this.context, - "-" + CommandInput.of("-") ); // Assert diff --git a/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/main/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethods.kt b/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/main/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethods.kt index d6a92d4d0..ae07d1cab 100644 --- a/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/main/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethods.kt +++ b/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/main/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethods.kt @@ -30,6 +30,7 @@ import cloud.commandframework.annotations.suggestions.SuggestionProviderFactory import cloud.commandframework.arguments.suggestion.Suggestion import cloud.commandframework.arguments.suggestion.SuggestionProvider import cloud.commandframework.context.CommandContext +import cloud.commandframework.context.CommandInput import cloud.commandframework.execution.CommandExecutionCoordinator import io.leangen.geantyref.GenericTypeReflector import kotlinx.coroutines.CoroutineScope @@ -186,10 +187,14 @@ private class KotlinSuggestionProvider( private val instance: Any ) : SuggestionProvider { - override fun suggestionsFuture(context: CommandContext, input: String): CompletableFuture> { + override fun suggestionsFuture(context: CommandContext, input: CommandInput): CompletableFuture> { return coroutineScope.future(coroutineContext) { try { - kFunction.callSuspend(instance, context, input) + if (kFunction.valueParameters[1].type.classifier == String::class) { + kFunction.callSuspend(instance, context, input.lastRemainingToken()) + } else { + kFunction.callSuspend(instance, context, input) + } } catch (e: InvocationTargetException) { e.cause?.let { throw it } ?: throw e // if cause exists, throw, else rethrow invocation exception } diff --git a/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/test/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethodsTest.kt b/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/test/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethodsTest.kt index 1c7609990..5e480bb7e 100644 --- a/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/test/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethodsTest.kt +++ b/cloud-kotlin/cloud-kotlin-coroutines-annotations/src/test/kotlin/cloud/commandframework/kotlin/coroutines/annotations/KotlinAnnotatedMethodsTest.kt @@ -30,6 +30,7 @@ import cloud.commandframework.annotations.CommandMethod import cloud.commandframework.annotations.suggestions.Suggestions import cloud.commandframework.arguments.suggestion.Suggestion import cloud.commandframework.context.CommandContext +import cloud.commandframework.context.CommandInput import cloud.commandframework.context.StandardCommandContextFactory import cloud.commandframework.exceptions.CommandExecutionException import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator @@ -105,7 +106,7 @@ class KotlinAnnotatedMethodsTest { TestCommandSender() ) val suggestions = commandManager.parserRegistry().getSuggestionProvider("suspending-suggestions").get() - .suggestionsFuture(commandContext, "") + .suggestionsFuture(commandContext, CommandInput.empty()) .await() .map(Suggestion::suggestion) .map(String::toInt) @@ -123,7 +124,7 @@ class KotlinAnnotatedMethodsTest { TestCommandSender() ) val suggestions = commandManager.parserRegistry().getSuggestionProvider("non-suspending-suggestions").get() - .suggestionsFuture(commandContext, "") + .suggestionsFuture(commandContext, CommandInput.empty()) .await() .map(Suggestion::suggestion) .map(String::toInt) diff --git a/cloud-kotlin/cloud-kotlin-coroutines/src/main/kotlin/cloud/commandframework/kotlin/coroutines/SuspendingSuggestionProvider.kt b/cloud-kotlin/cloud-kotlin-coroutines/src/main/kotlin/cloud/commandframework/kotlin/coroutines/SuspendingSuggestionProvider.kt index dc3ebe1fb..81fe1bf68 100644 --- a/cloud-kotlin/cloud-kotlin-coroutines/src/main/kotlin/cloud/commandframework/kotlin/coroutines/SuspendingSuggestionProvider.kt +++ b/cloud-kotlin/cloud-kotlin-coroutines/src/main/kotlin/cloud/commandframework/kotlin/coroutines/SuspendingSuggestionProvider.kt @@ -26,6 +26,7 @@ package cloud.commandframework.kotlin.coroutines import cloud.commandframework.arguments.suggestion.Suggestion import cloud.commandframework.arguments.suggestion.SuggestionProvider import cloud.commandframework.context.CommandContext +import cloud.commandframework.context.CommandInput import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator import cloud.commandframework.execution.CommandExecutionCoordinator import kotlinx.coroutines.CoroutineScope @@ -50,7 +51,7 @@ public fun interface SuspendingSuggestionProvider { * @param input the current input * @return the suggestions */ - public suspend operator fun invoke(context: CommandContext, input: String): Iterable + public suspend operator fun invoke(context: CommandContext, input: CommandInput): Iterable /** * Creates a new [SuggestionProvider] backed by this [SuspendingExecutionHandler]. @@ -95,7 +96,7 @@ public fun interface SuspendingSuggestionProvider { public suspend inline fun suspendingSuggestionProvider( scope: CoroutineScope = GlobalScope, context: CoroutineContext = EmptyCoroutineContext, - crossinline provider: suspend (CommandContext, String) -> Iterable + crossinline provider: suspend (CommandContext, CommandInput) -> Iterable ): SuggestionProvider = SuspendingSuggestionProvider { commandContext, input -> provider(commandContext, input) }.asSuggestionProvider(scope, context) diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/argument/WrappedBrigadierParser.java b/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/argument/WrappedBrigadierParser.java index 0186a71af..2c7a5c1b5 100644 --- a/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/argument/WrappedBrigadierParser.java +++ b/cloud-minecraft/cloud-brigadier/src/main/java/cloud/commandframework/brigadier/argument/WrappedBrigadierParser.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.apiguardian.api.API; @@ -163,7 +164,7 @@ public final ArgumentType getNativeArgument() { @Override public final @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { /* * Strictly, this is incorrect. @@ -185,7 +186,7 @@ public final ArgumentType getNativeArgument() { return this.nativeType.get().listSuggestions( reverseMappedContext, - new SuggestionsBuilder(input, 0) + new SuggestionsBuilder(input.input(), input.input().toLowerCase(Locale.ROOT), input.cursor()) ).thenApply(suggestions -> { final List cloud = new ArrayList<>(); for (final com.mojang.brigadier.suggestion.Suggestion suggestion : suggestions.getList()) { diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/EnchantmentParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/EnchantmentParser.java index fa2e27c6b..bb43dfa71 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/EnchantmentParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/EnchantmentParser.java @@ -42,7 +42,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; public final class EnchantmentParser implements ArgumentParser, - BlockingSuggestionProvider.Strings { + BlockingSuggestionProvider.ConstantStrings { /** * Creates a enchantment parser. @@ -101,10 +101,7 @@ public final class EnchantmentParser implements ArgumentParser stringSuggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input - ) { + public @NonNull Iterable<@NonNull String> stringSuggestions() { final List completions = new ArrayList<>(); for (Enchantment value : Enchantment.values()) { if (value.getKey().getNamespace().equals(NamespacedKey.MINECRAFT)) { diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackParser.java index 1934b60e3..4115abf84 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackParser.java @@ -276,7 +276,7 @@ public boolean hasExtraData() { } private static final class LegacyParser implements ArgumentParser.FutureArgumentParser, - BlockingSuggestionProvider.Strings { + BlockingSuggestionProvider.ConstantStrings { private final ArgumentParser parser = new MaterialParser() .mapSuccess((ctx, material) -> CompletableFuture.completedFuture(new LegacyProtoItemStack(material))); @@ -290,10 +290,7 @@ private static final class LegacyParser implements ArgumentParser.FutureArgum } @Override - public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input - ) { + public @NonNull Iterable<@NonNull String> stringSuggestions() { return Arrays.stream(Material.values()) .filter(Material::isItem) .map(value -> value.name().toLowerCase(Locale.ROOT)) diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/MaterialParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/MaterialParser.java index 61b5f046a..9db5a4460 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/MaterialParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/MaterialParser.java @@ -92,7 +92,7 @@ public final class MaterialParser implements ArgumentParser, Blo @Override public @NonNull Iterable<@NonNull Suggestion> suggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return Arrays.stream(Material.values()) .map(Material::name) diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/NamespacedKeyParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/NamespacedKeyParser.java index f3a8b59ff..cc7105b51 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/NamespacedKeyParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/NamespacedKeyParser.java @@ -184,13 +184,16 @@ public NamespacedKeyParser( @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { final List ret = new ArrayList<>(); ret.add(this.defaultNamespace + ":"); - if (!input.contains(":") && !input.isEmpty()) { - ret.add(input + ":"); + + final String token = input.peekString(); + if (!token.contains(":") && !token.isEmpty()) { + ret.add(token + ":"); } + return ret; } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerParser.java index 7507e8881..1deaa3af4 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerParser.java @@ -104,7 +104,7 @@ public final class OfflinePlayerParser implements ArgumentParser suggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); return Bukkit.getOnlinePlayers().stream() diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerParser.java index 025ff17fd..17ddba8f4 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerParser.java @@ -95,7 +95,7 @@ public final class PlayerParser implements ArgumentParser, Blockin @Override public @NonNull Iterable<@NonNull Suggestion> suggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); return Bukkit.getOnlinePlayers().stream() diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/WorldParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/WorldParser.java index fd1b3fb19..6f9a1de5d 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/WorldParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/WorldParser.java @@ -40,7 +40,7 @@ import org.bukkit.World; import org.checkerframework.checker.nullness.qual.NonNull; -public final class WorldParser implements ArgumentParser, BlockingSuggestionProvider.Strings { +public final class WorldParser implements ArgumentParser, BlockingSuggestionProvider.ConstantStrings { /** * Creates a new world parser. @@ -88,10 +88,7 @@ public final class WorldParser implements ArgumentParser, BlockingS } @Override - public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input - ) { + public @NonNull Iterable<@NonNull String> stringSuggestions() { return Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DParser.java index e3e8a4f64..63356ab82 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DParser.java @@ -159,9 +159,9 @@ public final class Location2DParser implements ArgumentParser, @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { - return LocationParser.getSuggestions(commandContext, input); + return LocationParser.getSuggestions(2, commandContext, input); } @Override diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationParser.java index 32df065e7..bedb9780b 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationParser.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationParser.java @@ -196,28 +196,30 @@ private static float toRadians(final float degrees) { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { - return LocationParser.getSuggestions(commandContext, input); + return LocationParser.getSuggestions(3, commandContext, input); } static @NonNull List<@NonNull String> getSuggestions( + final int components, final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { - final String workingInput; - final String prefix; - if (input.startsWith("~") || input.startsWith("^")) { - prefix = Character.toString(input.charAt(0)); - workingInput = input.substring(1); - } else { - prefix = ""; - workingInput = input; + final int toSkip = Math.min(components, input.remainingTokens()) - 1; + final StringBuilder prefix = new StringBuilder(); + for (int i = 0; i < toSkip; i++) { + prefix.append(input.readString()).append(" "); + } + + if (input.hasRemainingInput() && (input.peek() == '~' || input.peek() == '^')) { + prefix.append(input.read()); } + return IntegerParser.getSuggestions( Integer.MIN_VALUE, Integer.MAX_VALUE, - workingInput + input ).stream().map(string -> prefix + string).collect(Collectors.toList()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java index 70b4c6d81..7cf4a415f 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java @@ -179,7 +179,7 @@ protected CompletableFuture> legacyParse( protected @NonNull Iterable<@NonNull Suggestion> legacySuggestions( final CommandContext commandContext, - final String input + final CommandInput input ) { return Collections.emptyList(); } @@ -198,7 +198,7 @@ public CompletableFuture> parseFuture( @Override public CompletableFuture> suggestionsFuture( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { if (this.modernParser != null) { this.modernParser.suggestionProvider().suggestionsFuture(commandContext, input); @@ -249,7 +249,7 @@ protected PlayerSelectorParser(final boolean single) { @Override protected @NonNull Iterable<@NonNull Suggestion> legacySuggestions( final CommandContext commandContext, - final String input + final CommandInput input ) { final List suggestions = new ArrayList<>(); @@ -309,7 +309,7 @@ public CompletableFuture> parseFuture( @Override public CompletableFuture> suggestionsFuture( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { final Object commandSourceStack = commandContext.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER); final @Nullable Field bypassField = diff --git a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerParser.java b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerParser.java index e4058509d..f893982d3 100644 --- a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerParser.java +++ b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerParser.java @@ -99,7 +99,7 @@ public final class PlayerParser implements ArgumentParser, @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return commandContext.get("ProxyServer") .getPlayers() diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryParser.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryParser.java index 3b61afadc..ba8757f24 100644 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryParser.java +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryParser.java @@ -166,7 +166,7 @@ private Registry resolveRegistry(final CommandContext ctx) { @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { final Set ids = this.resolveRegistry(commandContext).keySet(); final List results = new ArrayList<>(ids.size()); diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/TeamParser.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/TeamParser.java index e2a384eb9..6695dfa3d 100644 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/TeamParser.java +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/TeamParser.java @@ -29,6 +29,7 @@ import cloud.commandframework.arguments.suggestion.BlockingSuggestionProvider; import cloud.commandframework.captions.CaptionVariable; import cloud.commandframework.context.CommandContext; +import cloud.commandframework.context.CommandInput; import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import cloud.commandframework.exceptions.parsing.ParserException; import cloud.commandframework.fabric.FabricCaptionKeys; @@ -77,7 +78,7 @@ public final class TeamParser extends SidedArgumentParser stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return new ArrayList<>(commandContext.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE).getAllTeams()); } diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/TextColorParser.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/TextColorParser.java index 6f32b06ef..8da3c00cb 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/TextColorParser.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/TextColorParser.java @@ -146,17 +146,18 @@ public final class TextColorParser implements ArgumentParser, B @Override public @NonNull Iterable<@NonNull String> stringSuggestions( - final @NonNull CommandContext commandContext, final @NonNull String input + final @NonNull CommandContext commandContext, final @NonNull CommandInput input ) { final List suggestions = new LinkedList<>(); - if (input.isEmpty() || input.equals("#") || (HEX_PREDICATE.matcher(input).matches() - && input.length() < (input.startsWith("#") ? 7 : 6))) { + final String token = input.readString(); + if (token.isEmpty() || token.equals("#") || (HEX_PREDICATE.matcher(token).matches() + && token.length() < (token.startsWith("#") ? 7 : 6))) { for (char c = 'a'; c <= 'f'; c++) { - suggestions.add(String.format("%s%c", input, c)); + suggestions.add(String.format("%s%c", token, c)); suggestions.add(String.format("&%c", c)); } for (char c = '0'; c <= '9'; c++) { - suggestions.add(String.format("%s%c", input, c)); + suggestions.add(String.format("%s%c", token, c)); suggestions.add(String.format("&%c", c)); } } diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/parser/KeyedWorldParser.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/parser/KeyedWorldParser.java index 8f0987f7f..6f60cf62c 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/parser/KeyedWorldParser.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/parser/KeyedWorldParser.java @@ -125,7 +125,7 @@ public KeyedWorldParser() { @Override public @NonNull CompletableFuture<@NonNull Iterable<@NonNull Suggestion>> suggestionsFuture( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { if (this.parser != null) { return this.parser.suggestionProvider().suggestionsFuture(commandContext, input); @@ -135,7 +135,7 @@ public KeyedWorldParser() { final List completions = new ArrayList<>(worlds.size() * 2); for (final World world : worlds) { final NamespacedKey key = world.getKey(); - if (!input.isEmpty() && key.getNamespace().equals(NamespacedKey.MINECRAFT_NAMESPACE)) { + if (input.hasRemainingInput() && key.getNamespace().equals(NamespacedKey.MINECRAFT_NAMESPACE)) { completions.add(Suggestion.simple(key.getKey())); } completions.add(Suggestion.simple(key.getNamespace() + ':' + key.getKey())); diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerParser.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerParser.java index 2e481700c..2797d912a 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerParser.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerParser.java @@ -102,7 +102,7 @@ public final class PlayerParser implements ArgumentParser, Blockin @Override public @NonNull Iterable<@NonNull String> stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return commandContext.get("ProxyServer").getAllPlayers() .stream().map(Player::getUsername).collect(Collectors.toList()); diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerParser.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerParser.java index 9d3c32e1e..01a7b25fb 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerParser.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerParser.java @@ -102,7 +102,7 @@ public final class ServerParser implements ArgumentParser stringSuggestions( final @NonNull CommandContext commandContext, - final @NonNull String input + final @NonNull CommandInput input ) { return commandContext.get("ProxyServer") .getAllServers() diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/annotations/feature/minecraft/LocationExample.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/annotations/feature/minecraft/LocationExample.java index c75ec1d57..45e1b3877 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/annotations/feature/minecraft/LocationExample.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/annotations/feature/minecraft/LocationExample.java @@ -26,6 +26,7 @@ import cloud.commandframework.annotations.AnnotationParser; import cloud.commandframework.annotations.Argument; import cloud.commandframework.annotations.CommandMethod; +import cloud.commandframework.annotations.Default; import cloud.commandframework.bukkit.parsers.location.Location2D; import cloud.commandframework.examples.bukkit.ExamplePlugin; import cloud.commandframework.examples.bukkit.annotations.AnnotationFeature; @@ -52,7 +53,7 @@ public void registerFeature( public void teleportComplex( final @NonNull Player sender, final @Argument("location") @NonNull Location location, - final @Argument("announce") boolean announce + final @Argument("announce") @Default("false") boolean announce ) { sender.teleport(location); if (announce) {