Writing zsh tab completions can be straightforward
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.
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
(“compinit”? It initializes the completion system).
To add completion for the command
mycommand, start by
creating a file
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
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
The comments under
args) are not necessary, but I find them to be helpful documentation.
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
case statement. Here again, arguments with the same description will be displayed together on the command line as synonyms. Here’s the pattern:
If an argument takes a file as its argument —for example, to support
mycommand --input-file <file path>— use this pattern in the
<argument> with the argument and
<description> with the description.
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.
Feb 1, 2024: Added plugin wrapper pattern.