After some years of using the great one and only zsh shell, I have accumulated a few plugins in my configuration. My plugin manager of choice has been antibody which is written in Go and consists of a single binary. It does everything I want and stays mostly out of my way, but the setup involves downloading or compiling a binary and placing it somewhere in your $PATH.

keyboard

This is fine if you have only one device with your shell configured, but if you want to manage and deploy your dotfiles automatically, it introduces unnecessary complexity and extra work.

I manage all my dotfiles with ansible, setting up antibody with all of it's plugins is defined in a dedicated ansible role. When deploying the dotfiles to a new machine, this role has often been the one to cause problems and involve manual fixing. I tried various approaches, the last one being to run the install_antibody.sh script that I put in the repository of my ansible role.

What I really want, is to have as little moving parts as possible for my shell setup. It should just work™ and just involve starting the ansible playbook, me getting a coffee and coming back to a fully configured shell the way I have it setup on all my other devices.

Enter Znap

A few days ago I saw an announcement on reddit about a new plugin manager, written in pure zsh code and decided to give it a try.

Znap is roughly 4 kilobytes of source code that does everything you could ask for from a plugin manager and nothing that you don't ask for.

The installation consists of cloning the repository and sourcing it from your .zshrc. Plugins are simply cloned from via some handy aliases it provides.

Here is the repository for those not knowing how google works.

Installing Znap

Create a folder to hold all your plugins and znap itself ~/.zsh-plugins is suggested, but you can use anything you want. In it clone the managers repository.

mkdir ~/.zsh-plugins
cd ~/.zsh-plugins
git clone https://github.com/marlonrichert/zsh-snap.git

That's it. Now just source the manager at the top of your .zshrc so it is loaded before any of the functions provided by it are used and restart your shell.

# ~/.zshrc
source ~/.zsh-plugins/zsh-snap/znap.zsh

Installing Plugins

To install a plugin you could manually clone it to your newly created ~/.zsh-plugins folder, but znap provides a function to do it for you that places it in the correct directory.

I currently use the following plugins

  • zsh-users/zsh-completions
  • zsh-users/zsh-syntax-highlighting
  • mafredri/zsh-async
  • rupa/z
  • sindresorhus/pure
  • ael-code/zsh-colored-man-pages
  • momo-lab/zsh-abbrev-alias
  • Aloxaf/fzf-tab

From on-my-zsh and prezto I only want to include certain parts and not the complete plugin eco-system

  • robbyrussell/oh-my-zsh
    • lib/completion
  • sorin-ionescu/prezto
    • modules/utility
    • modules/completion
    • modules/environment
    • modules/terminal
    • modules/editor
    • modules/history
    • modules/directory
    • modules/syntax-highlighting
    • modules/zsh-history-substring-search

All of them can be installed with a single command (of course you could also run multiple commands to split it up)

znap clone \
  git@github.com:zsh-users/zsh-completions.git \
  git@github.com:zsh-users/zsh-syntax-highlighting.git \
  git@github.com:mafredri/zsh-async.git \
  git@github.com:rupa/z.git \
  git@github.com:robbyrussell/oh-my-zsh.git \
  git@github.com:sorin-ionescu/prezto.git \
  git@github.com:sindresorhus/pure.git \
  git@github.com:ael-code/zsh-colored-man-pages.git \
  git@github.com:momo-lab/zsh-abbrev-alias.git \
  git@github.com:Aloxaf/fzf-tab.git

Znap will download all plugins to the .zsh-plugins directory. At this point you only have to source the folders of each plugin you want to use from your ~/.zshrc. For me this looks like this:

source ~/.zsh-plugins/zsh-snap/znap.zsh

znap source zsh-completions
znap source zsh-syntax-highlighting
znap source zsh-async
znap source z
znap source zsh-colored-man-pages
znap source zsh-abbrev-alias
znap source fzf-tab

# From Oh-my-ZSH
znap source oh-my-zsh lib/completion

# From Prezto
znap source prezto
znap source prezto \
  modules/helper \
  modules/completion \
  modules/environment \
  modules/terminal \
  modules/editor \
  modules/history \
  modules/directory \
  modules/syntax-highlighting \
  modules/zsh-history-substring-search \
  modules/utility
fpath+=( $(znap path prezto) )

# Pure prompt
znap source pure
fpath+=$(znap path pure)
autoload -Uz promptinit && promptinit
prompt pure

Final Words

That's all! Everything worked out of the box with the above configuration. My shell looks and behaves exactly like it did before, with the difference of being extremly portable and probably working on any system that has zsh installed.

I will update my ansible role for zsh to use znap, which should be only a matter of using the git module and copying my ~/.zshrc file like I did before. No compiling, no dependencies and hopefully no more broken shells.

Thanks to Marlon Richert, the author of znap, leave him a star on GitHub!