Created
          October 15, 2012 15:27 
        
      - 
      
 - 
        
Save dbb/3893091 to your computer and use it in GitHub Desktop.  
    example vi mode setup for zsh.
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | ### vi-mode.zsh - example vi mode setup for zsh. | |
| ### | |
| ### Zsh's line editor has two built-in editing models. One is modeled | |
| ### after the `emacs' editor's input system, the other is modeled after | |
| ### the `vi' editor's modal input mode. | |
| ### | |
| ### To use the latter, it is helpful to track the current input mode | |
| ### and update the prompt dynamically to reflect the current mode at | |
| ### all times. | |
| ### | |
| ### This setup tracks the current mode in `$psvar[x]', which is | |
| ### available in prompts in the `%xv' expansion. It'll be one of the | |
| ### following strings: | |
| ### | |
| ### "i" - insert mode | |
| ### "c" - command mode | |
| ### "im" - insert mode, with a minibuffer being active | |
| ### "cm" - ditto, but for command mode | |
| ### "r" - replace mode: insert mode with the overwrite bit set. | |
| ### | |
| ### The `x' is configurable via the `psvmodeidx' variable below. | |
| ### | |
| ### The code in this file requires zsh version 4.3.1 or newer. | |
| ### | |
| ### Copyright (c) 2010, Frank Terbeck <ft@bewatermyfriend.org> | |
| ### The same licensing terms as with zsh apply. | |
| ############################################################################ | |
| ### CONFIGURATION ########################################################## | |
| # Use either `ins' or `cmd' as the default input mode. | |
| zle_default_mode='ins' | |
| # This defines in which index of `$psvar[]' the information is tracked. | |
| # The default is `1'. Set this to whatever fits you best. | |
| psvmodeidx='1' | |
| # If this is set to `yes', use `C-d' instead of `ESC' to switch from insert | |
| # mode to command mode. `ESC' may require a timeout to actually take effect. | |
| # Using `C-d' will work immediately. Therefore that is the default. | |
| zle_use_ctrl_d='yes' | |
| # If set to `yes', make viins mode behave more like `emacs' mode. Bindings | |
| # such as `^r' and `^s' will work. Backspace and `^w' will work more like | |
| # emacs would, too. | |
| zle_ins_more_like_emacs='yes' | |
| # This is an example prompt to show how this can be used. Yours may | |
| # obviously be a lot more colourful and whatnot. As ugly as you like. | |
| PS1='%(?..[%?]-)%1v-(%!) %3~ %% ' | |
| ############################################################################ | |
| ### MODE SETUP - Do not change anything below! ############################# | |
| # Create _functions[] arrays for `zle-line-init', `zle-line-finish' | |
| # and `zle-keymap-select', too. Analogous to precmd_functions[] | |
| # etc. The actual functions only cycle through these arrays and | |
| # execute all existing functions in order. | |
| typeset -ga zle_init_functions | |
| typeset -ga zle_finish_functions | |
| typeset -ga zle_keymap_functions | |
| # This is an associative array, that tracks the current zle | |
| # state. This may contain the following key-value pairs: | |
| # minibuffer - values: yes/no; yes if a minibuffer is active. | |
| # overwrite - values: yes/no; yes if zle is in overwrite mode. | |
| typeset -gA ft_zle_state | |
| # This makes sure the first prompt is drawn correctly. | |
| if [[ ${zle_default_mode} == 'cmd' ]]; then | |
| psvar[$psvmodeidx]='c' | |
| else | |
| psvar[$psvmodeidx]='i' | |
| fi | |
| # We're not in overwrite mode, when zsh starts. | |
| ft_zle_state[overwrite]=no | |
| # When this is hooked into `zle-keymap-select', keymap changes are | |
| # correctly tracked in `psvar[x]', which may be used in PS1 as `%xv'. | |
| function ft-psvx() { | |
| if [[ ${ft_zle_state[minibuffer]} == yes ]]; then | |
| if [[ ${psvar[$psvmodeidx]} != *m ]]; then | |
| psvar[$psvmodeidx]="${psvar[$psvmodeidx]}m" | |
| fi | |
| else | |
| case ${KEYMAP} in | |
| vicmd) psvar[$psvmodeidx]='c';; | |
| *) | |
| if [[ ${ft_zle_state[overwrite]} == yes ]]; then | |
| psvar[$psvmodeidx]='r' | |
| else | |
| psvar[$psvmodeidx]='i' | |
| fi | |
| ;; | |
| esac | |
| fi | |
| zle 'reset-prompt' | |
| } | |
| # This needs to be hooked into `zle-line-finish' to make sure the next | |
| # newly drawn prompt has the correct mode display. | |
| function ft-psvx-default() { | |
| if [[ ${zle_default_mode} == 'cmd' ]]; then | |
| psvar[$psvmodeidx]='c' | |
| else | |
| psvar[$psvmodeidx]='i' | |
| fi | |
| } | |
| # Need to handle SIGINT, too (which is sent by ^C). | |
| # If we don't do this, being in a minibuffer and pressing ^C confuses | |
| # the zle state variable; the `minibuffer' state won't be turned off. | |
| function TRAPINT() { | |
| ft_zle_state[minibuffer]=no | |
| ft-psvx-default | |
| zle reset-prompt 2>/dev/null | |
| return 127 | |
| } | |
| # If a keymap change is done, we do need a status update, obviously. | |
| zle_keymap_functions=( "${zle_keymap_functions[@]}" ft-psvx ) | |
| # When a command line finishes, the next keyboard mode needs to be set | |
| # up, so that `psvar[x]' is correct when the next prompt is drawn. | |
| zle_finish_functions=( "${zle_finish_functions[@]}" ft-psvx-default ) | |
| if [[ ${zle_default_mode} == 'cmd' ]]; then | |
| # We cannot simply link `vicmd' to `main'. If we'd do that the | |
| # whole input system could not be set into insert mode. See the | |
| # zshzle(1) manual for details. To make vicmd the default mode | |
| # for new command lines, we simply turn it on in `zle-line-init()'. | |
| zle_init_functions=( "${zle_init_functions[@]}" ft-vi-cmd ) | |
| fi | |
| function zle-line-init() { | |
| local w | |
| for w in "${zle_init_functions[@]}"; do | |
| (( ${+functions[$w]} )) && "$w" | |
| done | |
| } | |
| zle -N zle-line-init | |
| function zle-line-finish() { | |
| local w | |
| for w in "${zle_finish_functions[@]}"; do | |
| (( ${+functions[$w]} )) && "$w" | |
| done | |
| } | |
| zle -N zle-line-finish | |
| function zle-keymap-select() { | |
| local w | |
| for w in "${zle_keymap_functions[@]}"; do | |
| (( ${+functions[$w]} )) && "$w" | |
| done | |
| } | |
| zle -N zle-keymap-select | |
| # Link `viins' to `main'. | |
| bindkey -v | |
| ############################################################################ | |
| ### VI WIDGETS ############################################################# | |
| # This setup may change the `ESC' keybinding to `C-d'. That defeats the | |
| # possibility to exit zsh by pressing `C-d' (which usually sends EOF). | |
| # With this widget, you can type `:q<RET>' to exit the shell from vicmd. | |
| function ft-zshexit { | |
| [[ -o hist_ignore_space ]] && BUFFER=' ' | |
| BUFFER="${BUFFER}exit" | |
| zle .accept-line | |
| } | |
| zle -N q ft-zshexit | |
| # First the ones that change the input method directly; namely cmd mode, | |
| # insert mode and replace mode. | |
| function ft-vi-replace() { | |
| ft_zle_state[overwrite]=yes | |
| zle vi-replace | |
| ft-psvx | |
| } | |
| function ft-vi-insert() { | |
| ft_zle_state[overwrite]=no | |
| zle vi-insert | |
| } | |
| # Since I want to bind `vi-cmd-mode' to Ctrl-D (which is what I'm doing in | |
| # vim and emacs-viper, too) I need to wrap this widget into a user-widget, | |
| # because only those have an effect with empty command buffers and bindings | |
| # to the key, which sends `EOF'. This also needs the ignore_eof option set. | |
| function ft-vi-cmd() { | |
| ft_zle_state[overwrite]=no | |
| zle vi-cmd-mode | |
| } | |
| function ft-vi-cmd-cmd() { | |
| zle -M 'Use `:q<RET>'\'' to exit the shell.' | |
| } | |
| # ...and now the widgets that open minibuffers... | |
| # Oh, yeah. You cannot wrap `execute-named-cmd', so no minibuffer-signaling | |
| # for that. See <http://www.zsh.org/mla/workers/2005/msg00384.html>. | |
| function ft-markminibuf() { | |
| ft_zle_state[minibuffer]=yes | |
| ft-psvx | |
| zle "$1" | |
| ft_zle_state[minibuffer]=no | |
| ft-psvx | |
| } | |
| if (( ${+widgets[.history-incremental-pattern-search-backward]} )); then | |
| function history-incremental-pattern-search-backward() { | |
| ft-markminibuf .history-incremental-pattern-search-backward | |
| } | |
| else | |
| function history-incremental-search-backward() { | |
| ft-markminibuf .history-incremental-search-backward | |
| } | |
| fi | |
| if (( ${+widgets[.history-incremental-pattern-search-forward]} )); then | |
| function history-incremental-pattern-search-forward() { | |
| ft-markminibuf .history-incremental-pattern-search-forward | |
| } | |
| else | |
| function history-incremental-search-forward() { | |
| ft-markminibuf .history-incremental-search-forward | |
| } | |
| fi | |
| function ft-vi-search-back() { | |
| ft-markminibuf vi-history-search-backward | |
| } | |
| function ft-vi-search-fwd() { | |
| ft-markminibuf vi-history-search-forward | |
| } | |
| function ft-replace-pattern() { | |
| ft-markminibuf replace-pattern | |
| } | |
| # register the created widgets | |
| for w in \ | |
| ft-replace-pattern \ | |
| ft-vi-{cmd,cmd-cmd,replace,insert,search-back,search-fwd} | |
| do | |
| zle -N "$w" | |
| done; unset w | |
| ############################################################################ | |
| ### ALTERED KEYBINDINGS #################################################### | |
| if [[ ${zle_use_ctrl_d} == 'yes' ]]; then | |
| setopt ignore_eof | |
| bindkey -M viins '^d' ft-vi-cmd | |
| bindkey -M vicmd '^d' ft-vi-cmd-cmd | |
| # Remove the escape key binding. | |
| bindkey -r '^[' | |
| fi | |
| bindkey -M vicmd '/' ft-vi-search-fwd | |
| bindkey -M vicmd '?' ft-vi-search-back | |
| bindkey -M vicmd 'i' ft-vi-insert | |
| bindkey -M vicmd 'R' ft-vi-replace | |
| # The following four widgets require something like the following, to | |
| # load and initialise the `replace-pattern' and `replace-string-again' | |
| # widgets: | |
| # | |
| # zstyle ':zle:replace-pattern' edit-previous false | |
| # autoload -Uz replace-string | |
| # autoload -Uz replace-string-again | |
| # zle -N replace-pattern replace-string | |
| # zle -N replace-string-again | |
| # | |
| # ...and that *before* this file is sourced. | |
| if (( ${+widgets[replace-pattern]} )); then | |
| bindkey -M vicmd '^x,' ft-replace-pattern | |
| bindkey -M viins '^x,' ft-replace-pattern | |
| fi | |
| if (( ${+widgets[replace-string-again]} )); then | |
| bindkey -M vicmd '^x.' replace-string-again | |
| bindkey -M viins '^x.' replace-string-again | |
| fi | |
| if [[ ${zle_ins_more_like_emacs} == 'yes' ]]; then | |
| if (( ${+widgets[.history-incremental-pattern-search-backward]} )); then | |
| bindkey -M viins '^r' history-incremental-pattern-search-backward | |
| bindkey -M vicmd '^r' history-incremental-pattern-search-backward | |
| else | |
| bindkey -M viins '^r' history-incremental-search-backward | |
| bindkey -M vicmd '^r' history-incremental-search-backward | |
| fi | |
| if (( ${+widgets[.history-incremental-pattern-search-forward]} )); then | |
| bindkey -M viins '^s' history-incremental-pattern-search-forward | |
| bindkey -M vicmd '^s' history-incremental-pattern-search-forward | |
| else | |
| bindkey -M viins '^s' history-incremental-search-forward | |
| bindkey -M vicmd '^s' history-incremental-search-forward | |
| fi | |
| bindkey -M vicmd '^[h' run-help | |
| bindkey -M viins '^[h' run-help | |
| bindkey -M viins '^p' up-line-or-history | |
| bindkey -M viins '^n' down-line-or-history | |
| bindkey -M viins '^w' backward-kill-word | |
| bindkey -M viins '^h' backward-delete-char | |
| bindkey -M viins '^?' backward-delete-char | |
| fi | |
| true | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment