Selectors
Selectors provide interactive input prompts that go beyond simple text entry. They let users type text, enter passwords with masking, choose one item from a list, or select multiple items with checkboxes – all driven by arrow keys and rendered directly in the terminal.
SelectorType
| Type | Description | User Interaction |
|---|---|---|
INPUT | Text input prompt | Type text, press Enter |
PASSWORD | Masked password input | Type text (shown as *), press Enter |
SELECT | Single selection from a list | Arrow keys to move, Enter to select |
MULTI_SELECT | Multiple selection from a list | Arrow keys to move, Space to toggle, Enter to finish |
CONFIRM | Yes/no confirmation | Press y/n or Enter for default |
EXPAND | Key-based expandable choice | Press key matching a choice, ? for help |
NO_OP | No selector (default) | Standard option parsing |
SELECTIONS is deprecated – use MULTI_SELECT instead.
Using Selectors with @Option
The selector property on @Option integrates selectors directly into option parsing. When an option has a selector and is flagged with askIfNotSet = true, the user is prompted interactively if the value was not provided on the command line.
Text Input
@CommandDefinition(name = "greet", description = "Greet someone")
public class GreetCommand implements Command<CommandInvocation> {
@Option(selector = SelectorType.INPUT, askIfNotSet = true,
description = "Name to greet")
private String name;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Hello, " + name + "!");
return CommandResult.SUCCESS;
}
}If the user runs greet without --name, they see a text prompt:
Name to greet: _Password Input
@CommandDefinition(name = "login", description = "Log in")
public class LoginCommand implements Command<CommandInvocation> {
@Option(description = "Username", required = true)
private String username;
@Option(selector = SelectorType.PASSWORD, askIfNotSet = true,
description = "Password")
private String password;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Logging in as " + username);
// authenticate...
return CommandResult.SUCCESS;
}
}If --password is omitted, the user is prompted with masked input:
Password: ****Single Selection
Provide a list of choices using defaultValue. The user navigates with arrow keys and confirms with Enter:
@CommandDefinition(name = "init", description = "Initialize project")
public class InitCommand implements Command<CommandInvocation> {
@Option(selector = SelectorType.SELECT, askIfNotSet = true,
defaultValue = {"java", "kotlin", "scala", "groovy"},
description = "Project language")
private String language;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Initializing " + language + " project");
return CommandResult.SUCCESS;
}
}If --language is omitted, the user sees an interactive list:
Project language [Use arrow up/down to move and enter/space to select]
> java
kotlin
scala
groovyThe > indicator moves with the arrow keys. Press Enter or Space to confirm.
Multiple Selection
Similar to single selection, but the user can toggle multiple items with Space before confirming with Enter:
@CommandDefinition(name = "setup", description = "Setup project features")
public class SetupCommand implements Command<CommandInvocation> {
@Option(selector = SelectorType.SELECTIONS, askIfNotSet = true,
defaultValue = {"logging", "metrics", "security", "caching"},
description = "Features to enable")
private String features;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Enabled: " + features);
return CommandResult.SUCCESS;
}
}If omitted, the user sees a multi-select list:
Features to enable [Use arrow up/down to move and space to select. Enter to finish]
> [ ] logging
[ ] metrics
[ ] security
[ ] cachingUse Space to toggle checkboxes, Enter to confirm. Selected items are marked with [x]. When the list exceeds the terminal height, pagination is handled automatically with the alternate screen buffer.
Using Selectors Programmatically
You can also use the Selector class directly in your command’s execute() method for full control:
import org.aesh.selector.Selector;
import org.aesh.selector.SelectorType;
@CommandDefinition(name = "deploy", description = "Deploy application")
public class DeployCommand implements Command<CommandInvocation> {
@Override
public CommandResult execute(CommandInvocation invocation)
throws InterruptedException {
Shell shell = invocation.getShell();
// Single selection
Selector envSelector = new Selector(
SelectorType.SELECT,
new String[]{"development", "staging", "production"},
"Target environment?"
);
List<String> chosen = envSelector.doSelect(shell);
String env = chosen.get(0);
// Confirmation via text input
Selector confirmSelector = new Selector(
SelectorType.INPUT,
new String[]{},
"Type '" + env + "' to confirm:"
);
List<String> confirmation = confirmSelector.doSelect(shell);
if (env.equals(confirmation.get(0))) {
invocation.println("Deploying to " + env);
} else {
invocation.println("Aborted.");
}
return CommandResult.SUCCESS;
}
}Multi-Step Wizards
Combine multiple selectors to build interactive setup wizards:
@CommandDefinition(name = "new-project", description = "Create a new project")
public class NewProjectCommand implements Command<CommandInvocation> {
@Override
public CommandResult execute(CommandInvocation invocation)
throws InterruptedException {
Shell shell = invocation.getShell();
// Step 1: Project name
Selector nameInput = new Selector(SelectorType.INPUT,
new String[]{}, "Project name:");
String name = nameInput.doSelect(shell).get(0);
// Step 2: Language
Selector langSelect = new Selector(SelectorType.SELECT,
new String[]{"java", "kotlin", "scala"},
"Language:");
String lang = langSelect.doSelect(shell).get(0);
// Step 3: Features (multi-select)
Selector featureSelect = new Selector(SelectorType.MULTI_SELECT,
new String[]{"tests", "ci", "docker", "docs"},
"Features:");
List<String> features = featureSelect.doSelect(shell);
invocation.println("Creating " + lang + " project '" + name
+ "' with: " + String.join(", ", features));
return CommandResult.SUCCESS;
}
}CommandInvocation Convenience Methods
Since 3.17.5, CommandInvocation provides convenience methods for interactive prompts, so you don’t need to create Selector objects directly:
confirm()
boolean deploy = ci.confirm("Deploy to production?", false);
// Shows: ? Deploy to production? (y/N)
// Returns: true for y/Y, false for n/N, default on Enterselect()
String env = ci.select("Choose environment:",
List.of("development", "staging", "production"));
// Shows arrow-key navigable list with ❯ indicator
// Returns the selected valuemultiSelect()
List<String> features = ci.multiSelect("Enable features:",
List.of("logging", "metrics", "tracing"));
// Shows checkboxes (◉/◯) with Space to toggle
// Returns list of selected valuesprompt() and prompt(validator)
// Simple text prompt
String name = ci.prompt("Project name:");
// Text prompt with validation (re-prompts until valid)
String name = ci.prompt("Project name:", input ->
input.matches("[a-z][a-z0-9-]*") ? null : "Must be lowercase with hyphens");
// Shows ✗ error message and re-prompts on invalid inputWizard example with convenience methods
The project init wizard from above is much simpler with convenience methods:
@Override
public CommandResult execute(CommandInvocation ci) throws InterruptedException {
String name = ci.prompt("Project name:", input ->
input.matches("[a-z][a-z0-9-]*") ? null : "Must be lowercase");
String lang = ci.select("Language:",
List.of("java", "kotlin", "scala"));
List<String> features = ci.multiSelect("Features:",
List.of("tests", "ci", "docker", "docs"));
boolean confirm = ci.confirm("Create project '" + name + "'?", true);
if (!confirm) return CommandResult.SUCCESS;
ci.println("Creating " + lang + " project '" + name
+ "' with: " + String.join(", ", features));
return CommandResult.SUCCESS;
}Rendering
Selectors render with Unicode indicators when available:
❯focused item marker (ASCII fallback:>)◉selected checkbox (ASCII fallback:[*])◯unselected checkbox (ASCII fallback:[ ])- Bold text for focused items, green for selected items
When NO_COLOR is set, selectors fall back to ASCII indicators and no ANSI styling.
Non-interactive fallback
When ci.isInteractive() returns false (piped input), the convenience methods consume from the inputLineResponses() queue if available. See Settings for details.