Using the Zsh shell in Vim mode
March 17, 2021 ‐ 2 min read
Whenever available I love to reach to the Vim keybindings. The Zsh line editor not being an exception. Vim all the things!
Enabling the Vim keybindings is a one-liner in the ~/.zshrc
config file. But to get it working somewhat more smoothy some other tweaks are necessary too in my opinion. I'm sure there are some oh-my-zsh
plugins available for this too if you use that.
First off, let's indeed add that one-liner to the ~/.zshrc
config file.
# ~/.zshrc
# Use vi mode
bindkey -v
The next time you open your shell you are using vi mode. You can exit insert mode with ESC
and enter it again with i
, and other keybindings you're expecting are available too.
There is somewhat of a delay between pressing a key combination and the actual effect. For me this timeout takes a bit too long. You can change it by setting the KEYTIMEOUT
environment variable in your ~/.zshrc
file too.
# ~/.zshrc
# Use vi mode
bindkey -v
export KEYTIMEOUT=1
What is missing for me is a proper mode indicator. I've moved the code for the indicator to a separate zsh theme.
The code that did the trick for me is pasted in bellow.
## Init
setopt PROMPT_SUBST
## Options
THEME_PROMPT_PREFIX=${THEME_PROMPT_PREFIX:-''}
THEME_VI_INS_MODE_SYMBOL=${THEME_VI_INS_MODE_SYMBOL:-'λ'}
THEME_VI_CMD_MODE_SYMBOL=${THEME_VI_CMD_MODE_SYMBOL:-'ᐅ'}
## Set symbol for the initial mode
THEME_VI_MODE_SYMBOL="${THEME_VI_INS_MODE_SYMBOL}"
# on keymap change, define the mode and redraw prompt
zle-keymap-select() {
if [ "${KEYMAP}" = 'vicmd' ]; then
THEME_VI_MODE_SYMBOL="${THEME_VI_CMD_MODE_SYMBOL}"
else
THEME_VI_MODE_SYMBOL="${THEME_VI_INS_MODE_SYMBOL}"
fi
zle reset-prompt
}
zle -N zle-keymap-select
# reset to default mode at the end of line input reading
zle-line-finish() {
THEME_VI_MODE_SYMBOL="${THEME_VI_INS_MODE_SYMBOL}"
}
zle -N zle-line-finish
# Fix a bug when you C-c in CMD mode, you'd be prompted with CMD mode indicator
# while in fact you would be in INS mode.
# Fixed by catching SIGINT (C-c), set mode to INS and repropagate the SIGINT,
# so if anything else depends on it, we will not break it.
TRAPINT() {
THEME_VI_MODE_SYMBOL="${THEME_VI_INS_MODE_SYMBOL}"
return $(( 128 + $1 ))
}
PROMPT='$THEME_PROMPT_PREFIX%f%B%F{240}%1~%f%b %(?.%F{green}$THEME_VI_MODE_SYMBOL.%F{red}$THEME_VI_MODE_SYMBOL) '