Suggestions and auto-completion
This page explains how we can create static, dynamic and context-aware auto-completions
Auto-completions are a vital part of commands. They help users input valid values and save them a lot of typing.
Lamp provides a lot of helpful APIs for creating auto-completions. We will go through them in this page.
The SuggestionProvider interface
This is the foundational building block that is responsible for handling suggestions and auto-completion. It is a basic functional interface that has access to the command actor and provided parameters (parsed) and returns a list of suggestions.
You will see SuggestionProvider
in the following places:
SuggestionProviders
: An immutable registry that contains all registrations forSuggestionProvider
s. It is maintained throughLamp#suggestionProviders()
and can be constructed insideLamp.Builder#suggestionProviders()
.SuggestionProvider.Factory
: An interface that can generateSuggestionProvider
s dynamically for parameters. This factory can access the parameter type (and generics), its annotations, and other SuggestionProviders. It is a powerful interface as it can generate custom suggestions based on the type generics, a common interface, a specific data type (such as enums), or suggestions bound to a particular annotation.ParameterType#defaultSuggestions()
: This is a method that can be overridden in subclasses of ParameterType. It allows parameter types to define a defaultSuggestionProvider
that is used when no other suggestion provider is available.
Let's move on to the creation of custom suggestions
Static completions
This is the simplest form of auto-completion: we have a static set of values that we would like to recommend to the user.
By static completions, we mean hard-coded, compile-time-defined constant completions. For that, we use the @Suggest
annotation.
This will automatically supply the users with all the suggestions in @Suggest
. A nicety of the @Suggest
annotation is that suggestions are allowed to contain spaces. We can define suggestions that contain spaces easily and Lamp will handle the rest.
As you can see, there isn't much you can do with @Suggest
. This is why we will need to introduce more complicated ways for creating suggestions.
Type-specific completions
It's common to have a certain set of completions bound to a particular Java class. Let's imagine we have a World
parameter. We can create a SuggestionProvider
that will retrieve the names of available worlds and give them back to us.
We can register this in our Lamp.Builder
:
Then, whenever we have a World
parameter, we will automatically receive all World suggestions that are retrieved on demand.
Annotation-specific completions
Lamp also provides a way to create auto-completions that are bound to certain annotations. This is a very flexible approach as it combines the benefits of dynamic parameters as well as annotations that allow you to tweak suggestions as needed.
Let's create an annotation named @WithPermission("some.permission.node")
. This annotation will automatically give us all players that have a certain annotation node.
Let's define our annotation:
Let's create our suggestion provider. We can register one with SuggestionProviders.Builder#addProviderForAnnotation
, which constructs a SuggestionProvider.Factory
under the hood.
Now, we can use our @WithPermission
annotation as needed.
Compare the above with the following, and decide for yourself:
A few extra lines, sure. But this will protect us from messy lambdas, and keep everything in its own separate place. Better organization and maintenance.
Suggestion provider factories
So far, our suggestion providers have been very specific, either to a particular class or to a particular annotation. But, what if we want to generate SuggestionProvider
s that work even beyond such scopes? This is where SuggestionProvider.Factory
comes in. Here are some use-cases where it would come useful:
Create annotation-bound SuggestionProviders that act differently depending on the parameter type or the annotations present on it
Create SuggestionProviders for a certain class or its subclasses
Create SuggestionProviders that respect generics (e.g.
List<String>
vsList<Integer>
)Create SuggestionProviders made of other SuggestionProviders (e.g.
T[]
which uses the completions ofT
)Create SuggestionProviders that act on a certain type of classes, e.g. automatically generate suggestions for all
enum
types without having to explicitly register them....
Let's create a basic factory that will generate suggestions for all enum types. This will save us from creating individual SuggestionProviders.
Let's check if our type is an enum, and if it is not, tell Lamp that we cannot deal with it:
We made sure our factory only works on enums. Let's proceed to creating the suggestion provider.
We will cache the names of enums
We will create a static suggestion provider that returns them in lowercase.
That's it! We can simply register this to our SuggestionProviders.Builder
, and automatically get suggestions for all enums generated. Effortless, efficient and easy!
Parameter-bound completions
Finally, Lamp provides a utility annotation: @SuggestWith
that allows you to specify either a SuggestionProvider or a SuggestionProvider.Factory for individual parameters. This is useful if we want a quick, dirty way of modifying suggestions for a particular parameter without creating individual annotations or wrapper types.
Let's imagine that we have this suggestion provider that automatically suggests all worlds that start with world
.
Then, we can supply this class using @SuggestWith
:
In the future, Lamp will try different things (
instance
,getInstance
,INSTANCE
, singleton methods, etc.) for providing instances, but in the meantime, this is what you have to do.
Last updated
Was this helpful?