Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: vi mode text objects for brackets/quotes



This adds two shell function implementations of Vim-style text objects.
select-bracketed is for matching characters between matching brackets
and select-quoted matches between quote characters. It'll also work with
other delimiter characters such as perhaps a comma. The functionality of
both of these is builtin to vim (i.e. not a plugin). A third function,
surround, is like the popular vim plugin for adding, removing or
deleting "surroundings", i.e. quotes or brackets. I had hoped that these
would serve as useful examples but trying to get good Vim compatibility
in subtle situations resulted in them becoming more complicated.

I've not written any documentation other than comments at the top of the
files but this does add two NEWS entries covering the vi-mode changes
since 5.0.7.

Oliver

diff --git a/NEWS b/NEWS
index 86b0bd4..55b1be7 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,15 @@ Changes from 5.0.7 to 5.0.8
   as a reference to a variable, e.g. ${(ps.$sep.)foo} to split $foo
   on a string given by $sep.
 
+- The default binding of 'u' in vi command mode has changed to undo
+  multiple changes when invoked repeatedly. '^R' is now bound to redo
+  changes. To revert to toggling of the last edit use:
+    bindkey -a u vi-undo-change
+
+- Compatibility with Vim has been improved for vi editing mode. Most
+  notably, Vim style text objects are supported and the region can be
+  manipulated with vi commands in the same manner as Vim's visual mode.
+
 - Elements of the watch variable may now be patterns.
 
 - The logic for retrying history locking has been improved.
diff --git a/Functions/Zle/select-bracketed b/Functions/Zle/select-bracketed
new file mode 100644
index 0000000..00f51be
--- /dev/null
+++ b/Functions/Zle/select-bracketed
@@ -0,0 +1,56 @@
+# Text object for matching characters between matching pairs of brackets
+#
+# So for example, given (( i+1 )), the vi command ci( will change
+# all the text between matching colons.
+#
+# The following is an example of how to enable this:
+#     autoload -U select-bracketed
+#     zle -N select-bracketed
+#     for m in visual viopp; do
+#	for c in {a,i}${(s..)^:-'()[]{}<>bB'}; do
+#	  bindkey -M $m $c select-bracketed
+#	done
+#     done
+
+local style=${${1:-$KEYS}[1]} matching="(){}[]<>bbBB"
+local -i find=${NUMERIC:-1} idx=${matching[(I)[${${1:-$KEYS}[2]}]]}%9
+(( idx )) || return 1 # no corresponding closing bracket
+local lmatch=${matching[1 + (idx-1) & ~1]}
+local rmatch=${matching[1 + (idx-1) | 1]}
+local -i start=CURSOR+1 end=CURSOR+1 rfind=find
+
+[[ $BUFFER[start] = "$rmatch" ]] && (( start--, end-- ))
+if (( REGION_ACTIVE  && MARK != CURSOR)); then
+  (( MARK < CURSOR && (start=end=MARK+1) ))
+  local -i origstart=start-1
+  [[ $style = i ]] && (( origstart-- ))
+fi
+
+while (( find )); do
+  for (( ; find && start; --start )); do
+    case $BUFFER[start] in
+      "$lmatch") (( find-- )) ;;
+      "$rmatch") (( find++ )) ;;
+    esac
+  done
+
+  (( find )) && return 1 # opening bracket not found
+
+  while (( rfind && end++ < $#BUFFER )); do
+    case $BUFFER[end] in
+      "$lmatch") (( rfind++ )) ;;
+      "$rmatch") (( rfind-- )) ;;
+    esac
+  done
+
+  (( rfind )) && return 1 # closing bracket not found
+
+  (( REGION_ACTIVE && MARK != CURSOR && start >= origstart &&
+    ( find=rfind=${NUMERIC:-1} ) ))
+done
+
+[[ $style = i ]] && (( start++, end-- ))
+(( REGION_ACTIVE = !!REGION_ACTIVE ))
+[[ $KEYMAP = vicmd ]] && (( REGION_ACTIVE && end-- ))
+MARK=$start
+CURSOR=$end
diff --git a/Functions/Zle/select-quoted b/Functions/Zle/select-quoted
new file mode 100644
index 0000000..904f1e4
--- /dev/null
+++ b/Functions/Zle/select-quoted
@@ -0,0 +1,71 @@
+# Text object for matching characters between a particular delimiter
+#
+# So for example, given "text", the vi command vi" will select
+# all the text between the double quotes
+#
+# The following is an example of how to enable this:
+#     autoload -U select-quoted
+#     zle -N select-quoted
+#     for m in visual viopp; do
+#	for c in {a,i}{\',\",\`}; do
+#	  bindkey -M $m $c select-quoted
+#	done
+#     done
+
+setopt localoptions noksharrays
+
+local matching=${${1:-$KEYS}[2]}
+local -i start=CURSOR+2 end=CURSOR+2 found=0 alt=0 count=0
+
+if ((REGION_ACTIVE )); then
+  if (( MARK < CURSOR )); then
+    start=MARK+2
+  else
+    end=MARK+2
+  fi
+fi
+
+[[ $BUFFER[CURSOR+1] = $matching && $BUFFER[CURSOR] != \\ ]] && count=1
+while (( (count || ! alt) && --start )) && [[ $BUFFER[start] != $'\n' ]]; do
+  if [[ $BUFFER[start] = "$matching" ]]; then
+    if [[ $BUFFER[start-1] = \\ ]]; then
+      (( start-- ))
+    elif (( ! found )); then
+      found=start
+    else
+      (( ! alt )) && alt=start
+      (( count && ++count ))
+    fi
+  fi
+done
+
+for (( start=CURSOR+2; ! found && start+1 < $#BUFFER; start++ )); do
+  case $BUFFER[start] in
+    $'\n') return 1 ;;
+    \\) (( start++ )) ;;
+    "$matching")
+      (( end=start+1, found=start ))
+    ;;
+  esac
+done
+
+[[ $BUFFER[end-1] = \\ ]] && (( end++ ))
+until [[ $BUFFER[end] == "$matching" ]]; do
+  [[ $BUFFER[end] = \\ ]] && (( end++ ))
+  if [[ $BUFFER[end] = $'\n' ]] || (( ++end > $#BUFFER )); then
+    end=0
+    break
+  fi
+done
+
+if (( alt && (!end || count == 2) )); then
+  end=found
+  found=alt
+fi
+(( end )) || return 1
+
+[[ ${${1:-$KEYS}[1]} = a ]] && (( found-- )) || (( end-- ))
+(( REGION_ACTIVE = !!REGION_ACTIVE ))
+[[ $KEYMAP = vicmd ]] && (( REGION_ACTIVE && end-- ))
+MARK=found
+CURSOR=end
diff --git a/Functions/Zle/surround b/Functions/Zle/surround
new file mode 100644
index 0000000..b7be30b
--- /dev/null
+++ b/Functions/Zle/surround
@@ -0,0 +1,75 @@
+# Implementation of some functionality of the popular vim surround plugin.
+# Allows "surroundings" to be changed: parentheses, brackets and quotes.
+
+# To use
+#   autoload -Uz surround
+#   zle -N delete-surround surround
+#   zle -N add-surround surround
+#   zle -N change-surround surround
+#   bindkey -a cs change-surround
+#   bindkey -a ds delete-surround
+#   bindkey -a ys add-surround
+#   bindkey -M visual S add-surround
+#
+#  This doesn't allow yss to operate on a line but VS will work
+
+setopt localoptions noksharrays
+autoload -Uz select-quoted select-bracketed
+local before after
+local -A matching
+matching=( \( \) \{ \} \< \> \[ \] )
+
+case $WIDGET in
+  change-*)
+    local MARK="$MARK" CURSOR="$CURSOR" call
+    read -k 1 before
+    if [[ ${(kvj::)matching} = *$before* ]]; then
+      call=select-bracketed
+    else
+      call=select-quoted
+    fi
+    read -k 1 after
+    $call "a$before" || return 1
+    before="$after"
+    if [[ -n $matching[$before] ]]; then
+      after=" $matching[$before]"
+      before+=' '
+    elif [[ -n $matching[(r)[$before:q]] ]]; then
+      before="${(k)matching[(r)[$before:q]]}"
+    fi
+    BUFFER[CURSOR]="$after"
+    BUFFER[MARK+1]="$before"
+    CURSOR=MARK
+  ;;
+  delete-*)
+    local MARK="$MARK" CURSOR="$CURSOR" call
+    read -k 1 before
+    if [[ ${(kvj::)matching} = *$before* ]]; then
+      call=select-bracketed
+    else
+      call=select-quoted
+    fi
+    if $call "a$before"; then
+      BUFFER[CURSOR]=''
+      BUFFER[MARK+1]=''
+      CURSOR=MARK
+    fi
+  ;;
+  add-*)
+    local save_cut="$CUTBUFFER"
+    zle .vi-change || return
+    local save_cur="$CURSOR"
+    zle .vi-cmd-mode
+    read -k 1 before
+    after="$before"
+    if [[ -n $matching[$before] ]]; then
+      after=" $matching[$before]"
+      before+=' '
+    elif [[ -n $matching[(r)[$before:q]] ]]; then
+      before="${(k)matching[(r)[$before:q]]}"
+    fi
+    CUTBUFFER="$before$CUTBUFFER$after"
+    zle .vi-put-after -n 1
+    CUTBUFFER="$save_cut" CURSOR="$save_cur"
+  ;;
+esac



Messages sorted by: Reverse Date, Date, Thread, Author