Writing zsh tab completions can be straightforward
Last Updated
Zsh has robust support for enhancing commands’ terminal UX with tab completions. Making sense of how to take advantage of that, and add tab completions to commands, can be tough. I find the official documentation hard to understand. The zsh-users org’s “how to” guide agrees, but I find that hard to understand too. zsh-users has many completions to study, but they use a range of code patterns, and many are quite complex.
For many commands it doesn’t have to be that difficult.
Contents
Completions?
The completion system isn’t enabled out of the box. If you don’t know whether you’ve enabled it or not, in a terminal type print -
and then Tab. If you see completion suggestions, the completion system has been initialized (ctrl c will clear the suggestions). If you don’t, the way to initialize it depends on the rest of your setup.
If you use a zsh plugin manager, check its documentation to see if it has an idiomatic way of initializing completions. For example, as of this writing, I use the zsh plugin manager zcomet, which has its own zcomet compinit
function (docs). If you don’t use a plugin manager or framework, or you use one that doesn’t have its own way of initializing the completion system, add this to your .zshrc
file:
(“compinit”? It initializes the completion system).
Prepare the file
To add completion for the command mycommand
, start by
-
creating a file
_mycommand
-
adding the file’s directory to
fpath
, the array of folders zsh will look in for (among other things) completion definitions
The file can live anywhere. When writing completions for my own zsh software, I colocate the completion file with the command file
I like to use this plugin wrapper pattern:
When adding completions to software I didn’t write, I put the file in ~/.config/zsh/completions/<command name>
.
Completions code
The pattern I use supports long and short options, and options which a file as an argument. That pretty well covers the completions I’ve wanted to write. For a command with these possibilities
to get this tab completion behavior
I would use this completion file
Tip
The comments under cmds)
and args)
are not necessary, but I find them to be helpful documentation.
Breaking it down
In the completions file, this much is boilerplate, with the caveat that the two instances of mycommand
need to be changed to the real command name.
Subcommands and top-level flags are handled in cmds) _values
. Values with the same description will be displayed together on the command line as synonyms. Here’s the pattern:
Subcommands’ arguments and flags are handled the args)
’s case
statement. Here again, arguments with the same description will be displayed together on the command line as synonyms. Here’s the pattern:
Special case: arguments which take a file
If an argument takes a file as its argument —for example, to support mycommand --input-file <file path>
— use this pattern in the _arguments
, replacing <argument>
with the argument and <description>
with the description.
Real-life examples
There are other ways to program the same completions, and file arguments are just one of the many special cases zsh’s completion system supports. Of the ones I’ve seen, I find this one easiest to understand. And it’s met most of my needs.
See it in action in zsh-abbr and in zsh-test-runner.
Updates
Feb 1, 2024: Added plugin wrapper pattern.