Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-generate Bash scripts and Bash/program interface for CLI tab completion #107

Merged
merged 3 commits into from
Oct 31, 2021

Conversation

harveywi
Copy link
Contributor

@harveywi harveywi commented Oct 31, 2021

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 the zio-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 (called completion-script.sh):

my-cli-app                                       \
    --shell-completion-script `which my-cli-app` \
    --shell-type bash > completion-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 to my-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 completion
results in the terminal.

For example, when the user types the following in the terminal

$ my-cli-app foo bar baz

and then moves the cursor over "foo" and hits the tab key, my-cli-app is called as follows:

COMP_WORD_0=my-cli-app     \
COMP_WORD_1=foo            \
COMP_WORD_2=bar            \
COMP_WORD_3=baz            \
my-cli-app                 \
--shell-completion-index 1 \
--shell-type bash

The COMP_WORD_ prefix of these environment variables is directly inspired by the COMP_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's optparse-applicative: The optparse-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 with zio-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

  • These "hidden" built-ins are not actually hidden: They appear in the --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.
  • Zsh support should be added.
  • The 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.
  • Unit test coverage would be nice. Normally I'd start there and do test-driven development, but making sure that the shell/program communication actually worked was more important on my end.
  • Replace the dummy completion method with a real one.

@kitlangton kitlangton merged commit 4c20c78 into zio:master Oct 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants