Skip to content

Commit d282ef2

Browse files
authored
feat: fix suggestion with nested aliases (#41)
1 parent 20a8125 commit d282ef2

File tree

3 files changed

+71
-17
lines changed

3 files changed

+71
-17
lines changed

core/src/main/java/fr/traqueur/commands/api/CommandManager.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,7 @@ public boolean isDebug() {
145145
*/
146146
public void registerCommand(Command<T,S> command) {
147147
try {
148-
List<String> aliases = new ArrayList<>(command.getAliases());
149-
aliases.add(command.getName());
150-
for (String alias : aliases) {
148+
for (String alias : command.getAliases()) {
151149
this.addCommand(command, alias);
152150
this.registerSubCommands(alias, command.getSubcommands());
153151
}

core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.util.*;
1414
import java.util.stream.Collectors;
15+
import java.util.stream.Stream;
1516

1617
/**
1718
* CommandInvoker is responsible for invoking and suggesting commands.
@@ -107,21 +108,53 @@ public boolean invoke(S source, String base, String[] rawArgs) {
107108
*/
108109
public List<String> suggest(S source, String base, String[] args) {
109110
Optional<MatchResult<T, S>> found = manager.getCommands().findNode(base, args);
110-
if (!found.isPresent()) return Collections.emptyList();
111-
MatchResult<T, S> result = found.get();
112-
CommandTree.CommandNode<T, S> node = result.node;
113-
String[] rawArgs = result.args;
114-
String label = node.getFullLabel() != null ? node.getFullLabel() : base;
115-
Map<Integer, TabCompleter<S>> map = manager.getCompleters().get(label);
116-
if (map != null && map.containsKey(args.length)) {
117-
return map.get(args.length)
118-
.onCompletion(source, Arrays.asList(rawArgs))
119-
.stream()
120-
.filter(opt -> allowedSuggestion(source, label, opt))
121-
.filter(opt -> matchesPrefix(opt, args[args.length - 1]))
122-
.collect(Collectors.toList());
111+
String lastArg = args.length > 0 ? args[args.length - 1] : "";
112+
if (found.isPresent()) {
113+
MatchResult<T, S> result = found.get();
114+
CommandTree.CommandNode<T, S> node = result.node;
115+
String[] rawArgs = result.args;
116+
String label = Optional.ofNullable(node.getFullLabel()).orElse(base);
117+
Map<Integer, TabCompleter<S>> map = manager.getCompleters().get(label);
118+
if (map != null) {
119+
TabCompleter<S> completer = map.get(args.length);
120+
if (completer != null) {
121+
return completer.onCompletion(source, Arrays.asList(rawArgs)).stream()
122+
.filter(opt -> allowedSuggestion(source, label, opt))
123+
.filter(opt -> matchesPrefix(opt, lastArg))
124+
.collect(Collectors.toList());
125+
}
126+
}
127+
}
128+
129+
CommandTree.CommandNode<T, S> current = manager.getCommands().getRoot().getChildren().get(base.toLowerCase());
130+
if (current == null) return Collections.emptyList();
131+
132+
current = traverseNode(current, args);
133+
String parentLabel = current.getFullLabel();
134+
135+
Stream<String> children = current.getChildren().keySet().stream();
136+
if (args.length > 0 && current.getChildren().containsKey(lastArg.toLowerCase())) {
137+
children = children.filter(opt -> matchesPrefix(opt, lastArg));
138+
}
139+
140+
return children
141+
.filter(opt -> allowedSuggestion(source, parentLabel, opt))
142+
.collect(Collectors.toList());
143+
}
144+
145+
private CommandTree.CommandNode<T, S> traverseNode(CommandTree.CommandNode<T, S> node, String[] args) {
146+
int index = 0;
147+
while (index < args.length - 1) {
148+
String arg = args[index].toLowerCase();
149+
CommandTree.CommandNode<T, S> child = node.getChildren().get(arg);
150+
if (child != null) {
151+
node = child;
152+
index++;
153+
} else {
154+
break;
155+
}
123156
}
124-
return Collections.emptyList();
157+
return node;
125158
}
126159

127160
private boolean matchesPrefix(String candidate, String current) {

core/src/test/java/fr/traqueur/commands/api/models/CommandInvokerTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.junit.jupiter.api.BeforeEach;
1010
import org.junit.jupiter.api.Test;
1111

12+
import java.util.List;
1213
import java.util.concurrent.atomic.AtomicBoolean;
1314

1415
import static org.junit.jupiter.api.Assertions.*;
@@ -121,6 +122,28 @@ public void execute(String sender, Arguments arguments) {
121122
assertTrue(executed.get());
122123
}
123124

125+
@Test
126+
void valid_AliasWithSubCommand_executesSubCommand() {
127+
cmd.addAlias("base.sub");
128+
129+
DummyCommand sub = new DummyCommand();
130+
cmd.addSubCommand(sub);
131+
132+
tree.addCommand("base.sub", cmd);
133+
tree.addCommand("base.sub.base", sub);
134+
tree.addCommand("base.base", sub);
135+
136+
List<String> suggests = invoker.suggest("user", "base", new String[]{""});
137+
assertTrue(suggests.contains("sub"));
138+
assertTrue(suggests.contains("base"));
139+
140+
List<String> suggests3 = invoker.suggest("user", "base", new String[]{"sub"});
141+
assertTrue(suggests3.contains("sub"));
142+
143+
List<String> suggests4 = invoker.suggest("user", "base", new String[]{"sub", ""});
144+
assertTrue(suggests4.contains("base"));
145+
}
146+
124147
static class DummyCommand extends Command<String, String> {
125148
DummyCommand() { super(null, "base"); }
126149
@Override public void execute(String sender, Arguments args) {}

0 commit comments

Comments
 (0)