-
-
Notifications
You must be signed in to change notification settings - Fork 48
Auto completions and suggestions
Auto-completions represent a fundamental feature when building commands, as they greatly help the end-user know what parameter to supply and give appropriate suggestions.
Luckily, Lamp provides multiple ways to create powerful suggestions and auto-completions.
This is the most common and convenient way of creating suggestions. We bind a specific suggestion provider to a parameter type.
Let's imagine we have this class, where we store the names of kits:
public final class Kits {
private static final Map<String, Kit> KITS = new HashMap<>();
public static void register(Kit kit) {
KITS.put(kit.getName(), kit);
}
public static Kit getKit(String name) {
return KITS.get(name);
}
public static Set<String> getKitsNames() {
return Collections.unmodifiableSet(KITS.keySet());
}
}
Then, we want every Kit
in commands to be automatically completed by the names of the kits registered. For this, we will use registerParameterSuggestions
in CommandHandler.getAutoCompleter()
CommandHandler handler = ...;
handler.getAutoCompleter().registerParameterSuggestions(Kit.class, (args, sender, command) -> Kits.getKitsNames());
That's it! Now, every Kit
parameter will automatically receive the completions in getKitsNames().
@Command("kit give")
public void giveKit(Kit kit, @Default("me") Player player) {
kit.give(player);
...
}
Sometimes, we may want more restrictions on completions, depending on the command being executed. Using the @AutoComplete
annotation, it is possible to bind IDs to suggestion providers.
handler.getAutoCompleter().registerSuggestion("vipKits", (args, sender, command) -> {
return KITS.values().stream().filter(Kit::isVIP).map(Kit::getName).collect(Collectors.toList());
});
handler.getAutoCompleter().registerSuggestion("adminKits", (args, sender, command) -> {
return KITS.values().stream().filter(Kit::isAdmin).map(Kit::getName).collect(Collectors.toList());
});
Then, we can reference our completions using our annotation, by preceding the ID with @
:
@Command("kit give vip")
@AutoComplete("@vipKits *")
public void giveVipKit(Kit kit, @Default("self") Player player) {
kit.give(player);
...
}
@Command("kit give admin")
@AutoComplete("@adminKits *")
public void giveAdminKit(Kit kit, @Default("self") Player player) {
kit.give(player);
...
}
There are a few things to note here:
- Notice the
*
that followsvipKits
andadminKits
: Since@AutoComplete
overwrites the entire command auto-completion, you are required to provide auto-completions for all the parameters. By using*
for any remaining parameter, we are telling Lamp to use the default suggestion provider for the given parameter. - Notice that IDs must begin with
@
. Otherwise, they will be considered a string literal and be passed directly as the completion. For example, doing@AutoComplete("1|2|3")
will return1
,2
, and3
as suggestions for the command.
Although the above 2 methods cover 80% of the cases, it is possible that you may want to extend your auto-completion functionality beyond what is provided in the other two methods. Such examples are:
- Custom annotations with fields that control how the completion occurs
- Registering the same provider for multiple classes (e.g. a common interface or superclass)
- Auto-completions based on parameter generics (e.g.
List<String>
vsList<Integer>
)
In this example, we will go by the first case, which is the most common. Let's say we want to return a completion for players who only have a specific permission.
Let's create our annotation:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithPermission {
String value();
}
Then, we register a SuggestionProviderFactory (as a lambda) using registerSuggestionFactory
:
handler.getAutoCompleter().registerSuggestionFactory(parameter -> {
if (parameter.hasAnnotation(WithPermission.class)) {
String permission = parameter.getAnnotation(WithPermission.class).value();
return (args, sender, command) -> { // Create a SuggestionProvider here
List<String> players = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.hasPermission(permission))
players.add(player.getName());
}
return players;
};
}
return null; // Parameter does not have @WithPermission, ignore it.
});
Then, we can use our annotation to provide completions for players who only have the required permission.
@Command("send")
public void send(CommandActor sender, @WithPermission("hello.lamp") Player player) {
...
}
This gives us the utmost flexibility in providing completions and makes it possible to abstract away all the completions logic behind a specific annotation, therefore reducing repetitive code and encouraging readability and declarativity. Combined with resolvers that respect custom annotations, this allows you to put all the command-related logic behind a single annotation, requiring you only to implement it on the parameter.
👋 If you're having trouble, need support, or just feel like chatting, feel free to hop by our Discord server!