Custom Zsh Completions

Posted 12/15/2024

Custom Zsh Completions

I've recently been writing a shared wrapper for the various package managers on my machines. Depending on the OS, my `.zshrc` sources one `pkg.zsh` of several containing identical shell functions. For example, `~/.config/zsh/os/arch/pkg.zsh` for Arch and `~/.config/zsh/os/debian/pkg.zsh` for Debian. Within these, I have functions with common signatures for updating, installing, and removing packages. These take the forms of pkg-update, pkg-install, and so on. Then, I have one master function, pkg, that takes an argument to carry out the desired behavior, for example, `pkg -u` that calls pkg-update.

This worked well for updating and listing installed packages, but it was subpar for installing and removing them. Being a custom function, `pkg` did not provide proper tab completions. This makes for a much worse experience, so I had to scrap the idea, or learn a little about how zsh handles completions. Attempting to learn this through trial and error was not the right way to go about it, but I eventually ended up with the following for my Arch-based installs:


function _pkg_all_pkgs_autocomp { packages=( $(pacman -Sql) ) compadd "${packages[@]}" } function _pkg_local_pkgs_autocomp { packages=( $(pacman -Qet) ) compadd "${packages[@]}" }

The final snippet below shows how I combined these to enable completions for the `pkg` function.


function _pkg_completions { local curcontext="$curcontext" state line typeset -A opt_args _arguments -C \ '-u[Upgrade all packages]' \ '--upgrade[Upgrade all packages]' \ '-l[List all packages]' \ '--list[List all packages]' \ '-i[Install one or more packages]:packages:_pkg_all_pkgs_autocomp' \ '--install[Install one or more packages]:packages:_pkg_all_pkgs_autocomp' \ '-r[Uninstall one or more packages]:packages:_pkg_local_pkgs_autocomp' \ '--remove[Uninstall one or more packages]:packages:_pkg_local_pkgs_autocomp' \ '*::arg:->args' } compdef _pkg_completions pkg

I've since changed and condensed things a little, but I left it in this form here as I felt it was easier to understand.


- Nate