25

Ok, so I finally made the great change. In my .zshenv, I changed my EDITOR:

export EDITOR=vim

There are a couple of questions I have that are so minor that I didn't want to start separate questions for them. Here they are:

  1. How do I get zsh to distinguish between insert mode and command mode like in vim? Preferably this would change the cursor from an underline to a block like in vim, but displaying text at the bottom would work as well.

  2. How do I get it to act more like vim? For instance, I'd rather it be in command mode by default and not go out of it after one command.

0

4 Answers 4

27

1.) (see http://zshwiki.org/home/examples/zlewidgets and http://pthree.org/2009/03/28/add-vim-editing-mode-to-your-zsh-prompt/ ):

function zle-line-init zle-keymap-select {
    RPS1="${${KEYMAP/vicmd/-- NORMAL --}/(main|viins)/-- INSERT --}"
    RPS2=$RPS1
    zle reset-prompt
}
zle -N zle-line-init
zle -N zle-keymap-select

Where:

2.) i suspect that you have to write another zsh-widget to do that, get inspired by the first of the two links for the first problem.

23

akira's solution has the following problem when using multi-line prompts: when going from ins to cmd mode, the prompt redraw causes few lines to be deleted from the previous output (and the new prompt is displayed few lines above). How many lines depends on how many lines you have in your prompt.

The way to deal with that is to use zle-line-finish, without using zle reset-prompt there. An example:

# perform parameter expansion/command substitution in prompt
setopt PROMPT_SUBST

vim_ins_mode="[INS]"
vim_cmd_mode="[CMD]"
vim_mode=$vim_ins_mode

function zle-keymap-select {
  vim_mode="${${KEYMAP/vicmd/${vim_cmd_mode}}/(main|viins)/${vim_ins_mode}}"
  zle reset-prompt
}
zle -N zle-keymap-select

function zle-line-finish {
  vim_mode=$vim_ins_mode
}
zle -N zle-line-finish

And then you can add it to your right-prompt, for example:

RPROMPT='${vim_mode}'

This is straight from my blog post about it:

10
  • This answer should get a lot of votes. This is the first time I've seen this solution, and it works where the others don't. I spent an hour fiddling around with the zle-line-init widget to no avail. Changed it to match what Pawel provided and bingo!
    – Colin R
    Nov 28, 2012 at 5:34
  • Thanks! It still isn't 100% bulletproof - from time to time I experience a situation where some lines from my previous output are deleted - but it happens so rarely that I just ignore it. Nov 28, 2012 at 10:06
  • 1
    RPROMPT2 should be set as well so that the status is displayed in continuation lines. Also, when using this you may want to do setopt transient_rprompt so that modes for previously accepted lines aren't displayed.
    – qqx
    Mar 24, 2013 at 15:59
  • One thing that is still missing: If I switch back to emacs mode (bindkey -e) it still shows [INS].
    – Daniel
    Oct 28, 2013 at 17:33
  • 1
    @cheflo you can see my current version of this setup here: github.com/pjg/dotfiles/blob/master/.zshrc#L518-L584 -- maybe that will help you Feb 24, 2016 at 8:29
5
zle-line-init() { zle -K vicmd; }
zle -N zle-line-init

these two lines make sure it starts in command mode

1
  • Does not work, zsh starts in insert mode anyway
    – baldrs
    Jan 15, 2016 at 13:55
1

The below will set you up with a modified cursor and a prompt displaying which mode you are in. You can change DEFAULT_VI_MODE to either viins or vicmd. Just paste the below into a fresh .zshrc to get started:

# Prefer vi shortcuts
bindkey -v
DEFAULT_VI_MODE=viins
KEYTIMEOUT=1

__set_cursor() {
    local style
    case $1 in
        reset) style=0;; # The terminal emulator's default
        blink-block) style=1;;
        block) style=2;;
        blink-underline) style=3;;
        underline) style=4;;
        blink-vertical-line) style=5;;
        vertical-line) style=6;;
    esac

    [ $style -ge 0 ] && print -n -- "\e[${style} q"
}

# Set your desired cursors here...
__set_vi_mode_cursor() {
    case $KEYMAP in
        vicmd)
          __set_cursor block
          ;;
        main|viins)
          __set_cursor vertical-line
          ;;
    esac
}

__get_vi_mode() {
    local mode
    case $KEYMAP in
        vicmd)
          mode=NORMAL
          ;;
        main|viins)
          mode=INSERT
          ;;
    esac
    print -n -- $mode
}

zle-keymap-select() {
    __set_vi_mode_cursor
    zle reset-prompt
}

zle-line-init() {
    zle -K $DEFAULT_VI_MODE
}

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

# Optional: allows you to open the in-progress command inside of $EDITOR
autoload -Uz edit-command-line
bindkey -M vicmd 'v' edit-command-line
zle -N edit-command-line

# PROMPT_SUBST enables functions and variables to re-run everytime the prompt
# is rendered
setopt PROMPT_SUBST

# Single quotes are important so that function is not run immediately and saved
# in the variable
RPROMPT='$(__get_vi_mode)'

Note: I only tested this in Terminal.app (2.7.3) on MacOS (10.12.6) with zsh (5.3.1). Also, if you ever add edit-command-line then the mode will be correctly set too.

You must log in to answer this question.

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