Brigadier Suggestions

As described in The ArgumentSuggestions interface, the ArgumentSuggestions interface has the following default method:

@FunctionalInterface
public interface ArgumentSuggestions {

    /**
     * Create a {@link CompletableFuture} resolving onto a brigadier {@link Suggestions} object.
     * @param info The suggestions info
     * @param builder The Brigadier {@link SuggestionsBuilder} object
     * @return a {@link CompletableFuture} resolving onto a brigadier {@link Suggestions} object.
     *
     * @throws CommandSyntaxException if there is an error making suggestions
     */
    CompletableFuture<Suggestions> suggest(SuggestionInfo info, SuggestionsBuilder builder)
        throws CommandSyntaxException;

}

This allows you to use Brigadier's SuggestionsBuilder and Suggestions classes to create more powerful suggestions beyond the basic capabilities of the CommandAPI.

In order to use this, you will need the Brigadier dependency, which you can find under the Brigadier installation instructions.

Example - Using a Minecraft command as an argument

Courtesy of 469512345, the following example shows how using Brigadier's suggestions and parser can be combined with the CommandAPI to create an argument which suggests valid Minecraft commands. This could be used for example as a sudo command, to run a command as another player.

A gif showcasing the a command suggestion for the /give command

For this command, we'll use a GreedyStringArgument because that allows users to enter any combination of characters (which therefore, allows users to enter any command). First, we start by defining the suggestions that we'll use for the GreedyStringArgument. We'll use the ArgumentSuggestions functional interface described above:

ArgumentSuggestions commandSuggestions = (info, builder) -> {
    // The current argument, which is a full command
    String arg = info.currentArg();

    // Identify the position of the current argument
    int start;
    if(arg.contains(" ")) {
        // Current argument contains spaces - it starts after the last space and after the start of this argument.
        start = builder.getStart() + arg.lastIndexOf(' ') + 1;
    } else {
        // Input starts at the start of this argument
        start = builder.getStart();
    }
    
    // Parse command using brigadier
    ParseResults<?> parseResults = Brigadier.getCommandDispatcher()
        .parse(info.currentArg(), Brigadier.getBrigadierSourceFromCommandSender(info.sender()));
    
    // Intercept any parsing errors indicating an invalid command
    for(CommandSyntaxException exception : parseResults.getExceptions().values()) {
        // Raise the error, with the cursor offset to line up with the argument
        throw new CommandSyntaxException(exception.getType(), exception.getRawMessage(), exception.getInput(), exception.getCursor() + start);
    }

    return Brigadier
        .getCommandDispatcher()
        .getCompletionSuggestions(parseResults)
        .thenApply((suggestionsObject) -> {
            // Brigadier's suggestions
            Suggestions suggestions = (Suggestions) suggestionsObject;

            return new Suggestions(
                // Offset the index range of the suggestions by the start of the current argument
                new StringRange(start, start + suggestions.getRange().getLength()),
                // Copy the suggestions
                suggestions.getList()
            );
        });
};

There's a lot to unpack there, but it's generally split up into 4 key sections:

  • Finding the start of the argument. We find the start of the argument so we know where the beginning of our command suggestion is. This is done easily using builder.getStart(), but we also have to take into account any spaces if our command argument contains spaces.

  • Parsing the command argument. We make use of Brigadier's parse() method to parse the argument and generate some ParseResults.

  • Reporting parsing errors. This is actually an optional step, but in general it's good practice to handle exceptions stored in ParseResults. While Brigadier doesn't actually handle suggestion exceptions, this has been included in this example to showcase exception handling.

  • Generating suggestions from parse results. We use our parse results with Brigadier's getCompletionSuggestions() method to generate some suggestions based on the parse results and the suggestion string range.

Now that we've declared our arguments suggestions, we can then create our simple command with the following syntax:

/commandargument <command>

We use the command suggestions declared above by using the replaceSuggestions method in our GreedyStringArgument, and write a simple executor which runs the command that the user provided:

new CommandAPICommand("commandargument")
    .withArguments(new GreedyStringArgument("command").replaceSuggestions(commandSuggestions))
    .executes((sender, args) -> {
        // Run the command using Bukkit.dispatchCommand()
        Bukkit.dispatchCommand(sender, (String) args[0]);
    }).register();