16

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.

6 Answers 6

18

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
  • 8
    But this doesn't change back to a block when going into normal mode
    – Max Coplan
    Nov 30, 2019 at 18:17
5

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
    – Hung Vo
    May 9 at 18:24
  • 1
    @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 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.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.