From c432237977b86d0e3d5bf8fca2576c0ef264c1cf Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Tue, 15 Jul 2025 12:29:49 +0200 Subject: [PATCH] feat: fix suggestion with nested aliases --- .../traqueur/commands/api/CommandManager.java | 4 +- .../commands/api/models/CommandInvoker.java | 61 ++++++++++++++----- .../api/models/CommandInvokerTest.java | 23 +++++++ 3 files changed, 71 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/fr/traqueur/commands/api/CommandManager.java b/core/src/main/java/fr/traqueur/commands/api/CommandManager.java index 75b5731..7e7d041 100644 --- a/core/src/main/java/fr/traqueur/commands/api/CommandManager.java +++ b/core/src/main/java/fr/traqueur/commands/api/CommandManager.java @@ -145,9 +145,7 @@ public boolean isDebug() { */ public void registerCommand(Command command) { try { - List aliases = new ArrayList<>(command.getAliases()); - aliases.add(command.getName()); - for (String alias : aliases) { + for (String alias : command.getAliases()) { this.addCommand(command, alias); this.registerSubCommands(alias, command.getSubcommands()); } diff --git a/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java b/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java index 27137c2..e4c0a43 100644 --- a/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java +++ b/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * CommandInvoker is responsible for invoking and suggesting commands. @@ -107,21 +108,53 @@ public boolean invoke(S source, String base, String[] rawArgs) { */ public List suggest(S source, String base, String[] args) { Optional> found = manager.getCommands().findNode(base, args); - if (!found.isPresent()) return Collections.emptyList(); - MatchResult result = found.get(); - CommandTree.CommandNode node = result.node; - String[] rawArgs = result.args; - String label = node.getFullLabel() != null ? node.getFullLabel() : base; - Map> map = manager.getCompleters().get(label); - if (map != null && map.containsKey(args.length)) { - return map.get(args.length) - .onCompletion(source, Arrays.asList(rawArgs)) - .stream() - .filter(opt -> allowedSuggestion(source, label, opt)) - .filter(opt -> matchesPrefix(opt, args[args.length - 1])) - .collect(Collectors.toList()); + String lastArg = args.length > 0 ? args[args.length - 1] : ""; + if (found.isPresent()) { + MatchResult result = found.get(); + CommandTree.CommandNode node = result.node; + String[] rawArgs = result.args; + String label = Optional.ofNullable(node.getFullLabel()).orElse(base); + Map> map = manager.getCompleters().get(label); + if (map != null) { + TabCompleter completer = map.get(args.length); + if (completer != null) { + return completer.onCompletion(source, Arrays.asList(rawArgs)).stream() + .filter(opt -> allowedSuggestion(source, label, opt)) + .filter(opt -> matchesPrefix(opt, lastArg)) + .collect(Collectors.toList()); + } + } + } + + CommandTree.CommandNode current = manager.getCommands().getRoot().getChildren().get(base.toLowerCase()); + if (current == null) return Collections.emptyList(); + + current = traverseNode(current, args); + String parentLabel = current.getFullLabel(); + + Stream children = current.getChildren().keySet().stream(); + if (args.length > 0 && current.getChildren().containsKey(lastArg.toLowerCase())) { + children = children.filter(opt -> matchesPrefix(opt, lastArg)); + } + + return children + .filter(opt -> allowedSuggestion(source, parentLabel, opt)) + .collect(Collectors.toList()); + } + + private CommandTree.CommandNode traverseNode(CommandTree.CommandNode node, String[] args) { + int index = 0; + while (index < args.length - 1) { + String arg = args[index].toLowerCase(); + CommandTree.CommandNode child = node.getChildren().get(arg); + if (child != null) { + node = child; + index++; + } else { + break; + } } - return Collections.emptyList(); + return node; } private boolean matchesPrefix(String candidate, String current) { diff --git a/core/src/test/java/fr/traqueur/commands/api/models/CommandInvokerTest.java b/core/src/test/java/fr/traqueur/commands/api/models/CommandInvokerTest.java index ca43250..a92f761 100644 --- a/core/src/test/java/fr/traqueur/commands/api/models/CommandInvokerTest.java +++ b/core/src/test/java/fr/traqueur/commands/api/models/CommandInvokerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.*; @@ -121,6 +122,28 @@ public void execute(String sender, Arguments arguments) { assertTrue(executed.get()); } + @Test + void valid_AliasWithSubCommand_executesSubCommand() { + cmd.addAlias("base.sub"); + + DummyCommand sub = new DummyCommand(); + cmd.addSubCommand(sub); + + tree.addCommand("base.sub", cmd); + tree.addCommand("base.sub.base", sub); + tree.addCommand("base.base", sub); + + List suggests = invoker.suggest("user", "base", new String[]{""}); + assertTrue(suggests.contains("sub")); + assertTrue(suggests.contains("base")); + + List suggests3 = invoker.suggest("user", "base", new String[]{"sub"}); + assertTrue(suggests3.contains("sub")); + + List suggests4 = invoker.suggest("user", "base", new String[]{"sub", ""}); + assertTrue(suggests4.contains("base")); + } + static class DummyCommand extends Command { DummyCommand() { super(null, "base"); } @Override public void execute(String sender, Arguments args) {}