From 0e284f6c2a32013582bc58e6128bcad987d1ac3f Mon Sep 17 00:00:00 2001
From: Sorin Ionescu <sorin.ionescu@gmail.com>
Date: Sun, 14 Aug 2011 21:14:57 -0400
Subject: [PATCH] Merged vi-mode plugin with Emacs key bindings.

---
 functions/keyboard.zsh             | 276 ++++++++++++++++++++++-------
 plugins/vi-mode/vi-mode.plugin.zsh | 104 -----------
 templates/zshrc.template.zsh       |   9 +
 3 files changed, 223 insertions(+), 166 deletions(-)
 delete mode 100644 plugins/vi-mode/vi-mode.plugin.zsh

diff --git a/functions/keyboard.zsh b/functions/keyboard.zsh
index 5ab37220..e71fb1b4 100644
--- a/functions/keyboard.zsh
+++ b/functions/keyboard.zsh
@@ -1,6 +1,13 @@
 # Beep on error in line editor.
 setopt beep
 
+# Reset to default key bindings.
+bindkey -d
+
+# Allow command line editing in an external editor.
+autoload -Uz edit-command-line
+zle -N edit-command-line
+
 # Use human-friendly identifiers.
 typeset -g -A keys
 keys=(
@@ -33,75 +40,220 @@ keys=(
   'Menu'      '^[[29~'
 )
 
-bindkey -d    # Reset to default key bindings.
-bindkey -e    # Use Emacs key bindings.
+if [[ "$KEYMAP" == (emacs|) ]]; then
+  # Use Emacs key bindings.
+  bindkey -e
 
-# Do history expansion on space.
+  bindkey "${keys[Escape]}b" emacs-backward-word
+  bindkey "${keys[Escape]}f" emacs-forward-word
+  bindkey "${keys[Escape]}${keys[Left]}" emacs-backward-word
+  bindkey "${keys[Escape]}${keys[Right]}" emacs-forward-word
+
+  # Kill to the beginning of the line.
+  bindkey "${keys[Control]}u" backward-kill-line
+
+  # Kill to the beginning of the word.
+  bindkey "${keys[Control]}w" backward-kill-word
+
+  # Undo/Redo
+  bindkey "${keys[Control]}_" undo
+  bindkey "${keys[Escape]}_" redo
+
+  # Search character.
+  bindkey "${keys[Control]}]" vi-find-next-char
+  bindkey "${keys[Escape]}${keys[Control]}]" vi-find-prev-char
+
+  # Edit command in an external editor.
+  bindkey "${keys[Control]}x${keys[Control]}e" edit-command-line
+
+  # Expand .... to ../..
+  if ! check-bool "$DISABLE_DOT_EXPANSION"; then
+    bindkey "." expand-dot-to-parent-directory-path
+  fi
+
+  # Bind to history substring search plugin if enabled;
+  # otherwise, bind to built-in ZSH history search.
+  if (( ${+widgets[history-incremental-pattern-search-backward]} )); then
+    bindkey "${keys[Control]}r" history-incremental-pattern-search-backward
+    bindkey "${keys[Control]}s" history-incremental-pattern-search-forward
+  else
+    bindkey "${keys[Control]}r" history-incremental-search-backward
+    bindkey "${keys[Control]}s" history-incremental-search-forward
+  fi
+elif [[ "$KEYMAP" == 'vi' ]]; then
+  # Use vi key bindings.
+  bindkey -v
+
+  # The default mode indicator.
+  MODE_INDICATOR="%B%F{red}❮%f%b%F{red}❮❮%f"
+
+  # Restores RPROMPT when exiting vicmd.
+  function vi-restore-rprompt() {
+    if (( $+RPROMPT_CACHED )); then
+      RPROMPT="$RPROMPT_CACHED"
+      unset RPROMPT_CACHED
+      zle reset-prompt
+      return 0
+    fi
+    return 1
+  }
+  add-zsh-trap INT vi-restore-rprompt
+
+  # Displays the current vi mode (command).
+  function zle-keymap-select() {
+    if ! vi-restore-rprompt && [[ "$KEYMAP" == 'vicmd' ]]; then
+      RPROMPT_CACHED="$RPROMPT"
+      RPROMPT="$MODE_INDICATOR"
+      zle reset-prompt
+    fi
+  }
+  zle -N zle-keymap-select
+
+  # Resets the prompt after exiting edit-command-line.
+  function zle-line-init() {
+    vi-restore-rprompt
+  }
+  zle -N zle-line-init
+
+  # Resets the prompt after the line has been accepted.
+  function zle-line-finish() {
+    vi-restore-rprompt
+  }
+  zle -N zle-line-finish
+
+  # Edit command in an external editor.
+  bindkey -M vicmd "v" edit-command-line
+
+  # Show cursor position.
+  bindkey -M vicmd "ga" what-cursor-position
+
+  # Undo/Redo
+  bindkey -M vicmd "u" undo
+  bindkey -M vicmd "${keys[Control]}r" redo
+
+  # Expand .... to ../..
+  if ! check-bool "$DISABLE_DOT_EXPANSION"; then
+    bindkey -M viins "." expand-dot-to-parent-directory-path
+  fi
+
+  # Switch to command mode.
+  bindkey -M viins "jk" vi-cmd-mode
+  bindkey -M viins "kj" vi-cmd-mode
+
+  # Emacs key bindings in insert mode.
+  bindkey -M viins "${keys[Control]}a" beginning-of-line
+  bindkey -M viins "${keys[Control]}b" backward-char
+  bindkey -M viins "${keys[Escape]}b" emacs-backward-word
+  bindkey -M viins "${keys[Control]}d" delete-char-or-list
+  bindkey -M viins "${keys[Escape]}d" kill-word
+  bindkey -M viins "${keys[Control]}e" end-of-line
+  bindkey -M viins "${keys[Control]}f" forward-char
+  bindkey -M viins "${keys[Escape]}f" emacs-forward-word
+  bindkey -M viins "${keys[Control]}k" kill-line
+  bindkey -M viins "${keys[Control]}u" backward-kill-line
+  bindkey -M viins "${keys[Control]}w" backward-kill-word
+  bindkey -M viins "${keys[Escape]}w" copy-region-as-kill
+  bindkey -M viins "${keys[Escape]}h" run-help
+  bindkey -M viins "${keys[Escape]}${keys[Left]}" emacs-backward-word
+  bindkey -M viins "${keys[Escape]}${keys[Right]}" emacs-forward-word
+
+  # History
+  bindkey -M vicmd "gg" beginning-of-history
+  bindkey -M vicmd "G" end-of-history
+
+  # Bind to history substring search plugin if enabled;
+  # otherwise, bind to built-in ZSH history search.
+  if (( $+plugins[(er)history-substring-search] )); then
+    bindkey -M vicmd "k" history-substring-search-up
+    bindkey -M vicmd "j" history-substring-search-down
+  else
+    bindkey -M vicmd "k" up-line-or-history
+    bindkey -M vicmd "j" down-line-or-history
+  fi
+
+  if (( ${+widgets[history-incremental-pattern-search-backward]} )); then
+    bindkey -M vicmd "?" history-incremental-pattern-search-backward
+    bindkey -M vicmd "/" history-incremental-pattern-search-forward
+
+    # Emacs key bindings in insert mode.
+    bindkey -M viins "${keys[Control]}r" history-incremental-pattern-search-backward
+    bindkey -M viins "${keys[Control]}s" history-incremental-pattern-search-forward
+  else
+    bindkey -M vicmd "?" history-incremental-search-backward
+    bindkey -M vicmd "/" history-incremental-search-forward
+
+    # Emacs key bindings in insert mode.
+    bindkey -M viins "${keys[Control]}r" history-incremental-search-backward
+    bindkey -M viins "${keys[Control]}s" history-incremental-search-forward
+  fi
+else
+  echo "oh-my-zsh: KEYMAP must be set 'emacs' or 'vi' but is set to '$KEYMAP'" >&2
+  return 1
+fi
+
+# The next key bindings are for both Emacs and Vi.
+bindkey "${keys[Home]}" beginning-of-line
+bindkey "${keys[End]}" end-of-line
+
+bindkey "${keys[Insert]}" overwrite-mode
+bindkey "${keys[Delete]}" delete-char
+bindkey "${keys[Backspace]}" backward-delete-char
+
+bindkey "${keys[Left]}" backward-char
+bindkey "${keys[Right]}" forward-char
+
+# Expand history on space.
 bindkey ' ' magic-space
 
-# Avoid binding ^J, ^M,  ^C, ^?, ^S, ^Q, etc.
-bindkey "${keys[Home]}"                 beginning-of-line
-bindkey "${keys[End]}"                  end-of-line
-
-bindkey "${keys[Insert]}"               overwrite-mode
-bindkey "${keys[Delete]}"               delete-char
-
-bindkey "${keys[Up]}"                   up-line-or-history
-bindkey "${keys[Down]}"                 down-line-or-history
-
-bindkey "${keys[Left]}"                 backward-char
-bindkey "${keys[Right]}"                forward-char
-
-bindkey "${keys[Meta]}b"                emacs-backward-word
-bindkey "${keys[Meta]}f"                emacs-forward-word
-bindkey "${keys[Escape]}${keys[Left]}"  emacs-backward-word
-bindkey "${keys[Escape]}${keys[Right]}" emacs-forward-word
-
-bindkey "${keys[Control]}w"             kill-region
-
-bindkey "${keys[Escape]}e"              expand-cmd-path
-bindkey "${keys[Escape]}m"              copy-prev-shell-word
-
-bindkey '^[[Z'                          reverse-menu-complete       # Shift + Tab
-bindkey "${keys[Control]}i"             expand-or-complete-prefix   # Complete in middle of word.
-
-bindkey "${keys[Control]}_"             undo
-bindkey "${keys[Escape]}_"              redo
-
-# History
-if autoloadable history-search-end; then
-  autoload -U history-search-end
-  zle -N history-beginning-search-backward-end history-search-end
-  zle -N history-beginning-search-forward-end history-search-end
-  bindkey "${keys[Control]}p" history-beginning-search-backward-end
-  bindkey "${keys[Control]}n" history-beginning-search-forward-end
+if (( $+plugins[(er)history-substring-search] )); then
+  bindkey "${keys[Up]}" history-substring-search-up
+  bindkey "${keys[Down]}" history-substring-search-down
+  bindkey "${keys[Control]}p" history-substring-search-up
+  bindkey "${keys[Control]}n" history-substring-search-down
 else
-  bindkey "${keys[Control]}p" history-beginning-search-backward
-  bindkey "${keys[Control]}n" history-beginning-search-forward
+  bindkey "${keys[Up]}" up-line-or-history
+  bindkey "${keys[Down]}" down-line-or-history
+  bindkey "${keys[Control]}p" up-line-or-history
+  bindkey "${keys[Control]}n" down-line-or-history
 fi
 
-if (( ${+widgets[history-incremental-pattern-search-backward]} )); then
-  bindkey "${keys[Control]}r" history-incremental-pattern-search-backward
-  bindkey "${keys[Control]}s" history-incremental-pattern-search-forward
-else
-  bindkey "${keys[Control]}r" history-incremental-search-backward
-  bindkey "${keys[Control]}s" history-incremental-search-forward
-fi
+# Clear screen.
+bindkey "${keys[Control]}l" clear-screen
 
-# Allow command line editing in an external editor.
-autoload -Uz edit-command-line
-zle -N edit-command-line
-bindkey "${keys[Control]}x${keys[Control]}e" edit-command-line
+# Expand command name to full path.
+bindkey "${keys[Escape]}e" expand-cmd-path
+
+# Duplicate the previous word.
+bindkey "${keys[Escape]}m" copy-prev-shell-word
+
+# Bind Shift + Tab to go to the previous menu item.
+bindkey '^[[Z' reverse-menu-complete
+
+# Complete in the middle of word.
+bindkey "${keys[Control]}i" expand-or-complete-prefix
 
 # Convert .... to ../.. automatically.
-function rationalize-dot() {
-  if [[ $LBUFFER = *.. ]]; then
-    LBUFFER+=/..
-  else
-    LBUFFER+=.
-  fi
-}
-zle -N rationalize-dot
-bindkey '.' rationalize-dot
-bindkey -M isearch . self-insert 2>/dev/null
+if ! check-bool "$DISABLE_DOT_EXPANSION"; then
+  function expand-dot-to-parent-directory-path() {
+    if [[ $LBUFFER = *.. ]]; then
+      LBUFFER+=/..
+    else
+      LBUFFER+=.
+    fi
+  }
+  zle -N expand-dot-to-parent-directory-path
+  # Do not expand .... to ../..  during incremental search.
+  bindkey -M isearch . self-insert 2>/dev/null
+fi
+
+# Display an indicator when completing.
+if ! check-bool "$DISABLE_COMPLETION_INDICATOR"; then
+  function expand-or-complete-prefix-with-indicator() {
+    echo -n "\e[31m...\e[0m"
+    zle expand-or-complete-prefix
+    zle redisplay
+  }
+  zle -N expand-or-complete-prefix-with-indicator
+  bindkey "${keys[Control]}i" expand-or-complete-prefix-with-indicator
+fi
 
diff --git a/plugins/vi-mode/vi-mode.plugin.zsh b/plugins/vi-mode/vi-mode.plugin.zsh
deleted file mode 100644
index f9aafb95..00000000
--- a/plugins/vi-mode/vi-mode.plugin.zsh
+++ /dev/null
@@ -1,104 +0,0 @@
-# ------------------------------------------------------------------------------
-#          FILE:  vi-mode.plugin.zsh
-#   DESCRIPTION:  oh-my-zsh plugin file.
-#        AUTHOR:  Sorin Ionescu <sorin.ionescu@gmail.com>
-#       VERSION:  1.0.3
-# ------------------------------------------------------------------------------
-
-
-# Allow command line editing in an external editor.
-autoload -Uz edit-command-line
-
-# If mode indicator wasn't setup by theme, define a default.
-if [[ "$MODE_INDICATOR" == "" ]]; then
-  MODE_INDICATOR="%B%F{red}<%f%b%F{red}<<%f"
-fi
-
-# If I am using vi keys, I want to know what mode I'm currently using.
-# zle-keymap-select is executed every time KEYMAP changes.
-# From http://zshwiki.org/home/examples/zlewidgets
-function zle-line-init zle-keymap-select {
-  if [[ "$KEYMAP" == 'vicmd' ]]; then
-    rprompt_cached="$RPROMPT"
-    RPROMPT="$MODE_INDICATOR"
-  elif [[ -n "$rprompt_cached" ]]; then
-    RPROMPT="$rprompt_cached"
-    rprompt_cached=""
-  fi
-  zle reset-prompt
-}
-
-# Accept RETURN in vi command mode.
-function accept_line {
-  if [[ -n "$rprompt_cached" ]]; then
-    RPROMPT="$rprompt_cached"
-    rprompt_cached=""
-  fi
-  builtin zle .accept-line
-}
-
-zle -N zle-line-init
-zle -N zle-keymap-select
-zle -N accept_line
-zle -N edit-command-line
-
-# Avoid binding ^J, ^M,  ^C, ^?, ^S, ^Q, etc.
-bindkey -d # Reset to default.
-bindkey -v # Use vi key bindings.
-bindkey -M vicmd "^M" accept_line # Alow RETURN in vi command.
-bindkey -M vicmd v edit-command-line # ESC-v to edit in an external editor.
-
-bindkey ' ' magic-space
-bindkey -M vicmd "gg" beginning-of-history
-bindkey -M vicmd "G" end-of-history
-
-# Bind to history substring search plugin if enabled;
-# otherwise, bind to built-in ZSH history search.
-if (( $+plugins[(er)history-substring-search] )); then
-  bindkey -M vicmd "k" history-substring-search-up
-  bindkey -M vicmd "j" history-substring-search-down
-else
-  bindkey -M vicmd "k" history-search-backward
-  bindkey -M vicmd "j" history-search-forward
-fi
-
-bindkey "^P"          up-line-or-search
-bindkey -M vicmd "k"  up-line-or-search
-bindkey -M vicmd "^k" up-line-or-search
-bindkey -M viins "^k" up-line-or-search
-bindkey "^N"          down-line-or-search
-bindkey -M vicmd "j"  down-line-or-search
-bindkey -M vicmd "^j" down-line-or-search
-bindkey -M viins "^j" down-line-or-search
-
-bindkey -M viins '^r' history-incremental-pattern-search-backward
-bindkey -M viins '^f' history-incremental-pattern-search-forward
-bindkey -M vicmd "?"  history-incremental-pattern-search-backward
-bindkey -M vicmd "/"  history-incremental-pattern-search-forward
-
-bindkey -M vicmd "^l" clear-screen
-bindkey -M viins "^l" clear-screen
-
-bindkey -M vicmd "^w" backward-kill-word
-bindkey -M viins "^w" backward-kill-word
-
-bindkey -M vicmd "^a" beginning-of-line
-bindkey -M viins "^a" beginning-of-line
-
-bindkey -M vicmd "^e" end-of-line
-bindkey -M viins "^e" end-of-line
-
-bindkey -M vicmd '^d' delete
-bindkey -M viins '^d' delete
-
-bindkey -M vicmd '^?' backward-delete-char
-bindkey -M viins '^?' backward-delete-char
-
-# 'jj' = ESC
-bindkey -M viins 'jj' vi-cmd-mode
-
-if (( ${+functions[rationalize-dot]} )); then
-  bindkey -M viins '.' rationalize-dot
-  bindkey -M isearch . self-insert 2>/dev/null
-fi
-
diff --git a/templates/zshrc.template.zsh b/templates/zshrc.template.zsh
index 510fcf95..d630e9ea 100644
--- a/templates/zshrc.template.zsh
+++ b/templates/zshrc.template.zsh
@@ -1,6 +1,9 @@
 # Path to oh-my-zsh.
 OMZ="$HOME/.oh-my-zsh"
 
+# Set the key mapping style to 'emacs' or 'vi'.
+KEYMAP='emacs'
+
 # Set to 'true' to enable case-sensitivity.
 CASE_SENSITIVE='false'
 
@@ -10,6 +13,12 @@ DISABLE_COLOR='false'
 # Set to 'true' to disable auto setting the tab and window titles.
 DISABLE_AUTO_TITLE='false'
 
+# Set to 'false' to enable converting .... to ../.. automatically.
+DISABLE_DOT_EXPANSION='true'
+
+# Set to 'false' to enable the completion indicator.
+DISABLE_COMPLETION_INDICATOR='true'
+
 # Set the plugins to load (see $OMZ/plugins/).
 # Example: plugins=(git lighthouse rails ruby textmate)
 plugins=(git)