Brigadier Suggestions
As described in The ArgumentSuggestions interface, the ArgumentSuggestions
interface has the following default method:
@FunctionalInterface
public interface ArgumentSuggestions<CommandSender> {
/**
* 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<CommandSender> 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 - Making an emoji broadcasting message
Say we want to let users broadcast a message, but also allow them to enter emojis into the message they're typing:
For this command, we'll use a GreedyStringArgument
as if we were making a generic broadcasted message. We create a map of emojis to their descriptions to use as tooltips and then we use Brigadier to display the suggestions at the end of the message where the cursor is.
Map<String, String> emojis = new HashMap<>();
emojis.put("☻", "smile");
emojis.put("❤", "heart");
emojis.put("🔥", "fire");
emojis.put("★", "star");
emojis.put("☠", "death");
emojis.put("⚠", "warning");
emojis.put("☀", "sun");
emojis.put("☺", "smile");
emojis.put("☹", "frown");
emojis.put("✉", "mail");
emojis.put("☂", "umbrella");
emojis.put("✘", "cross");
emojis.put("♪", "music note (eighth)");
emojis.put("♬", "music note (beamed sixteenth)");
emojis.put("♩", "music note (quarter)");
emojis.put("♫", "music note (beamed eighth)");
emojis.put("☄", "comet");
emojis.put("✦", "star");
emojis.put("🗡", "sword");
emojis.put("🪓", "axe");
emojis.put("🔱", "trident");
emojis.put("🎣", "fishing rod");
emojis.put("🏹", "bow");
emojis.put("⛏", "pickaxe");
emojis.put("🍖", "food");
Argument<String> messageArgument = new GreedyStringArgument("message")
.replaceSuggestions((info, builder) -> {
// Only display suggestions at the very end character
builder = builder.createOffset(builder.getStart() + info.currentArg().length());
// Suggest all the emojis!
for (Entry<String, String> str : emojis.entrySet()) {
builder.suggest(str.getKey(), new LiteralMessage(str.getValue()));
}
return builder.buildFuture();
});
new CommandAPICommand("emoji")
.withArguments(messageArgument)
.executes((sender, args) -> {
Bukkit.broadcastMessage((String) args.get("message"));
})
.register();
val emojis = mapOf(
"☻" to "smile",
"❤" to "heart",
"🔥" to "fire",
"★" to "star",
"☠" to "death",
"⚠" to "warning",
"☀" to "sun",
"☺" to "smile",
"☹" to "frown",
"✉" to "mail",
"☂" to "umbrella",
"✘" to "cross",
"♪" to "music note (eighth)",
"♬" to "music note (beamed sixteenth)",
"♩" to "music note (quarter)",
"♫" to "music note (beamed eighth)",
"☄" to "comet",
"✦" to "star",
"🗡" to "sword",
"🪓" to "axe",
"🔱" to "trident",
"🎣" to "fishing rod",
"🏹" to "bow",
"⛏" to "pickaxe",
"🍖" to "food"
)
val messageArgument = GreedyStringArgument("message")
.replaceSuggestions { info, builder ->
// Only display suggestions at the very end character
val newBuilder = builder.createOffset(builder.start + info.currentArg().length)
// Suggest all the emojis!
emojis.forEach { (emoji, description) ->
newBuilder.suggest(emoji, LiteralMessage(description))
}
newBuilder.buildFuture()
}
CommandAPICommand("emoji")
.withArguments(messageArgument)
.executes(CommandExecutor { _, args ->
Bukkit.broadcastMessage(args["message"] as String)
})
.register()
In this example, we simply create the GreedyStringArgument
and use replaceSuggestions()
to specify our suggestion rules. We create an offset using the current builder to make suggestions start at the last character (the current builder start builder.getStart()
and the current length of what the user has already typed info.currentArg().length()
). Finally, we build the suggestions with builder.buildFuture()
and then register our command as normal.
Example - Using a Minecraft command as an argument
Developer's Note:
This example has been superseded by the Command argument. This example is still present as it gives an example of much more complicated brigadier suggestions which may be useful for readers!
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.
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<CommandSender> 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
if(!parseResults.getExceptions().isEmpty()) {
CommandSyntaxException exception = parseResults.getExceptions().values().iterator().next();
// 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()
);
});
};
val commandSuggestions: ArgumentSuggestions<CommandSender> = ArgumentSuggestions { info, builder ->
// The current argument, which is a full command
val arg: String = info.currentArg()
// Identify the position of the current argument
var start = if (arg.contains(" ")) {
// Current argument contains spaces - it starts after the last space and after the start of this argument.
builder.start + arg.lastIndexOf(' ') + 1
} else {
// Input starts at the start of this argument
builder.start
}
// Parse command using brigadier
val parseResults: ParseResults<*> = Brigadier.getCommandDispatcher()
.parse(info.currentArg(), Brigadier.getBrigadierSourceFromCommandSender(info.sender))
// Intercept any parsing errors indicating an invalid command
for ((_, exception) in parseResults.exceptions) {
// Raise the error, with the cursor offset to line up with the argument
throw CommandSyntaxException(exception.type, exception.rawMessage, exception.input, exception.cursor + start)
}
val completableFutureSuggestions: CompletableFuture<Suggestions> =
Brigadier.getCommandDispatcher().getCompletionSuggestions(parseResults) as CompletableFuture<Suggestions>
completableFutureSuggestions.thenApply { suggestions: Suggestions ->
Suggestions(
// Offset the index range of the suggestions by the start of the current argument
StringRange(start, start + suggestions.range.length),
// Copy the suggestions
suggestions.list
)
}
}
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 someParseResults
. -
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.get("command"));
}).register();
CommandAPICommand("commandargument")
.withArguments(GreedyStringArgument("command").replaceSuggestions(commandSuggestions))
.executes(CommandExecutor { sender, args ->
// Run the command using Bukkit.dispatchCommand()
Bukkit.dispatchCommand(sender, args["command"] as String)
})
.register()