Brigadier commands registered from Commands do not allow to get previous argument in a SuggestionProvider
Expected behavior
I expect this code to work and to give the "config" argument
public final class BrigadierTest extends JavaPlugin {
@Override
public void onEnable() {
// Plugin startup logic
LifecycleEventManager<Plugin> manager = this.getLifecycleManager();
manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
final Commands commands = event.registrar();
SuggestionProvider<CommandSourceStack> configSuggestionProvider = (context, builder) -> {
builder.suggest("config1");
builder.suggest("config2");
return builder.buildFuture();
};
SuggestionProvider<CommandSourceStack> optionSuggestionProvider = (context, builder) -> {
var configName = context.getArgument("config", String.class);
builder.suggest("option");
return builder.buildFuture();
};
commands.register(
literal("testcommand")
.requires(source -> source.getSender().hasPermission("lumenconfig.manage"))
.then(literal("copy")
.then(argument("config", StringArgumentType.string())
.suggests(configSuggestionProvider)
.then(argument("from", StringArgumentType.string())
.suggests(optionSuggestionProvider)
.then(argument("to", StringArgumentType.string())
.executes(context -> {
context.getSource().getSender().sendMessage("Config: " + context.getArgument("config", String.class));
context.getSource().getSender().sendMessage("From: " + context.getArgument("from", String.class));
context.getSource().getSender().sendMessage("To: " + context.getArgument("to", String.class));
return 1;
})
)
)
)
)
.build(),
"testcommand",
List.of("testalias")
);
});
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
}
Observed/Actual behavior
[22:12:49 WARN]: Unhandled exception when tab completing
java.util.concurrent.ExecutionException: org.bukkit.command.CommandException: Unhandled exception executing tab-completer for 'testcommand copy config1 ' in io.papermc.paper.command.brigadier.PluginVanillaCommandWrapper(testcommand)
at org.bukkit.craftbukkit.util.Waitable.get(Waitable.java:41) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.craftbukkit.command.ConsoleCommandCompleter.complete(ConsoleCommandCompleter.java:103) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.jline.reader.impl.LineReaderImpl.doComplete(LineReaderImpl.java:4381) ~[jline-reader-3.20.0.jar:?]
at org.jline.reader.impl.LineReaderImpl.doComplete(LineReaderImpl.java:4347) ~[jline-reader-3.20.0.jar:?]
at org.jline.reader.impl.LineReaderImpl.expandOrComplete(LineReaderImpl.java:4286) ~[jline-reader-3.20.0.jar:?]
at org.jline.reader.impl.LineReaderImpl$1.apply(LineReaderImpl.java:3778) ~[jline-reader-3.20.0.jar:?]
at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:679) ~[jline-reader-3.20.0.jar:?]
at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:468) ~[jline-reader-3.20.0.jar:?]
at net.minecrell.terminalconsole.SimpleTerminalConsole.readCommands(SimpleTerminalConsole.java:158) ~[terminalconsoleappender-1.3.0.jar:?]
at net.minecrell.terminalconsole.SimpleTerminalConsole.start(SimpleTerminalConsole.java:141) ~[terminalconsoleappender-1.3.0.jar:?]
at net.minecraft.server.dedicated.DedicatedServer$1.run(DedicatedServer.java:117) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
Caused by: org.bukkit.command.CommandException: Unhandled exception executing tab-completer for 'testcommand copy config1 ' in io.papermc.paper.command.brigadier.PluginVanillaCommandWrapper(testcommand)
at org.bukkit.command.SimpleCommandMap.tabComplete(SimpleCommandMap.java:256) ~[paper-mojangapi-1.21.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.command.SimpleCommandMap.tabComplete(SimpleCommandMap.java:201) ~[paper-mojangapi-1.21.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.command.ConsoleCommandCompleter$2.evaluate(ConsoleCommandCompleter.java:93) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.craftbukkit.command.ConsoleCommandCompleter$2.evaluate(ConsoleCommandCompleter.java:90) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.craftbukkit.util.Waitable.run(Waitable.java:23) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.tickChildren(MinecraftServer.java:1754) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.dedicated.DedicatedServer.tickChildren(DedicatedServer.java:473) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.tickServer(MinecraftServer.java:1598) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1304) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:330) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]
Caused by: java.lang.IllegalArgumentException: No such argument 'config' exists on this command
at com.mojang.brigadier.context.CommandContext.getArgument(CommandContext.java:102) ~[brigadier-1.3.10.jar:?]
at BrigadierTest-1.0.0-SNAPSHOT.jar/com.test.brigadiertest.BrigadierTest.lambda$onEnable$1(BrigadierTest.java:34) ~[BrigadierTest-1.0.0-SNAPSHOT.jar:?]
at com.mojang.brigadier.tree.ArgumentCommandNode.listSuggestions(ArgumentCommandNode.java:71) ~[brigadier-1.3.10.jar:1.21.1-74-971a7a5]
at com.mojang.brigadier.CommandDispatcher.getCompletionSuggestions(CommandDispatcher.java:551) ~[paper-1.21.1.jar:?]
at com.mojang.brigadier.CommandDispatcher.getCompletionSuggestions(CommandDispatcher.java:531) ~[paper-1.21.1.jar:?]
at org.bukkit.craftbukkit.command.VanillaCommandWrapper.tabComplete(VanillaCommandWrapper.java:69) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.command.SimpleCommandMap.tabComplete(SimpleCommandMap.java:250) ~[paper-mojangapi-1.21.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.command.SimpleCommandMap.tabComplete(SimpleCommandMap.java:201) ~[paper-mojangapi-1.21.1-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.command.ConsoleCommandCompleter$2.evaluate(ConsoleCommandCompleter.java:93) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.craftbukkit.command.ConsoleCommandCompleter$2.evaluate(ConsoleCommandCompleter.java:90) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at org.bukkit.craftbukkit.util.Waitable.run(Waitable.java:23) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.tickChildren(MinecraftServer.java:1754) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.dedicated.DedicatedServer.tickChildren(DedicatedServer.java:473) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.tickServer(MinecraftServer.java:1598) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1304) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:330) ~[paper-1.21.1.jar:1.21.1-74-971a7a5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]
Steps/models to reproduce
Type testcommand copy config1 and press tab
Plugin and Datapack List
Just this test plugin, no datapacks
Paper version
[22:13:27 INFO]: Checking version, please wait...
[22:13:27 INFO]: This server is running Paper version 1.21.1-74-master@971a7a5 (2024-09-08T20:24:20Z) (Implementing API version 1.21.1-R0.1-SNAPSHOT)
You are running the latest version
Previous version: 1.21.1-69-925c3b9 (MC: 1.21.1)
Other
When registering the same command using the dispatcher, code below, it does work fine and the tab complete works
public final class BrigadierTest extends JavaPlugin {
@Override
public void onEnable() {
// Plugin startup logic
LifecycleEventManager<Plugin> manager = this.getLifecycleManager();
manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
final Commands commands = event.registrar();
SuggestionProvider<CommandSourceStack> configSuggestionProvider = (context, builder) -> {
builder.suggest("config1");
builder.suggest("config2");
return builder.buildFuture();
};
SuggestionProvider<CommandSourceStack> optionSuggestionProvider = (context, builder) -> {
var configName = context.getArgument("config", String.class);
builder.suggest("option");
return builder.buildFuture();
};
commands.getDispatcher().register(
literal("testcommand")
.requires(source -> source.getSender().hasPermission("lumenconfig.manage"))
.then(literal("copy")
.then(argument("config", StringArgumentType.string())
.suggests(configSuggestionProvider)
.then(argument("from", StringArgumentType.string())
.suggests(optionSuggestionProvider)
.then(argument("to", StringArgumentType.string())
.executes(context -> {
context.getSource().getSender().sendMessage("Config: " + context.getArgument("config", String.class));
context.getSource().getSender().sendMessage("From: " + context.getArgument("from", String.class));
context.getSource().getSender().sendMessage("To: " + context.getArgument("to", String.class));
return 1;
})
)
)
)
)
);
});
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
}
Contexts are linked. Use getLastChild() on the context in the suggestion provider to get the correct context to get the parsed argument.
Yeah, this is a combination of how Brigadier behaves (Mojang/brigadier#142) and how Paper registers commands.
When you register a command through Paper's Commands#register, the main label ("testcommand" here) gets registered as a redirect to the namespaced node ("brigadiertest:testcommand" for example). When a command redirects, Brigadier puts future arguments into a child CommandContext. So, to access the previous arguments in the SuggestionProvider, you need to call CommandContext.getLastChild()#getArgument.
On the other hand, using Commands.getDispatcher()#register does not create a redirected node, which is why the command you gave under Other works. Note that this command can still generate suggestions in a redirected situation using /execute run testcommand..., so it can help to call CommandContext#getLastChild anyway.