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.

αғsнιη
  • 41,407
maddingl
  • 594

7 Answers7

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.
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.
  • 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 (https://unix.stackexchange.com/a/409587/459602) – kctong529 Mar 07 '21 at 20:36
  • This works well, except that I cannot search for history using ctrl+R – Strong Bear May 09 '22 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 '22 at 04:26
3

Simply add the line:

export VI_MODE_SET_CURSOR=true

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

Tera
  • 31
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'" 
maddingl
  • 594
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

serge
  • 1
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