# Picocli Shell JLine3 - build interactive shells with ease
Picocli Shell JLine3 contains components and documentation for building
interactive shell command line applications with JLine 3 and picocli.
JLine and picocli complement each other very well and have little or none functional overlap.
JLine provides interactive shell functionality but has no built-in command line parsing functionality.
What it does provide is a tokenizer for splitting a single command line String into an array of command line argument Strings.
Given an array of Strings, picocli can execute a command or subcommand.
Combining these two libraries makes it easy to build powerful interactive shell applications.
Here is an example of what can be achieved:
[![asciicast](https://asciinema.org/a/284482.svg)](https://asciinema.org/a/284482)
## About JLine 3
[JLine 3](https://github.com/jline/jline3) is a well-known library for building interactive shell applications.
From the JLine [web site](https://github.com/jline/jline.github.io/blob/master/index.md):
> JLine is a Java library for handling console input. It is similar in functionality to [BSD editline](http://www.thrysoee.dk/editline/) and [GNU readline](http://www.gnu.org/s/readline/) but with additional features that bring it in par with [ZSH line editor](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html).
## About picocli
Picocli is a Java command line parser with both an annotations API and a programmatic API, featuring usage help with ANSI colors, autocomplete and nested subcommands.
The picocli user manual is [here](https://picocli.info), and the GitHub project is [here](https://github.com/remkop/picocli).
## Command Completer
`PicocliJLineCompleter` is a small component that generates completion candidates to allow users to
get command line TAB auto-completion for a picocli-based application running in a JLine 3 shell.
It does not use JLine's `TailTipWidgets` terminal status bar and can be used with older versions of JLine.
## PicocliCommands
Completion with description.
`PicocliCommands` is a small component, analogous to JLine's `Builtins`, that takes a `picocli.CommandLine`
object with a picocli-based command hierarchy and creates a JLine `SystemCompleter` for all commands in the hierarchy to set up command TAB completion in JLine.
In addition, it makes a `org.jline.builtins.Widgets.CmdDesc` object available for each command,
which allows a detailed description of the command and its options to be displayed in the JLine `TailTipWidgets` terminal status bar.
This component requires JLine 3.14.1 or greater.
### PicocliCommands Version Compatibility
The JLine `TailTipWidgets` terminal status bar is still under development, and the JLine API has undergone some backwards incompatible changes.
As a result, applications that use the `PicocliCommands` component for completion must take care to use compatible versions.
The following versions of `jline` and `picocli-shell-jline3` are compatible:
| JLine Version | Picocli Version |
| -------------- | --------------- |
| 3.13.2 - 3.14.0 | 4.1.2 - 4.2.0 |
| 3.14.1 | 4.3.0 - 4.3.2 |
| 3.15.0 - | 4.4.0 - |
Note: JLine v3.17.1 is not compatible as it is affected by [this bug](https://github.com/jline/jline3/issues/640).
See [examples for the older versions](https://github.com/remkop/picocli/wiki/JLine-3-Examples).
## Demo
JLine [Wiki](https://github.com/jline/jline3/wiki) and some more [Demos](https://github.com/jline/jline3/wiki/Demos).
## Example
### Maven
```xml
info.picocli
picocli-shell-jline3
4.7.5
```
### Older versions
See examples for older versions on the [wiki](https://github.com/remkop/picocli/wiki/JLine-3-Examples).
* [Example for JLine 3.13.2 and Picocli 4.1.2](https://github.com/remkop/picocli/wiki/JLine-3-Examples#example-for-jline-3132-and-picocli-412)
* [Example for JLine 3.14.1 and Picocli 4.3.0 - 4.3.2](https://github.com/remkop/picocli/wiki/JLine-3-Examples#example-for-jline-3141-and-picocli-430---432)
* [Example for JLine 3.15 and Picocli 4.4+](https://github.com/remkop/picocli/wiki/JLine-3-Examples#example-for-jline-315-and-picocli-44)
### JLine 3.16 and Picocli 4.4+ Example
```java
package picocli.shell.jline3.example;
import org.fusesource.jansi.AnsiConsole;
import org.jline.builtins.ConfigurationPath;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.Builtins;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.keymap.KeyMap;
import org.jline.reader.*;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.LineReaderImpl;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.widget.TailTipWidgets;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParentCommand;
import picocli.shell.jline3.PicocliCommands;
import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Example that demonstrates how to build an interactive shell with JLine3 and picocli.
* This example requires JLine 3.16+ and picocli 4.4+.
*
* The built-in {@code PicocliCommands.ClearScreen} command was introduced in picocli 4.6.
*
*/
public class Example {
/**
* Top-level command that just prints help.
*/
@Command(name = "",
description = {
"Example interactive shell with completion and autosuggestions. " +
"Hit @|magenta |@ to see available commands.",
"Hit @|magenta ALT-S|@ to toggle tailtips.",
""},
footer = {"", "Press Ctrl-D to exit."},
subcommands = {
MyCommand.class, PicocliCommands.ClearScreen.class, CommandLine.HelpCommand.class})
static class CliCommands implements Runnable {
PrintWriter out;
CliCommands() {}
public void setReader(LineReader reader){
out = reader.getTerminal().writer();
}
public void run() {
out.println(new CommandLine(this).getUsageMessage());
}
}
/**
* A command with some options to demonstrate completion.
*/
@Command(name = "cmd", mixinStandardHelpOptions = true, version = "1.0",
description = {"Command with some options to demonstrate TAB-completion.",
" (Note that enum values also get completed.)"},
subcommands = {Nested.class, CommandLine.HelpCommand.class})
static class MyCommand implements Runnable {
@Option(names = {"-v", "--verbose"},
description = { "Specify multiple -v options to increase verbosity.",
"For example, `-v -v -v` or `-vvv`"})
private boolean[] verbosity = {};
@ArgGroup(exclusive = false)
private MyDuration myDuration = new MyDuration();
static class MyDuration {
@Option(names = {"-d", "--duration"},
description = "The duration quantity.",
required = true)
private int amount;
@Option(names = {"-u", "--timeUnit"},
description = "The duration time unit.",
required = true)
private TimeUnit unit;
}
@ParentCommand CliCommands parent;
public void run() {
if (verbosity.length > 0) {
parent.out.printf("Hi there. You asked for %d %s.%n",
myDuration.amount, myDuration.unit);
} else {
parent.out.println("hi!");
}
}
}
@Command(name = "nested", mixinStandardHelpOptions = true, subcommands = {CommandLine.HelpCommand.class},
description = "Hosts more sub-subcommands")
static class Nested implements Runnable {
public void run() {
System.out.println("I'm a nested subcommand. I don't do much, but I have sub-subcommands!");
}
@Command(mixinStandardHelpOptions = true, subcommands = {CommandLine.HelpCommand.class},
description = "Multiplies two numbers.")
public void multiply(@Option(names = {"-l", "--left"}, required = true) int left,
@Option(names = {"-r", "--right"}, required = true) int right) {
System.out.printf("%d * %d = %d%n", left, right, left * right);
}
@Command(mixinStandardHelpOptions = true, subcommands = {CommandLine.HelpCommand.class},
description = "Adds two numbers.")
public void add(@Option(names = {"-l", "--left"}, required = true) int left,
@Option(names = {"-r", "--right"}, required = true) int right) {
System.out.printf("%d + %d = %d%n", left, right, left + right);
}
@Command(mixinStandardHelpOptions = true, subcommands = {CommandLine.HelpCommand.class},
description = "Subtracts two numbers.")
public void subtract(@Option(names = {"-l", "--left"}, required = true) int left,
@Option(names = {"-r", "--right"}, required = true) int right) {
System.out.printf("%d - %d = %d%n", left, right, left - right);
}
}
public static void main(String[] args) {
AnsiConsole.systemInstall();
try {
Supplier workDir = () -> Paths.get(System.getProperty("user.dir"));
// set up JLine built-in commands
Builtins builtins = new Builtins(workDir, new ConfigurationPath(workDir.get(), workDir.get()), null);
builtins.rename(Builtins.Command.TTOP, "top");
builtins.alias("zle", "widget");
builtins.alias("bindkey", "keymap");
// set up picocli commands
CliCommands commands = new CliCommands();
PicocliCommandsFactory factory = new PicocliCommandsFactory();
// Or, if you have your own factory, you can chain them like this:
// MyCustomFactory customFactory = createCustomFactory(); // your application custom factory
// PicocliCommandsFactory factory = new PicocliCommandsFactory(customFactory); // chain the factories
CommandLine cmd = new CommandLine(commands, factory);
PicocliCommands picocliCommands = new PicocliCommands(cmd);
Parser parser = new DefaultParser();
try (Terminal terminal = TerminalBuilder.builder().build()) {
SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null);
systemRegistry.setCommandRegistries(builtins, picocliCommands);
systemRegistry.register("help", picocliCommands);
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.completer(systemRegistry.completer())
.parser(parser)
.variable(LineReader.LIST_MAX, 50) // max tab completion candidates
.build();
builtins.setLineReader(reader);
commands.setReader(reader);
factory.setTerminal(terminal);
TailTipWidgets widgets = new TailTipWidgets(reader, systemRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER);
widgets.enable();
KeyMap keyMap = reader.getKeyMaps().get("main");
keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s"));
String prompt = "prompt> ";
String rightPrompt = null;
// start the shell and process input until the user quits with Ctrl-D
String line;
while (true) {
try {
systemRegistry.cleanUp();
line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
systemRegistry.execute(line);
} catch (UserInterruptException e) {
// Ignore
} catch (EndOfFileException e) {
return;
} catch (Exception e) {
systemRegistry.trace(e);
}
}
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
AnsiConsole.systemUninstall();
}
}
}
```