Fix last argument always being treated as greedy in suggestions (#428)

This commit is contained in:
Pablo Herrera 2023-02-22 04:25:05 +01:00 committed by Jason
parent 6fe744f33c
commit 1d3f1d3a56
2 changed files with 43 additions and 11 deletions

View file

@ -630,16 +630,13 @@ public final class CommandTree<C> {
if (commandQueue.isEmpty()) { if (commandQueue.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} else if (child.isLeaf()) { } else if (child.isLeaf()) {
final String input; // Handles only simple cases, others will attempt to parse and then decide based on what gets consumed.
if (commandQueue.size() == 1) { if (commandQueue.size() == 1) {
input = commandQueue.peek(); return this.directSuggestions(commandContext, child, commandQueue.peek());
} else { } else if (child.getValue() instanceof CompoundArgument) {
input = child.getValue() instanceof CompoundArgument return this.directSuggestions(commandContext, child, ((LinkedList<String>) commandQueue).getLast());
? ((LinkedList<String>) commandQueue).getLast()
: String.join(" ", commandQueue);
} }
return this.directSuggestions(commandContext, child, input); } else if (commandQueue.size() == 1 && commandQueue.peek().isEmpty()) {
} else if (commandQueue.peek().isEmpty()) {
return this.directSuggestions(commandContext, child, commandQueue.peek()); return this.directSuggestions(commandContext, child, commandQueue.peek());
} }
@ -662,6 +659,18 @@ public final class CommandTree<C> {
final Optional<?> parsedValue = result.getParsedValue(); final Optional<?> parsedValue = result.getParsedValue();
final boolean parseSuccess = parsedValue.isPresent(); final boolean parseSuccess = parsedValue.isPresent();
// It's the last node, we don't care for success or not as we don't need to delegate to a child
if (child.isLeaf()) {
if (commandQueue.isEmpty()) {
// Greedy parser took all the input, we can restore and just ask for suggestions
commandQueue.addAll(commandQueueOriginal);
return this.directSuggestions(commandContext, child, String.join(" ", commandQueue));
} else {
// It's a leaf and there's leftover stuff, no possible suggestions!
return Collections.emptyList();
}
}
if (parseSuccess && !commandQueue.isEmpty()) { if (parseSuccess && !commandQueue.isEmpty()) {
// the current argument at the position is parsable and there are more arguments following // the current argument at the position is parsable and there are more arguments following
commandContext.store(child.getValue().getName(), parsedValue.get()); commandContext.store(child.getValue().getName(), parsedValue.get());
@ -688,10 +697,10 @@ public final class CommandTree<C> {
// Therefore we shouldn't list the suggestions of the current argument, as clearly the suggestions of // Therefore we shouldn't list the suggestions of the current argument, as clearly the suggestions of
// one of the following arguments is requested // one of the following arguments is requested
return Collections.emptyList(); return Collections.emptyList();
} else {
// Fallback: use suggestion provider of argument
return this.directSuggestions(commandContext, child, commandQueue.peek());
} }
// Fallback: use suggestion provider of argument
return this.directSuggestions(commandContext, child, commandQueue.peek());
} }
private @NonNull String stringOrEmpty(final @Nullable String string) { private @NonNull String stringOrEmpty(final @Nullable String string) {

View file

@ -26,6 +26,7 @@ package cloud.commandframework;
import cloud.commandframework.arguments.compound.ArgumentTriplet; import cloud.commandframework.arguments.compound.ArgumentTriplet;
import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.standard.BooleanArgument; import cloud.commandframework.arguments.standard.BooleanArgument;
import cloud.commandframework.arguments.standard.DurationArgument;
import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.EnumArgument;
import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.arguments.standard.StringArgument;
@ -113,6 +114,8 @@ public class CommandSuggestionsTest {
manager.command(manager.commandBuilder("numberswithmin") manager.command(manager.commandBuilder("numberswithmin")
.argument(IntegerArgument.<TestCommandSender>builder("num").withMin(5).withMax(100))); .argument(IntegerArgument.<TestCommandSender>builder("num").withMin(5).withMax(100)));
manager.command(manager.commandBuilder("duration").argument(DurationArgument.of("duration")));
manager.command(manager.commandBuilder("partial") manager.command(manager.commandBuilder("partial")
.argument( .argument(
StringArgument.<TestCommandSender>builder("arg") StringArgument.<TestCommandSender>builder("arg")
@ -316,6 +319,10 @@ public class CommandSuggestionsTest {
final String input5 = "numberswithmin "; final String input5 = "numberswithmin ";
final List<String> suggestions5 = manager.suggest(new TestCommandSender(), input5); final List<String> suggestions5 = manager.suggest(new TestCommandSender(), input5);
Assertions.assertEquals(Arrays.asList("5", "6", "7", "8", "9"), suggestions5); Assertions.assertEquals(Arrays.asList("5", "6", "7", "8", "9"), suggestions5);
final String input6 = "numbers 1 ";
final List<String> suggestions6 = manager.suggest(new TestCommandSender(), input6);
Assertions.assertEquals(Collections.emptyList(), suggestions6);
} }
@Test @Test
@ -337,6 +344,22 @@ public class CommandSuggestionsTest {
); );
} }
@Test
void testDurations() {
final String input = "duration ";
final List<String> suggestions = manager.suggest(new TestCommandSender(), input);
Assertions.assertEquals(Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9"), suggestions);
final String input2 = "duration 5";
final List<String> suggestions2 = manager.suggest(new TestCommandSender(), input2);
Assertions.assertEquals(Arrays.asList("5d", "5h", "5m", "5s"), suggestions2);
final String input3 = "duration 5s";
final List<String> suggestions3 = manager.suggest(new TestCommandSender(), input3);
Assertions.assertEquals(Collections.emptyList(), suggestions3);
final String input4 = "duration 5s ";
final List<String> suggestions4 = manager.suggest(new TestCommandSender(), input4);
Assertions.assertEquals(Collections.emptyList(), suggestions4);
}
@Test @Test
void testInvalidLiteralThenSpace() { void testInvalidLiteralThenSpace() {
final String input = "test o"; final String input = "test o";