diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index 7486d3bc..615c6d3c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -630,16 +630,13 @@ public final class CommandTree { if (commandQueue.isEmpty()) { return Collections.emptyList(); } 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) { - input = commandQueue.peek(); - } else { - input = child.getValue() instanceof CompoundArgument - ? ((LinkedList) commandQueue).getLast() - : String.join(" ", commandQueue); + return this.directSuggestions(commandContext, child, commandQueue.peek()); + } else if (child.getValue() instanceof CompoundArgument) { + return this.directSuggestions(commandContext, child, ((LinkedList) commandQueue).getLast()); } - return this.directSuggestions(commandContext, child, input); - } else if (commandQueue.peek().isEmpty()) { + } else if (commandQueue.size() == 1 && commandQueue.peek().isEmpty()) { return this.directSuggestions(commandContext, child, commandQueue.peek()); } @@ -662,6 +659,18 @@ public final class CommandTree { final Optional parsedValue = result.getParsedValue(); 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()) { // the current argument at the position is parsable and there are more arguments following commandContext.store(child.getValue().getName(), parsedValue.get()); @@ -688,10 +697,10 @@ public final class CommandTree { // Therefore we shouldn't list the suggestions of the current argument, as clearly the suggestions of // one of the following arguments is requested 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) { diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java index c1627fd1..dd51bd30 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java @@ -26,6 +26,7 @@ package cloud.commandframework; import cloud.commandframework.arguments.compound.ArgumentTriplet; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.standard.BooleanArgument; +import cloud.commandframework.arguments.standard.DurationArgument; import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArgument; @@ -113,6 +114,8 @@ public class CommandSuggestionsTest { manager.command(manager.commandBuilder("numberswithmin") .argument(IntegerArgument.builder("num").withMin(5).withMax(100))); + manager.command(manager.commandBuilder("duration").argument(DurationArgument.of("duration"))); + manager.command(manager.commandBuilder("partial") .argument( StringArgument.builder("arg") @@ -316,6 +319,10 @@ public class CommandSuggestionsTest { final String input5 = "numberswithmin "; final List suggestions5 = manager.suggest(new TestCommandSender(), input5); Assertions.assertEquals(Arrays.asList("5", "6", "7", "8", "9"), suggestions5); + + final String input6 = "numbers 1 "; + final List suggestions6 = manager.suggest(new TestCommandSender(), input6); + Assertions.assertEquals(Collections.emptyList(), suggestions6); } @Test @@ -337,6 +344,22 @@ public class CommandSuggestionsTest { ); } + @Test + void testDurations() { + final String input = "duration "; + final List 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 suggestions2 = manager.suggest(new TestCommandSender(), input2); + Assertions.assertEquals(Arrays.asList("5d", "5h", "5m", "5s"), suggestions2); + final String input3 = "duration 5s"; + final List suggestions3 = manager.suggest(new TestCommandSender(), input3); + Assertions.assertEquals(Collections.emptyList(), suggestions3); + final String input4 = "duration 5s "; + final List suggestions4 = manager.suggest(new TestCommandSender(), input4); + Assertions.assertEquals(Collections.emptyList(), suggestions4); + } + @Test void testInvalidLiteralThenSpace() { final String input = "test o";