TL,DR: In normal operations, just drop the file into the appropriate directory. While testing, you need to remove the cache file (.zcompdump
by default, but users can put it in a different location, and oh-my-zsh does put it in a different location).
The simple answer is to write the completion function in a file where the first line is #compdef fab
. The file must be in a directory on $fpath
.
The file can either contain the function body, or a definition of the function followed by a call to the function. That is, either the file contains something like
#compdef fab
_arguments …
or
#compdef fab
function _fab {
_arguments …
}
_fab "$@"
The file must be present on $fpath
before compinit
runs. That means you need to pay attention to the order of things in .zshrc
: first add any custom directories to $fpath
, then call compinit
. If you use a framework such as oh-my-zsh, make sure to add any custom directories to $fpath
before the oh-my-zsh code.
compinit
is the function that initializes the completion system. It reads all the files in $fpath
and checks their first line for magic directives #autoload
and #compdef
.
.zcompdump
is a cache file used by compinit
. ~/.zcompdump
is the default location; you can choose a different location when running compinit
. Oh-my-zsh calls compinit
with the -d
option to use a different cache file name given by the variable ZSH_COMPDUMP
, which defaults to
ZSH_COMPDUMP="${ZDOTDIR:-${HOME}}/.zcompdump-${SHORT_HOST}-${ZSH_VERSION}"
The host name is included for the sake of people whose home directory is shared between machines and who may have different software installed on different machines. The zsh version is included because the cache file is incompatible between versions (it includes code that changes from version to version).
I think all of your problems are due to a stale cache file (and that's made you overcomplicate the situation). Unfortunately, zsh's algorithm to determine whether the cache file is stale is not perfect, presumably in the interest of speed. It doesn't check the content or the timestamps of the files on $fpath
, it just counts them. A .zcompdump
file starts with a line like
#files: 858 version: 5.1.1
If the zsh version and the number of files are correct, zsh loads the cache file.
The cache file only contains associations between command names, not the code of completion functions. Here's are some common scenarios where the cache works transparently:
- If you add a new file to
$fpath
, this invalidates the cache.
- More generally, if you add and remove files on
$fpath
, and the total number of removed files is not the same as the total number of removed files, this invalidates the cache.
- If you move a file to a different directory in
$fpath
without changing its name, this does not affect anything that's in the cache, so the cache remains correct.
- If you modify a file in
$fpath
without changing its first line, this does not affect anything that's in the cache, so the cache remains correct.
Here are some common scenarios where the cache becomes invalid, but zsh doesn't realize it.
- You add some files to
$fpath
and remove exactly the same number of files.
- You rename a file in
$fpath
.
- You add or modify the
#compdef
(or #autoload
) line at the top of the file.
That last point is what tends to bite during testing. If you change the #compdef
line, you need to remove the .zcompdump
file and restart zsh (or rerun compinit
).
If you put completions in a redistributable package, just drop the completion file into a directory that's in the system-wide $fpath
. For an Ubuntu package, the appropriate place is /usr/share/zsh/vendor-completions
. For something installed under /usr/local
, that's /usr/local/share/zsh/site-functions
. That's all you need to do.
The one thing that isn't transparent is if you need to change the #compdef
line in an upgrade, or if you remove or rename some files. In such cases, users will need to remove their cache file, and that's not something you can do from a package that gets installed on a multiuser machine.