PaperMC / Paper

The most widely used, high performance Minecraft server that aims to fix gameplay and mechanics inconsistencies
https://papermc.io/
Other
10.05k stars 2.34k forks source link

Brigadier commands registered from Commands do not allow to get previous argument in a SuggestionProvider #11384

Open DevSolaris opened 2 months ago

DevSolaris commented 2 months ago

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
    }
}
Machine-Maker commented 2 months ago

Contexts are linked. Use getLastChild() on the context in the suggestion provider to get the correct context to get the parsed argument.

willkroboth commented 2 months ago

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.