Auto-generate Bash scripts and Bash/program interface for CLI tab completion #107
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview
This PR adds a mechanism for performing tab completion of command line options and arguments in bash and zsh. This approach was heavily inspired by the tab completion plumbing used in the excellent Haskell optparse-applicative library.
Every
CliApp
is extended with a few hidden built-in options for providing tab completions to shell environments.How It Works
In what follows, pretend that your CLI application (called
my-cli-app
) has been installed into a stable location in your path (such as the~/.local/bin
directory) favored by thezio-cli
installer script.Generating a completion shell script
The
--shell-completion-script
and--shell-type
built-in options produce a shell script that enables tab completion. In the example below, we generate a completion script (calledcompletion-script.sh
):After generating the script, you can quickly enable tab completion via:
source completion-script.sh
Unfortunately, the tab completion will only be enabled within the current shell session. Normally, the output of
--shell-completion-script
should be shipped with the program and copied to the appropriate directory (e.g.,/etc/bash_completion.d/
) during program installation.How Bash and Zsh Completions are Generated
The shell completion scripts register an event handler that fires whenever
my-cli-app
is the first term at the terminal prompt and the tab key is pressed. This event handler sends information about the terminal contents and cursor position back tomy-cli-app
using another built-in option called--shell-completion-index
and some special environment variables (COMP_WORD_0
,COMP_WORD_1
, ...).When
my-cli-app
receives these values, it runs a completion algorithm and prints the completion terms to the console (one line per completion term). The console output feeds back into the shell machinery, which renders the completionresults in the terminal.
For example, when the user types the following in the terminal
and then moves the cursor over "foo" and hits the tab key,
my-cli-app
is called as follows:The
COMP_WORD_
prefix of these environment variables is directly inspired by theCOMP_WORD
array-valued Bash variable that is part of its programmable completion system. Unfortunately, array-valued variables cannot be used as environment variables, so our approach instead uses one variable per term in the array.A notable difference between
zio-cli
and Haskell'soptparse-applicative
: Theoptparse-applicative
library sends these words to the user's program via multiple repeating--bash-completion-word
CLI options. Unfortunately, I was not able to get that to work withzio-cli
, so in the interest of time I sidestepped the issue and went with environment variables instead.Further Reading
The optparse-applicative documentation is an excellent resource that may help to clarify the implementation above.
Future Work for Future PRs
--help
messages. A mechanism to toggle the visibility of options should be considered. Or, maybe these hidden built-ins should be masked out of the help messages as a special case.zio-cli
sbt
plugin should be modified to do something with the completion scripts. I wasn't aware that the plugin existed until the very end of the hackathon, so I ran out of time to hook it up.