
A fast zsh prompt that packs in a lot of Git status info
Last Updated

Updates
Feb 1, 2024: Added plugin wrapper pattern.
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 .zshrc file:
autoload -U compinit
compinit(“compinit”? It initializes the completion system).
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
fpath+=absolute/path/to/_mycommandThe file can live anywhere.
When writing completions for commands I’ve authored, I colocate the completion file with the command file
% cd path/to/mycommand
% tree
.
├── completions
│ └── _mycommand
└── mycommandWhen writing completions for plugins I’ve authored, I like to use this wrapper pattern:
% cd path/to/mycommand
% tree
.
├── completions
│ └── _mycommand
├── mycommand.plugin.zsh
└── mycommand.zsh
% cat mycommand.plugin.zsh
fpath+=${0:A:h}/completions
source ${0:A:h}/mycommand.zsh
% cat mycommand.zsh
#!/usr/bin/env zsh
# ---snip---
mycommand() {
# ---snip---
}When adding completions to software I didn’t write, I put the file in ~/.config/zsh/completions/<command name>.
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
mycommand subcommand [(--my-flag | -mf)] [(--file <file>)]
mycommand (different-subcommand | ds) [(--my-flag | -mf)]
mycommand (--help | help)
mycommand (--version | -v)to get this tab completion behavior
% mycommand <tab>
--help help -- Show the manpage.
--version -v -- Show the current version.
different-subcommand ds -- Different description
subcommand -- The description
% mycommand su<tab> # expands to `mycommand subcommand`
% mycommand subcommand -<tab>
--file -- path to a file
--my-flag -mf -- the my-flag description
% mycommand subcommand --file <tab>
# files listed hereI would use this completion file
#compdef mycommand
# -------------------------------
#
# info about mycommand goes here
# (e.g. author, license, etc)
#
# -------------------------------
local line state
local -i ret
ret=1
_arguments -C \
'1: :->cmds' \
'*:: :->args' \
&& ret=0
case $state in
cmds)
# mycommand subcommand
# mycommand (different-subcommand | ds)
# mycommand (--help | help)
# mycommand (--version | v)
_values "mycommand command" \
"subcommand[The description]" \
"different-subcommand[Different description]" \
"ds[Different description]" \
"help[Show the manpage.]" \
"--help[Show the manpage.]" \
"-v[Show the current version.]" \
"--version[Show the current version.]"
ret=0
;;
args)
case $line[1] in
subcommand)
# mycommand subcommand [(--my-flag | -mf)] [--file <file path>]
_arguments \
"(--my-flag)--my-flag[the my-flag description]" \
"(-mf)-mf[the my-flag description]" \
"(--file)--file[path to a file]:file:_files -/"
ret=0
;;
different-subcommand|\
ds)
# mycommand (different-subcommand | ds) [(--flag | -f)]
_arguments \
"(--my-flag)--my-flag[the --my-flag description]" \
"(-mf)-mf[the --my-flag description]" \
ret=0
;;
esac
;;
esac
return retTip
The comments under cmds) and 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.
#compdef mycommand
local line state
local -i ret
ret=1
_arguments -C \
'1: :->cmds' \
'*:: :->args' \
&& ret=0
case $state in
cmds)
_values "mycommand command"
ret=0
;;
args)
case $line[1] in
esac
;;
esac
return retSubcommands 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:
# snip
case $state in
cmds)
# <command in manpage-like format>
# <another command in manpage-like format>
# etc
_values "<command-name> command" \
"<subcommand-with-synonym>[<description-1-shared-by-the-synonyms>]" \
"<the-subcommand-synonym>[<description-1-shared-by-the-synonyms>]" \
"<subcommand-without-synonym>[<description-2>]"
# snipSubcommands’ 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:
# snip
# <command in manpage-like format>
<subcommand-with-synonym>|\
<the-subcommand-synonym>)
_arguments \
"(<argument>)<argument>[<description>]"
ret=0
;;
<subcommand-without-synonym>)
# <command in manpage-like format>
_arguments \
"(<argument-with-synonym>)<argument-with-synonym>[description-1]" \
"(<argument-synonym>)<argument-synonym>[description-1]" \
"(<argument-2>)<argument-2>[<description-2>]"
ret=0
;;
# snipIf 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.
"(<argument>)<argument>[<description>]:file:_files -/"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.



Hometown: A Dynamic, Highly Configurable Git-Focused Zsh Theme
A fast zsh prompt that packs in a lot of Git status info

Git Prompt Kit: Configurable, Fast Git Status Components For Custom Zsh Themes
A collection of components for displaying Git information in your prompt

My .zshrc Zsh Configuration, Annotated
How I set up my interactive shell