]> git.huck.website - zsh.git/commitdiff
creation
authorHuck Boles <huck@huck.website>
Mon, 17 Apr 2023 00:09:02 +0000 (19:09 -0500)
committerHuck Boles <huck@huck.website>
Mon, 17 Apr 2023 00:09:02 +0000 (19:09 -0500)
15 files changed:
.gitignore [new file with mode: 0644]
.zprofile [new file with mode: 0644]
.zshenv [new file with mode: 0644]
.zshrc [new file with mode: 0644]
include/aliases.zsh [new file with mode: 0644]
include/autopair.zsh [new file with mode: 0644]
include/dircycle.zsh [new file with mode: 0644]
include/functions.zsh [new file with mode: 0644]
include/fzf.zsh [new file with mode: 0644]
include/git-prompt-enhanced.plugin.zsh [new file with mode: 0644]
include/gitstatus.zsh [new file with mode: 0644]
include/plugins.zsh [new file with mode: 0644]
include/tmuxvim.zsh [new file with mode: 0644]
include/zsh-vi-mode.zsh [new file with mode: 0644]
include/zshmux.zsh [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ceca2e2
--- /dev/null
@@ -0,0 +1,8 @@
+*
+!.gitignore
+!*.zsh
+!.zshrc
+!.zshenv
+!.zprofile
+!include/
+
diff --git a/.zprofile b/.zprofile
new file mode 100644 (file)
index 0000000..35e9ccf
--- /dev/null
+++ b/.zprofile
@@ -0,0 +1,4 @@
+unset XDG_RUNTIME_DIR
+export XDG_RUNTIME_DIR=$(mktemp -d /tmp/$(id -u)-runtime-dir.XXX)
+
+[[ -t 0 && $(tty) == /dev/tty1 && ! $DISPLAY ]] && exec startx
diff --git a/.zshenv b/.zshenv
new file mode 100644 (file)
index 0000000..a065e23
--- /dev/null
+++ b/.zshenv
@@ -0,0 +1,80 @@
+# xdg variables
+export XDG_CONFIG_HOME="$HOME/.config"
+export XDG_DATA_HOME="$HOME/.local/share"
+export XDG_STATE_HOME="$HOME/.local/state"
+export XDG_CACHE_HOME="$HOME/.local/cache"
+
+# shell variables
+export SHELL="/bin/zsh"
+export ZSH="$ZDOTDIR"
+export ZSH_CUSTOM="$ZSH/custom"
+export ZSH_COMPDUMP="$ZSH/cache/.zcompdump-$HOST"
+
+# my folders
+export REPOS="$HOME/repos"
+export DOTFILES="$REPOS/dotfiles"
+export SCRIPTS="$REPOS/script"
+export DOWNLOADS="$HOME/downloads"
+export PACKAGES="$HOME/.packages"
+export CONFIG="$XDG_CONFIG_HOME"
+
+# system variables
+export LANG=en_US.UTF-8
+
+# path variables
+export PATH="$PATH:$HOME/.local/bin:$REPOS/script:$REPOS/ruby:$XDG_DATA_HOME/cargo/bin:."
+export CDPATH=".:$HOME:$HOME/$REPOS:$HOME/$XDG_CONFIG_HOME:/"
+
+# language variables
+export GOPATH="$XDG_DATA_HOME/go"
+export PERL_LOCAL_LIB_ROOT="$XDG_DATA_HOME/perl5"
+export CARGO_HOME="$XDG_DATA_HOME/cargo"
+export RUSTUP_HOME="$XDG_DATA_HOME/rustup"
+
+# terminal setting:
+[[ -n $TMUX ]] && export TERM=tmux-256color || export TERM=st-256color
+
+# preferred programs
+export PAGER='less'
+export LESS='-R --use-color -Dd+r$Du+b'
+export MANPAGER='less -R --use-color -Dd+r -Du+b'
+export EDITOR="/usr/bin/nvim"
+export DIFFPROG="/usr/bin/nvim -d"
+export VISUAL="/usr/bin/nvim"
+export BROWSER="/usr/bin/firefox"
+export EMAIL='huck@huck.website'
+
+# vim config
+export MYVIMRC="$XDG_CONFIG_HOME/nvim/init.lua"
+
+# config paths
+export GTK_2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc"
+export LESSHISTFILE="$XDG_STATE_HOME/less/history"
+export TERMINFO="$XDG_DATA_HOME/terminfo"
+export TERMINFO_DIRS="$XDG_DATA_HOME/terminfo:/usr/share/terminfo"
+export INPUTRC="$XDG_CONFIG_HOME/readline/inputrc"
+export CUDA_CACHE_PATH="$XDG_CACHE_HOME/nv"
+
+# zoxide
+export _ZO_ECHO='1'
+export _ZO_RESOLVE_SYMLINKS='1'
+
+# 16 theme colors
+export BACKGROUND='#151515'
+export FOREGROUND='#d7d0c7'
+export BLACK='#101010'
+export GREY='#404040'
+export RED='#e84f4f'
+export BRRED='#d23d3d'
+export GREEN='#b8d68c'
+export BRGREEN='#a0cf5d'
+export YELLOW='#e1aa5d'
+export BRYELLOW='#f39d21'
+export BLUE='#4D88FF'
+export BRBLUE='#2B76D4'
+export MAGENTA='#9b64fb'
+export BRMAGENTA='#8542ff'
+export CYAN='#00CCCC'
+export BRCYAN='#21d6c7'
+export WHITE='#909090'
+export BRWHITE='#dddddd'
diff --git a/.zshrc b/.zshrc
new file mode 100644 (file)
index 0000000..bcf2641
--- /dev/null
+++ b/.zshrc
@@ -0,0 +1,104 @@
+#shellcheck disable=all
+
+# sourcing  -------------------------------------------------
+. $ZSH/.zshenv
+ZSHEXTRA=$ZSH/extra
+ZSHINCLUDE=$ZSH/include
+[[ ! -d $ZSH ]] && mkdir $ZSH/{cache,extra,include}
+
+fpath+="$ZSHEXTRA/zfunc"
+
+# source files from include dir
+for file in $ZSHINCLUDE/*; do . ${file}; done
+
+# start programs 
+[[ -f $XDG_CONFIG_HOME/.dircolors ]] && eval $(dircolors -b $XDG_CONFIG_HOME/.dircolors)
+[[ $(which zoxide) ]] && eval "$(zoxide init zsh --cmd z)"
+
+# options ------------------------------------------------------------
+ENABLE_CORRECTION="true"
+COMPLETION_WAITING_DOTS="true"
+HISTFILE=$ZSH/history/zsh-history
+HISTORY_LOG=$ZSH/history/zsh-history.log
+SAVEHIST=1000000
+HISTSIZE=10000
+HIST_STAMPS="yyyy-mm-dd"
+REPORTTIME=5
+DIRSTACKSISZE=10
+
+setopt autopushd chase_links
+setopt share_history append_history inc_append_history extended_history
+setopt hist_expire_dups_first hist_ignore_dups hist_find_no_dups hist_ignore_all_dups hist_reduce_blanks
+setopt correct correct_all
+setopt list_ambiguous
+setopt mark_dirs interactive_comments
+setopt append_create clobber_empty
+setopt hash_cmds hash_dirs
+setopt path_script prompt_subst
+setopt auto_param_keys auto_param_slash auto_remove_slash
+setopt null_glob glob glob_dots glob_star_short bang_hist
+unsetopt flow_control
+
+# completion ---------------------------------------------------------
+zstyle ':completion:*' auto-description '%d'
+zstyle ':completion:*' completer _expand _complete _ignored _correct _approximate
+zstyle ':completion:*' completions 1
+zstyle ':completion:*' expand prefix suffix
+zstyle ':completion:*' file-sort access
+zstyle ':completion:*' format '%d'
+zstyle ':completion:*' glob 1
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*' ignore-parents parent pwd
+zstyle ':completion:*' insert-unambiguous true
+zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
+zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
+zstyle ':completion:*' matcher-list '' 'm:{[:lower:]}={[:upper:]}' 'r:|[._-,]=** r:|=**' 'l:|=* r:|=*'
+zstyle ':completion:*' max-errors 3
+zstyle ':completion:*' menu select=long interactive
+zstyle ':completion:*' original true
+zstyle ':completion:*' prompt '%e'
+zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
+zstyle ':completion:*' squeeze-slashes true
+zstyle ':completion:*' substitute 1
+zstyle ':completion:*' verbose true
+
+autoload -Uz compinit
+for dump in ~/.zcompdump(N.mh+24); do
+    compinit
+done
+compinit -C
+zshcache_time="$(date +%s%N)"
+
+# zle plugins -------------------------------------------------------
+autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
+
+add-zsh-hook chpwd chpwd_recent_dirs
+
+chpwd() { ls -Ah }
+
+autoload -Uz run-help
+
+# keybindings -------------------------------------------------------
+bindkey "\e[1~" beginning-of-line
+bindkey "\e[4~" end-of-line
+bindkey "\e[5~" beginning-of-history
+bindkey "\e[6~" end-of-history
+bindkey "\e[3~" delete-char
+bindkey "\e[2~" quoted-insert
+
+# custom prompt -----------------------------------------------------
+if [[ -n $SSH_CONNECTION ]]; then
+PROMPT='%(?..%B%F{red}x) 
+%B%F{yellow}%n%b%f@%B%F{blue}%M%b%f -> %F{green}%d %F{red}[SSH] %B%F{magenta}[gentoo]%f%b %B%F{yellow}%t %W 
+%B%F{green}%(!.#.$) %f%b> '
+RPROMPT='$(git_prompt_enhanced_status) %f[%!]'
+else
+PROMPT='%(?..%B%F{red}x)
+%B%F{green}%n%b%f@%B%F{red}%m%b%f: %F{blue}%~ %f$(git_prompt_enhanced_status)
+%B%F{green}%(!.#.$) %f%b> '
+RPROMPT='%f[%!]'
+fi
+
+PS2='%B%F{green}%(!.#.$)%f%b >  %F{cyan}*%f '
+PS3='%B%F{green}%(!.#.$)%f%b %B%F{magenta}? '
+
diff --git a/include/aliases.zsh b/include/aliases.zsh
new file mode 100644 (file)
index 0000000..94d65eb
--- /dev/null
@@ -0,0 +1,50 @@
+#shell aliases
+alias cp='cp -vr'
+alias rm='rm -v'
+alias mv='mv -v'
+alias mkdir='mkdir -pv'
+alias mount='mount --mkdir'
+alias lsblk='lsblk -f'
+alias reload='source $ZSH/.zshrc'
+alias quit='disown -a && exit'
+alias :q='exit'
+
+# convinience aliases
+alias v='nvim'
+alias sv='sudoedit'
+alias vsesh='nvim -S'
+alias vdiff='nvim -d'
+alias ..='cd ..'
+alias ...='cd ../..'
+alias ....='cd ../../..'
+alias h='history 0'
+alias j='jobs -l'
+alias c='clear'
+alias ear='clear'
+alias zc='z;clear'
+alias make='make -j16 -l6 CFLAGS="-march=bdver1"'
+alias dust='dust -rb'
+
+# ls/exa aliases
+alias ls='lsd'
+alias l='ls'
+alias ll='ls -lAh'
+alias la='ls -Ah'
+alias lt='ls --tree'
+
+# color aliases
+alias ip='ip --color=auto'
+alias grep='grep --color=auto'
+
+# tmux aliases
+alias tmux='tmux'
+alias tlist='tmux list-sessions'
+alias tsesh='tmux new-session -s'
+alias tkill='tmux kill-session -t'
+alias tattach='tmux attach-session -t'
+
+# config aliases
+alias wget='wget --hsts-file="$XDG_DATA_HOME/wget-hsts"'
+alias xrdb='xrdb -load "XDG_CONFIG_HOME/X11/xresources"'
+alias background='feh --bg-fill --no-fehbg --randomize /home/huck/media/photos/backgrounds'
+alias nvidia-settings='nvidia-settings --config=$XDG_CONFIG_HOME/nvidia/settings'
diff --git a/include/autopair.zsh b/include/autopair.zsh
new file mode 100644 (file)
index 0000000..3295ada
--- /dev/null
@@ -0,0 +1,218 @@
+#!/usr/bin/env zsh
+
+AUTOPAIR_INHIBIT_INIT=${AUTOPAIR_INHIBIT_INIT:-}
+AUTOPAIR_BETWEEN_WHITESPACE=${AUTOPAIR_BETWEEN_WHITESPACE:-}
+AUTOPAIR_SPC_WIDGET=${AUTOPAIR_SPC_WIDGET:-"$(bindkey " " | cut -c5-)"}
+AUTOPAIR_BKSPC_WIDGET=${AUTOPAIR_BKSPC_WIDGET:-"$(bindkey "^?" | cut -c6-)"}
+
+typeset -gA AUTOPAIR_PAIRS
+AUTOPAIR_PAIRS=('`' '`' "'" "'" '"' '"' '{' '}' '[' ']' '(' ')' ' ' ' ')
+
+typeset -gA AUTOPAIR_LBOUNDS
+AUTOPAIR_LBOUNDS=(all '[.:/\!]')
+AUTOPAIR_LBOUNDS+=(quotes '[]})a-zA-Z0-9]')
+AUTOPAIR_LBOUNDS+=(spaces '[^{([]')
+AUTOPAIR_LBOUNDS+=(braces '')
+AUTOPAIR_LBOUNDS+=('`' '`')
+AUTOPAIR_LBOUNDS+=('"' '"')
+AUTOPAIR_LBOUNDS+=("'" "'")
+
+typeset -gA AUTOPAIR_RBOUNDS
+AUTOPAIR_RBOUNDS=(all '[[{(<,.:?/%$!a-zA-Z0-9]')
+AUTOPAIR_RBOUNDS+=(quotes '[a-zA-Z0-9]')
+AUTOPAIR_RBOUNDS+=(spaces '[^]})]')
+AUTOPAIR_RBOUNDS+=(braces '')
+
+
+### Helpers ############################
+
+# Returns the other pair for $1 (a char), blank otherwise
+_ap-get-pair() {
+    if [[ -n $1 ]]; then
+        echo $AUTOPAIR_PAIRS[$1]
+    elif [[ -n $2 ]]; then
+        local i
+        for i in ${(@k)AUTOPAIR_PAIRS}; do
+            [[ $2 == $AUTOPAIR_PAIRS[$i] ]] && echo $i && break
+        done
+    fi
+}
+
+# Return 0 if cursor's surroundings match either regexp: $1 (left) or $2 (right)
+_ap-boundary-p() {
+    [[ -n $1 && $LBUFFER =~ "$1$" ]] || [[ -n $2 && $RBUFFER =~ "^$2" ]]
+}
+
+# Return 0 if the surrounding text matches any of the AUTOPAIR_*BOUNDS regexps
+_ap-next-to-boundary-p() {
+    local -a groups
+    groups=(all)
+    case $1 in
+        \'|\"|\`)    groups+=quotes ;;
+        \{|\[|\(|\<) groups+=braces ;;
+        " ")         groups+=spaces ;;
+    esac
+    groups+=$1
+    local group
+    for group in $groups; do
+        _ap-boundary-p $AUTOPAIR_LBOUNDS[$group] $AUTOPAIR_RBOUNDS[$group] && return 0
+    done
+    return 1
+}
+
+# Return 0 if there are the same number of $1 as there are $2 (chars; a
+# delimiter pair) in the buffer.
+_ap-balanced-p() {
+    local lbuf="${LBUFFER//\\$1}"
+    local rbuf="${RBUFFER//\\$2}"
+    local llen="${#lbuf//[^$1]}"
+    local rlen="${#rbuf//[^$2]}"
+    if (( rlen == 0 && llen == 0 )); then
+        return 0
+    elif [[ $1 == $2 ]]; then
+        if [[ $1 == " " ]]; then
+            # Silence WARN_CREATE_GLOBAL errors
+            local match=
+            local mbegin=
+            local mend=
+            # Balancing spaces is unnecessary. If there is at least one space on
+            # either side of the cursor, it is considered balanced.
+            [[ $LBUFFER =~ "[^'\"]([   ]+)$" && $RBUFFER =~ "^${match[1]}" ]] && return 0
+            return 1
+        elif (( llen == rlen || (llen + rlen) % 2 == 0 )); then
+            return 0
+        fi
+    else
+        local l2len="${#lbuf//[^$2]}"
+        local r2len="${#rbuf//[^$1]}"
+        local ltotal=$((llen - l2len))
+        local rtotal=$((rlen - r2len))
+
+        (( ltotal < 0 )) && ltotal=0
+        (( ltotal < rtotal )) && return 1
+        return 0
+    fi
+    return 1
+}
+
+# Return 0 if the last keypress can be auto-paired.
+_ap-can-pair-p() {
+    local rchar="$(_ap-get-pair $KEYS)"
+
+    [[ -n $rchar ]] || return 1
+
+    if [[ $rchar != " " ]]; then
+        # Force pair if surrounded by space/[BE]OL, regardless of
+        # boundaries/balance
+        [[ -n $AUTOPAIR_BETWEEN_WHITESPACE && \
+            $LBUFFER =~ "(^|[  ])$" && \
+            $RBUFFER =~ "^($|[         ])" ]] && return 0
+
+        # Don't pair quotes if the delimiters are unbalanced
+        ! _ap-balanced-p $KEYS $rchar && return 1
+    elif [[ $RBUFFER =~ "^[    ]*$" ]]; then
+        # Don't pair spaces surrounded by whitespace
+        return 1
+    fi
+
+    # Don't pair when in front of characters that likely signify the start of a
+    # string, path or undesirable boundary.
+    _ap-next-to-boundary-p $KEYS $rchar && return 1
+
+    return 0
+}
+
+# Return 0 if the adjacent character (on the right) can be safely skipped over.
+_ap-can-skip-p() {
+    if [[ -z $LBUFFER ]]; then
+        return 1
+    elif [[ $1 == $2 ]]; then
+        if [[ $1 == " " ]]; then
+            return 1
+        elif ! _ap-balanced-p $1 $2; then
+            return 1
+        fi
+    fi
+    if ! [[ -n $2 && $RBUFFER[1] == $2 && $LBUFFER[-1] != '\' ]]; then
+        return 1
+    fi
+    return 0
+}
+
+# Return 0 if the adjacent character (on the right) can be safely deleted.
+_ap-can-delete-p() {
+    local lchar="$LBUFFER[-1]"
+    local rchar="$(_ap-get-pair $lchar)"
+    ! [[ -n $rchar && $RBUFFER[1] == $rchar ]] && return 1
+    if [[ $lchar == $rchar ]]; then
+        if [[ $lchar == ' ' && ( $LBUFFER =~ "[^{([] +$" || $RBUFFER =~ "^ +[^]})]" ) ]]; then
+            # Don't collapse spaces unless in delimiters
+            return 1
+        elif ! _ap-balanced-p $lchar $rchar; then
+            return 1
+        fi
+    fi
+    return 0
+}
+
+# Insert $1 and add $2 after the cursor
+_ap-self-insert() {
+    LBUFFER+=$1
+    RBUFFER="$2$RBUFFER"
+}
+
+
+### Widgets ############################
+
+autopair-insert() {
+    local rchar="$(_ap-get-pair $KEYS)"
+    if [[ $KEYS == (\'|\"|\`| ) ]] && _ap-can-skip-p $KEYS $rchar; then
+        zle forward-char
+    elif _ap-can-pair-p; then
+        _ap-self-insert $KEYS $rchar
+    elif [[ $rchar == " " ]]; then
+        zle ${AUTOPAIR_SPC_WIDGET:-self-insert}
+    else
+        zle self-insert
+    fi
+}
+
+autopair-close() {
+    if _ap-can-skip-p "$(_ap-get-pair "" $KEYS)" $KEYS; then
+        zle forward-char
+    else
+        zle self-insert
+    fi
+}
+
+autopair-delete() {
+    _ap-can-delete-p && RBUFFER=${RBUFFER:1}
+    zle ${AUTOPAIR_BKSPC_WIDGET:-backward-delete-char}
+}
+
+
+### Initialization #####################
+
+autopair-init() {
+    zle -N autopair-insert
+    zle -N autopair-close
+    zle -N autopair-delete
+
+    local p
+    for p in ${(@k)AUTOPAIR_PAIRS}; do
+        bindkey "$p" autopair-insert
+        bindkey -M isearch "$p" self-insert
+
+        local rchar="$(_ap-get-pair $p)"
+        if [[ $p != $rchar ]]; then
+            bindkey "$rchar" autopair-close
+            bindkey -M isearch "$rchar" self-insert
+        fi
+    done
+
+    bindkey "^?" autopair-delete
+    bindkey "^h" autopair-delete
+    bindkey -M isearch "^?" backward-delete-char
+    bindkey -M isearch "^h" backward-delete-char
+}
+[[ -n $AUTOPAIR_INHIBIT_INIT ]] || autopair-init
diff --git a/include/dircycle.zsh b/include/dircycle.zsh
new file mode 100644 (file)
index 0000000..2269e25
--- /dev/null
@@ -0,0 +1,41 @@
+# A Zsh plugin for cycling through the directory stack.
+
+_dircycle_update_cycled() {
+    setopt localoptions nopushdminus
+
+    [[ ${#dirstack} -eq 0 ]] && return
+
+    while ! builtin pushd -q $1 &>/dev/null; do
+        # A missing directory was found; pop it out of the directory stack.
+        builtin popd -q $1
+
+        # Stop trying if there are no more directories in the directory stack.
+        [[ ${#dirstack} -eq 0 ]] && break
+    done
+    
+    # Trigger a prompt update if using Pure (https://github.com/sindresorhus/pure).
+    if typeset -f prompt_pure_setup > /dev/null; then
+        prompt_pure_async_tasks
+        prompt_pure_preprompt_render
+    else
+        zle reset-prompt
+    fi
+}
+
+_dircycle_insert_cycled_left() {
+    _dircycle_update_cycled +1 || true
+}
+
+zle -N _dircycle_insert_cycled_left
+
+_dircycle_insert_cycled_right() {
+     _dircycle_update_cycled -0 || true
+}
+
+zle -N _dircycle_insert_cycled_right
+
+# Bind Ctrl + Shift + Left key combination to backwards direction.
+bindkey "\e[1;6D" _dircycle_insert_cycled_left
+
+# Bind Ctrl + Shift + Right key combination to forwards direction.
+bindkey "\e[1;6C" _dircycle_insert_cycled_right
diff --git a/include/functions.zsh b/include/functions.zsh
new file mode 100644 (file)
index 0000000..b382f7f
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# easy find files
+f() {
+    find . | grep "$1";
+}
+
+cd() {
+    z $1
+    la
+}
+
+# smart remove
+rem() {
+    if [ ! -d "$HOME/.trash" ]; then mkdir "$HOME/.trash"; fi
+    for arg in "$@"
+    do
+        if [ "$(ls -A $arg)" ]; then  # move to trash if non empty dir
+            mv -f "$arg" "$HOME/.trash/$1"
+        elif [ -d "$arg" ]; then  # delete empty directories
+            rmdir "$arg"                   
+        elif [ -f "$arg" ]; then  # move files to trash
+            mv -f "$arg" "$HOME/.trash/$1" 
+        else
+            echo -e "\n\033[31;1;4mFile Not Found:\033[0m\033[34;1m $1\033[0m\n"
+        fi
+    done
+}
diff --git a/include/fzf.zsh b/include/fzf.zsh
new file mode 100644 (file)
index 0000000..acf63a8
--- /dev/null
@@ -0,0 +1,406 @@
+#     ____      ____
+#    / __/___  / __/
+#   / /_/_  / / /_
+#  / __/ / /_/ __/
+# /_/   /___/_/ completion.zsh
+#
+# - $FZF_TMUX               (default: 0)
+# - $FZF_TMUX_OPTS          (default: '-d 40%')
+# - $FZF_COMPLETION_TRIGGER (default: '**')
+# - $FZF_COMPLETION_OPTS    (default: empty)
+
+# Both branches of the following `if` do the same thing -- define
+# __fzf_completion_options such that `eval $__fzf_completion_options` sets
+# all options to the same values they currently have. We'll do just that at
+# the bottom of the file after changing options to what we prefer.
+#
+# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted
+# *must* be quoted in order to prevent alias expansion. In addition, code must
+# be written in a way works with any set of zsh options. This is very tricky, so
+# careful when you change it.
+#
+# Start by loading the builtin zsh/parameter module. It provides `options`
+# associative array that stores current shell options.
+if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
+  # This is the fast branch and it gets taken on virtually all Zsh installations.
+  #
+  # ${(kv)options[@]} expands to array of keys (option names) and values ("on"
+  # or "off"). The subsequent expansion# with (j: :) flag joins all elements
+  # together separated by spaces. __fzf_completion_options ends up with a value
+  # like this: "options=(shwordsplit off aliases on ...)".
+  __fzf_completion_options="options=(${(j: :)${(kv)options[@]}})"
+else
+  # This branch is much slower because it forks to get the names of all
+  # zsh options. It's possible to eliminate this fork but it's not worth the
+  # trouble because this branch gets taken only on very ancient or broken
+  # zsh installations.
+  () {
+    # That `()` above defines an anonymous function. This is essentially a scope
+    # for local parameters. We use it to avoid polluting global scope.
+    'local' '__fzf_opt'
+    __fzf_completion_options="setopt"
+    # `set -o` prints one line for every zsh option. Each line contains option
+    # name, some spaces, and then either "on" or "off". We just want option names.
+    # Expansion with (@f) flag splits a string into lines. The outer expansion
+    # removes spaces and everything that follow them on every line. __fzf_opt
+    # ends up iterating over option names: shwordsplit, aliases, etc.
+    for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
+      if [[ -o "$__fzf_opt" ]]; then
+        # Option $__fzf_opt is currently on, so remember to set it back on.
+        __fzf_completion_options+=" -o $__fzf_opt"
+      else
+        # Option $__fzf_opt is currently off, so remember to set it back off.
+        __fzf_completion_options+=" +o $__fzf_opt"
+      fi
+    done
+    # The value of __fzf_completion_options here looks like this:
+    # "setopt +o shwordsplit -o aliases ..."
+  }
+fi
+
+# Enable the default zsh options (those marked with <Z> in `man zshoptions`)
+# but without `aliases`. Aliases in functions are expanded when functions are
+# defined, so if we disable aliases here, we'll be sure to have no pesky
+# aliases in any of our functions. This way we won't need prefix every
+# command with `command` or to quote every word to defend against global
+# aliases. Note that `aliases` is not the only option that's important to
+# control. There are several others that could wreck havoc if they are set
+# to values we don't expect. With the following `emulate` command we
+# sidestep this issue entirely.
+'emulate' 'zsh' '-o' 'no_aliases'
+
+# This brace is the start of try-always block. The `always` part is like
+# `finally` in lesser languages. We use it to *always* restore user options.
+{
+
+# Bail out if not interactive shell.
+[[ -o interactive ]] || return 0
+
+# To use custom commands instead of find, override _fzf_compgen_{path,dir}
+if ! declare -f _fzf_compgen_path > /dev/null; then
+  _fzf_compgen_path() {
+    echo "$1"
+    command find -L "$1" \
+      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
+      -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+  }
+fi
+
+if ! declare -f _fzf_compgen_dir > /dev/null; then
+  _fzf_compgen_dir() {
+    command find -L "$1" \
+      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
+      -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+  }
+fi
+
+###########################################################
+
+__fzf_comprun() {
+  if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
+    _fzf_comprun "$@"
+  elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
+    shift
+    if [ -n "$FZF_TMUX_OPTS" ]; then
+      fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
+    else
+      fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
+    fi
+  else
+    shift
+    fzf "$@"
+  fi
+}
+
+# Extract the name of the command. e.g. foo=1 bar baz**<tab>
+__fzf_extract_command() {
+  local token tokens
+  tokens=(${(z)1})
+  for token in $tokens; do
+    token=${(Q)token}
+    if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
+      echo "$token"
+      return
+    fi
+  done
+  echo "${tokens[1]}"
+}
+
+__fzf_generic_path_completion() {
+  local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
+  base=$1
+  lbuf=$2
+  cmd=$(__fzf_extract_command "$lbuf")
+  compgen=$3
+  fzf_opts=$4
+  suffix=$5
+  tail=$6
+
+  setopt localoptions nonomatch
+  eval "base=$base"
+  [[ $base = *"/"* ]] && dir="$base"
+  while [ 1 ]; do
+    if [[ -z "$dir" || -d ${dir} ]]; then
+      leftover=${base/#"$dir"}
+      leftover=${leftover/#\/}
+      [ -z "$dir" ] && dir='.'
+      [ "$dir" != "/" ] && dir="${dir/%\//}"
+      matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
+        item="${item%$suffix}$suffix"
+        echo -n "${(q)item} "
+      done)
+      matches=${matches% }
+      if [ -n "$matches" ]; then
+        LBUFFER="$lbuf$matches$tail"
+      fi
+      zle reset-prompt
+      break
+    fi
+    dir=$(dirname "$dir")
+    dir=${dir%/}/
+  done
+}
+_fzf_path_completion() {
+  __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
+    "-m" "" " "
+}
+_fzf_dir_completion() {
+  __fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
+    "" "/" ""
+}
+_fzf_feed_fifo() (
+  command rm -f "$1"
+  mkfifo "$1"
+  cat <&0 > "$1" &
+)
+_fzf_complete() {
+  setopt localoptions ksh_arrays
+  # Split arguments around --
+  local args rest str_arg i sep
+  args=("$@")
+  sep=
+  for i in {0..${#args[@]}}; do
+    if [[ "${args[$i]}" = -- ]]; then
+      sep=$i
+      break
+    fi
+  done
+  if [[ -n "$sep" ]]; then
+    str_arg=
+    rest=("${args[@]:$((sep + 1)):${#args[@]}}")
+    args=("${args[@]:0:$sep}")
+  else
+    str_arg=$1
+    args=()
+    shift
+    rest=("$@")
+  fi
+  local fifo lbuf cmd matches post
+  fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
+  lbuf=${rest[0]}
+  cmd=$(__fzf_extract_command "$lbuf")
+  post="${funcstack[1]}_post"
+  type $post > /dev/null 2>&1 || post=cat
+  _fzf_feed_fifo "$fifo"
+  matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
+  if [ -n "$matches" ]; then
+    LBUFFER="$lbuf$matches"
+  fi
+  command rm -f "$fifo"
+}
+_fzf_complete_telnet() {
+  _fzf_complete +m -- "$@" < <(
+    command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
+        awk '{if (length($2) > 0) {print $2}}' | sort -u
+  )
+}
+_fzf_complete_ssh() {
+  _fzf_complete +m -- "$@" < <(
+    setopt localoptions nonomatch
+    command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
+        <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
+        <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
+        awk '{if (length($2) > 0) {print $2}}' | sort -u
+  )
+}
+_fzf_complete_export() {
+  _fzf_complete -m -- "$@" < <(
+    declare -xp | sed 's/=.*//' | sed 's/.* //'
+  )
+}
+_fzf_complete_unset() {
+  _fzf_complete -m -- "$@" < <(
+    declare -xp | sed 's/=.*//' | sed 's/.* //'
+  )
+}
+_fzf_complete_unalias() {
+  _fzf_complete +m -- "$@" < <(
+    alias | sed 's/=.*//'
+  )
+}
+_fzf_complete_kill() {
+  _fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
+    command ps -ef | sed 1d
+  )
+}
+_fzf_complete_kill_post() {
+  awk '{print $2}'
+}
+fzf-completion() {
+  local tokens cmd prefix trigger tail matches lbuf d_cmds
+  setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
+  # http://zsh.sourceforge.net/FAQ/zshfaq03.html
+  # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
+  tokens=(${(z)LBUFFER})
+  if [ ${#tokens} -lt 1 ]; then
+    zle ${fzf_default_completion:-expand-or-complete}
+    return
+  fi
+  cmd=$(__fzf_extract_command "$LBUFFER")
+  # Explicitly allow for empty trigger.
+  trigger=${FZF_COMPLETION_TRIGGER-'**'}
+  [ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
+  # When the trigger starts with ';', it becomes a separate token
+  if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
+    tokens[-2]="${tokens[-2]}${tokens[-1]}"
+    tokens=(${tokens[0,-2]})
+  fi
+  lbuf=$LBUFFER
+  tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
+  # Trigger sequence given
+  if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
+    d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
+    [ -z "$trigger"      ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
+    [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
+    if eval "type _fzf_complete_${cmd} > /dev/null"; then
+      prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
+      zle reset-prompt
+    elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
+      _fzf_dir_completion "$prefix" "$lbuf"
+    else
+      _fzf_path_completion "$prefix" "$lbuf"
+    fi
+  # Fall back to default completion
+  else
+    zle ${fzf_default_completion:-expand-or-complete}
+  fi
+}
+[ -z "$fzf_default_completion" ] && {
+  binding=$(bindkey '^I')
+  [[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(s: :w)2]
+  unset binding
+}
+zle     -N   fzf-completion
+bindkey '^I' fzf-completion
+} always {
+  # Restore the original options.
+  eval $__fzf_completion_options
+  'unset' '__fzf_completion_options'
+}
+# Key bindings
+# ------------
+
+# The code at the top and the bottom of this file is the same as in completion.zsh.
+# Refer to that file for explanation.
+if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
+  __fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
+else
+  () {
+    __fzf_key_bindings_options="setopt"
+    'local' '__fzf_opt'
+    for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
+      if [[ -o "$__fzf_opt" ]]; then
+        __fzf_key_bindings_options+=" -o $__fzf_opt"
+      else
+        __fzf_key_bindings_options+=" +o $__fzf_opt"
+      fi
+    done
+  }
+fi
+
+'emulate' 'zsh' '-o' 'no_aliases'
+
+{
+
+[[ -o interactive ]] || return 0
+
+# CTRL-T - Paste the selected file path(s) into the command line
+__fsel() {
+  local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
+    -o -type f -print \
+    -o -type d -print \
+    -o -type l -print 2> /dev/null | cut -b3-"}"
+  setopt localoptions pipefail no_aliases 2> /dev/null
+  local item
+  eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
+    echo -n "${(q)item} "
+  done
+  local ret=$?
+  echo
+  return $ret
+}
+
+__fzfcmd() {
+  [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
+    echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
+}
+
+fzf-file-widget() {
+  LBUFFER="${LBUFFER}$(__fsel)"
+  local ret=$?
+  zle reset-prompt
+  return $ret
+}
+zle     -N            fzf-file-widget
+bindkey -M emacs '^T' fzf-file-widget
+bindkey -M vicmd '^T' fzf-file-widget
+bindkey -M viins '^T' fzf-file-widget
+
+# ALT-C - cd into the selected directory
+fzf-cd-widget() {
+  local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
+    -o -type d -print 2> /dev/null | cut -b3-"}"
+  setopt localoptions pipefail no_aliases 2> /dev/null
+  local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
+  if [[ -z "$dir" ]]; then
+    zle redisplay
+    return 0
+  fi
+  zle push-line # Clear buffer. Auto-restored on next prompt.
+  BUFFER="builtin cd -- ${(q)dir}"
+  zle accept-line
+  local ret=$?
+  unset dir # ensure this doesn't end up appearing in prompt expansion
+  zle reset-prompt
+  return $ret
+}
+zle     -N             fzf-cd-widget
+bindkey -M emacs '\ec' fzf-cd-widget
+bindkey -M vicmd '\ec' fzf-cd-widget
+bindkey -M viins '\ec' fzf-cd-widget
+
+# CTRL-R - Paste the selected command from history into the command line
+fzf-history-widget() {
+  local selected num
+  setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
+  selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
+    FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
+  local ret=$?
+  if [ -n "$selected" ]; then
+    num=$selected[1]
+    if [ -n "$num" ]; then
+      zle vi-fetch-history -n $num
+    fi
+  fi
+  zle reset-prompt
+  return $ret
+}
+zle     -N            fzf-history-widget
+bindkey -M emacs '^R' fzf-history-widget
+bindkey -M vicmd '^R' fzf-history-widget
+bindkey -M viins '^R' fzf-history-widget
+
+} always {
+  eval $__fzf_key_bindings_options
+  'unset' '__fzf_key_bindings_options'
+}
diff --git a/include/git-prompt-enhanced.plugin.zsh b/include/git-prompt-enhanced.plugin.zsh
new file mode 100644 (file)
index 0000000..592e06d
--- /dev/null
@@ -0,0 +1,126 @@
+# Handle $0 according to the standard:
+# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
+0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
+0="${${(M)0:#/*}:-$PWD/$0}"
+
+function update_git_branch_variables() {
+    GIT_BRANCH="${$(git branch --show-current):-"$(git rev-parse --short HEAD)"}"
+    GIT_AHEAD=$(git rev-list --count @{upstream}..HEAD 2>/dev/null)
+    GIT_BEHIND=$(git rev-list --count HEAD..@{upstream} 2>/dev/null)
+
+    GIT_STASHED=0
+    if $(git rev-parse --verify refs/stash &>/dev/null); then
+        GIT_STASHED=1
+    fi
+
+    GIT_NO_UPSTREAM=1
+    if $(git rev-parse --abbrev-ref --symbolic-full-name @{u} &>/dev/null); then
+        GIT_NO_UPSTREAM=0
+    fi
+}
+
+function update_git_status_varaibles() {
+    GIT_MODIFIED=0
+    GIT_DELETED=0
+    GIT_STAGED=0
+    GIT_UNTRACKED=0
+    GIT_CONFLICTS=0
+
+    GIT_STATUS=$(git status --porcelain -uall 2>/dev/null)
+
+    while IFS= read -r line; do
+        if [[ "$line" =~ '^\?\?.*' ]]; then
+            GIT_UNTRACKED=$(($GIT_UNTRACKED+1))
+        else
+            if [[ "$line" =~ ^U.* ]]; then
+                GIT_CONFLICTS=$(($GIT_CONFLICTS+1))
+            elif [[ "$line" =~ ^[MRAD].* ]]; then
+                GIT_STAGED=$(($GIT_STAGED+1))
+            fi
+
+            if [[ "$line" =~ ^.M.* ]]; then
+                GIT_MODIFIED=$(($GIT_MODIFIED+1))
+            elif [[ "$line" =~ ^.D.* ]]; then
+                GIT_DELETED=$(($GIT_DELETED+1))
+            fi
+        fi
+    done <<< "$GIT_STATUS"
+}
+
+function compose_git_prompt_string() {
+    SEPARATOR=$ZSH_THEME_GIT_PROMPT_STATUS_SEPARATOR
+
+    GIT_PROMPT="$ZSH_THEME_GIT_PROMPT_PREFIX$ZSH_THEME_GIT_PROMPT_BRANCH$GIT_BRANCH%{${reset_color}%}"
+
+    if [ "$GIT_NO_UPSTREAM" -eq "1" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_NO_UPSTREAM%{${reset_color}%}"
+    fi
+    if [ "$GIT_AHEAD" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_AHEAD$GIT_AHEAD%{${reset_color}%}"
+    fi
+    if [ "$GIT_BEHIND" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_BEHIND$GIT_BEHIND%{${reset_color}%}"
+    fi
+    if [ "$GIT_STASHED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_STASHED%{${reset_color}%}"
+    fi
+
+    GIT_PROMPT+="$ZSH_THEME_GIT_PROMPT_SEPARATOR"
+    if [ "$GIT_MODIFIED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_MODIFIED$GIT_MODIFIED%{${reset_color}%}"
+    fi
+    if [ "$GIT_DELETED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_DELETED$GIT_DELETED%{${reset_color}%}"
+    fi
+    if [ "$GIT_STAGED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_STAGED$GIT_STAGED%{${reset_color}%}"
+    fi
+    if [ "$GIT_UNTRACKED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_UNTRACKED$GIT_UNTRACKED%{${reset_color}%}"
+    fi
+    if [ "$GIT_CONFLICTS" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_CONFLICTS$GIT_CONFLICTS%{${reset_color}%}"
+    fi
+
+    if [ "$GIT_MODIFIED" -eq "0" ] \
+        && [ "$GIT_DELETED" -eq "0" ] \
+        && [ "$GIT_STAGED" -eq "0" ] \
+        && [ "$GIT_UNTRACKED" -eq "0" ] \
+        && [ "$GIT_CONFLICTS" -eq "0" ]
+        then
+            GIT_PROMPT+="$STATUS_SEPARATOR$ZSH_THEME_GIT_PROMPT_CLEAN%{${reset_color}%}"
+    fi
+
+    GIT_PROMPT+="%{${reset_color}%}$ZSH_THEME_GIT_PROMPT_SUFFIX%{${reset_color}%}"
+    echo "$GIT_PROMPT"
+}
+
+function git_prompt_enhanced_status() {
+    if git rev-parse --git-dir &> /dev/null; then
+        update_git_branch_variables
+        update_git_status_varaibles
+        compose_git_prompt_string
+    fi
+}
+
+# Default values for the appearance of the prompt.
+ZSH_THEME_GIT_PROMPT_PREFIX="["
+ZSH_THEME_GIT_PROMPT_SUFFIX="]"
+ZSH_THEME_GIT_PROMPT_SEPARATOR=" |"
+ZSH_THEME_GIT_PROMPT_STATUS_SEPARATOR=" "
+
+ZSH_THEME_GIT_PROMPT_BRANCH="%{$fg_bold[magenta]%}"
+ZSH_THEME_GIT_PROMPT_NO_UPSTREAM="%{$fg_bold[red]%}X"
+ZSH_THEME_GIT_PROMPT_BEHIND="%{$fg[yellow]%}%{↓%G%}"
+ZSH_THEME_GIT_PROMPT_AHEAD="%{$fg[cyan]%}%{↑%G%}"
+ZSH_THEME_GIT_PROMPT_STASHED="%{$fg_bold[yellow]%}S"
+
+ZSH_THEME_GIT_PROMPT_MODIFIED="%{$fg[blue]%}%{+%G%}"
+ZSH_THEME_GIT_PROMPT_DELETED="%{$fg[red]%}%{-%G%}"
+ZSH_THEME_GIT_PROMPT_STAGED="%{$fg[yellow]%}%{s%G%}"
+ZSH_THEME_GIT_PROMPT_UNTRACKED="%{$fg[cyan]%}%{u%G%}"
+ZSH_THEME_GIT_PROMPT_CONFLICTS="%{$fg[red]%}%{!%G%}"
+ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[green]%}%{%G%}"
+
+# Set the prompt.
+RPROMPT='$(git_prompt_enhanced_status)'
diff --git a/include/gitstatus.zsh b/include/gitstatus.zsh
new file mode 100644 (file)
index 0000000..e571c6d
--- /dev/null
@@ -0,0 +1,126 @@
+# Handle $0 according to the standard:
+# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
+0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
+0="${${(M)0:#/*}:-$PWD/$0}"
+
+function update_git_branch_variables() {
+    GIT_BRANCH="${$(git branch --show-current):-"$(git rev-parse --short HEAD)"}"
+    GIT_AHEAD=$(git rev-list --count @{upstream}..HEAD 2>/dev/null)
+    GIT_BEHIND=$(git rev-list --count HEAD..@{upstream} 2>/dev/null)
+
+    GIT_STASHED=0
+    if $(git rev-parse --verify refs/stash &>/dev/null); then
+        GIT_STASHED=1
+    fi
+
+    GIT_NO_UPSTREAM=1
+    if $(git rev-parse --abbrev-ref --symbolic-full-name @{u} &>/dev/null); then
+        GIT_NO_UPSTREAM=0
+    fi
+}
+
+function update_git_status_varaibles() {
+    GIT_MODIFIED=0
+    GIT_DELETED=0
+    GIT_STAGED=0
+    GIT_UNTRACKED=0
+    GIT_CONFLICTS=0
+
+    GIT_STATUS=$(git status --porcelain -uall 2>/dev/null)
+
+    while IFS= read -r line; do
+        if [[ "$line" =~ '^\?\?.*' ]]; then
+            GIT_UNTRACKED=$(($GIT_UNTRACKED+1))
+        else
+            if [[ "$line" =~ ^U.* ]]; then
+                GIT_CONFLICTS=$(($GIT_CONFLICTS+1))
+            elif [[ "$line" =~ ^[MRAD].* ]]; then
+                GIT_STAGED=$(($GIT_STAGED+1))
+            fi
+
+            if [[ "$line" =~ ^.M.* ]]; then
+                GIT_MODIFIED=$(($GIT_MODIFIED+1))
+            elif [[ "$line" =~ ^.D.* ]]; then
+                GIT_DELETED=$(($GIT_DELETED+1))
+            fi
+        fi
+    done <<< "$GIT_STATUS"
+}
+
+function compose_git_prompt_string() {
+    SEPARATOR=$ZSH_THEME_GIT_PROMPT_STATUS_SEPARATOR
+
+    GIT_PROMPT="$ZSH_THEME_GIT_PROMPT_PREFIX$ZSH_THEME_GIT_PROMPT_BRANCH$GIT_BRANCH%{${reset_color}%}"
+
+    if [ "$GIT_NO_UPSTREAM" -eq "1" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_NO_UPSTREAM%{${reset_color}%}"
+    fi
+    if [ "$GIT_AHEAD" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_AHEAD$GIT_AHEAD%{${reset_color}%}"
+    fi
+    if [ "$GIT_BEHIND" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_BEHIND$GIT_BEHIND%{${reset_color}%}"
+    fi
+    if [ "$GIT_STASHED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_STASHED%{${reset_color}%}"
+    fi
+
+    GIT_PROMPT+="$ZSH_THEME_GIT_PROMPT_SEPARATOR"
+    if [ "$GIT_MODIFIED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_MODIFIED$GIT_MODIFIED%{${reset_color}%}"
+    fi
+    if [ "$GIT_DELETED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_DELETED$GIT_DELETED%{${reset_color}%}"
+    fi
+    if [ "$GIT_STAGED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_STAGED$GIT_STAGED%{${reset_color}%}"
+    fi
+    if [ "$GIT_UNTRACKED" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_UNTRACKED$GIT_UNTRACKED%{${reset_color}%}"
+    fi
+    if [ "$GIT_CONFLICTS" -ne "0" ]; then
+        GIT_PROMPT+="$SEPARATOR$ZSH_THEME_GIT_PROMPT_CONFLICTS$GIT_CONFLICTS%{${reset_color}%}"
+    fi
+
+    if [ "$GIT_MODIFIED" -eq "0" ] \
+        && [ "$GIT_DELETED" -eq "0" ] \
+        && [ "$GIT_STAGED" -eq "0" ] \
+        && [ "$GIT_UNTRACKED" -eq "0" ] \
+        && [ "$GIT_CONFLICTS" -eq "0" ]
+        then
+            GIT_PROMPT+="$STATUS_SEPARATOR$ZSH_THEME_GIT_PROMPT_CLEAN%{${reset_color}%}"
+    fi
+
+    GIT_PROMPT+="%{${reset_color}%}$ZSH_THEME_GIT_PROMPT_SUFFIX%{${reset_color}%}"
+    echo "$GIT_PROMPT"
+}
+
+function git_prompt_enhanced_status() {
+    if git rev-parse --git-dir &> /dev/null; then
+        update_git_branch_variables
+        update_git_status_varaibles
+        compose_git_prompt_string
+    fi
+}
+
+# Default values for the appearance of the prompt.
+ZSH_THEME_GIT_PROMPT_PREFIX="["
+ZSH_THEME_GIT_PROMPT_SUFFIX="]"
+ZSH_THEME_GIT_PROMPT_SEPARATOR=""
+ZSH_THEME_GIT_PROMPT_STATUS_SEPARATOR=" "
+
+ZSH_THEME_GIT_PROMPT_BRANCH="%B%F{cyan}"
+ZSH_THEME_GIT_PROMPT_NO_UPSTREAM="(local)"
+ZSH_THEME_GIT_PROMPT_BEHIND="%F{yellow}v"
+ZSH_THEME_GIT_PROMPT_AHEAD="%F{magenta}^"
+ZSH_THEME_GIT_PROMPT_STASHED="%F{yellow}&"
+
+ZSH_THEME_GIT_PROMPT_MODIFIED="%B%F{green}+"
+ZSH_THEME_GIT_PROMPT_DELETED="%B%F{red}-"
+ZSH_THEME_GIT_PROMPT_STAGED="%F{yellow}s"
+ZSH_THEME_GIT_PROMPT_UNTRACKED="%F{cyan}u"
+ZSH_THEME_GIT_PROMPT_CONFLICTS="%B%F{red}!"
+ZSH_THEME_GIT_PROMPT_CLEAN=""
+
+# Set the prompt.
+RPROMPT='$(git_prompt_enhanced_status)'
diff --git a/include/plugins.zsh b/include/plugins.zsh
new file mode 100644 (file)
index 0000000..b81330b
--- /dev/null
@@ -0,0 +1,16 @@
+if [[ -d $ZSHEXTRA/zsh-autosuggestions ]]; then
+    source $ZSHEXTRA/zsh-autosuggestions/zsh-autosuggestions.zsh
+    ZSH_AUTOSUGGEST_STRATEGY=(history completion)
+    set ZSH_AUTOSUGGEST_USE_ASYNC
+fi
+
+if [[ -d $ZSHEXTRA/zsh-fast-syntax-highlighting ]]; then
+    source $ZSHEXTRA/zsh-fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh
+    fast-theme -q $ZSH/extra/zsh-fast-syntax-highlighting/themes/custom.ini
+fi
+
+if [[ -d $ZSHEXTRA/zsh-history-substring-search ]]; then
+    source $ZSHEXTRA/zsh-history-substring-search/zsh-history-substring-search.zsh
+    bindkey '^[[A' history-substring-search-up
+    bindkey '^[[B' history-substring-search-down
+fi
diff --git a/include/tmuxvim.zsh b/include/tmuxvim.zsh
new file mode 100644 (file)
index 0000000..86408f3
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/zsh
+
+__tmux_get_vim_pane() {
+  local vim_pane
+
+  tmux list-panes -F '#I #P [command:#{pane_current_command}] #{?pane_active,(active),}' | while read -r pane; do
+    if [[ "${pane#*(active)}" = "${pane}" ]] && [[ "${pane}" =~ "\[command:n?vim\]" ]]; then
+      vim_pane=$(echo "${pane}" | awk '{print $2}')
+    fi
+  done
+
+  echo $vim_pane
+}
+
+# Open a file in adjacent vim
+# $1 is the vim command to use (tabedit, split, vsplit)
+# $2 is the path of the file to open
+__tmux_vim_open() {
+  local vim_command="$1"
+  local filepath=$(realpath "$2")
+  local vim_pane=$(__tmux_get_vim_pane)
+
+  if [ -z $vim_pane ]; then
+    echo "error: Vim pane not found"
+    return
+  fi
+
+  tmux send-keys -t $vim_pane Escape ":${vim_command} $(printf "%q" "${filepath}")" Enter
+  tmux select-pane -t $vim_pane
+}
+
+# Make available if inside a Tmux session
+if [ -n "${TMUX}" ]; then
+  tmux-vim-tabedit() {
+    __tmux_vim_open "tabedit" "$1"
+  }
+
+  tmux-vim-split() {
+    __tmux_vim_open "split" "$1"
+  }
+
+  tmux-vim-vsplit() {
+    __tmux_vim_open "vsplit" "$1"
+  }
+fi
diff --git a/include/zsh-vi-mode.zsh b/include/zsh-vi-mode.zsh
new file mode 100644 (file)
index 0000000..993a33b
--- /dev/null
@@ -0,0 +1,3459 @@
+# zsh-vi-mode.zsh -- A better and friendly vi(vim) mode for Zsh
+# https://github.com/jeffreytse/zsh-vi-mode
+#
+# Copyright (c) 2020 Jeffrey Tse
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# All Settings
+# Some of these variables should be set before sourcing this file.
+#
+# ZVM_CONFIG_FUNC
+# the config function (default is `zvm_config`), if this config function
+# exists, it will be called automatically, you can do some configurations
+# in this aspect before you source this plugin.
+#
+# For example:
+#
+# ```zsh
+# function zvm_config() {
+#   ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT
+#   ZVM_VI_INSERT_ESCAPE_BINDKEY=jk
+# }
+#
+# source ~/zsh-vi-mode.zsh
+# ```
+#
+# ZVM_INIT_MODE
+# the plugin initial mode (default is doing the initialization when the first
+# new command line is starting. For doing the initialization instantly, you
+# can set it to `sourcing`.
+#
+# ZVM_VI_ESCAPE_BINDKEY
+# the vi escape key for all modes (default is ^[ => <ESC>), you can set it
+# to whatever you like, such as `jj`, `jk` and so on.
+#
+# ZVM_VI_INSERT_ESCAPE_BINDKEY
+# the vi escape key of insert mode (default is $ZVM_VI_ESCAPE_BINDKEY), you
+# can set it to whatever, such as `jj`, `jk` and so on.
+#
+# ZVM_VI_VISUAL_ESCAPE_BINDKEY
+# the vi escape key of visual mode (default is $ZVM_VI_ESCAPE_BINDKEY), you
+# can set it to whatever, such as `jj`, `jk` and so on.
+#
+# ZVM_VI_OPPEND_ESCAPE_BINDKEY
+# the vi escape key of operator pendding mode (default is
+# $ZVM_VI_ESCAPE_BINDKEY), you can set it to whatever, such as `jj`, `jk`
+# and so on.
+#
+# ZVM_VI_INSERT_MODE_LEGACY_UNDO:
+# using legacy undo behavior in vi insert mode
+#
+# ZVM_VI_HIGHLIGHT_FOREGROUND:
+# the behavior of highlight foreground (surrounds, visual-line, etc) in vi mode
+#
+# ZVM_VI_HIGHLIGHT_BACKGROUND:
+# the behavior of highlight background (surrounds, visual-line, etc) in vi mode
+#
+# ZVM_VI_HIGHLIGHT_EXTRASTYLE:
+# the behavior of highlight extra style (i.e. bold, underline) in vi mode
+#
+# For example:
+#   ZVM_VI_HIGHLIGHT_FOREGROUND=green           # Color name
+ZVM_VI_HIGHLIGHT_FOREGROUND=#b8d68c
+#   ZVM_VI_HIGHLIGHT_BACKGROUND=red             # Color name
+ZVM_VI_HIGHLIGHT_BACKGROUND=#e84f4f
+#   ZVM_VI_HIGHLIGHT_EXTRASTYLE=bold,underline  # bold and underline
+#
+# ZVM_VI_SURROUND_BINDKEY
+# the key binding mode for surround operating (default is 'classic')
+#
+# 1. 'classic' mode (verb->s->surround):
+#   S"    Add " for visual selection
+#   ys"   Add " for visual selection
+#   cs"'  Change " to '
+#   ds"   Delete "
+#
+# 2. 's-prefix' mode (s->verb->surround):
+#   sa"   Add " for visual selection
+#   sd"   Delete "
+#   sr"'  Change " to '
+#
+# How to select surround text object?
+#   vi"   Select the text object inside the quotes
+#   va(   Select the text object including the brackets
+#
+# Then you can do any operation for the selection:
+#
+# 1. Add surrounds for text object
+#   vi" -> S[ or sa[ => "object" -> "[object]"
+#
+# 2. Delete/Yank/Change text object
+#   di( or vi( -> d
+#   ca( or va( -> c
+#   yi( or vi( -> y
+#
+# ZVM_READKEY_ENGINE
+# the readkey engine for reading and processing the key events, and the
+# below engines are supported:
+#  ZVM_READKEY_ENGINE_NEX (Default)
+#  ZVM_READKEY_ENGINE_ZLE
+#
+# the NEX is a better engine for reading and handling the key events than
+# the Zsh's ZLE engine, currently the NEX engine is at beta stage, and
+# you can change to Zsh's ZLE engine if you want.
+#
+# ZVM_KEYTIMEOUT:
+# the key input timeout for waiting for next key (default is 0.4 seconds)
+#
+# ZVM_ESCAPE_KEYTIMEOUT:
+# the key input timeout for waiting for next key if it is beginning with
+# an escape character (default is 0.03 seconds), and this option is just
+# available for the NEX readkey engine
+#
+# ZVM_LINE_INIT_MODE
+# the setting for init mode of command line (default is empty), empty will
+# keep the last command mode, for the first command line it will be insert
+# mode, you can also set it to a specific vi mode to alway keep the mode
+# for each command line
+#
+# For example:
+#   ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT
+#   ZVM_LINE_INIT_MODE=$ZVM_MODE_NORMAL
+#
+ZVM_LAZY_KEYBINDINGS=false
+# the setting for lazy keybindings (default is true), and lazy keybindings
+# will postpone the keybindings of vicmd and visual keymaps to the first
+# time entering normal mode
+#
+# ZVM_NORMAL_MODE_CURSOR:
+# the prompt cursor in normal mode
+#
+# ZVM_INSERT_MODE_CURSOR:
+# the prompt cursor in insert mode
+#
+# ZVM_VISUAL_MODE_CURSOR:
+# the prompt cursor in visual mode
+#
+# ZVM_VISUAL_LINE_MODE_CURSOR:
+# the prompt cursor in visual line mode
+#
+# ZVM_OPPEND_MODE_CURSOR:
+# the prompt cursor in operator pending mode
+#
+# You can change the cursor style by below:
+#  ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BLOCK
+#
+# and the below cursor style are supported:
+#  ZVM_CURSOR_USER_DEFAULT
+#  ZVM_CURSOR_BLOCK
+#  ZVM_CURSOR_UNDERLINE
+#  ZVM_CURSOR_BEAM
+#  ZVM_CURSOR_BLINKING_BLOCK
+#  ZVM_CURSOR_BLINKING_UNDERLINE
+#  ZVM_CURSOR_BLINKING_BEAM
+#
+# ZVM_VI_EDITOR
+# the editor to edit your command line (default is $EDITOR)
+#
+# ZVM_TMPDIR
+# the temporary directory (default is $TMPDIR, otherwise it's /tmp)
+#
+# ZVM_TERM
+# the term for handling terminal sequences, it's important for some
+# terminal emulators to show cursor properly (default is $TERM)
+#
+# ZVM_CURSOR_STYLE_ENABLED
+# enable the cursor style feature (default is true)
+#
+
+# Avoid sourcing plugin multiple times
+command -v 'zvm_version' >/dev/null && return
+
+# Plugin information
+typeset -gr ZVM_NAME='zsh-vi-mode'
+typeset -gr ZVM_DESCRIPTION='💻 A better and friendly vi(vim) mode plugin for ZSH.'
+typeset -gr ZVM_REPOSITORY='https://github.com/jeffreytse/zsh-vi-mode'
+typeset -gr ZVM_VERSION='0.8.5'
+
+# Plugin initial status
+ZVM_INIT_DONE=false
+
+# Postpone reset prompt (i.e. postpone the widget `reset-prompt`)
+# empty (No postponing)
+# true (Enter postponing)
+# false (Trigger reset prompt)
+ZVM_POSTPONE_RESET_PROMPT=
+
+# Operator pending mode
+ZVM_OPPEND_MODE=false
+
+# Insert mode could be
+# `i` (insert)
+# `a` (append)
+# `I` (insert at the non-blank beginning of current line)
+# `A` (append at the end of current line)
+ZVM_INSERT_MODE='i'
+
+# The mode could be the below value:
+# `n` (normal)
+# `i` (insert)
+# `v` (visual)
+# `vl` (visual-line)
+ZVM_MODE='i'
+
+# The keys typed to invoke this widget, as a literal string
+ZVM_KEYS=''
+
+# The region hilight information
+ZVM_REGION_HIGHLIGHT=()
+
+# Default zvm readkey engines
+ZVM_READKEY_ENGINE_NEX='nex'
+ZVM_READKEY_ENGINE_ZLE='zle'
+ZVM_READKEY_ENGINE_DEFAULT=$ZVM_READKEY_ENGINE_NEX
+
+# Default alternative character for escape characters
+ZVM_ESCAPE_SPACE='\s'
+ZVM_ESCAPE_NEWLINE='^J'
+
+# Default vi modes
+ZVM_MODE_LAST=''
+ZVM_MODE_NORMAL='n'
+ZVM_MODE_INSERT='i'
+ZVM_MODE_VISUAL='v'
+ZVM_MODE_VISUAL_LINE='vl'
+ZVM_MODE_REPLACE='r'
+
+# Default cursor styles
+ZVM_CURSOR_USER_DEFAULT='ud'
+ZVM_CURSOR_BLOCK='bl'
+ZVM_CURSOR_UNDERLINE='ul'
+ZVM_CURSOR_BEAM='be'
+ZVM_CURSOR_BLINKING_BLOCK='bbl'
+ZVM_CURSOR_BLINKING_UNDERLINE='bul'
+ZVM_CURSOR_BLINKING_BEAM='bbe'
+
+# The commands need to be repeated
+ZVM_REPEAT_MODE=false
+ZVM_REPEAT_RESET=false
+ZVM_REPEAT_COMMANDS=($ZVM_MODE_NORMAL i)
+
+##########################################
+# Initial all default settings
+
+# Default config function
+: ${ZVM_CONFIG_FUNC:='zvm_config'}
+
+# Load config by calling the config function
+if command -v "$ZVM_CONFIG_FUNC" >/dev/null; then
+  $ZVM_CONFIG_FUNC
+fi
+
+# Set the readkey engine (default is NEX engine)
+: ${ZVM_READKEY_ENGINE:=$ZVM_READKEY_ENGINE_DEFAULT}
+
+# Set key input timeout (default is 0.4 seconds)
+: ${ZVM_KEYTIMEOUT:=0.4}
+
+# Set the escape key timeout (default is 0.03 seconds)
+: ${ZVM_ESCAPE_KEYTIMEOUT:=0.03}
+
+# Set keybindings mode (default is true)
+# The lazy keybindings will post the keybindings of vicmd and visual
+# keymaps to the first time entering the normal mode
+: ${ZVM_LAZY_KEYBINDINGS:=true}
+
+# All keybindings for lazy loading
+if $ZVM_LAZY_KEYBINDINGS; then
+  ZVM_LAZY_KEYBINDINGS_LIST=()
+fi
+
+# Set the cursor style in defferent vi modes, the value you could use
+# the predefined value, such as $ZVM_CURSOR_BLOCK, $ZVM_CURSOR_BEAM,
+# $ZVM_CURSOR_BLINKING_BLOCK and so on.
+: ${ZVM_INSERT_MODE_CURSOR:=$ZVM_CURSOR_BEAM}
+: ${ZVM_NORMAL_MODE_CURSOR:=$ZVM_CURSOR_BLOCK}
+: ${ZVM_VISUAL_MODE_CURSOR:=$ZVM_CURSOR_BLOCK}
+: ${ZVM_VISUAL_LINE_MODE_CURSOR:=$ZVM_CURSOR_BLOCK}
+
+# Operator pending mode cursor style (default is underscore)
+: ${ZVM_OPPEND_MODE_CURSOR:=$ZVM_CURSOR_UNDERLINE}
+
+# Set the vi escape key (default is ^[ => <ESC>)
+: ${ZVM_VI_ESCAPE_BINDKEY:=^[}
+: ${ZVM_VI_INSERT_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY}
+: ${ZVM_VI_VISUAL_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY}
+: ${ZVM_VI_OPPEND_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY}
+
+# Set the line init mode (empty will keep the last mode)
+# you can also set it to others, such as $ZVM_MODE_INSERT.
+: ${ZVM_LINE_INIT_MODE:=$ZVM_MODE_LAST}
+
+: ${ZVM_VI_INSERT_MODE_LEGACY_UNDO:=false}
+: ${ZVM_VI_SURROUND_BINDKEY:=classic}
+: ${ZVM_VI_HIGHLIGHT_BACKGROUND:=#cc0000}
+: ${ZVM_VI_HIGHLIGHT_FOREGROUND:=#eeeeee}
+: ${ZVM_VI_HIGHLIGHT_EXTRASTYLE:=default}
+: ${ZVM_VI_EDITOR:=${EDITOR:-vim}}
+: ${ZVM_TMPDIR:=${TMPDIR:-/tmp}}
+
+# Set the term for handling terminal sequences, it's important for some
+# terminal emulators to show cursor properly (default is $TERM)
+: ${ZVM_TERM:=${TERM:-xterm-256color}}
+
+# Enable the cursor style feature
+: ${ZVM_CURSOR_STYLE_ENABLED:=true}
+
+# All the extra commands
+commands_array_names=(
+  zvm_before_init_commands
+  zvm_after_init_commands
+  zvm_before_select_vi_mode_commands
+  zvm_after_select_vi_mode_commands
+  zvm_before_lazy_keybindings_commands
+  zvm_after_lazy_keybindings_commands
+)
+for commands_array_name in $commands_array_names; do
+  # Ensure commands set to an empty array, if not already set.
+  if [[ -z "${(P)commands_array_name}" ]]; then
+    typeset -g -a $commands_array_name
+  fi
+done
+
+# All the handlers for switching keyword
+zvm_switch_keyword_handlers=(
+  zvm_switch_number
+  zvm_switch_boolean
+  zvm_switch_operator
+  zvm_switch_weekday
+  zvm_switch_month
+)
+
+# Display version information
+function zvm_version() {
+  echo -e "$ZVM_NAME $ZVM_VERSION"
+  echo -e "\e[4m$ZVM_REPOSITORY\e[0m"
+  echo -e "$ZVM_DESCRIPTION"
+}
+
+# The widget wrapper
+function zvm_widget_wrapper() {
+  local rawfunc=$1;
+  local func=$2;
+  local -i retval
+  $func "${@:3}"
+  return retval
+}
+
+# Define widget function
+function zvm_define_widget() {
+  local widget=$1
+  local func=$2 || $1
+  local result=($(zle -l -L "${widget}"))
+
+  # Check if existing the same name
+  if [[ ${#result[@]} == 4 ]]; then
+    local rawfunc=${result[4]}
+    local wrapper="zvm_${widget}-wrapper"
+    eval "$wrapper() { zvm_widget_wrapper $rawfunc $func \"\$@\" }"
+    func=$wrapper
+  fi
+
+  zle -N $widget $func
+}
+
+# Get the keys typed to invoke this widget, as a literal string
+function zvm_keys() {
+  local keys=${ZVM_KEYS:-$KEYS}
+
+  # Append the prefix of keys if it is visual or visual-line mode
+  case "${ZVM_MODE}" in
+    $ZVM_MODE_VISUAL)
+      if [[ "$keys" != v* ]]; then
+        keys="v${keys}"
+      fi
+      ;;
+    $ZVM_MODE_VISUAL_LINE)
+      if [[ "$keys" != V* ]]; then
+        keys="V${keys}"
+      fi
+      ;;
+  esac
+
+  # Escape the newline and space characters, otherwise, we can't
+  # get the output from subshell correctly.
+  keys=${keys//$'\n'/$ZVM_ESCAPE_NEWLINE}
+  keys=${keys// /$ZVM_ESCAPE_SPACE}
+
+  echo $keys
+}
+
+# Find the widget on a specified bindkey
+function zvm_find_bindkey_widget() {
+  local keymap=$1
+  local keys=$2
+  local prefix_mode=${3:-false}
+  retval=()
+
+  if $prefix_mode; then
+    local pos=0
+    local spos=3
+    local prefix_keys=
+
+    # Get the prefix keys
+    if [[ $prefix_keys ]]; then
+      prefix_keys=${prefix_keys:0:-1}
+
+      # If the last key is an escape key (e.g. \", \`, \\) we still
+      # need to remove the escape backslash `\`
+      if [[ ${prefix_keys: -1} == '\' ]]; then
+        prefix_keys=${prefix_keys:0:-1}
+      fi
+    fi
+
+    local result=$(bindkey -M ${keymap} -p "$prefix_keys")$'\n'
+
+    # Split string to array by newline
+    for ((i=$spos;i<$#result;i++)); do
+
+      # Save the last whitespace character of the line
+      # and continue continue handling while meeting `\n`
+      case "${result:$i:1}" in
+        ' ') spos=$i; i=$i+1; continue;;
+        [$'\n']);;
+        *) continue;;
+      esac
+
+      # Check if it has the same prefix keys and retrieve the widgets
+      if [[ "${result:$((pos+1)):$#keys}" == "$keys" ]]; then
+
+        # Get the binding keys
+        local k=${result:$((pos+1)):$((spos-pos-2))}
+
+        # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE)
+        k=${k// /$ZVM_ESCAPE_SPACE}
+        retval+=($k ${result:$((spos+1)):$((i-spos-1))})
+      fi
+
+      # Save as new position
+      pos=$i+1
+
+      # Skip 3 characters
+      # One key and quotes at least (i.e \n"_" )
+      i=$i+3
+    done
+  else
+    local result=$(bindkey -M ${keymap} "$keys")
+    if [[ "${result: -14}" == ' undefined-key' ]]; then
+      return
+    fi
+
+    # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE)
+    for ((i=$#result;i>=0;i--)); do
+
+      # Backward find the first whitespace character
+      [[ "${result:$i:1}" == ' ' ]] || continue
+
+      # Retrieve the keys and widget
+      local k=${result:1:$i-2}
+
+      # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE)
+      k=${k// /$ZVM_ESCAPE_SPACE}
+      retval+=($k ${result:$i+1})
+
+      break
+    done
+  fi
+}
+
+# Read keys for retrieving widget
+function zvm_readkeys() {
+  local keymap=$1
+  local key=${2:-$(zvm_keys)}
+  local keys=
+  local widget=
+  local result=
+  local pattern=
+  local timeout=
+
+  while :; do
+    # Keep reading key for escape character
+    if [[ "$key" == $'\e' ]]; then
+      while :; do
+        local k=
+        read -t $ZVM_ESCAPE_KEYTIMEOUT -k 1 k || break
+        key="${key}${k}"
+      done
+    fi
+
+    keys="${keys}${key}"
+
+    # Handle the pattern
+    if [[ -n "$key" ]]; then
+      # Transform the non-printed characters
+      local k=$(zvm_escape_non_printed_characters "${key}")
+
+      # Escape keys
+      # " -> \" It's a special character in bash syntax
+      # ` -> \` It's a special character in bash syntax
+      # <space> -> ` ` It's a special character in bash syntax
+      k=${k//\"/\\\"}
+      k=${k//\`/\\\`}
+      k=${k//$ZVM_ESCAPE_SPACE/ }
+
+      pattern="${pattern}${k}"
+    fi
+
+    # Find out widgets that match this key pattern
+    zvm_find_bindkey_widget $keymap "$pattern" true
+    result=(${retval[@]})
+
+    # Exit key input if there is only one widget matched
+    # or no more widget matched.
+    case ${#result[@]} in
+      2) key=; widget=${result[2]}; break;;
+      0) break;;
+    esac
+
+    # Evaluate the readkey timeout
+    # Special timeout for the escape sequence
+    if [[ "${keys}" == $'\e' ]]; then
+      timeout=$ZVM_ESCAPE_KEYTIMEOUT
+      # Check if there is any one custom escape sequence
+      for ((i=1; i<=${#result[@]}; i=i+2)); do
+        if [[ "${result[$i]}" =~ '^\^\[\[?[A-Z0-9]*~?\^\[' ]]; then
+          timeout=$ZVM_KEYTIMEOUT
+          break
+        fi
+      done
+    else
+      timeout=$ZVM_KEYTIMEOUT
+    fi
+
+    # Wait for reading next key, and we should save the widget
+    # as the final widget if it is full matching
+    key=
+    if [[ "${result[1]}" == "${pattern}" ]]; then
+      widget=${result[2]}
+      # Get current widget as final widget when reading key timeout
+      read -t $timeout -k 1 key || break
+    else
+      zvm_enter_oppend_mode
+      read -k 1 key
+    fi
+  done
+
+  # Exit operator pending mode
+  if $ZVM_OPPEND_MODE; then
+    zvm_exit_oppend_mode
+  fi
+
+  if [[ -z "$key" ]]; then
+    retval=(${keys} $widget)
+  else
+    retval=(${keys:0:-$#key} $widget $key)
+  fi
+}
+
+# Add key bindings
+function zvm_bindkey() {
+  local keymap=$1
+  local keys=$2
+  local widget=$3
+  local params=$4
+  local key=
+
+  # We should bind keys with an existing widget
+  [[ -z $widget ]] && return
+
+  # If lazy keybindings is enabled, we need to add to the lazy list
+  if [[ ${ZVM_LAZY_KEYBINDINGS_LIST+x} && ${keymap} != viins ]]; then
+    keys=${keys//\"/\\\"}
+    keys=${keys//\`/\\\`}
+    ZVM_LAZY_KEYBINDINGS_LIST+=(
+      "${keymap} \"${keys}\" ${widget} \"${params}\""
+    )
+    return
+  fi
+
+  # Hanle the keybinding of NEX readkey engine
+  if [[ $ZVM_READKEY_ENGINE == $ZVM_READKEY_ENGINE_NEX ]]; then
+    # Get the first key (especially check if ctrl characters)
+    if [[ $#keys -gt 1 && "${keys:0:1}" == '^' ]]; then
+      key=${keys:0:2}
+    else
+      key=${keys:0:1}
+    fi
+    bindkey -M $keymap "${key}" zvm_readkeys_handler
+  fi
+
+  # Wrap params to a new widget
+  if [[ -n $params ]]; then
+    local suffix=$(zvm_string_to_hex $params)
+    eval "$widget:$suffix() { $widget $params }"
+    widget="$widget:$suffix"
+    zvm_define_widget $widget
+  fi
+
+  # Bind keys with with a widget
+  bindkey -M $keymap "${keys}" $widget
+}
+
+# Convert string to hexadecimal
+function zvm_string_to_hex() {
+  local str=
+  for ((i=1;i<=$#1;i++)); do
+    str+=$(printf '%x' "'${1[$i]}")
+  done
+  echo "$str"
+}
+
+# Escape non-printed characters
+function zvm_escape_non_printed_characters() {
+  local str=
+  for ((i=0;i<$#1;i++)); do
+    local c=${1:$i:1}
+    if [[ "$c" < ' ' ]]; then
+      local ord=$(($(printf '%d' "'$c")+64))
+      c=$(printf \\$(printf '%03o' $ord))
+      str="${str}^${c}"
+    elif [[ "$c" == '\7f' ]]; then
+      str="${str}^?"
+    elif [[ "$c" == '\0' ]]; then
+      str="${str}^@"
+    else
+      str="${str}${c}"
+    fi
+  done
+
+  # Escape the newline and space characters, otherwise, we can't
+  # get the output from subshell correctly.
+  str=${str// /$ZVM_ESCAPE_SPACE}
+  str=${str//$'\n'/$ZVM_ESCAPE_NEWLINE}
+
+  echo -n $str
+}
+
+# Backward remove characters of an emacs region in the line
+function zvm_backward_kill_region() {
+  local bpos=$CURSOR-1 epos=$CURSOR
+
+  # Backward search the boundary of current region
+  for ((; bpos >= 0; bpos--)); do
+    # Break when cursor is at the beginning of line
+    [[ "${BUFFER:$bpos:1}" == $'\n' ]] && break
+
+    # Break when cursor is at the boundary of a word region
+    [[ "${BUFFER:$bpos:2}" =~ ^\ [^\ $'\n']$ ]] && break
+  done
+
+  bpos=$bpos+1
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+  CURSOR=$bpos
+}
+
+# Remove all characters between the cursor position and the
+# beginning of the line.
+function zvm_backward_kill_line() {
+  BUFFER=${BUFFER:$CURSOR:$#BUFFER}
+  CURSOR=0
+}
+
+# Remove all characters between the cursor position and the
+# end of the line.
+function zvm_forward_kill_line() {
+  BUFFER=${BUFFER:0:$CURSOR}
+}
+
+# Remove all characters of the line.
+function zvm_kill_line() {
+  local ret=($(zvm_calc_selection $ZVM_MODE_VISUAL_LINE))
+  local bpos=${ret[1]} epos=${ret[2]}
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}$'\n'
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+  CURSOR=$bpos
+}
+
+# Remove all characters of the whole line.
+function zvm_kill_whole_line() {
+  local ret=($(zvm_calc_selection $ZVM_MODE_VISUAL_LINE))
+  local bpos=$ret[1] epos=$ret[2] cpos=$ret[3]
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}$'\n'
+
+  # Adjust region range of deletion
+  if (( $epos < $#BUFFER )); then
+    epos=$epos+1
+  fi
+
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+  CURSOR=$cpos
+}
+
+# Exchange the point and mark
+function zvm_exchange_point_and_mark() {
+  cursor=$MARK
+  MARK=$CURSOR CURSOR=$cursor
+  zvm_highlight
+}
+
+# Open line below
+function zvm_open_line_below() {
+  local i=$CURSOR
+
+  # If there is a completion suffix, we should break at the
+  # postion of suffix begin, otherwise, it should break when
+  # forward finding out the first newline character.
+  for ((; i<$#BUFFER; i++)); do
+    if ((SUFFIX_ACTIVE == 1)) && ((i >= SUFFIX_BEGIN)); then
+      break
+    fi
+    if [[ "${BUFFER[$i]}" == $'\n' ]]; then
+      i=$((i-1))
+      break
+    fi
+  done
+
+  CURSOR=$i
+  LBUFFER+=$'\n'
+
+  zvm_reset_repeat_commands $ZVM_MODE_NORMAL o
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+}
+
+# Open line above
+function zvm_open_line_above() {
+  local i=$CURSOR
+
+  # Break when backward finding out the first newline character.
+  for ((; i>0; i--)); do
+    if [[ "${BUFFER[$i]}" == $'\n' ]]; then
+      break
+    fi
+  done
+
+  CURSOR=$i
+  LBUFFER+=$'\n'
+  CURSOR=$((CURSOR-1))
+
+  zvm_reset_repeat_commands $ZVM_MODE_NORMAL O
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+}
+
+# Replace characters one by one (Replacing mode)
+function zvm_vi_replace() {
+  if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then
+    local cursor=$CURSOR
+    local cache=()
+    local cmds=()
+    local key=
+
+    zvm_select_vi_mode $ZVM_MODE_REPLACE
+
+    while :; do
+      # Read a character for replacing
+      zvm_update_cursor
+
+      # Redisplay the command line, this is to be called from within
+      # a user-defined widget to allow changes to become visible
+      zle -R
+
+      read -k 1 key
+
+      # Escape key will break the replacing process, and enter key
+      # will repace with a newline character.
+      case $(zvm_escape_non_printed_characters $key) in
+        '^['|$ZVM_VI_OPPEND_ESCAPE_BINDKEY) break;;
+        '^M') key=$'\n';;
+      esac
+
+      # If the key is backspace, we should move backward the cursor
+      if [[ $key == '\7f' ]]; then
+        # Cursor position should not be less than zero
+        if ((cursor > 0)); then
+          cursor=$((cursor-1))
+        fi
+
+        # We should recover the character when cache size is not zero
+        if ((${#cache[@]} > 0)); then
+          key=${cache[-1]}
+
+          if [[ $key == '<I>' ]]; then
+            key=
+          fi
+
+          cache=(${cache[@]:0:-1})
+          BUFFER[$cursor+1]=$key
+
+          # Remove from commands
+          cmds=(${cmds[@]:0:-1})
+        fi
+      else
+        # If the key or the character at cursor is a newline character,
+        # or the cursor is at the end of buffer, we should insert the
+        # key instead of replacing with the key.
+        if [[ $key == $'\n' ||
+          $BUFFER[$cursor+1] == $'\n' ||
+          $BUFFER[$cursor+1] == ''
+        ]]; then
+          cache+=('<I>')
+          LBUFFER+=$key
+        else
+          cache+=(${BUFFER[$cursor+1]})
+          BUFFER[$cursor+1]=$key
+        fi
+
+        cursor=$((cursor+1))
+
+        # Push to commands
+        cmds+=($key)
+      fi
+
+      # Update next cursor position
+      CURSOR=$cursor
+
+      zle redisplay
+    done
+
+    # The cursor position should go back one character after
+    # exiting the replace mode
+    zle vi-backward-char
+
+    zvm_select_vi_mode $ZVM_MODE_NORMAL
+    zvm_reset_repeat_commands $ZVM_MODE R $cmds
+  elif [[ $ZVM_MODE == $ZVM_MODE_VISUAL ]]; then
+    zvm_enter_visual_mode V
+    zvm_vi_change
+  elif [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then
+    zvm_vi_change
+  fi
+}
+
+# Replace characters in one time
+function zvm_vi_replace_chars() {
+  local cmds=()
+  local key=
+
+  # Read a character for replacing
+  zvm_enter_oppend_mode
+
+  # Redisplay the command line, this is to be called from within
+  # a user-defined widget to allow changes to become visible
+  zle redisplay
+  zle -R
+
+  read -k 1 key
+
+  zvm_exit_oppend_mode
+
+  # Escape key will break the replacing process
+  case $(zvm_escape_non_printed_characters $key) in
+    $ZVM_VI_OPPEND_ESCAPE_BINDKEY)
+      zvm_exit_visual_mode
+      return
+  esac
+
+  if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then
+    cmds+=($key)
+    BUFFER[$CURSOR+1]=$key
+  else
+    local ret=($(zvm_calc_selection))
+    local bpos=${ret[1]} epos=${ret[2]}
+    for ((bpos=bpos+1; bpos<=epos; bpos++)); do
+      # Newline character is no need to be replaced
+      if [[ $BUFFER[$bpos] == $'\n' ]]; then
+        cmds+=($'\n')
+        continue
+      fi
+
+      cmds+=($key)
+      BUFFER[$bpos]=$key
+    done
+    zvm_exit_visual_mode
+  fi
+
+  # Reset the repeat commands
+  zvm_reset_repeat_commands $ZVM_MODE r $cmds
+}
+
+# Substitute characters of selection
+function zvm_vi_substitute() {
+  # Substitute one character in normal mode
+  if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then
+    BUFFER="${BUFFER:0:$CURSOR}${BUFFER:$((CURSOR+1))}"
+    zvm_reset_repeat_commands $ZVM_MODE c 0 1
+    zvm_select_vi_mode $ZVM_MODE_INSERT
+  else
+    zvm_vi_change
+  fi
+}
+
+# Substitute all characters of a line
+function zvm_vi_substitute_whole_line() {
+  zvm_select_vi_mode $ZVM_MODE_VISUAL_LINE false
+  zvm_vi_substitute
+}
+
+# Check if cursor is at an empty line
+function zvm_is_empty_line() {
+  local cursor=${1:-$CURSOR}
+  if [[ ${BUFFER:$cursor:1} == $'\n' &&
+    ${BUFFER:$((cursor-1)):1} == $'\n' ]]; then
+    return
+  fi
+  return 1
+}
+
+# Get the beginning and end position of selection
+function zvm_selection() {
+  local bpos= epos=
+  if (( MARK > CURSOR )) ; then
+    bpos=$CURSOR epos=$((MARK+1))
+  else
+    bpos=$MARK epos=$((CURSOR+1))
+  fi
+  echo $bpos $epos
+}
+
+# Calculate the region of selection
+function zvm_calc_selection() {
+  local ret=($(zvm_selection))
+  local bpos=${ret[1]} epos=${ret[2]} cpos=
+
+  # Save the current cursor position
+  cpos=$bpos
+
+  # Check if it is visual-line mode
+  if [[ "${1:-$ZVM_MODE}" == $ZVM_MODE_VISUAL_LINE ]]; then
+
+    # Extend the selection to whole line
+    for ((bpos=$bpos-1; $bpos>0; bpos--)); do
+      if [[ "${BUFFER:$bpos:1}" == $'\n' ]]; then
+        bpos=$((bpos+1))
+        break
+      fi
+    done
+    for ((epos=$epos-1; $epos<$#BUFFER; epos++)); do
+      if [[ "${BUFFER:$epos:1}" == $'\n' ]]; then
+        break
+      fi
+    done
+
+    # The begin position must not be less than zero
+    if (( bpos < 0 )); then
+      bpos=0
+    fi
+
+    ###########################################
+    # Calculate the new cursor position, here we consider that
+    # the selection will be delected.
+
+    # Calculate the indent of current cursor line
+    for ((cpos=$((CURSOR-1)); $cpos>=0; cpos--)); do
+      [[ "${BUFFER:$cpos:1}" == $'\n' ]] && break
+    done
+
+    local indent=$((CURSOR-cpos-1))
+
+    # If the selection includes the last line, the cursor
+    # will move up to above line. Otherwise the cursor will
+    # keep in the same line.
+
+    local hpos= # Line head position
+    local rpos= # Reference position
+
+    if (( $epos < $#BUFFER )); then
+      # Get the head position of next line
+      hpos=$((epos+1))
+      rpos=$bpos
+    else
+      # Get the head position of above line
+      for ((hpos=$((bpos-2)); $hpos>0; hpos--)); do
+        if [[ "${BUFFER:$hpos:1}" == $'\n' ]]; then
+          break
+        fi
+      done
+      if (( $hpos < -1 )); then
+        hpos=-1
+      fi
+      hpos=$((hpos+1))
+      rpos=$hpos
+    fi
+
+    # Calculate the cursor postion, the indent must be
+    # less than the line characters.
+    for ((cpos=$hpos; $cpos<$#BUFFER; cpos++)); do
+      if [[ "${BUFFER:$cpos:1}" == $'\n' ]]; then
+        break
+      fi
+      if (( $hpos + $indent <= $cpos )); then
+        break
+      fi
+    done
+
+    cpos=$((rpos+cpos-hpos))
+  fi
+
+  echo $bpos $epos $cpos
+}
+
+# Yank characters of the marked region
+function zvm_yank() {
+  local ret=($(zvm_calc_selection $1))
+  local bpos=$ret[1] epos=$ret[2] cpos=$ret[3]
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}
+  if [[ ${1:-$ZVM_MODE} == $ZVM_MODE_VISUAL_LINE ]]; then
+    CUTBUFFER=${CUTBUFFER}$'\n'
+  fi
+  CURSOR=$bpos MARK=$epos
+}
+
+# Up case of the visual selection
+function zvm_vi_up_case() {
+  local ret=($(zvm_selection))
+  local bpos=${ret[1]} epos=${ret[2]}
+  local content=${BUFFER:$bpos:$((epos-bpos))}
+  BUFFER="${BUFFER:0:$bpos}${(U)content}${BUFFER:$epos}"
+  zvm_exit_visual_mode
+}
+
+# Down case of the visual selection
+function zvm_vi_down_case() {
+  local ret=($(zvm_selection))
+  local bpos=${ret[1]} epos=${ret[2]}
+  local content=${BUFFER:$bpos:$((epos-bpos))}
+  BUFFER="${BUFFER:0:$bpos}${(L)content}${BUFFER:$epos}"
+  zvm_exit_visual_mode
+}
+
+# Opposite case of the visual selection
+function zvm_vi_opp_case() {
+  local ret=($(zvm_selection))
+  local bpos=${ret[1]} epos=${ret[2]}
+  local content=${BUFFER:$bpos:$((epos-bpos))}
+  for ((i=1; i<=$#content; i++)); do
+    if [[ ${content[i]} =~ [A-Z] ]]; then
+      content[i]=${(L)content[i]}
+    elif [[ ${content[i]} =~ [a-z] ]]; then
+      content[i]=${(U)content[i]}
+    fi
+  done
+  BUFFER="${BUFFER:0:$bpos}${content}${BUFFER:$epos}"
+  zvm_exit_visual_mode
+}
+
+# Yank characters of the visual selection
+function zvm_vi_yank() {
+  zvm_yank
+  zvm_exit_visual_mode
+}
+
+# Put cutbuffer after the cursor
+function zvm_vi_put_after() {
+  local head= foot=
+  local content=${CUTBUFFER}
+  local offset=1
+
+  if [[ ${content: -1} == $'\n' ]]; then
+    local pos=${CURSOR}
+
+    # Find the end of current line
+    for ((; $pos<$#BUFFER; pos++)); do
+      if [[ ${BUFFER:$pos:1} == $'\n' ]]; then
+        pos=$pos+1
+        break
+      fi
+    done
+
+    # Special handling if cursor at an empty line
+    if zvm_is_empty_line; then
+      head=${BUFFER:0:$pos}
+      foot=${BUFFER:$pos}
+    else
+      head=${BUFFER:0:$pos}
+      foot=${BUFFER:$pos}
+      if [[ $pos == $#BUFFER ]]; then
+        content=$'\n'${content:0:-1}
+        pos=$pos+1
+      fi
+    fi
+
+    offset=0
+    BUFFER="${head}${content}${foot}"
+    CURSOR=$pos
+  else
+    # Special handling if cursor at an empty line
+    if zvm_is_empty_line; then
+      head="${BUFFER:0:$((CURSOR-1))}"
+      foot="${BUFFER:$CURSOR}"
+    else
+      head="${BUFFER:0:$CURSOR}"
+      foot="${BUFFER:$((CURSOR+1))}"
+    fi
+
+    BUFFER="${head}${BUFFER:$CURSOR:1}${content}${foot}"
+    CURSOR=$CURSOR+$#content
+  fi
+
+  # Reresh display and highlight buffer
+  zvm_highlight clear
+  zvm_highlight custom $(($#head+$offset)) $(($#head+$#content+$offset))
+}
+
+# Put cutbuffer before the cursor
+function zvm_vi_put_before() {
+  local head= foot=
+  local content=${CUTBUFFER}
+
+  if [[ ${content: -1} == $'\n' ]]; then
+    local pos=$CURSOR
+
+    # Find the beginning of current line
+    for ((; $pos>0; pos--)); do
+      if [[ "${BUFFER:$pos:1}" == $'\n' ]]; then
+        pos=$pos+1
+        break
+      fi
+    done
+
+    # Check if it is an empty line
+    if zvm_is_empty_line; then
+      head=${BUFFER:0:$((pos-1))}
+      foot=$'\n'${BUFFER:$pos}
+      pos=$((pos-1))
+    else
+      head=${BUFFER:0:$pos}
+      foot=${BUFFER:$pos}
+    fi
+
+    BUFFER="${head}${content}${foot}"
+    CURSOR=$pos
+  else
+    head="${BUFFER:0:$CURSOR}"
+    foot="${BUFFER:$((CURSOR+1))}"
+    BUFFER="${head}${content}${BUFFER:$CURSOR:1}${foot}"
+    CURSOR=$CURSOR+$#content
+    CURSOR=$((CURSOR-1))
+  fi
+
+  # Reresh display and highlight buffer
+  zvm_highlight clear
+  zvm_highlight custom $#head $(($#head+$#content))
+}
+
+# Delete characters of the visual selection
+function zvm_vi_delete() {
+  local ret=($(zvm_calc_selection))
+  local bpos=$ret[1] epos=$ret[2] cpos=$ret[3]
+
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}
+
+  # Check if it is visual line mode
+  if [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then
+    if (( $epos < $#BUFFER )); then
+      epos=$epos+1
+    elif (( $bpos > 0 )); then
+      bpos=$bpos-1
+    fi
+    CUTBUFFER=${CUTBUFFER}$'\n'
+  fi
+
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+  CURSOR=$cpos
+
+  zvm_exit_visual_mode ${1:-true}
+}
+
+# Yank characters of the visual selection
+function zvm_vi_change() {
+  local ret=($(zvm_calc_selection))
+  local bpos=$ret[1] epos=$ret[2]
+
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}
+
+  # Check if it is visual line mode
+  if [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then
+    CUTBUFFER=${CUTBUFFER}$'\n'
+  fi
+
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+  CURSOR=$bpos
+
+  # Return when it's repeating mode
+  $ZVM_REPEAT_MODE && return
+
+  # Reset the repeat commands
+  if [[ $ZVM_MODE != $ZVM_MODE_NORMAL ]]; then
+    local npos=0 ncount=0 ccount=0
+    # Count the amount of newline character and the amount of
+    # characters after the last newline character.
+    while :; do
+      # Forward find the last newline character's position
+      npos=$(zvm_substr_pos $CUTBUFFER $'\n' $npos)
+      if [[ $npos == -1 ]]; then
+        if (($ncount == 0)); then
+          ccount=$#CUTBUFFER
+        fi
+        break
+      fi
+      npos=$((npos+1))
+      ncount=$(($ncount + 1))
+      ccount=$(($#CUTBUFFER - $npos))
+    done
+    zvm_reset_repeat_commands $ZVM_MODE c $ncount $ccount
+  fi
+
+  zvm_exit_visual_mode false
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+}
+
+# Change characters from cursor to the end of current line
+function zvm_vi_change_eol() {
+  local bpos=$CURSOR epos=$CURSOR
+
+  # Find the end of current line
+  for ((; $epos<$#BUFFER; epos++)); do
+    if [[ "${BUFFER:$epos:1}" == $'\n' ]]; then
+      break
+    fi
+  done
+
+  CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}
+  BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+
+  zvm_reset_repeat_commands $ZVM_MODE c 0 $#CUTBUFFER
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+}
+
+# Default handler for unhandled key events
+function zvm_default_handler() {
+  local keys=$(zvm_keys)
+  local extra_keys=$1
+
+  # Exit vi mode if keys is the escape keys
+  case $(zvm_escape_non_printed_characters "$keys") in
+    '^['|$ZVM_VI_INSERT_ESCAPE_BINDKEY)
+      zvm_exit_insert_mode
+      ZVM_KEYS=${extra_keys}
+      return
+      ;;
+    [vV]'^['|[vV]$ZVM_VI_VISUAL_ESCAPE_BINDKEY)
+      zvm_exit_visual_mode
+      ZVM_KEYS=${extra_keys}
+      return
+      ;;
+  esac
+
+  case "$KEYMAP" in
+    vicmd)
+      case "$keys" in
+        [vV]c) zvm_vi_change;;
+        [vV]d) zvm_vi_delete;;
+        [vV]y) zvm_vi_yank;;
+        [vV]S) zvm_change_surround S;;
+        [cdyvV]*) zvm_range_handler "${keys}${extra_keys}";;
+        *)
+          for ((i=0;i<$#keys;i++)) do
+            zvm_navigation_handler ${keys:$i:1}
+            zvm_highlight
+          done
+          ;;
+      esac
+      ;;
+    viins|main)
+      if [[ "${keys:0:1}" =~ [a-zA-Z0-9\ ] ]]; then
+        zvm_self_insert "${keys:0:1}"
+        zle redisplay
+        ZVM_KEYS="${keys:1}${extra_keys}"
+        return
+      elif [[ "${keys:0:1}" == $'\e' ]]; then
+        zvm_exit_insert_mode
+        ZVM_KEYS="${keys:1}${extra_keys}"
+        return
+      fi
+      ;;
+    visual)
+      ;;
+  esac
+
+  ZVM_KEYS=
+}
+
+# Read keys for retrieving and executing a widget
+function zvm_readkeys_handler() {
+  local keymap=${1}
+  local keys=${2:-$KEYS}
+  local key=
+  local widget=
+
+  # Get the keymap if keymap is empty
+  if [[ -z $keymap ]]; then
+    case "$ZVM_MODE" in
+      $ZVM_MODE_INSERT) keymap=viins;;
+      $ZVM_MODE_NORMAL) keymap=vicmd;;
+      $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) keymap=visual;;
+    esac
+  fi
+
+  # Read keys and retrieve the widget
+  zvm_readkeys $keymap $keys
+  keys=${retval[1]}
+  widget=${retval[2]}
+  key=${retval[3]}
+
+  # Escape space in keys
+  keys=${keys//$ZVM_ESCAPE_SPACE/ }
+  key=${key//$ZVM_ESCAPE_SPACE/ }
+
+  ZVM_KEYS="${keys}"
+
+  # If the widget is current handler, we should call the default handler
+  if [[ "${widget}" == "${funcstack[1]}" ]]; then
+    widget=
+  fi
+
+  # If the widget isn't matched, we should call the default handler
+  if [[ -z ${widget} ]]; then
+    zle zvm_default_handler "$key"
+
+    # Push back to the key input stack
+    if [[ -n "$ZVM_KEYS" ]]; then
+      zle -U "$ZVM_KEYS"
+    fi
+  else
+    zle $widget
+  fi
+
+  ZVM_KEYS=
+}
+
+# Find and move cursor to next character
+function zvm_find_and_move_cursor() {
+  local char=$1
+  local count=${2:-1}
+  local forward=${3:-true}
+  local skip=${4:-false}
+  local cursor=$CURSOR
+
+  [[ -z $char ]] && return 1
+
+  # Find the specific character
+  while :; do
+    if $forward; then
+      cursor=$((cursor+1))
+      ((cursor > $#BUFFER)) && break
+    else
+      cursor=$((cursor-1))
+      ((cursor < 0)) && break
+    fi
+    if [[ ${BUFFER[$cursor+1]} == $char ]]; then
+      count=$((count-1))
+    fi
+    ((count == 0)) && break
+  done
+
+  [[ $count > 0 ]] && return 1
+
+  # Skip the character
+  if $skip; then
+    if $forward; then
+      cursor=$((cursor-1))
+    else
+      cursor=$((cursor+1))
+    fi
+  fi
+
+  CURSOR=$cursor
+}
+
+# Handle the navigation action
+function zvm_navigation_handler() {
+  # Return if no keys provided
+  [[ -z $1 ]] && return 1
+
+  local keys=$1
+  local count=
+  local cmd=
+
+  # Retrieve the calling command
+  if [[ $keys =~ '^([1-9][0-9]*)?([fFtT].?)$' ]]; then
+    count=${match[1]:-1}
+
+    # The length of keys must be 2
+    if (( ${#match[2]} < 2)); then
+      zvm_enter_oppend_mode
+
+      read -k 1 cmd
+      keys+=$cmd
+
+      case "$(zvm_escape_non_printed_characters ${keys[-1]})" in
+        $ZVM_VI_OPPEND_ESCAPE_BINDKEY) return 1;;
+      esac
+
+      zvm_exit_oppend_mode
+    fi
+
+    local forward=true
+    local skip=false
+
+    [[ ${keys[-2]} =~ '[FT]' ]] && forward=false
+    [[ ${keys[-2]} =~ '[tT]' ]] && skip=true
+
+    cmd=(zvm_find_and_move_cursor ${keys[-1]} $count $forward $skip)
+    count=1
+  else
+    count=${keys:0:-1}
+    case ${keys: -1} in
+      '^') cmd=(zle vi-first-non-blank);;
+      '$') cmd=(zle vi-end-of-line);;
+      ' ') cmd=(zle vi-forward-char);;
+      '0') cmd=(zle vi-digit-or-beginning-of-line);;
+      'h') cmd=(zle vi-backward-char);;
+      'j') cmd=(zle down-line-or-history);;
+      'k') cmd=(zle up-line-or-history);;
+      'l') cmd=(zle vi-forward-char);;
+      'w') cmd=(zle vi-forward-word);;
+      'W') cmd=(zle vi-forward-blank-word);;
+      'e') cmd=(zle vi-forward-word-end);;
+      'E') cmd=(zle vi-forward-blank-word-end);;
+      'b') cmd=(zle vi-backward-word);;
+      'B') cmd=(zle vi-backward-blank-word);;
+    esac
+  fi
+
+  # Check widget if the widget is empty
+  if [[ -z $cmd ]]; then
+    return 0
+  fi
+
+  # Check if keys includes the count
+  if [[ ! $count =~ ^[0-9]+$ ]]; then
+    count=1
+  fi
+
+  # Call the widget, we can not use variable `i`, since
+  # some widgets will affect the variable `i`, and it
+  # will cause an infinite loop.
+  local init_cursor=$CURSOR
+  local last_cursor=$CURSOR
+  local exit_code=0
+  for ((c=0; c<count; c++)); do
+    $cmd
+
+    exit_code=$?
+
+    if [[ ${cmd[1]} == 'zle' ]]; then
+      exit_code=0
+    elif [[ $exit_code != 0 ]]; then
+      break
+    fi
+
+    # If the cursor position is no change, we can break
+    # the loop and no need to loop so many times, thus
+    # when the count is quite large, it will not be
+    # stuck for a long time.
+    [[ $last_cursor == $CURSOR ]] && break
+
+    last_cursor=$CURSOR
+  done
+
+  if [[ $exit_code == 0 ]]; then
+    retval=$keys
+  else
+    CURSOR=$init_cursor
+  fi
+
+  return $exit_code
+}
+
+# Handle a range of characters
+function zvm_range_handler() {
+  local keys=$1
+  local cursor=$CURSOR
+  local key=
+  local mode=
+  local cmds=($ZVM_MODE)
+  local count=1
+  local exit_code=0
+
+  # Enter operator pending mode
+  zvm_enter_oppend_mode false
+
+  # If the keys is less than 2 keys, we should read more
+  # keys (e.g. d, c, y, etc.)
+  while (( ${#keys} < 2 )); do
+    zvm_update_cursor
+    read -k 1 key
+    keys="${keys}${key}"
+  done
+
+  # If the keys ends in numbers, we should read more
+  # keys (e.g. d2, c3, y10, etc.)
+  while [[ ${keys: 1} =~ ^[1-9][0-9]*$ ]]; do
+    zvm_update_cursor
+    read -k 1 key
+    keys="${keys}${key}"
+  done
+
+  # If the last character is `i` or `a`, we should, we
+  # should read one more key
+  if [[ ${keys: -1} =~ [ia] ]]; then
+    zvm_update_cursor
+    read -k 1 key
+    keys="${keys}${key}"
+  fi
+
+  # Enter visual mode or visual line mode
+  if [[ $ZVM_MODE != $ZVM_MODE_VISUAL &&
+    $ZVM_MODE != $ZVM_MODE_VISUAL_LINE ]]; then
+    case "${keys}" in
+      [cdy][jk]) mode=$ZVM_MODE_VISUAL_LINE;;
+      cc|dd|yy) mode=$ZVM_MODE_VISUAL_LINE;;
+      *) mode=$ZVM_MODE_VISUAL;;
+    esac
+    # Select the mode
+    if [[ ! -z $mode ]]; then
+      zvm_select_vi_mode $mode false
+    fi
+  fi
+
+  #######################################
+  # Selection Cases:
+  #
+  # 1. SAMPLE: `word1  word2  w`, CURSOR: at `w` of `word1`
+  #
+  #  c[we] -> `word1`
+  #  c2[we] -> `word1  word2`
+  #  ve -> `word1`
+  #  v2e -> `word1  word2`
+  #  vw -> `word1  w`
+  #  v2w -> `word1  word2  w`
+  #  [dy]e -> `word1`
+  #  [dy]2e -> `word1  word2`
+  #  [dy]w -> `word1  `
+  #  [dy]2w -> `word1  word2  `
+  #  [cdyv]iw -> `word1`
+  #  [cdyv]aw -> `word1  `
+  #  [cdyv]2iw -> `word1  `
+  #  [cdyv]2aw -> `word1  word2  `
+  #
+  # 2. SAMPLE: `a  bb  c  dd`, CURSOR: at `a`
+  #
+  #  cw -> `a`
+  #  c2w -> `a bb`
+  #  ce -> `a bb`
+  #  c2e -> `a bb c`
+  #
+  # 3. SAMPLE: ` .foo.  bar.  baz.`, CURSOR: at `f`
+  #
+  #  c[WE] -> `foo.`
+  #  c2[WE] -> `foo.  bar.`
+  #  vE -> `foo.`
+  #  v2E -> `foo.  bar.`
+  #  vW -> `foo.  b`
+  #  v2W -> `foo.  bar.  b`
+  #  d2W -> `foo.  bar.  b`
+  #  [dy]E -> `foo.`
+  #  [dy]2E -> `foo.  bar.`
+  #  [dy]W -> `foo.  `
+  #  [dy]2W -> `foo.  bar.  `
+  #  [cdyv]iW -> `.foo.`
+  #  [cdyv]aW -> `.foo.  `
+  #  [cdyv]2iW -> `.foo.  `
+  #  [cdyv]2aW -> `.foo.  bar.  `
+  #
+  # 4. SAMPLE: ` .foo.bar.baz.`, CURSOR: at `r`
+  #
+  #  [cdy]b -> `ba`
+  #  [cdy]B -> `.foo.ba`
+  #  vb -> `bar`
+  #  vB -> `.foo.bar`
+  #  vFf -> `foo.bar`
+  #  vTf -> `oo.bar`
+  #  [cdyv]fz -> `r.baz`
+  #  [cdy]Ff -> `foo.ba`
+  #  [cdyv]tz -> `r.ba`
+  #  [cdy]Tf -> `oo.ba`
+  #
+
+  # Pre navigation handling
+  local navkey=
+
+  if [[ $keys =~ '^c([1-9][0-9]*)?[ia][wW]$' ]]; then
+    count=${match[1]:-1}
+    navkey=${keys: -2}
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?[ia][eE]$' ]]; then
+    navkey=
+  elif [[ $keys =~ '^c([1-9][0-9]*)?w$' ]]; then
+    zle vi-backward-char
+    count=${match[1]:-1}
+    navkey='e'
+  elif [[ $keys =~ '^c([1-9][0-9]*)?W$' ]]; then
+    zle vi-backward-blank-char
+    count=${match[1]:-1}
+    navkey='E'
+  elif [[ $keys =~ '^c([1-9][0-9]*)?e$' ]]; then
+    count=${match[1]:-1}
+    navkey='e'
+  elif [[ $keys =~ '^c([1-9][0-9]*)?E$' ]]; then
+    count=${match[1]:-1}
+    navkey='E'
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?[bB]$' ]]; then
+    MARK=$((MARK-1))
+    count=${match[1]:-1}
+    navkey=${keys: -1}
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?([FT].?)$' ]]; then
+    MARK=$((MARK-1))
+    count=${match[1]:-1}
+    navkey=${match[2]}
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?j$' ]]; then
+    # Exit if there is no line below
+    count=${match[1]:-1}
+    for ((i=$((CURSOR+1)); i<=$#BUFFER; i++)); do
+      [[ ${BUFFER[$i]} == $'\n' ]] && navkey='j'
+    done
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?k$' ]]; then
+    # Exit if there is no line above
+    count=${match[1]:-1}
+    for ((i=$((CURSOR+1)); i>0; i--)); do
+      [[ ${BUFFER[$i]} == $'\n' ]] && navkey='k'
+    done
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?h$' ]]; then
+    MARK=$((MARK-1))
+    count=${match[1]:-1}
+    navkey='h'
+
+    # Exit if the cursor is at the beginning of a line
+    if ((MARK < 0)); then
+      navkey=
+    elif [[ ${BUFFER[$MARK+1]} == $'\n' ]]; then
+      navkey=
+    fi
+  elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?l$' ]]; then
+    count=${match[1]:-1}
+    count=$((count-1))
+    navkey=${count}l
+  elif [[ $keys =~ '^.([1-9][0-9]*)?([^0-9]+)$' ]]; then
+    count=${match[1]:-1}
+    navkey=${match[2]}
+  else
+    navkey=
+  fi
+
+  # Handle navigation
+  case $navkey in
+    '') exit_code=1;;
+    *[ia][wW])
+      local widget=
+      local mark=
+
+      # At least 1 time
+      if [[ -z $count ]]; then
+        count=1
+      fi
+
+      # Retrieve the widget
+      case ${navkey: -2} in
+        iw) widget=select-in-word;;
+        aw) widget=select-a-word;;
+        iW) widget=select-in-blank-word;;
+        aW) widget=select-a-blank-word;;
+      esac
+
+      # Execute the widget for `count` times, and
+      # save the `mark` position of the first time
+      for ((c=0; c<count; c++)); do
+        zle $widget
+        if (( c == 0 )); then
+          mark=$MARK
+        fi
+        CURSOR=$((CURSOR+1))
+        if (($CURSOR >= $#BUFFER)); then
+          break
+        fi
+      done
+
+      MARK=$mark
+      CURSOR=$((CURSOR-1))
+      ;;
+    *)
+      local retval=
+
+      # Prevent some actions(e.g. w, e) from affecting the auto
+      # suggestion suffix
+      BUFFER+=$'\0'
+
+      if zvm_navigation_handler "${count}${navkey}"; then
+        keys="${keys[1]}${retval}"
+      else
+        exit_code=1
+      fi
+
+      BUFFER[-1]=''
+      ;;
+  esac
+
+  # Check if there is no range selected
+  if [[ $exit_code != 0 ]]; then
+    zvm_exit_visual_mode
+    return
+  fi
+
+  # Post navigation handling
+  if [[ $keys =~ '^[cdy]([1-9][0-9]*)?[ia][wW]$' ]]; then
+    cursor=$MARK
+  elif [[ $keys =~ '[dy]([1-9][0-9]*)?[wW]' ]]; then
+    CURSOR=$((CURSOR-1))
+    # If the CURSOR is at the newline character, we should
+    # move backward a character
+    if [[ "${BUFFER:$CURSOR:1}" == $'\n' ]]; then
+      CURSOR=$((CURSOR-1))
+    fi
+  else
+    cursor=$CURSOR
+  fi
+
+  # Handle operation
+  case "${keys}" in
+    c*) zvm_vi_change; cursor=;;
+    d*) zvm_vi_delete; cursor=;;
+    y*) zvm_vi_yank;;
+    [vV]*) cursor=;;
+  esac
+
+  # Reset the repeat commands when it's changing or deleting
+  if $ZVM_REPEAT_MODE; then
+    zvm_exit_visual_mode false
+  elif [[ $keys =~ '^[cd].*' ]]; then
+    cmds+=($keys)
+    zvm_reset_repeat_commands $cmds
+  fi
+
+  # Change the cursor position if the cursor is not null
+  if [[ ! -z $cursor ]]; then
+    CURSOR=$cursor
+  fi
+}
+
+# Edit command line in EDITOR
+function zvm_vi_edit_command_line() {
+  # Create a temporary file and save the BUFFER to it
+  local tmp_file=$(mktemp ${ZVM_TMPDIR}/zshXXXXXX)
+
+  # Some users may config the noclobber option to prevent from
+  # overwriting existing files with the > operator, we should
+  # use >! operator to ignore the noclobber.
+  echo "$BUFFER" >! "$tmp_file"
+
+  # Edit the file with the specific editor, in case of
+  # the warning about input not from a terminal (e.g.
+  # vim), we should tell the editor input is from the
+  # terminal and not from standard input.
+  "${(@Q)${(z)${ZVM_VI_EDITOR}}}" $tmp_file </dev/tty
+
+  # Reload the content to the BUFFER from the temporary
+  # file after editing, and delete the temporary file.
+  BUFFER=$(cat $tmp_file)
+  rm "$tmp_file"
+
+  # Exit the visual mode
+  case $ZVM_MODE in
+    $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE)
+      zvm_exit_visual_mode
+      ;;
+  esac
+}
+
+# Get the substr position in a string
+function zvm_substr_pos() {
+  local pos=-1
+  local len=${#1}
+  local slen=${#2}
+  local i=${3:-0}
+  local forward=${4:-true}
+  local init=${i:-$($forward && echo "$i" || echo "i=$len-1")}
+  local condition=$($forward && echo "i<$len" || echo "i>=0")
+  local step=$($forward && echo 'i++' || echo 'i--')
+  for (($init;$condition;$step)); do
+    if [[ ${1:$i:$slen} == "$2" ]]; then
+      pos=$i
+      break
+    fi
+  done
+  echo $pos
+}
+
+# Parse surround from keys
+function zvm_parse_surround_keys() {
+  local keys=${1:-${$(zvm_keys)//$ZVM_ESCAPE_SPACE/ }}
+  local action=
+  local surround=
+  case "${keys}" in
+    vS*) action=S; surround=${keys:2};;
+    vsa*) action=a; surround=${keys:3};;
+    vys*) action=y; surround=${keys:3};;
+    s[dr]*) action=${keys:1:1}; surround=${keys:2};;
+    [acd]s*) action=${keys:0:1}; surround=${keys:2};;
+    [cdvy][ia]*) action=${keys:0:2}; surround=${keys:2};;
+  esac
+  echo $action ${surround// /$ZVM_ESCAPE_SPACE}
+}
+
+# Move around code structure (e.g. (..), {..})
+function zvm_move_around_surround() {
+  local slen=
+  local bpos=-1
+  local epos=-1
+  for ((i=$CURSOR;i>=0;i--)); do
+    # Check if it's one of the surrounds
+    for s in {\',\",\`,\(,\[,\{,\<}; do
+      slen=${#s}
+      if [[ ${BUFFER:$i:$slen} == "$s" ]]; then
+        bpos=$i
+        break
+      fi
+    done
+    if (($bpos == -1)); then
+      continue
+    fi
+    # Search the nearest surround
+    local ret=($(zvm_search_surround "$s"))
+    if [[ -z ${ret[@]} ]]; then
+      continue
+    fi
+    bpos=${ret[1]}
+    epos=${ret[2]}
+    # Move between the openning and close surrounds
+    if (( $CURSOR > $((bpos-1)) )) && (( $CURSOR < $((bpos+slen)) )); then
+      CURSOR=$epos
+    else
+      CURSOR=$bpos
+    fi
+    break
+  done
+}
+
+# Match the surround pair from the part
+function zvm_match_surround() {
+  local bchar=${1// /$ZVM_ESCAPE_SPACE}
+  local echar=$bchar
+  case $bchar in
+    '(') echar=')';;
+    '[') echar=']';;
+    '{') echar='}';;
+    '<') echar='>';;
+    ')') bchar='(';echar=')';;
+    ']') bchar='[';echar=']';;
+    '}') bchar='{';echar='}';;
+    '>') bchar='<';echar='>';;
+  esac
+  echo $bchar $echar
+}
+
+# Search surround from the string
+function zvm_search_surround() {
+  local ret=($(zvm_match_surround "$1"))
+  local bchar=${${ret[1]//$ZVM_ESCAPE_SPACE/ }:- }
+  local echar=${${ret[2]//$ZVM_ESCAPE_SPACE/ }:- }
+  local bpos=$(zvm_substr_pos $BUFFER $bchar $CURSOR false)
+  local epos=$(zvm_substr_pos $BUFFER $echar $CURSOR true)
+  if [[ $bpos == $epos ]]; then
+      epos=$(zvm_substr_pos $BUFFER $echar $((CURSOR+1)) true)
+      if [[ $epos == -1 ]]; then
+        epos=$(zvm_substr_pos $BUFFER $echar $((CURSOR-1)) false)
+        if [[ $epos != -1 ]]; then
+          local tmp=$epos; epos=$bpos; bpos=$tmp
+        fi
+      fi
+  fi
+  if [[ $bpos == -1 ]] || [[ $epos == -1 ]]; then
+    return
+  fi
+  echo $bpos $epos $bchar $echar
+}
+
+# Select surround and highlight it in visual mode
+function zvm_select_surround() {
+  local ret=($(zvm_parse_surround_keys))
+  local action=${ret[1]}
+  local surround=${ret[2]//$ZVM_ESCAPE_SPACE/ }
+  ret=($(zvm_search_surround ${surround}))
+  if [[ ${#ret[@]} == 0 ]]; then
+    zvm_exit_visual_mode
+    return
+  fi
+  local bpos=${ret[1]}
+  local epos=${ret[2]}
+  if [[ ${action:1:1} == 'i' ]]; then
+    ((bpos++))
+  else
+    ((epos++))
+  fi
+  MARK=$bpos; CURSOR=$epos-1
+
+  # refresh current mode for prompt redraw
+  zle reset-prompt
+}
+
+# Change surround in vicmd or visual mode
+function zvm_change_surround() {
+  local ret=($(zvm_parse_surround_keys))
+  local action=${1:-${ret[1]}}
+  local surround=${2:-${ret[2]//$ZVM_ESCAPE_SPACE/ }}
+  local bpos=${3} epos=${4}
+  local is_appending=false
+  case $action in
+    S|y|a) is_appending=true;;
+  esac
+  if $is_appending; then
+    if [[ -z $bpos && -z $epos ]]; then
+      ret=($(zvm_selection))
+      bpos=${ret[1]} epos=${ret[2]}
+    fi
+  else
+    ret=($(zvm_search_surround "$surround"))
+    (( ${#ret[@]} )) || return
+    bpos=${ret[1]} epos=${ret[2]}
+    zvm_highlight custom $bpos $(($bpos+1))
+    zvm_highlight custom $epos $(($epos+1))
+  fi
+  local key=
+  case $action in
+    c|r)
+      zvm_enter_oppend_mode
+      read -k 1 key
+      zvm_exit_oppend_mode
+      ;;
+    S|y|a)
+      if [[ -z $surround ]]; then
+        zvm_enter_oppend_mode
+        read -k 1 key
+        zvm_exit_oppend_mode
+      else
+        key=$surround
+      fi
+      if [[ $ZVM_MODE == $ZVM_MODE_VISUAL ]]; then
+        zle visual-mode
+      fi
+      ;;
+  esac
+
+  # Check if it is ESCAPE key (<ESC> or ZVM_VI_ESCAPE_BINDKEY)
+  case "$key" in
+    $'\e'|"${ZVM_VI_ESCAPE_BINDKEY//\^\[/$'\e'}")
+      zvm_highlight clear
+      return
+  esac
+
+  # Start changing surround
+  ret=($(zvm_match_surround "$key"))
+  local bchar=${${ret[1]//$ZVM_ESCAPE_SPACE/ }:-$key}
+  local echar=${${ret[2]//$ZVM_ESCAPE_SPACE/ }:-$key}
+  local value=$($is_appending && echo 0 || echo 1 )
+  local head=${BUFFER:0:$bpos}
+  local body=${BUFFER:$((bpos+value)):$((epos-(bpos+value)))}
+  local foot=${BUFFER:$((epos+value))}
+  BUFFER="${head}${bchar}${body}${echar}${foot}"
+
+  # Clear highliht
+  zvm_highlight clear
+
+  case $action in
+    S|y|a) zvm_select_vi_mode $ZVM_MODE_NORMAL;;
+  esac
+}
+
+# Change surround text object
+function zvm_change_surround_text_object() {
+  local ret=($(zvm_parse_surround_keys))
+  local action=${ret[1]}
+  local surround=${ret[2]//$ZVM_ESCAPE_SPACE/ }
+  ret=($(zvm_search_surround "${surround}"))
+  if [[ ${#ret[@]} == 0 ]]; then
+    zvm_select_vi_mode $ZVM_MODE_NORMAL
+    return
+  fi
+  local bpos=${ret[1]}
+  local epos=${ret[2]}
+  if [[ ${action:1:1} == 'i' ]]; then
+    ((bpos++))
+  else
+    ((epos++))
+  fi
+  CUTBUFFER=${BUFFER:$bpos:$(($epos-$bpos))}
+  case ${action:0:1} in
+    c)
+      BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+      CURSOR=$bpos
+      zvm_select_vi_mode $ZVM_MODE_INSERT
+      ;;
+    d)
+      BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}"
+      CURSOR=$bpos
+      ;;
+  esac
+}
+
+# Repeat last change
+function zvm_repeat_change() {
+  ZVM_REPEAT_MODE=true
+
+  local cmd=${ZVM_REPEAT_COMMANDS[2]}
+
+  # Handle repeat command
+  case $cmd in
+    [aioAIO]) zvm_repeat_insert;;
+    c) zvm_repeat_vi_change;;
+    [cd]*) zvm_repeat_range_change;;
+    R) zvm_repeat_replace;;
+    r) zvm_repeat_replace_chars;;
+    *) zle vi-repeat-change;;
+  esac
+
+  zle redisplay
+
+  ZVM_REPEAT_MODE=false
+}
+
+# Repeat inserting characters
+function zvm_repeat_insert() {
+  local cmd=${ZVM_REPEAT_COMMANDS[2]}
+  local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]})
+
+  # Pre-handle the command
+  case $cmd in
+    a) CURSOR+=1;;
+    o)
+      zle vi-backward-char
+      zle vi-end-of-line
+      LBUFFER+=$'\n'
+      ;;
+    A)
+      zle vi-end-of-line
+      CURSOR=$((CURSOR+1))
+      ;;
+    I) zle vi-first-non-blank;;
+    O)
+      zle vi-digit-or-beginning-of-line
+      LBUFFER+=$'\n'
+      CURSOR=$((CURSOR-1))
+      ;;
+  esac
+
+  # Insert characters
+  for ((i=1; i<=${#cmds[@]}; i++)); do
+    cmd="${cmds[$i]}"
+
+    # Hanlde the backspace command
+    if [[ $cmd == '\7f' ]]; then
+      if (($#LBUFFER > 0)); then
+        LBUFFER=${LBUFFER:0:-1}
+      fi
+      continue
+    fi
+
+    # The length of character should be 1
+    if (($#cmd == 1)); then
+      LBUFFER+=$cmd
+    fi
+  done
+}
+
+# Repeat changing visual characters
+function zvm_repeat_vi_change() {
+  local mode=${ZVM_REPEAT_COMMANDS[1]}
+  local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]})
+
+  # Backward move cursor to the beginning of line
+  if [[ $mode == $ZVM_MODE_VISUAL_LINE ]]; then
+    zle vi-digit-or-beginning-of-line
+  fi
+
+  local ncount=${cmds[1]}
+  local ccount=${cmds[2]}
+  local pos=$CURSOR epos=$CURSOR
+
+  # Forward expand the characters to the Nth newline character
+  for ((i=0; i<$ncount; i++)); do
+    pos=$(zvm_substr_pos $BUFFER $'\n' $pos)
+    if [[ $pos == -1 ]]; then
+      epos=$#BUFFER
+      break
+    fi
+    pos=$((pos+1))
+    epos=$pos
+  done
+
+  # Forward expand the remaining characters
+  for ((i=0; i<$ccount; i++)); do
+    local char=${BUFFER[$epos+i]}
+    if [[ $char == $'\n' || $char == '' ]]; then
+      ccount=$i
+      break
+    fi
+  done
+
+  epos=$((epos+ccount))
+  RBUFFER=${RBUFFER:$((epos-CURSOR))}
+}
+
+# Repeat changing a range of characters
+function zvm_repeat_range_change() {
+  local cmd=${ZVM_REPEAT_COMMANDS[2]}
+
+  # Remove characters
+  zvm_range_handler $cmd
+
+  # Insert characters
+  zvm_repeat_insert
+}
+
+# Repeat replacing
+function zvm_repeat_replace() {
+  local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]})
+  local cmd=
+  local cursor=$CURSOR
+
+  for ((i=1; i<=${#cmds[@]}; i++)); do
+    cmd="${cmds[$i]}"
+
+    # If the cmd or the character at cursor is a newline character,
+    # or the cursor is at the end of buffer, we should insert the
+    # cmd instead of replacing with the cmd.
+    if [[ $cmd == $'\n' ||
+      $BUFFER[$cursor+1] == $'\n' ||
+      $BUFFER[$cursor+1] == ''
+    ]]; then
+      LBUFFER+=$cmd
+    else
+      BUFFER[$cursor+1]=$cmd
+    fi
+
+    cursor=$((cursor+1))
+    CURSOR=$cursor
+  done
+
+  # The cursor position should go back one character after
+  # exiting the replace mode
+  zle vi-backward-char
+}
+
+# Repeat replacing characters
+function zvm_repeat_replace_chars() {
+  local mode=${ZVM_REPEAT_COMMANDS[1]}
+  local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]})
+  local cmd=
+
+  # Replacment of visual mode should move backward cursor to the
+  # begin of current line, and replacing to the end of last line.
+  if [[ $mode == $ZVM_MODE_VISUAL_LINE ]]; then
+    zle vi-digit-or-beginning-of-line
+    cmds+=($'\n')
+  fi
+
+  local cursor=$((CURSOR+1))
+
+  for ((i=1; i<=${#cmds[@]}; i++)); do
+    cmd="${cmds[$i]}"
+
+    # If we meet a newline character in the buffer, we should keep
+    # stop replacing, util we meet next newline character command.
+    if [[ ${BUFFER[$cursor]} == $'\n' ]]; then
+      if [[ $cmd == $'\n' ]]; then
+        cursor=$((cursor+1))
+      fi
+      continue
+    fi
+
+    # A newline character command should keep replacing with last
+    # character, until we meet a newline character in the buffer,
+    # then we use next command.
+    if [[ $cmd == $'\n' ]]; then
+      i=$((i-1))
+      cmd="${cmds[$i]}"
+    fi
+
+    # The length of character should be 1
+    if (($#cmd == 1)); then
+      BUFFER[$cursor]="${cmd}"
+    fi
+
+    cursor=$((cursor+1))
+
+    # Break when it reaches the end
+    if ((cursor > $#BUFFER)); then
+      break
+    fi
+  done
+}
+
+# Select a word under the cursor
+function zvm_select_in_word() {
+  local cursor=${1:-$CURSOR}
+  local buffer=${2:-$BUFFER}
+  local bpos=$cursor epos=$cursor
+  local pattern='[0-9a-zA-Z_]'
+
+  if ! [[ "${buffer:$cursor:1}" =~ $pattern ]]; then
+    pattern="[^${pattern:1:-1} ]"
+  fi
+
+  for ((; $bpos>=0; bpos--)); do
+    [[ "${buffer:$bpos:1}" =~ $pattern ]] || break
+  done
+  for ((; $epos<$#buffer; epos++)); do
+    [[ "${buffer:$epos:1}" =~ $pattern ]] || break
+  done
+
+  bpos=$((bpos+1))
+
+  # The ending position must be greater than 0
+  if (( epos > 0 )); then
+    epos=$((epos-1))
+  fi
+
+  echo $bpos $epos
+}
+
+# Switch keyword
+function zvm_switch_keyword() {
+  local bpos= epos= cpos=$CURSOR
+
+  # Cursor position cases:
+  #
+  # 1. Cursor on symbol:
+  # 2+2   => +
+  # 2-2   => -
+  # 2 + 2 => +
+  # 2 +2  => +2
+  # 2 -2  => -2
+  # 2 -a  => -a
+  #
+  # 2. Cursor on number or alpha:
+  # 2+2   => +2
+  # 2-2   => -2
+  # 2 + 2 => 2
+  # 2 +2  => +2
+  # 2 -2  => -2
+  # 2 -a  => -a
+
+  # If cursor is on the `+` or `-`, we need to check if it is a
+  # number with a sign or an operator, only the number needs to
+  # forward the cursor.
+  if [[ ${BUFFER:$cpos:2} =~ [+-][0-9] ]]; then
+    if [[ $cpos == 0 || ${BUFFER:$((cpos-1)):1} =~ [^0-9] ]]; then
+      cpos=$((cpos+1))
+    fi
+
+  # If cursor is on the `+` or `-`, we need to check if it is a
+  # short option, only the short option needs to forward the cursor.
+  elif [[ ${BUFFER:$cpos:2} =~ [+-][a-zA-Z] ]]; then
+    if [[ $cpos == 0 || ${BUFFER:$((cpos-1)):1} == ' ' ]]; then
+      cpos=$((cpos+1))
+    fi
+  fi
+
+  local result=($(zvm_select_in_word $cpos))
+  bpos=${result[1]} epos=$((${result[2]}+1))
+
+  # Move backward the cursor
+  if [[ $bpos != 0 && ${BUFFER:$((bpos-1)):1} == [+-] ]]; then
+    bpos=$((bpos-1))
+  fi
+
+  local word=${BUFFER:$bpos:$((epos-bpos))}
+  local keys=$(zvm_keys)
+
+  if [[ $keys == '\ 1' ]]; then
+    local increase=true
+  else
+    local increase=false
+  fi
+
+  # Execute extra commands
+  for handler in $zvm_switch_keyword_handlers; do
+    if ! zvm_exist_command ${handler}; then
+      continue
+    fi
+
+    result=($($handler $word $increase));
+
+    if (( $#result == 0 )); then
+      continue
+    fi
+
+    epos=$(( bpos + ${result[3]} ))
+    bpos=$(( bpos + ${result[2]} ))
+
+    if (( cpos < bpos )) || (( cpos >= epos )); then
+      continue
+    fi
+
+    BUFFER="${BUFFER:0:$bpos}${result[1]}${BUFFER:$epos}"
+    CURSOR=$((bpos + ${#result[1]} - 1))
+
+    zle reset-prompt
+    return
+  done
+}
+
+# Switch number keyword
+function zvm_switch_number {
+  local word=$1
+  local increase=${2:-true}
+  local result= bpos= epos=
+
+  # Hexadecimal
+  if [[ $word =~ [^0-9]?(0[xX][0-9a-fA-F]*) ]]; then
+    local number=${match[1]}
+    local prefix=${number:0:2}
+    bpos=$((mbegin-1)) epos=$mend
+
+    # Hexadecimal cases:
+    #
+    # 1. Increment:
+    # 0xDe => 0xdf
+    # 0xdE => 0xDF
+    # 0xde0 => 0xddf
+    # 0xffffffffffffffff => 0x0000000000000000
+    # 0X9 => 0XA
+    # 0Xdf => 0Xe0
+    #
+    # 2. Decrement:
+    # 0xdE0 => 0xDDF
+    # 0xffFf0 => 0xfffef
+    # 0xfffF0 => 0xFFFEF
+    # 0x0 => 0xffffffffffffffff
+    # 0X0 => 0XFFFFFFFFFFFFFFFF
+    # 0Xf => 0Xe
+
+    local lower=true
+    if [[ $number =~ [A-Z][0-9]*$ ]]; then
+      lower=false
+    fi
+
+    # Fix the number truncated after 15 digits issue
+    if (( $#number > 17 )); then
+      local d=$(($#number - 15))
+      local h=${number:0:$d}
+      number="0x${number:$d}"
+    fi
+
+    local p=$(($#number - 2))
+
+    if $increase; then
+      if (( $number == 0x${(l:15::f:)} )); then
+        h=$(([##16]$h+1))
+        h=${h: -1}
+        number=${(l:15::0:)}
+      else
+        h=${h:2}
+        number=$(([##16]$number + 1))
+      fi
+    else
+      if (( $number == 0 )); then
+        if (( ${h:-0} == 0 )); then
+          h=f
+        else
+          h=$(([##16]$h-1))
+          h=${h: -1}
+        fi
+        number=${(l:15::f:)}
+      else
+        h=${h:2}
+        number=$(([##16]$number - 1))
+      fi
+    fi
+
+    # Padding with zero
+    if (( $#number < $p )); then
+      number=${(l:$p::0:)number}
+    fi
+
+    result="${h}${number}"
+
+    # Transform the case
+    if $lower; then
+      result="${(L)result}"
+    fi
+
+    result="${prefix}${result}"
+
+  # Binary
+  elif [[ $word =~ [^0-9]?(0[bB][01]*) ]]; then
+    # Binary cases:
+    #
+    # 1. Increment:
+    # 0b1 => 0b10
+    # 0x1111111111111111111111111111111111111111111111111111111111111111 =>
+    # 0x0000000000000000000000000000000000000000000000000000000000000000
+    # 0B0 => 0B1
+    #
+    # 2. Decrement:
+    # 0b1 => 0b0
+    # 0b100 => 0b011
+    # 0B010 => 0B001
+    # 0b0 =>
+    # 0x1111111111111111111111111111111111111111111111111111111111111111
+
+    local number=${match[1]}
+    local prefix=${number:0:2}
+    bpos=$((mbegin-1)) epos=$mend
+
+    # Fix the number truncated after 63 digits issue
+    if (( $#number > 65 )); then
+      local d=$(($#number - 63))
+      local h=${number:0:$d}
+      number="0b${number:$d}"
+    fi
+
+    local p=$(($#number - 2))
+
+    if $increase; then
+      if (( $number == 0b${(l:63::1:)} )); then
+        h=$(([##2]$h+1))
+        h=${h: -1}
+        number=${(l:63::0:)}
+      else
+        h=${h:2}
+        number=$(([##2]$number + 1))
+      fi
+    else
+      if (( $number == 0b0 )); then
+        if (( ${h:-0} == 0 )); then
+          h=1
+        else
+          h=$(([##2]$h-1))
+          h=${h: -1}
+        fi
+        number=${(l:63::1:)}
+      else
+        h=${h:2}
+        number=$(([##2]$number - 1))
+      fi
+    fi
+
+    # Padding with zero
+    if (( $#number < $p )); then
+      number=${(l:$p::0:)number}
+    fi
+
+    result="${prefix}${number}"
+
+  # Decimal
+  elif [[ $word =~ ([-+]?[0-9]+) ]]; then
+    # Decimal cases:
+    #
+    # 1. Increment:
+    # 0 => 1
+    # 99 => 100
+    #
+    # 2. Decrement:
+    # 0 => -1
+    # 10 => 9
+    # aa1230xa => aa1231xa
+    # aa1230bb => aa1231bb
+    # aa123a0bb => aa124a0bb
+
+    local number=${match[1]}
+    bpos=$((mbegin-1)) epos=$mend
+
+    if $increase; then
+      result=$(($number + 1))
+    else
+      result=$(($number - 1))
+    fi
+
+    # Check if need the plus sign prefix
+    if [[ ${word:$bpos:1} == '+' ]]; then
+      result="+${result}"
+    fi
+  fi
+
+  if [[ $result ]]; then
+    echo $result $bpos $epos
+  fi
+}
+
+# Switch boolean keyword
+function zvm_switch_boolean() {
+  local word=$1
+  local increase=$2
+  local result=
+  local bpos=0 epos=$#word
+
+  # Remove option prefix
+  if [[ $word =~ (^[+-]{0,2}) ]]; then
+    local prefix=${match[1]}
+    bpos=$mend
+    word=${word:$bpos}
+  fi
+
+  case ${(L)word} in
+    true) result=false;;
+    false) result=true;;
+    yes) result=no;;
+    no) result=yes;;
+    on) result=off;;
+    off) result=on;;
+    y) result=n;;
+    n) result=y;;
+    t) result=f;;
+    f) result=t;;
+    *) return;;
+  esac
+
+  # Transform the case
+  if [[ $word =~ ^[A-Z]+$ ]]; then
+    result=${(U)result}
+  elif [[ $word =~ ^[A-Z] ]]; then
+    result=${(U)result:0:1}${result:1}
+  fi
+
+  echo $result $bpos $epos
+}
+
+# Switch weekday keyword
+function zvm_switch_weekday() {
+  local word=$1
+  local increase=$2
+  local result=${(L)word}
+  local weekdays=(
+    sunday
+    monday
+    tuesday
+    wednesday
+    thursday
+    friday
+    saturday
+  )
+
+  local i=1
+
+  for ((; i<=${#weekdays[@]}; i++)); do
+    if [[ ${weekdays[i]:0:$#result} == ${result} ]]; then
+      result=${weekdays[i]}
+      break
+    fi
+  done
+
+  # Return if no match
+  if (( i > ${#weekdays[@]} )); then
+    return
+  fi
+
+  if $increase; then
+    if (( i == ${#weekdays[@]} )); then
+      i=1
+    else
+      i=$((i+1))
+    fi
+  else
+    if (( i == 1 )); then
+      i=${#weekdays[@]}
+    else
+      i=$((i-1))
+    fi
+  fi
+
+  # Abbreviation
+  if (( $#result == $#word )); then
+    result=${weekdays[i]}
+  else
+    result=${weekdays[i]:0:$#word}
+  fi
+
+  # Transform the case
+  if [[ $word =~ ^[A-Z]+$ ]]; then
+    result=${(U)result}
+  elif [[ $word =~ ^[A-Z] ]]; then
+    result=${(U)result:0:1}${result:1}
+  fi
+
+  echo $result 0 $#word
+}
+
+# Switch operator keyword
+function zvm_switch_operator() {
+  local word=$1
+  local increase=$2
+  local result=
+
+  case ${(L)word} in
+    '&&') result='||';;
+    '||') result='&&';;
+    '++') result='--';;
+    '--') result='++';;
+    '==') result='!=';;
+    '!=') result='==';;
+    '===') result='!==';;
+    '!==') result='===';;
+    '+') result='-';;
+    '-') result='*';;
+    '*') result='/';;
+    '/') result='+';;
+    'and') result='or';;
+    'or') result='and';;
+    *) return;;
+  esac
+
+  # Transform the case
+  if [[ $word =~ ^[A-Z]+$ ]]; then
+    result=${(U)result}
+  elif [[ $word =~ ^[A-Z] ]]; then
+    result=${(U)result:0:1}${result:1}
+  fi
+
+  # Since the `echo` command can not print the character
+  # `-`, here we use `printf` command alternatively.
+  printf "%s 0 $#word" "${result}"
+}
+
+# Switch month keyword
+function zvm_switch_month() {
+  local word=$1
+  local increase=$2
+  local result=${(L)word}
+  local months=(
+    january
+    february
+    march
+    april
+    may
+    june
+    july
+    august
+    september
+    october
+    november
+    december
+  )
+
+  local i=1
+
+  for ((; i<=${#months[@]}; i++)); do
+    if [[ ${months[i]:0:$#result} == ${result} ]]; then
+      result=${months[i]}
+      break
+    fi
+  done
+
+  # Return if no match
+  if (( i > ${#months[@]} )); then
+    return
+  fi
+
+  if $increase; then
+    if (( i == ${#months[@]} )); then
+      i=1
+    else
+      i=$((i+1))
+    fi
+  else
+    if (( i == 1 )); then
+      i=${#months[@]}
+    else
+      i=$((i-1))
+    fi
+  fi
+
+  # Abbreviation
+  if (( $#result == $#word )); then
+    result=${months[i]}
+  else
+    result=${months[i]:0:$#word}
+  fi
+
+  # Transform the case
+  if [[ $word =~ ^[A-Z]+$ ]]; then
+    result=${(U)result}
+  elif [[ $word =~ ^[A-Z] ]]; then
+    result=${(U)result:0:1}${result:1}
+  fi
+
+  echo $result 0 $#word
+}
+
+# Highlight content
+function zvm_highlight() {
+  local opt=${1:-mode}
+  local region=()
+  local redraw=false
+
+  # Hanlde region by the option
+  case "$opt" in
+    mode)
+      case "$ZVM_MODE" in
+        $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE)
+          local ret=($(zvm_calc_selection))
+          local bpos=$((ret[1])) epos=$((ret[2]))
+          local bg=$ZVM_VI_HIGHLIGHT_BACKGROUND
+          local fg=$ZVM_VI_HIGHLIGHT_FOREGROUND
+          local es=$ZVM_VI_HIGHLIGHT_EXTRASTYLE
+          region=("$bpos $epos fg=$fg,bg=$bg,$es")
+          ;;
+      esac
+      redraw=true
+      ;;
+    custom)
+      local bpos=$2 epos=$3
+      local bg=${4:-$ZVM_VI_HIGHLIGHT_BACKGROUND}
+      local fg=${5:-$ZVM_VI_HIGHLIGHT_FOREGROUND}
+      local es=${6:-$ZVM_VI_HIGHLIGHT_EXTRASTYLE}
+      region=("${ZVM_REGION_HIGHLIGHT[@]}")
+      region+=("$bpos $epos fg=$fg,bg=$bg,$es")
+      redraw=true
+      ;;
+    clear)
+      zle redisplay
+      redraw=true
+      ;;
+    redraw) redraw=true;;
+  esac
+
+  # Update region highlight
+  if (( $#region > 0 )) || [[ "$opt" == 'clear' ]]; then
+
+    # Remove old region highlight
+    local rawhighlight=()
+    for ((i=1; i<=${#region_highlight[@]}; i++)); do
+      local raw=true
+      local spl=(${(@s/ /)region_highlight[i]})
+      local pat="${spl[1]} ${spl[2]}"
+      for ((j=1; j<=${#ZVM_REGION_HIGHLIGHT[@]}; j++)); do
+        if [[ "$pat" == "${ZVM_REGION_HIGHLIGHT[j]:0:$#pat}" ]]; then
+          raw=false
+          break
+        fi
+      done
+      if $raw; then
+        rawhighlight+=("${region_highlight[i]}")
+      fi
+    done
+
+    # Assign new region highlight
+    ZVM_REGION_HIGHLIGHT=("${region[@]}")
+    region_highlight=("${rawhighlight[@]}" "${ZVM_REGION_HIGHLIGHT[@]}")
+  fi
+
+  # Check if we need to refresh the region highlight
+  if $redraw; then
+    zle -R
+  fi
+}
+
+# Enter the visual mode
+function zvm_enter_visual_mode() {
+  local mode=
+  local last_mode=$ZVM_MODE
+  local last_region=
+
+  # Exit the visual mode
+  case $last_mode in
+    $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE)
+      last_region=($MARK $CURSOR)
+      zvm_exit_visual_mode
+      ;;
+  esac
+
+  case "${1:-$(zvm_keys)}" in
+    v) mode=$ZVM_MODE_VISUAL;;
+    V) mode=$ZVM_MODE_VISUAL_LINE;;
+    *) mode=$last_mode;;
+  esac
+
+  # We should just exit the visual mdoe if current mode
+  # is the same with last visual mode
+  if [[ $last_mode == $mode ]]; then
+    return
+  fi
+
+  zvm_select_vi_mode $mode
+
+  # Recover the region when changing to another visual mode
+  if [[ -n $last_region ]]; then
+    MARK=$last_region[1]
+    CURSOR=$last_region[2]
+    zle redisplay
+  fi
+}
+
+# Exit the visual mode
+function zvm_exit_visual_mode() {
+  case "$ZVM_MODE" in
+    $ZVM_MODE_VISUAL) zle visual-mode;;
+    $ZVM_MODE_VISUAL_LINE) zle visual-line-mode;;
+  esac
+  zvm_highlight clear
+  zvm_select_vi_mode $ZVM_MODE_NORMAL ${1:-true}
+}
+
+# Enter the vi insert mode
+function zvm_enter_insert_mode() {
+  local keys=${1:-$(zvm_keys)}
+
+  if [[ $keys == 'i' ]]; then
+    ZVM_INSERT_MODE='i'
+  elif [[ $keys == 'a' ]]; then
+    ZVM_INSERT_MODE='a'
+    if ! zvm_is_empty_line; then
+      CURSOR=$((CURSOR+1))
+    fi
+  fi
+
+  zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+}
+
+# Exit the vi insert mode
+function zvm_exit_insert_mode() {
+  ZVM_INSERT_MODE=
+  zvm_select_vi_mode $ZVM_MODE_NORMAL
+}
+
+# Enter the vi operator pending mode
+function zvm_enter_oppend_mode() {
+  ZVM_OPPEND_MODE=true
+  ${1:-true} && zvm_update_cursor
+}
+
+# Exit the vi operator pending mode
+function zvm_exit_oppend_mode() {
+  ZVM_OPPEND_MODE=false
+  ${1:-true} && zvm_update_cursor
+}
+
+# Insert at the beginning of the line
+function zvm_insert_bol() {
+  ZVM_INSERT_MODE='I'
+  zle vi-first-non-blank
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+  zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE
+}
+
+# Append at the end of the line
+function zvm_append_eol() {
+  ZVM_INSERT_MODE='A'
+  zle vi-end-of-line
+  CURSOR=$((CURSOR+1))
+  zvm_select_vi_mode $ZVM_MODE_INSERT
+  zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE
+}
+
+# Self insert content to cursor position
+function zvm_self_insert() {
+  local keys=${1:-$KEYS}
+
+  # Update the autosuggestion
+  if [[ ${POSTDISPLAY:0:$#keys} == $keys ]]; then
+    POSTDISPLAY=${POSTDISPLAY:$#keys}
+  else
+    POSTDISPLAY=
+  fi
+
+  LBUFFER+=${keys}
+}
+
+# Reset the repeat commands
+function zvm_reset_repeat_commands() {
+  ZVM_REPEAT_RESET=true
+  ZVM_REPEAT_COMMANDS=($@)
+}
+
+# Select vi mode
+function zvm_select_vi_mode() {
+  local mode=$1
+  local reset_prompt=${2:-true}
+
+  # Check if current mode is the same with the new mode
+  if [[ $mode == "$ZVM_MODE" ]]; then
+    zvm_update_cursor
+    mode=
+  fi
+
+  zvm_exec_commands 'before_select_vi_mode'
+
+  # Some plugins would reset the prompt when we select the
+  # keymap, so here we postpone executing reset-prompt.
+  zvm_postpone_reset_prompt true
+
+  # Exit operator pending mode
+  if $ZVM_OPPEND_MODE; then
+    zvm_exit_oppend_mode false
+  fi
+
+  case $mode in
+    $ZVM_MODE_NORMAL)
+      ZVM_MODE=$ZVM_MODE_NORMAL
+      zvm_update_cursor
+      zle vi-cmd-mode
+      ;;
+    $ZVM_MODE_INSERT)
+      ZVM_MODE=$ZVM_MODE_INSERT
+      zvm_update_cursor
+      zle vi-insert
+      ;;
+    $ZVM_MODE_VISUAL)
+      ZVM_MODE=$ZVM_MODE_VISUAL
+      zvm_update_cursor
+      zle visual-mode
+      ;;
+    $ZVM_MODE_VISUAL_LINE)
+      ZVM_MODE=$ZVM_MODE_VISUAL_LINE
+      zvm_update_cursor
+      zle visual-line-mode
+      ;;
+    $ZVM_MODE_REPLACE)
+      ZVM_MODE=$ZVM_MODE_REPLACE
+      zvm_enter_oppend_mode
+      ;;
+  esac
+
+  # This aspect provides you a moment to do something, such as
+  # update the cursor, prompt and so on.
+  zvm_exec_commands 'after_select_vi_mode'
+
+  # Stop and trigger reset-prompt
+  $reset_prompt && zvm_postpone_reset_prompt false true
+
+  # Start the lazy keybindings when the first time entering the
+  # normal mode, when the mode is the same as last mode, we get
+  # empty value for $mode.
+  if [[ $mode == $ZVM_MODE_NORMAL ]] &&
+    (( $#ZVM_LAZY_KEYBINDINGS_LIST > 0 )); then
+
+    zvm_exec_commands 'before_lazy_keybindings'
+
+    # Here we should unset the list for normal keybindings
+    local list=("${ZVM_LAZY_KEYBINDINGS_LIST[@]}")
+    unset ZVM_LAZY_KEYBINDINGS_LIST
+
+    for r in "${list[@]}"; do
+      eval "zvm_bindkey ${r}"
+    done
+
+    zvm_exec_commands 'after_lazy_keybindings'
+  fi
+}
+
+# Postpone reset prompt
+function zvm_postpone_reset_prompt() {
+  local toggle=$1
+  local force=$2
+
+  if $toggle; then
+    ZVM_POSTPONE_RESET_PROMPT=true
+  else
+    if [[ $ZVM_POSTPONE_RESET_PROMPT == false || $force ]]; then
+      ZVM_POSTPONE_RESET_PROMPT=
+      zle reset-prompt
+    else
+      ZVM_POSTPONE_RESET_PROMPT=
+    fi
+  fi
+}
+
+# Reset prompt
+function zvm_reset_prompt() {
+  # Return if postponing is enabled
+  if [[ -n $ZVM_POSTPONE_RESET_PROMPT ]]; then
+    ZVM_POSTPONE_RESET_PROMPT=false
+    return
+  fi
+
+  local -i retval
+  if [[ -z "$rawfunc" ]]; then
+    zle .reset-prompt -- "$@"
+  else
+    $rawfunc -- "$@"
+  fi
+
+  return retval
+}
+
+# Undo action in vi insert mode
+#
+# CTRL-U  Remove all characters between the cursor position and
+#         the beginning of the line.  Previous versions of vim
+#         deleted all characters on the line.
+function zvm_viins_undo() {
+  if [[ $ZVM_VI_INS_LEGACY_UNDO ]]; then
+    zvm_kill_line
+  else
+    zvm_backward_kill_line
+  fi
+}
+
+function zvm_set_cursor() {
+  # Term of vim isn't supported
+  if [[ -n $VIMRUNTIME ]]; then
+    return
+  fi
+
+  echo -ne "$1"
+}
+
+# Get the escape sequence of cursor style
+function zvm_cursor_style() {
+  local style=${(L)1}
+  local term=${2:-$ZVM_TERM}
+
+  case $term in
+    # For xterm and rxvt and their derivatives use the same escape
+    # sequences as the VT520 terminal. And screen, konsole, alacritty
+    # and st implement a superset of VT100 and VT100, they support
+    # 256 colors the same way xterm does.
+    xterm*|rxvt*|screen*|tmux*|konsole*|alacritty*|st*)
+      case $style in
+        $ZVM_CURSOR_BLOCK) style='\e[2 q';;
+        $ZVM_CURSOR_UNDERLINE) style='\e[4 q';;
+        $ZVM_CURSOR_BEAM) style='\e[6 q';;
+        $ZVM_CURSOR_BLINKING_BLOCK) style='\e[1 q';;
+        $ZVM_CURSOR_BLINKING_UNDERLINE) style='\e[3 q';;
+        $ZVM_CURSOR_BLINKING_BEAM) style='\e[5 q';;
+        $ZVM_CURSOR_USER_DEFAULT) style='\e[0 q';;
+      esac
+      ;;
+    *) style='\e[0 q';;
+  esac
+
+  # Restore default cursor color
+  if [[ $style == '\e[0 q' ]]; then
+    local old_style=
+
+    case $ZVM_MODE in
+      $ZVM_MODE_INSERT) old_style=$ZVM_INSERT_MODE_CURSOR;;
+      $ZVM_MODE_NORMAL) old_style=$ZVM_NORMAL_MODE_CURSOR;;
+      $ZVM_MODE_OPPEND) old_style=$ZVM_OPPEND_MODE_CURSOR;;
+    esac
+
+    if [[ $old_style =~ '\e\][0-9]+;.+\a' ]]; then
+      style=$style'\e\e]112\a'
+    fi
+  fi
+
+  echo $style
+}
+
+# Update the cursor according current vi mode
+function zvm_update_cursor() {
+
+  # Check if we need to update the cursor style
+  $ZVM_CURSOR_STYLE_ENABLED || return
+
+  local mode=$1
+  local shape=
+
+  # Check if it is operator pending mode
+  if $ZVM_OPPEND_MODE; then
+    mode=opp
+    shape=$(zvm_cursor_style $ZVM_OPPEND_MODE_CURSOR)
+  fi
+
+  # Get cursor shape by the mode
+  case "${mode:-$ZVM_MODE}" in
+    $ZVM_MODE_NORMAL)
+      shape=$(zvm_cursor_style $ZVM_NORMAL_MODE_CURSOR)
+      ;;
+    $ZVM_MODE_INSERT)
+      shape=$(zvm_cursor_style $ZVM_INSERT_MODE_CURSOR)
+      ;;
+    $ZVM_MODE_VISUAL)
+      shape=$(zvm_cursor_style $ZVM_VISUAL_MODE_CURSOR)
+      ;;
+    $ZVM_MODE_VISUAL_LINE)
+      shape=$(zvm_cursor_style $ZVM_VISUAL_LINE_MODE_CURSOR)
+      ;;
+  esac
+
+  if [[ $shape ]]; then
+    zvm_set_cursor $shape
+  fi
+}
+
+# Updates highlight region
+function zvm_update_highlight() {
+  case "$ZVM_MODE" in
+    $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE)
+      zvm_highlight
+      ;;
+  esac
+}
+
+# Updates repeat commands
+function zvm_update_repeat_commands() {
+  # We don't need to update the repeat commands if current
+  # mode is already the repeat mode.
+  $ZVM_REPEAT_MODE && return
+
+  # We don't need to update the repeat commands if it is
+  # reseting the repeat commands.
+  if $ZVM_REPEAT_RESET; then
+    ZVM_REPEAT_RESET=false
+    return
+  fi
+
+  # We update the repeat commands when it's the insert mode
+  [[ $ZVM_MODE == $ZVM_MODE_INSERT ]] || return
+
+  local char=$KEYS
+
+  # If current key is an arrow key, we should do something
+  if [[ "$KEYS" =~ '\e\[[ABCD]' ]]; then
+    # If last key is also an arrow key, we just replace it
+    if [[ ${ZVM_REPEAT_COMMANDS[-1]} =~ '\e\[[ABCD]' ]]; then
+      ZVM_REPEAT_COMMANDS=(${ZVM_REPEAT_COMMANDS[@]:0:-1})
+    fi
+  else
+    # If last command is arrow key movement, we should reset
+    # the repeat commands with i(nsert) command
+    if [[ ${ZVM_REPEAT_COMMANDS[-1]} =~ '\e\[[ABCD]' ]]; then
+      zvm_reset_repeat_commands $ZVM_MODE_NORMAL i
+    fi
+    char=${BUFFER[$CURSOR]}
+  fi
+
+  # If current key is backspace key, we should remove last
+  # one, until it has only the mode and inital command
+  if [[ "$KEYS" == '\7f' ]]; then
+    if ((${#ZVM_REPEAT_COMMANDS[@]} > 2)) &&
+      [[ ${ZVM_REPEAT_COMMANDS[-1]} != '\7f' ]]; then
+      ZVM_REPEAT_COMMANDS=(${ZVM_REPEAT_COMMANDS[@]:0:-1})
+    elif (($#LBUFFER > 0)); then
+      ZVM_REPEAT_COMMANDS+=($KEYS)
+    fi
+  else
+    ZVM_REPEAT_COMMANDS+=($char)
+  fi
+}
+
+# Updates editor information when line pre redraw
+function zvm_zle-line-pre-redraw() {
+  # Fix cursor style is not updated in tmux environment, when
+  # there are one more panel in the same window, the program
+  # in other panel could change the cursor shape, we need to
+  # update cursor style when line is redrawing.
+  if [[ -n $TMUX ]]; then
+    zvm_update_cursor
+    # Fix display is not updated in the terminal of IntelliJ IDE.
+    [[ "$TERMINAL_EMULATOR" == "JetBrains-JediTerm" ]] && zle redisplay
+  fi
+  zvm_update_highlight
+  zvm_update_repeat_commands
+}
+
+# Start every prompt in the correct vi mode
+function zvm_zle-line-init() {
+  # Save last mode
+  local mode=${ZVM_MODE:-$ZVM_MODE_INSERT}
+
+  # It's neccessary to set to insert mode when line init
+  # and we don't need to reset prompt.
+  zvm_select_vi_mode $ZVM_MODE_INSERT false
+
+  # Select line init mode and reset prompt
+  case ${ZVM_LINE_INIT_MODE:-$mode} in
+    $ZVM_MODE_INSERT) zvm_select_vi_mode $ZVM_MODE_INSERT;;
+    *) zvm_select_vi_mode $ZVM_MODE_NORMAL;;
+  esac
+}
+
+# Restore the user default cursor style after prompt finish
+function zvm_zle-line-finish() {
+  # When we start a program (e.g. vim, bash, etc.) from the
+  # command line, the cursor style is inherited by other
+  # programs, so that we need to reset the cursor style to
+  # default before executing a command and set the custom
+  # style again when the command exits. This way makes any
+  # other interactive CLI application would not be affected
+  # by it.
+  local shape=$(zvm_cursor_style $ZVM_CURSOR_USER_DEFAULT)
+  zvm_set_cursor $shape
+}
+
+# Initialize vi-mode for widgets, keybindings, etc.
+function zvm_init() {
+  # Check if it has been initalized
+  if $ZVM_INIT_DONE; then
+    return;
+  fi
+
+  # Mark plugin initial status
+  ZVM_INIT_DONE=true
+
+  zvm_exec_commands 'before_init'
+
+  # Correct the readkey engine
+  case $ZVM_READKEY_ENGINE in
+    $ZVM_READKEY_ENGINE_NEX|$ZVM_READKEY_ENGINE_ZLE);;
+    *)
+      echo -n "Warning: Unsupported readkey engine! "
+      echo "ZVM_READKEY_ENGINE=$ZVM_READKEY_ENGINE"
+      ZVM_READKEY_ENGINE=$ZVM_READKEY_ENGINE_DEFAULT
+      ;;
+  esac
+
+  # Reduce ESC delay (zle default is 0.4 seconds)
+  # Set to 0.01 second delay for taking over the key input processing
+  case $ZVM_READKEY_ENGINE in
+    $ZVM_READKEY_ENGINE_NEX) KEYTIMEOUT=1;;
+    $ZVM_READKEY_ENGINE_ZLE) KEYTIMEOUT=$(($ZVM_KEYTIMEOUT*100));;
+  esac
+
+  # Create User-defined widgets
+  zvm_define_widget zvm_default_handler
+  zvm_define_widget zvm_readkeys_handler
+  zvm_define_widget zvm_backward_kill_region
+  zvm_define_widget zvm_backward_kill_line
+  zvm_define_widget zvm_forward_kill_line
+  zvm_define_widget zvm_kill_line
+  zvm_define_widget zvm_viins_undo
+  zvm_define_widget zvm_select_surround
+  zvm_define_widget zvm_change_surround
+  zvm_define_widget zvm_move_around_surround
+  zvm_define_widget zvm_change_surround_text_object
+  zvm_define_widget zvm_enter_insert_mode
+  zvm_define_widget zvm_exit_insert_mode
+  zvm_define_widget zvm_enter_visual_mode
+  zvm_define_widget zvm_exit_visual_mode
+  zvm_define_widget zvm_enter_oppend_mode
+  zvm_define_widget zvm_exit_oppend_mode
+  zvm_define_widget zvm_exchange_point_and_mark
+  zvm_define_widget zvm_open_line_below
+  zvm_define_widget zvm_open_line_above
+  zvm_define_widget zvm_insert_bol
+  zvm_define_widget zvm_append_eol
+  zvm_define_widget zvm_self_insert
+  zvm_define_widget zvm_vi_replace
+  zvm_define_widget zvm_vi_replace_chars
+  zvm_define_widget zvm_vi_substitute
+  zvm_define_widget zvm_vi_substitute_whole_line
+  zvm_define_widget zvm_vi_change
+  zvm_define_widget zvm_vi_change_eol
+  zvm_define_widget zvm_vi_delete
+  zvm_define_widget zvm_vi_yank
+  zvm_define_widget zvm_vi_put_after
+  zvm_define_widget zvm_vi_put_before
+  zvm_define_widget zvm_vi_up_case
+  zvm_define_widget zvm_vi_down_case
+  zvm_define_widget zvm_vi_opp_case
+  zvm_define_widget zvm_vi_edit_command_line
+  zvm_define_widget zvm_repeat_change
+  zvm_define_widget zvm_switch_keyword
+
+  # Override standard widgets
+  zvm_define_widget zle-line-pre-redraw zvm_zle-line-pre-redraw
+
+  # Ensure the correct cursor style when an interactive program
+  # (e.g. vim, bash, etc.) starts and exits
+  zvm_define_widget zle-line-init zvm_zle-line-init
+  zvm_define_widget zle-line-finish zvm_zle-line-finish
+
+  # Override reset-prompt widget
+  zvm_define_widget reset-prompt zvm_reset_prompt
+
+  # All Key bindings
+  # Emacs-like bindings
+  # Normal editing
+  zvm_bindkey viins '^A' beginning-of-line
+  zvm_bindkey viins '^E' end-of-line
+  zvm_bindkey viins '^B' backward-char
+  zvm_bindkey viins '^F' forward-char
+  zvm_bindkey viins '^K' zvm_forward_kill_line
+  zvm_bindkey viins '^W' backward-kill-word
+  zvm_bindkey viins '^U' zvm_viins_undo
+  zvm_bindkey viins '^Y' yank
+  zvm_bindkey viins '^_' undo
+
+  # Mode agnostic editing
+  zvm_bindkey viins '^[[H'  beginning-of-line
+  zvm_bindkey vicmd '^[[H'  beginning-of-line
+  zvm_bindkey viins '^[[F'  end-of-line
+  zvm_bindkey vicmd '^[[F'  end-of-line
+  zvm_bindkey viins '^[[3~' delete-char
+  zvm_bindkey vicmd '^[[3~' delete-char
+
+  # History search
+  #zvm_bindkey viins '^R' history-incremental-search-backward
+  zvm_bindkey viins '^S' history-incremental-search-forward
+  zvm_bindkey viins '^P' up-line-or-history
+  zvm_bindkey viins '^N' down-line-or-history
+
+  # Insert mode
+  zvm_bindkey vicmd 'i'  zvm_enter_insert_mode
+  zvm_bindkey vicmd 'a'  zvm_enter_insert_mode
+  zvm_bindkey vicmd 'I'  zvm_insert_bol
+  zvm_bindkey vicmd 'A'  zvm_append_eol
+
+  # Other key bindings
+  zvm_bindkey vicmd  'v' zvm_enter_visual_mode
+  zvm_bindkey vicmd  'V' zvm_enter_visual_mode
+  zvm_bindkey visual 'o' zvm_exchange_point_and_mark
+  zvm_bindkey vicmd  'o' zvm_open_line_below
+  zvm_bindkey vicmd  'O' zvm_open_line_above
+  zvm_bindkey vicmd  'r' zvm_vi_replace_chars
+  zvm_bindkey vicmd  'R' zvm_vi_replace
+  zvm_bindkey vicmd  's' zvm_vi_substitute
+  zvm_bindkey vicmd  'S' zvm_vi_substitute_whole_line
+  zvm_bindkey vicmd  'C' zvm_vi_change_eol
+  zvm_bindkey visual 'c' zvm_vi_change
+  zvm_bindkey visual 'd' zvm_vi_delete
+  zvm_bindkey visual 'y' zvm_vi_yank
+  zvm_bindkey vicmd  'p' zvm_vi_put_after
+  zvm_bindkey vicmd  'P' zvm_vi_put_before
+  zvm_bindkey visual 'U' zvm_vi_up_case
+  zvm_bindkey visual 'u' zvm_vi_down_case
+  zvm_bindkey visual '~' zvm_vi_opp_case
+  zvm_bindkey visual 'v' zvm_vi_edit_command_line
+  zvm_bindkey vicmd  '.' zvm_repeat_change
+
+  zvm_bindkey vicmd '^A' zvm_switch_keyword
+  zvm_bindkey vicmd '^X' zvm_switch_keyword
+
+  # Keybindings for escape key and some specials
+  local exit_oppend_mode_widget=
+  local exit_insert_mode_widget=
+  local exit_visual_mode_widget=
+  local default_handler_widget=
+
+  case $ZVM_READKEY_ENGINE in
+    $ZVM_READKEY_ENGINE_NEX)
+      exit_oppend_mode_widget=zvm_readkeys_handler
+      exit_insert_mode_widget=zvm_readkeys_handler
+      exit_visual_mode_widget=zvm_readkeys_handler
+      ;;
+    $ZVM_READKEY_ENGINE_ZLE)
+      exit_insert_mode_widget=zvm_exit_insert_mode
+      exit_visual_mode_widget=zvm_exit_visual_mode
+      default_handler_widget=zvm_default_handler
+      ;;
+  esac
+
+  # Bind custom escape key
+  zvm_bindkey vicmd  "$ZVM_VI_OPPEND_ESCAPE_BINDKEY" $exit_oppend_mode_widget
+  zvm_bindkey viins  "$ZVM_VI_INSERT_ESCAPE_BINDKEY" $exit_insert_mode_widget
+  zvm_bindkey visual "$ZVM_VI_VISUAL_ESCAPE_BINDKEY" $exit_visual_mode_widget
+
+  # Bind the default escape key if the escape key is not the default
+  case "$ZVM_VI_OPPEND_ESCAPE_BINDKEY" in
+    '^['|'\e') ;;
+    *) zvm_bindkey vicmd '^[' $exit_oppend_mode_widget;;
+  esac
+  case "$ZVM_VI_INSERT_ESCAPE_BINDKEY" in
+    '^['|'\e') ;;
+    *) zvm_bindkey viins '^[' $exit_insert_mode_widget;;
+  esac
+  case "$ZVM_VI_VISUAL_ESCAPE_BINDKEY" in
+    '^['|'\e') ;;
+    *) zvm_bindkey visual '^[' $exit_visual_mode_widget;;
+  esac
+
+  # Bind and overwrite original y/d/c of vicmd
+  for c in {y,d,c}; do
+    zvm_bindkey vicmd "$c" $default_handler_widget
+  done
+
+  # Surround text-object
+  # Enable surround text-objects (quotes, brackets)
+  local surrounds=()
+
+  # Append brackets
+  for s in ${(s..)^:-'()[]{}<>'}; do
+    surrounds+=($s)
+  done
+
+  # Append quotes
+  for s in {\',\",\`,\ ,'^['}; do
+    surrounds+=($s)
+  done
+
+  # Append for escaping visual mode
+  if $is_custom_escape_key; then
+    surrounds+=("$ZVM_VI_ESCAPE_BINDKEY")
+  fi
+
+  # Surround key bindings
+  for s in $surrounds; do
+    for c in {a,i}${s}; do
+      zvm_bindkey visual "$c" zvm_select_surround
+    done
+    for c in {c,d,y}{a,i}${s}; do
+      zvm_bindkey vicmd "$c" zvm_change_surround_text_object
+    done
+    if [[ $ZVM_VI_SURROUND_BINDKEY == 's-prefix' ]]; then
+      for c in s{d,r}${s}; do
+        zvm_bindkey vicmd "$c" zvm_change_surround
+      done
+      for c in sa${s}; do
+        zvm_bindkey visual "$c" zvm_change_surround
+      done
+    else
+      for c in {d,c}s${s}; do
+        zvm_bindkey vicmd "$c" zvm_change_surround
+      done
+      for c in {S,ys}${s}; do
+        zvm_bindkey visual "$c" zvm_change_surround
+      done
+    fi
+  done
+
+  # Moving around surrounds
+  zvm_bindkey vicmd '%' zvm_move_around_surround
+
+  # Fix BACKSPACE was stuck in zsh
+  # Since normally '^?' (backspace) is bound to vi-backward-delete-char
+  zvm_bindkey viins '^?' backward-delete-char
+
+  # Enable vi keymap
+  bindkey -v
+
+  zvm_exec_commands 'after_init'
+}
+
+# Check if a command is existed
+function zvm_exist_command() {
+  command -v "$1" >/dev/null
+}
+
+# Execute commands
+function zvm_exec_commands() {
+  local commands="zvm_${1}_commands"
+  commands=(${(P)commands})
+
+  # Execute the default command
+  if zvm_exist_command "zvm_$1"; then
+    eval "zvm_$1" ${@:2}
+  fi
+
+  # Execute extra commands
+  for cmd in $commands; do
+    if zvm_exist_command ${cmd}; then
+      cmd="$cmd ${@:2}"
+    fi
+    eval $cmd
+  done
+}
+
+# Initialize this plugin according to the mode
+case $ZVM_INIT_MODE in
+  sourcing) zvm_init;;
+  *) precmd_functions+=(zvm_init);;
+esac
+
diff --git a/include/zshmux.zsh b/include/zshmux.zsh
new file mode 100644 (file)
index 0000000..f82c848
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/zsh
+
+# start tmux server if it's not already running
+[[ ! $(pgrep tmux) ]] && tmux start
+
+# exit if already in a tmux session
+[[ -n $TMUX ]] && return
+
+# check if any sessions exit
+if [[ $(tmux list-sessions | wc -l) -ne 0 ]]; then
+    if [[ $(tmux list-sessions -F"#{?session_attached,,x}" | grep "x" | wc -l) -eq 1 ]] && [[ -z $SSH_CLIENT ]]; then
+        tmux attach-session -t $(tmux list-sessions -F"#{?session_attached,,#S}"  )
+        return
+    else
+        printf '\033[32;1mCurrent \033[33;1mtmux \033[32;1msessions\033[0m:\n'
+        tmux list-sessions -F"#S - (#{?session_attached,Attached,Not Attached})"
+        printf '\033[34;1mAttach session?\033[0;m: [\033[35;1mname\033[0m] \033[37;2m(empty if none)\033[0m: '
+    fi
+else
+    printf '\033[34;1mNo \033[33;1mtmux \033[34;1msessions active\n'
+    printf '\033[34;1mCreate new session?\033[0;m: [\033[35;1mname\033[0m] \033[37;2m(empty if none)\033[0m: '
+fi
+
+read session
+
+# start requested session - since it uses grep you just have to put enough of a unique identifier to select
+if [[ -n $session ]]; then
+    if tmux list-sessions -F"#S" | grep -q "$session"; then
+        tmux attach-session -t $session 
+        return
+    elif [[ $session == new ]]; then
+        tmux new-session
+        return
+    else
+        tmux new-session -s $session
+        return
+    fi
+fi
+
+return