15

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.

15

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 '19 at 14:28
  • @maddingl Maybe you should consider changing the accepted answer to this one instead :)
    – statox
    Mar 19 '19 at 11:05
  • 1
    I use zle-line-finish() { echo -ne "\e[2 q" } and it's working as well.
    – Unix
    Nov 10 '19 at 0:57
  • 5
    But this doesn't change back to a block when going into normal mode
    – Max Coplan
    Nov 30 '19 at 18:17
3

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.
1
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'" 
1

Simply add the line:

export VI_MODE_SET_CURSOR=true

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

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.