18

I use vi-mode in oh-my-zsh with the af-magic theme.

I want the cursor style to indicate whether I am in normal mode (block) or insert mode (beam), both in zsh and in vim.

This is what I have so far:

In my ~/.zshrc:

    # vim mode config
    # ---------------

    # Activate vim mode.
    bindkey -v

    # Remove mode switching delay.
    KEYTIMEOUT=5

    # Change cursor shape for different vi modes.
    function zle-keymap-select {
      if [[ ${KEYMAP} == vicmd ]] ||
         [[ $1 = 'block' ]]; then
        echo -ne '\e[1 q'

      elif [[ ${KEYMAP} == main ]] ||
           [[ ${KEYMAP} == viins ]] ||
           [[ ${KEYMAP} = '' ]] ||
           [[ $1 = 'beam' ]]; then
        echo -ne '\e[5 q'
      fi
    }
    zle -N zle-keymap-select

    # Use beam shape cursor on startup.
    echo -ne '\e[5 q'

    # Use beam shape cursor for each new prompt.
    preexec() {
       echo -ne '\e[5 q'
    }

As found here.

In vim, I use Vundle and terminus.

With these configurations, both zsh and vim work as they should when considered independently. However, when I enter vim from zsh in insert mode, vim starts in normal mode (as it should) but still shows the beam shape cursor. Similarly, when I exit vim, I get back to zsh in insert mode, but the cursor is still in block shape (since the last mode in vim was normal).

When after this, I switch modes for the first time (in both zsh and vim), the cursor behaves the way it should again.

How can I make them display the correct cursor after entering and exiting vim as well?

I tried putting

    autocmd VimEnter * stopinsert
    autocmd VimLeave * startinsert

in my ~.vimrc, but this does not affect the cursor.

7 Answers 7

19

I think it's better to use precmd() instead of preexec():

# .zshrc

_fix_cursor() {
   echo -ne '\e[5 q'
}

precmd_functions+=(_fix_cursor)

This way:

  • you don't have to change .vimrc
  • cursor is fixed also when you create a new prompt without executing a command
  • you don't have to write echo -ne '\e[5 q' twice in your .zshrc.
4
  • 1
    yes, this is nicer and works pretty well. Thanks! :)
    – maddingl
    Jan 27, 2019 at 14:28
  • @maddingl Maybe you should consider changing the accepted answer to this one instead :)
    – statox
    Mar 19, 2019 at 11:05
  • 1
    I use zle-line-finish() { echo -ne "\e[2 q" } and it's working as well.
    – Unix
    Nov 10, 2019 at 0:57
  • 9
    But this doesn't change back to a block when going into normal mode
    – Max Coplan
    Nov 30, 2019 at 18:17
12

This works perfectly for me, it's taken from here: https://gist.github.com/LukeSmithxyz/e62f26e55ea8b0ed41a65912fbebbe52

# vi mode
bindkey -v
export KEYTIMEOUT=1

# Change cursor shape for different vi modes.
function zle-keymap-select {
  if [[ ${KEYMAP} == vicmd ]] ||
     [[ $1 = 'block' ]]; then
    echo -ne '\e[1 q'
  elif [[ ${KEYMAP} == main ]] ||
       [[ ${KEYMAP} == viins ]] ||
       [[ ${KEYMAP} = '' ]] ||
       [[ $1 = 'beam' ]]; then
    echo -ne '\e[5 q'
  fi
}
zle -N zle-keymap-select
zle-line-init() {
    zle -K viins # initiate `vi insert` as keymap (can be removed if `bindkey -V` has been set elsewhere)
    echo -ne "\e[5 q"
}
zle -N zle-line-init
echo -ne '\e[5 q' # Use beam shape cursor on startup.
preexec() { echo -ne '\e[5 q' ;} # Use beam shape cursor for each new prompt.

You can customise the type of cursor you want (blinking or not, |, rectangle or _) by changing the numbers in the following sequences \e[5 q (5 is for beam, 1 is for block) as follows:

Set cursor style (DECSCUSR), VT520.
0  ⇒  blinking block.
1  ⇒  blinking block (default).
2  ⇒  steady block.
3  ⇒  blinking underline.
4  ⇒  steady underline.
5  ⇒  blinking bar, xterm.
6  ⇒  steady bar, xterm.
3
  • 1
    Works like a charm. For those who want to extend this feature to interactive shell (e.g. python, scheme), you might find this helpful (unix.stackexchange.com/a/409587/459602)
    – kctong529
    Mar 7, 2021 at 20:36
  • This works well, except that I cannot search for history using ctrl+R May 9, 2022 at 18:24
  • 2
    @HungVo Ctrl+R is an emacs key binding. As soon as you set zsh to use vi key bindings, you need to have another short cut. The best is to use this bindkey -M vicmd '?' history-incremental-search-backward then when in command mode you can search backward like in vi with the ? key. You can use / instead if it makes more sens to you.
    – Mig
    May 27, 2022 at 4:26
3

Simply add the line:

export VI_MODE_SET_CURSOR=true

to your ~/.zshrc. It is mentioned in the discussion of issue #9570.

1
  • This only works if you are using ohmyzsh.
    – chb
    Oct 11, 2023 at 15:33
2

I have found a solution:

I put this in my ~/.vimrc:

autocmd VimEnter * silent exec "! echo -ne '\e[1 q'"
autocmd VimLeave * silent exec "! echo -ne '\e[5 q'" 
0

You can also use zle-line-init()

zle-line-init() {
    zle -K viins # initiate `vi insert` as keymap (can be removed if `bindkey -V` has been set elsewhere)
    echo -ne "\e[5 q"
}

I think this problem is better solved using this, rather than precmd() or preexec() as both are intended to be used to execute commands, instead of fixing prompt.

0

The following script works for me. It changes the cursor every time after starting/exiting a program.

function _set_cursor() {
    if [[ $TMUX = '' ]]; then
      echo -ne $1
    else
      echo -ne "\ePtmux;\e\e$1\e\\"
    fi
}

# Remove mode switching delay.
KEYTIMEOUT=5

function _set_block_cursor() { _set_cursor '\e[2 q' }
function _set_beam_cursor() { _set_cursor '\e[0 q' }

function zle-keymap-select {
  if [[ ${KEYMAP} == vicmd ]] || [[ $1 = 'block' ]]; then
      _set_block_cursor
  else
      _set_beam_cursor
  fi
}
zle -N zle-keymap-select
# ensure beam cursor when starting new terminal
precmd_functions+=(_set_beam_cursor) #
# ensure insert mode and beam cursor when exiting vim
zle-line-init() { zle -K viins; _set_beam_cursor }
zle-line-finish() { _set_block_cursor }
zle -N zle-line-finish
0

The simplest way I do it:

setopt vi
KEYTIMEOUT=1
# change cursor shape in vi mode
zle-keymap-select () {
    if [[ $KEYMAP == vicmd ]]; then
        # the command mode for vi
        echo -ne "\e[2 q"
    else
        # the insert mode for vi
        echo -ne "\e[5 q"
    fi
}
precmd_functions+=(zle-keymap-select)
zle -N zle-keymap-select

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .