Change Your Cursor Shape on the Fly in Zsh’s “vi-mode”

Note: I no longer use the configuration below, and I no longer maintain it, so I provide the information as is.

I decided to switch to using something called “vi-mode” when using Zsh. If you’re into using the command line a lot, and you’re using Zsh, then you might find this interesting.

If you’re using “vi-mode” — and this entry isn’t really about using “vi-mode”, so I suggest you look elsewhere on what that is and how to use it — then you might desire to know offhand what mode you’re in when you look at your shell prompt. This is probably a natural desire, and it’s what occurred to me when I made the switch. I took a look at the vi-mode plugin that comes with oh-my-zsh. It has some nifty thing that can be inserted into the prompt.

I wanted something subtle, so I made it change the color of my lightning bolt. I wasn’t satisfied, though, because I remembered a Vim tip that let me change the cursor shape on the fly in exactly the same circumstances when using Vim proper. It uses a trick that works in iTerm2 (and evidently Konsole over on Linux), using a proprietary escape sequence.

I searched a little bit more, but I only ever saw information about this escape sequence in the context of Vim, so I’m evidently the first ever to want to use this escape sequence outside of Vim. I think it’s actually pretty useful, so I thought I’d jot down how I did it.

First, here’s what it looks like…

vi-mode-cursor-shape

Now, as for how I did it. This is the relevant excerpt from my ~/.zshrc, omitting all the other things I have in there that accomplish the color change and other features I like.

function zle-keymap-select zle-line-init zle-line-finish {
  case $KEYMAP in
    vicmd)      print -n -- "\E]50;CursorShape=0\C-G";; # block cursor
    viins|main) print -n -- "\E]50;CursorShape=1\C-G";; # line cursor
  esac

  zle reset-prompt
  zle -R
}

zle -N zle-line-init
zle -N zle-line-finish
zle -N zle-keymap-select

That’s about it. Again, this only really works if you’re using iTerm 2 on a Mac.

Update:

I noticed one pernicious drawback of the methodology I outlined above. Whenever executing a command, it would use the line-style cursor just as a matter of course, since that’s what the shell had set earlier, and there was nothing to reset it when a command were issued.

For this reason, I split out the above Zsh widget functions so that the block cursor is always restored when the shell finishes reading a line of input. That is to say, I choose the line-style cursor when initializing a prompt to accept a new line of input (or when $KEYMAP changes), but as soon as I finish reading in input and move on to execute a command, I restore the block-style cursor. Here’s what that looks like:

function zle-keymap-select zle-line-init
{
    # change cursor shape in iTerm2
    case $KEYMAP in
        vicmd)      print -n -- "\E]50;CursorShape=0\C-G";;  # block cursor
        viins|main) print -n -- "\E]50;CursorShape=1\C-G";;  # line cursor
    esac

    zle reset-prompt
    zle -R
}

function zle-line-finish
{
    print -n -- "\E]50;CursorShape=0\C-G"  # block cursor
}

zle -N zle-line-init
zle -N zle-line-finish
zle -N zle-keymap-select

You can always find the most recent version of my settings in my .zshrc configuration script.