Completers
Completers provide tab-completion suggestions for options and arguments.
OptionCompleter Interface
public interface OptionCompleter<T extends CompleterInvocation> {
void complete(T completerInvocation);
}Built-in Completers
Æsh includes built-in completers:
BooleanOptionCompleter- Completes boolean valuesDefaultValueOptionCompleter- Uses defined default valuesFileOptionCompleter- Completes file paths
Custom Color Completer
public class ColorCompleter implements OptionCompleter<CompleterInvocation> {
private static final List<String> COLORS = Arrays.asList(
"red", "green", "blue", "yellow", "orange", "purple",
"cyan", "magenta", "white", "black", "gray", "pink"
);
@Override
public void complete(CompleterInvocation invocation) {
String input = invocation.getGivenCompleteValue();
if (input == null || input.isEmpty()) {
// No input yet, show all colors
invocation.addAllCompleterValues(COLORS);
} else {
// Filter colors that start with the input
String lowerInput = input.toLowerCase();
for (String color : COLORS) {
if (color.startsWith(lowerInput)) {
invocation.addCompleterValue(color);
}
}
}
}
}Usage:
@CommandDefinition(name = "theme", description = "Set application theme")
public class ThemeCommand implements Command<CommandInvocation> {
@Option(
shortName = 'b',
name = "background",
completer = ColorCompleter.class,
description = "Background color"
)
private String backgroundColor;
@Option(
shortName = 'f',
name = "foreground",
completer = ColorCompleter.class,
description = "Foreground/text color"
)
private String foregroundColor;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Setting theme: " + foregroundColor + " on " + backgroundColor);
return CommandResult.SUCCESS;
}
}When users press Tab:
[myapp]$ theme --background <TAB>
red green blue yellow orange purple
cyan magenta white black gray pink
[myapp]$ theme --background gr<TAB>
gray green
[myapp]$ theme --background green --foreground wh<TAB>
whiteCompleterInvocation
Provides access to:
getGivenCompleteValue()- Current partial inputgetAllGivenValues()- All values for this optiongetCommand()- The command instancegetAeshContext()- Access to runtime contextaddCompleterValue(String)- Add a completion suggestion
Dynamic Completion Based on Other Options
public class DatabaseTableCompleter implements OptionCompleter<CompleterInvocation> {
@Override
public void complete(CompleterInvocation invocation) {
// Access the command to get other option values
MyCommand cmd = (MyCommand) invocation.getCommand();
if (cmd.database != null) {
// Complete tables based on selected database
invocation.addAllCompleterValues(
getTablesForDatabase(cmd.database)
);
}
}
}
@CommandDefinition(name = "query")
public class MyCommand implements Command<CommandInvocation> {
@Option(name = "database", description = "Database name")
private String database;
@Option(
name = "table",
completer = DatabaseTableCompleter.class,
description = "Table name"
)
private String table;
@Override
public CommandResult execute(CommandInvocation invocation) {
return CommandResult.SUCCESS;
}
}Completer for Arguments
Works with @Arguments as well:
@Arguments(completer = CommandNameCompleter.class)
private List<String> commandNames;Shell Completion Script Generation
Aesh can generate completion scripts for bash, zsh, fish, and PowerShell (pwsh) shells. Everything works automatically with the standard AeshRuntimeRunner pattern — no manual flag handling required.
Quick Start
With the standard runner setup, your CLI tool automatically supports shell completion:
public class MyApp {
public static void main(String[] args) {
AeshRuntimeRunner.builder()
.command(MyCommand.class)
.args(args)
.execute();
}
}That’s it. Your users can now generate and install completion scripts:
# Generate completion script (auto-detects shell from $SHELL)
$ myapp --aesh-completion
# Generate for a specific shell
$ myapp --aesh-completion bash
$ myapp --aesh-completion zsh
$ myapp --aesh-completion fish
$ myapp --aesh-completion pwsh
# Auto-detect shell, generate, and install to the standard directory
$ myapp --aesh-completion-installBuilt-in Flags
| Flag | Purpose |
|---|---|
--aesh-completion [bash|zsh|fish|pwsh] | Generate completion script to stdout (dynamic by default) |
--aesh-completion --static [bash|zsh|fish|pwsh] | Generate a static completion script (no JVM at tab-time) |
--aesh-completion-install | Auto-detect shell, generate script, install with confirmation |
--aesh-complete -- <args> | Runtime callback used by the generated shell scripts |
All flags are intercepted by AeshRuntimeRunner.execute() before your command runs, so they never reach your command’s execute() method.
Static vs Dynamic
By default, --aesh-completion generates dynamic scripts. Use --static for static scripts:
| Static | Dynamic (default) | |
|---|---|---|
Custom OptionCompleter support | No | Yes |
| JVM required at tab-time | No | Yes |
| Startup cost per Tab press | None | ~10ms (native) / ~200ms (JVM) |
| Best for | Option names, default values | Runtime-dependent completions |
Supported Shells
| Shell | Generator | Script format |
|---|---|---|
| Bash | BashCompletionGenerator | complete -F functions with compgen -W |
| Zsh | ZshCompletionGenerator | Native compdef/_arguments format |
| Fish | FishCompletionGenerator | complete -c commands with conditions |
| PowerShell | PowerShellCompletionGenerator | Register-ArgumentCompleter -Native with CompletionResult |
Installing Completions
The easiest way is --aesh-completion-install, which auto-detects your shell, generates the script, and installs it with confirmation:
$ myapp --aesh-completion-install
Write completion script to: /home/user/.bash_completion.d/myapp
Proceed? [y/N] y
Completion script installed to /home/user/.bash_completion.d/myapp
Note: 'myapp' must be on your $PATH for completions to work.
Restart your shell or source the file to activate completions.Or install manually by redirecting the output:
# Bash
$ myapp --aesh-completion bash > ~/.local/share/bash-completion/completions/myapp
# Zsh
$ myapp --aesh-completion zsh > ~/.zsh/completions/_myapp
# Fish
$ myapp --aesh-completion fish > ~/.config/fish/completions/myapp.fish
# PowerShell
$ myapp --aesh-completion pwsh > myapp_complete.ps1
# Then add to your $PROFILE:
# . /path/to/myapp_complete.ps1PowerShell Setup
PowerShell completions use Register-ArgumentCompleter -Native, which works with PowerShell 5.1+ (Windows PowerShell) and PowerShell 7+ (cross-platform).
To set up completions permanently, add the generated script to your PowerShell profile:
# Generate the completion script
myapp --aesh-completion pwsh > "$HOME/.config/powershell/completions/myapp_complete.ps1"
# Add to your $PROFILE (run once)
Add-Content -Path $PROFILE -Value '. "$HOME/.config/powershell/completions/myapp_complete.ps1"'
# Reload profile
. $PROFILEPowerShell completions include tooltips — descriptions from your @Option(description = ...) annotations are shown when the user browses completion candidates with Ctrl+Space.
--aesh-completion-install auto-detects the shell and writes the file, PowerShell install writes the script file but requires you to source it from your $PROFILE manually. This is because modifying $PROFILE automatically could surprise users.How Dynamic Completion Works
- You generate a completion script and install it in your shell (via
--aesh-completion-installor manual redirect) - When the user presses Tab, the shell script calls
myapp --aesh-complete -- <partial-args> - Aesh’s completion engine runs (including custom
OptionCompleterimplementations) and prints candidates to stdout - The shell presents the candidates as completion suggestions
What Gets Generated
The generators introspect the command model and produce:
- Option names (long and short forms)
- Option aliases (e.g.,
--eafor--enableassertions) - Negatable options (e.g.,
--no-verbosefor--verbose) - Default value completions (offered when completing option values)
- File path completion for
File,Path, andResource-typed options - Subcommand names for group commands
- Positional argument completion (file completion for
@Argument/@Argumentswith file types)
Completion Fallback Control
By default, when no completion candidates are available for an argument, the shell falls back to file/path completion for String, File, and Path types, and no fallback for enum types. You can override this per-argument using completeFallback:
@Argument(description = "Script file")
String script; // DEFAULT: auto-detected as FILES (String type)
@Argument(description = "Catalog name", completeFallback = CompletionFallback.NONE)
String catalog; // No file fallback -- only completer candidates
@Option(name = "workspace", completeFallback = CompletionFallback.DIRECTORIES)
String workspace; // Only directories, no regular files| Value | Behavior |
|---|---|
DEFAULT | Auto-detect: FILES for String/File/Path, NONE for enums |
FILES | Offer file and directory path completion |
DIRECTORIES | Offer only directory completion (no regular files) |
NONE | No fallback – only show candidates from completers |
This is especially useful for commands where some arguments expect file paths and others expect non-file values like identifiers, versions, or names:
@CommandDefinition(name = "jdk", description = "Manage JDKs",
groupCommands = { JdkInstallCommand.class })
public class JdkCommand implements Command<CommandInvocation> { }
@CommandDefinition(name = "install", description = "Install a JDK version")
public class JdkInstallCommand implements Command<CommandInvocation> {
@Argument(description = "JDK version", completeFallback = CompletionFallback.NONE)
String version; // "17", "21" -- not a file path
}GraalVM Native Images
Dynamic callback completion is ideal for GraalVM native images. Since native binaries start in ~10ms, tab completion feels instant:
# Build native image
$ mvn package -Pnative
# Install completion — the native binary handles --aesh-complete directly
$ ./target/myapp --aesh-completion-installFor JVM-based tools where users run via java -jar, use a wrapper script and set completionProgramName():
AeshRuntimeRunner.builder()
.command(MyCommand.class)
.completionProgramName("myapp") // matches the wrapper script name
.args(args)
.execute();Programmatic API
For advanced use cases, you can generate scripts programmatically:
import org.aesh.util.completer.ShellCompletionGenerator;
import org.aesh.util.completer.ShellCompletionGenerator.ShellType;
// Static scripts
String bashScript = ShellCompletionGenerator.generate(
ShellType.BASH, MyCommand.class, "myapp");
// Dynamic scripts
String fishScript = ShellCompletionGenerator.generateDynamic(
ShellType.FISH, MyCommand.class, "myapp");
// Using the strategy interface for more control
ShellCompletionGenerator generator = ShellCompletionGenerator.forShell(ShellType.ZSH);
String script = generator.generate(parser, "myapp");