Please forgive me if I'm doing something stupid here, the documentation is huge and searching didn't turn up anything yet.
I'm trying to create shell completions for my custom script called fab
. For bash it was easy, just drop them in /etc/bash_completion.d
and they work. But oh boy, is zsh a PITA…
I have the completion function _fab
and it works fine when enabled with compdef _fab fab
. I put it in /usr/share/zsh/vendor-completions/_fab
which already was in my $fpath
. The file starts with #compdef fab
and ends with compdef _fab fab
. Looks good:
$ type _fab
_fab is an autoload shell function
But whenever I started a new shell, fab
completions didn't work (other functions from vendor-completions
, like _docker
, were fine). compinit
fixed this for that specific shell. I figured out that rm ~/.zcompdump ~/.zcompdump-$(hostname)-5.1.1; compinit
make it work permanently (5.1.1 = my zsh version).
Questions:
- What and when reads
~/.zcompdump
to set up initial completions? -
man zshall
says:The next invocation of compinit will read the dumped file instead of performing a full initialization.
If that was the case,
compinit
wouldn't fix my completions before I deleted~/.zcompdump
, right? Am I missing something? - What's
~/.zcompdump-$(hostname)-5.1.1
and how is it related to.zcompdump
? The only difference is one completion which is in~/.oh-my-zsh/completions
(because$ZSH
points to~/.oh-my-zsh
). Is it an oh-my-zsh thing? - If I were to package these completions into a redistributable package or create an installer script, where should I place zsh completions and what else should I do during installation to make sure everything just works?
I'm targeting Ubuntu 16.04, 18.04 and 19.04, but non-distro-specific information is welcome. I'm testing this on Ubuntu 16.04 with zsh 5.1.1 and recent oh-my-zsh.
Best Answer
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
or
The file must be present on
$fpath
beforecompinit
runs. That means you need to pay attention to the order of things in.zshrc
: first add any custom directories to$fpath
, then callcompinit
. 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 bycompinit
.~/.zcompdump
is the default location; you can choose a different location when runningcompinit
. Oh-my-zsh callscompinit
with the-d
option to use a different cache file name given by the variableZSH_COMPDUMP
, which defaults toThe 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 likeIf 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:
$fpath
, this invalidates the cache.$fpath
, and the total number of removed files is not the same as the total number of removed files, this invalidates the cache.$fpath
without changing its name, this does not affect anything that's in the cache, so the cache remains correct.$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.
$fpath
and remove exactly the same number of files.$fpath
.#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 reruncompinit
).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.