Image of the glider from the Game of Life by John Conway
Skip to content

Add Vim Editing Mode To Your ZSH Prompt

I've decided to go back to vi mode with my shell. Of course, by default, BASH, ZSH and others use emacs mode for the keyboard bindings. This is fine for the generic case, but if I'm using Vim for my default editor, it makes sense to use the Vi keyboard bindings in my shell as well. However, I have an extremely dynamic shell, so if I'm going to start using Vi mode, I better have my shell tell me when I'm in command mode or insert mode.

On Vim, the default mode is command mode, and when you go into a different mode, you're notified, such as going into insert mode. However, with the ZSH, when you start up your prompt, you are in insert mode by default, and you must press your ESC key to go into command mode. So, seeing as though insert mode is default, rather than be notified constantly that I'm in insert mode, I'd rather be notified when I enter command mode. Also, I wanted a way to do this dynamically, so when I pressed my ESC key, I got an immediate visual that I entered command mode. The same event should happen when I go back into insert mode- my visual should disappear.

I wasn't sure how to approach this, so after a bit of Googling, I discovered that I need to write a ZSH widget. I've never written a ZSH widget before. In fact, to be honest, I didn't even know they existed. However, I was curious, and learned that I need to use the 'zle -N' command on a widget. By doing so, this widget will operate on the shell in real time, without waiting for the enter key to be pressed. This is exactly what I'm after. So, the only thing left to do was write a bit of logic handling when I'm in command mode, and when I'm not. Here's the relevant bit of code:

# If I am using vi keys, I want to know what mode I'm currently using.
# zle-keymap-select is executed every time KEYMAP changes.
# From
function zle-keymap-select {
    VIMODE="${${KEYMAP/vicmd/ M:command}/(main|viins)/}"
    zle reset-prompt

zle -N zle-keymap-select

Fairly straight forward I think. If the KEYMAP variable is set to "vicmd", then we're in command mode with vi on the shell, and we can set our VIMODE variable to " M:command" (to match the rest of my prompt). If the KEYMAP variable is set to "main" or "viins", then we can unset VIMODE. The way we do this is using inline ZSH substitution using ${VARIABLE/PATTERN} syntax. This says to search for our pattern anywhere within our variable. Check out the zshexpn(1) man page for more info and examples. Notice I'm running 'zle reset-prompt'. Because this is a widget, I can take advantage of redrawing the prompt whenever I need. Now, the only thing left to do is put the $VIMODE variable in my prompt, and I'll have a dynamic visual of when I enter command mode and when I leave it. Here's some screenshot candy:

Screenshot showing a ZSH prompt without the command mode visual. Screenshot showing a ZSH prompt with the command mode visual

For the need-to-have-it readers, here's the full source to my prompt. Just source the file in your .zshrc, and make sure that your TERM variable is set appropriately to handle terminal colors.

{ 9 } Comments