about summary refs log tree commit diff
diff options
context:
space:
mode:
authormidchildan <git@midchildan.org>2024-02-03 01:28:00 +0900
committerOliver Kiddle <opk@zsh.org>2024-02-15 15:40:37 +0100
commitfb9a7cc5dd07910afa7ebea174b379350cae4c91 (patch)
tree25584d9a0a71ec0a7bce61e8c14dda6b8ee32aa9
parent2d2086557ef80117e939253a68580abe07c89fe2 (diff)
downloadzsh-fb9a7cc5dd07910afa7ebea174b379350cae4c91.tar.gz
zsh-fb9a7cc5dd07910afa7ebea174b379350cae4c91.tar.xz
zsh-fb9a7cc5dd07910afa7ebea174b379350cae4c91.zip
52520: add new features and improvements to the "incarg" ZLE widget
- Decrement integers without defining a new widget
- Preserve the number of leading zeros
- Increment binaries, octals, and hexadecimals
- Move the cursor to the end of the incremented integer
- Create a sequence of integers across terminal panes
- Add a Vim variant
- Also add tests
-rw-r--r--ChangeLog4
-rw-r--r--Doc/Zsh/contrib.yo30
-rw-r--r--Functions/Zle/incarg273
-rw-r--r--Test/X05zleincarg.ztst360
4 files changed, 630 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index 36029a2a8..faae11c80 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2024-02-15  Oliver Kiddle  <opk@zsh.org>
 
+	* 52520: midchildan: Doc/Zsh/contrib.yo, Functions/Zle/incarg,
+	Test/X05zleincarg.ztst: add new features and improvements to the
+	"incarg" ZLE widget
+
 	* github #112: Poncho: Completion/Unix/Command/_todo.sh:
 	Completion: todo.sh uses shorthelp and not showhelp
 
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index e1781a5e1..718686587 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2620,12 +2620,30 @@ zle -N history-pattern-search-forward history-pattern-search)
 tindex(incarg)
 vindex(incarg, use of)
 item(tt(incarg))(
-Typing the keystrokes for this widget with the cursor placed on or to the
-left of an integer causes that integer to be incremented by one.  With a
-numeric argument, the number is incremented by the amount of the
-argument (decremented if the numeric argument is negative).  The shell
-parameter tt(incarg) may be set to change the default increment to
-something other than one.
+This widget allows you to increment integers on the current line. In addition
+to decimals, it can handle hexadecimals prefixed with tt(0x), binaries with
+tt(0b), and octals with tt(0o).
+
+By default, the target integer will be incremented by one. With a numeric
+argument, the integer is incremented by the amount of the argument. The shell
+parameter tt(incarg) may be set to change the default increment to something
+other than one.
+
+The behavior of this widget changes depending on the widget name.
+
+When the widget is named tt(incarg), the widget will increment an integer
+placed under the cursor placed or just to the left of it. tt(decarg), on the
+other hand, decrements the integer. When the name is prefixed with tt(vim-),
+the cursor will jump to the nearest integer after the cursor before incrementing
+it.
+
+There's also a tt(sync-) prefix that can be added to the widget name. This
+variant is used for creating a sequence of numbers on split terminals with
+synchronized key input. The first pane won't increment the integer at all, but
+each pane after that will have the integer incremented once more than the
+previous pane. It currently supports tmux and iTerm2.
+
+The prefixes tt(vim-) and tt(sync-) can be combined into tt(vim-sync-).
 
 example(bindkey '^X+' incarg)
 )
diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg
index cff0cfe4c..1131b148b 100644
--- a/Functions/Zle/incarg
+++ b/Functions/Zle/incarg
@@ -1,43 +1,254 @@
-# Shell function to increment an integer either under the cursor or just
-# to the left of it.  Use
+emulate -L zsh
+
+# A ZLE widget to increment an integer.
+#
+# In addition to decimals, it can handle hexadecimals prefixed with "0x",
+# binaries with "0b", and octals with "0o".
+#
+# By default, the target integer will be incremented by one. With a numeric
+# argument, the integer is incremented by the amount of the argument. The shell
+# parameter "incarg" may be set to change the default increment to something
+# other than one.
+#
+# The behavior of this widget changes depending on how it is named.
+#
+# - incarg / decarg
+#
+#   incarg will increment an integer either under the cursor or just to the left
+#   of it. decarg, on the other hand, will decrement it.
+#
+#   For example,
+#
+#       echo 41
+#            ^^^ cursor anywhere here
+#
+#   with incarg gives
+#
+#       echo 42
+#              ^ cursor will move here
+#
+# - sync-incarg / sync-decarg
+#
+#   The sync- variant is used for creating a sequence of numbers on split
+#   terminals with synchronized key input. The first pane won't be incremented
+#   at all, but each pane after that will have the number incremented once more
+#   than the previous pane.
+#
+#   Currently supports tmux and iTerm2.
+#
+# - vim-incarg / vim-decarg
+#
+#   This behaves like Vim's CTRL-A / CTRL-X. It moves the cursor to the nearest
+#   number after the cursor and increments or decrements it.
+#
+# - vim-sync-incarg / vim-sync-decarg
+#
+#   This combines the behavior of the vim- and sync- variants. It's inspired by
+#   Vim's g_CTRL-A / g_CTRL-X.
+#
+# Example Usage:
+#
 #   autoload -Uz incarg
-#   zle -N incarg
-#   bindkey "..." incarg
-# to define it.  For example,
-#   echo 41
-#        ^^^ cursor anywhere here
-# with incarg gives
-#   echo 42
-# with the cursor in the same place.
-#
-# A numeric argument gives a number other than 1 to add (may be negative).
-# If you're going to do it a lot with one particular number, you can set
-# the parameter incarg to that number (a numeric argument still takes
-# precedence).
+#   for widget in vim-{,sync-}{inc,dec}arg; do
+#     zle -N "$widget" incarg
+#   done
+#   bindkey -a \
+#     '^A' vim-incarg \
+#     '^X' vim-decarg \
+#     'g^A' vim-sync-incarg \
+#     'g^X' vim-sync-decarg
 
-emulate -L zsh
-setopt extendedglob
+setopt localoptions extended_glob
+local match mbegin mend MATCH MBEGIN MEND i
 
-local rrest lrest num
+# find the number and determine the base
+integer pos=$(( CURSOR + 1 )) base=0
 
-rrest=${RBUFFER##[0-9]#}
-if [[ $RBUFFER = [0-9]* ]]; then
-  if [[ -z $rrest ]]; then
-    num=$RBUFFER
-  else
-    num=${RBUFFER[1,-$#rrest-1]}
+# avoid miscalculating positions when cursor is at the end of the line
+while (( pos > 0 )) && [[ "$BUFFER[pos]" == '' ]]; do
+  (( pos-- ))
+done
+
+# check for a prefix (e.g., 0x) before the cursor
+for (( i = 0; i < 2; i++ )); do
+  case "$BUFFER[1,pos]" in
+    *0[xX][0-9a-fA-F]##) base=16 ;;
+    *0[oO][0-7]##) base=8 ;;
+    *0[bB][01]##) base=2 ;;
+    *[1-9]) base=10 ;;
+    *0) ;; # there may be a prefix right after the cursor
+    *)
+      # the non-Vim variant looks right before the cursor too, but not after it
+      if [[ "$WIDGET" != vi* ]]; then
+        if (( i == 0 )); then
+          (( pos-- ))
+          continue
+        else
+          return 1
+        fi
+      fi
+      ;;
+  esac
+
+  break
+done
+
+# check for a prefix on the cursor
+if (( base == 0 && pos < $#BUFFER )); then
+  case "$BUFFER[1,pos+1]" in
+    *0[xX][0-9a-fA-F]) base=16; (( pos++ )) ;;
+    *0[oO][0-7]) base=8; (( pos++ )) ;;
+    *0[bB][01]) base=2; (( pos++ )) ;;
+  esac
+fi
+
+if (( base == 0 )); then
+  if [[ "$WIDGET" == vi* ]]; then
+    # jump to the nearest number after the cursor
+    while [[ "$BUFFER[pos]" == [^0-9] ]]; do
+      (( pos++ ))
+      (( pos > $#BUFFER )) && return 1
+    done
   fi
+
+  # check for a prefix right after the cursor and jump right after it, if any
+  if (( pos <= 1 )) || [[ "$BUFFER[pos-1]" == [^0-9] ]]; then
+    case "$BUFFER[pos,-1]" in
+      0[xX][0-9a-fA-F]*) base=16; (( pos += 2 )) ;;
+      0[oO][0-7]*) base=8; (( pos += 2 )) ;;
+      0[bB][01]*) base=2; (( pos += 2 )) ;;
+    esac
+  fi
+fi
+
+if (( base == 0 )); then
+  base=10
 fi
 
-lrest=${LBUFFER%%[0-9]#}
-if [[ $LBUFFER = *[0-9] ]]; then
-  if [[ -z $lrest ]]; then
-    num="$LBUFFER$num"
+# find the start of the number
+integer first="$pos"
+case "$base" in
+  10)
+    while [[ "$BUFFER[first-1]" == [0-9] ]]; do
+      (( first-- ))
+    done
+    if [[ $BUFFER[first-1] = - ]]; then
+      (( first-- ))
+    fi
+    ;;
+  2)
+    while [[ "$BUFFER[first-1]" == [01] ]]; do
+      (( first-- ))
+    done
+    ;;
+  8)
+    while [[ "$BUFFER[first-1]" == [0-7] ]]; do
+      (( first-- ))
+    done
+    ;;
+  16)
+    while [[ "$BUFFER[first-1]" == [0-9a-fA-F] ]]; do
+      (( first-- ))
+    done
+    ;;
+esac
+
+# find the end of the number
+integer last="$pos"
+case "$base" in
+  10)
+    while [[ "$BUFFER[last+1]" == [0-9] ]]; do
+      (( last++ ))
+    done
+    ;;
+  2)
+    while [[ "$BUFFER[last+1]" == [01] ]]; do
+      (( last++ ))
+    done
+    ;;
+  8)
+    while [[ "$BUFFER[last+1]" == [0-7] ]]; do
+      (( last++ ))
+    done
+    ;;
+  16)
+    while [[ "$BUFFER[last+1]" == [0-9a-fA-F] ]]; do
+      (( last++ ))
+    done
+    ;;
+esac
+
+# calculate the number of digits
+integer ndigits=0
+case "$BUFFER[first,first+1]" in
+  0*|-0) ndigits=$(( last - first + 1 )) ;;
+esac
+
+# determine the amount to increment
+integer delta=${NUMERIC:-${incarg:-1}}
+if [[ "$WIDGET" = *decarg ]]; then
+  (( delta = -delta ))
+fi
+if [[ "$WIDGET" = *sync-* ]]; then
+  integer pane_index=0
+  if [[ -n "$TMUX_PANE" ]]; then
+    pane_index="$(tmux display-message -pt "$TMUX_PANE" '#{pane_index}')"
+  elif [[ "$ITERM_SESSION_ID" =~ '^w[0-9]+t[0-9]+p([0-9]+)' ]]; then
+    pane_index="$match[1]"
   else
-    num="${LBUFFER[$#lrest+1,-1]}$num"
+    zle -M "[$WIDGET] unsupported terminal"
+    return 1
+  fi
+  (( delta *= pane_index ))
+fi
+
+local old="$BUFFER[first,last]"
+integer oldlen=$#BUFFER
+
+local fmt1 fmt2
+case "$base" in
+  10) fmt1=d; fmt2='#10' ;;
+  2) fmt1=s; fmt2='##2' ;;
+  8) fmt1=s; fmt2='##8' ;;
+  16) fmt1="$BUFFER[first-1]"; fmt2='#16' ;;
+esac
+
+local raw_result padded
+raw_result="$( \
+  printf "%0$ndigits$fmt1" $(( [$fmt2] "$base#$old" + delta )) 2> /dev/null)"
+padded="${raw_result// /0}"
+
+integer oldnum="$base#$old" newnum="$base#$padded" 2> /dev/null
+if (( base != 10 && newnum < 0
+        || delta > 0 && newnum < oldnum
+        || delta < 0 && newnum > oldnum  )); then
+  zle -M "[$WIDGET] The resulting number is either too big or too small."
+  return 1
+fi
+
+# adjust the number of leading zeros if the sign of the integer changed
+local new
+if (( base == 10 && ndigits == $#padded )); then
+  if (( oldnum < 0 && newnum >= 0 )); then
+    new="${padded#0}"
+  elif (( oldnum >= 0 && newnum < 0 )); then
+    new="-0${padded#-}"
   fi
 fi
+if [[ -z "$new" ]]; then
+  new="$padded"
+fi
+
+if zstyle -t ":zle:$WIDGET" debug; then
+  zle -M "[$WIDGET] base: $base delta: $delta old: '$old' new: '$new'"
+fi
+
+BUFFER[first,last]="$new"
 
-[[ -n $num ]] && (( num += ${NUMERIC:-${incarg:-1}} ))
+integer offset=0
+if [[ "$WIDGET" == vi* ]]; then
+  offset=-1
+fi
+(( CURSOR = last + $#BUFFER - oldlen + offset ))
 
-BUFFER="$lrest$num$rrest"
+return 0
diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst
new file mode 100644
index 000000000..2a6aa2d3f
--- /dev/null
+++ b/Test/X05zleincarg.ztst
@@ -0,0 +1,360 @@
+# Tests the incarg ZLE widget
+
+%prep
+  ZSH_TEST_LANG=$(ZTST_find_UTF8)
+  if ( zmodload zsh/zpty 2>/dev/null ); then
+    . $ZTST_srcdir/comptest
+    comptestinit -v -z $ZTST_testdir/../Src/zsh
+  else
+    ZTST_unimplemented="the zsh/zpty module is not available"
+  fi
+  zpty_run '
+    autoload -Uz incarg
+    for name in {,vim-}{,sync-}{inc,dec}arg; do
+      zle -N "$name" incarg
+    done
+    bindkey -v "^N" incarg
+    bindkey -v "^P" decarg
+    bindkey -v "^F" sync-incarg
+    bindkey -v "^B" sync-decarg
+    bindkey -a "^N" vim-incarg
+    bindkey -a "^P" vim-decarg
+    bindkey -a "^F" vim-sync-incarg
+    bindkey -a "^B" vim-sync-decarg
+    unset TMUX_PANE ITERM_SESSION_ID
+    tmux() {
+      echo "$TMUX_PANE"
+    }
+  '
+
+%test
+
+  zletest $'0\C-n'
+0:increment an integer with incarg
+>BUFFER: 1
+>CURSOR: 1
+
+  zletest $'0\C-p'
+0:decrement an integer with decarg
+>BUFFER: -1
+>CURSOR: 2
+
+  zletest $'echo 0\e0\C-n'
+0:increment an integer with vim-incarg
+>BUFFER: echo 1
+>CURSOR: 5
+
+  zletest $'echo 0\e0\C-p'
+0:decrement an integer with vim-decarg
+>BUFFER: echo -1
+>CURSOR: 6
+
+  zletest $'0\C-f'
+0:sync-incarg does nothing on unsupported terminals
+>BUFFER: 0
+>CURSOR: 1
+
+  zpty_run 'TMUX_PANE=0'
+  zletest $'0\C-f'
+  zpty_run 'unset TMUX_PANE'
+0:sync-incarg on tmux in pane 0
+>BUFFER: 0
+>CURSOR: 1
+
+  zpty_run 'TMUX_PANE=1'
+  zletest $'0\C-f'
+  zpty_run 'unset TMUX_PANE'
+0:sync-incarg on tmux in pane 1
+>BUFFER: 1
+>CURSOR: 1
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'0\C-f'
+  zpty_run 'unset TMUX_PANE'
+0:sync-incarg on tmux in pane 2
+>BUFFER: 2
+>CURSOR: 1
+
+  zpty_run 'ITERM_SESSION_ID=w0t0p0:00000000-0000-0000-0000-000000000000'
+  zletest $'0\C-f'
+  zpty_run 'unset ITERM_SESSION_ID'
+0:sync-incarg on tmux in pane 0
+>BUFFER: 0
+>CURSOR: 1
+
+  zpty_run 'ITERM_SESSION_ID=w0t0p1:00000000-0000-0000-0000-000000000000'
+  zletest $'0\C-f'
+  zpty_run 'unset ITERM_SESSION_ID'
+0:sync-incarg on tmux in pane 1
+>BUFFER: 1
+>CURSOR: 1
+
+  zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000'
+  zletest $'0\C-f'
+  zpty_run 'unset ITERM_SESSION_ID'
+0:sync-incarg on tmux in pane 2
+>BUFFER: 2
+>CURSOR: 1
+
+  zpty_run 'TMUX_PANE=1'
+  zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000'
+  zletest $'0\C-f'
+  zpty_run 'unset TMUX_PANE ITERM_SESSION_ID'
+0:tmux pane number takes precedence over iTerm2's
+>BUFFER: 1
+>CURSOR: 1
+
+  zletest $'0\e2\C-n'
+0:Providing a numeric argument will change the incremented amount
+>BUFFER: 2
+>CURSOR: 0
+
+  zpty_run 'incarg=3'
+  zletest $'0\e\C-n'
+  zpty_run 'unset incarg'
+0:Setting the incarg variable will change the default incremented amount
+>BUFFER: 3
+>CURSOR: 0
+
+  zpty_run 'incarg=3'
+  zletest $'0\e2\C-n'
+  zpty_run 'unset incarg'
+0:A numeric argument will take precedence over the incarg variable
+>BUFFER: 2
+>CURSOR: 0
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'0\e2\C-f'
+  zpty_run 'unset TMUX_PANE'
+0:Providing a numeric argument will work for the sync- variants of incarg
+>BUFFER: 4
+>CURSOR: 0
+
+  zletest $'000\C-n'
+0:Incrementing a decimal integer preserves leading zeros
+>BUFFER: 001
+>CURSOR: 3
+
+  zletest $'-001\C-n\C-n'
+0:Leading zeros are preserved when the digit turns from negative to positive
+>BUFFER: 001
+>CURSOR: 3
+
+  zletest $'001\C-p\C-p'
+0:Leading zeros are preserved when the digit turns from positive to negative
+>BUFFER: -001
+>CURSOR: 4
+
+  zletest $'001\e1000\C-n'
+0:Incrementing an integer works when the result has more zeros than the original
+>BUFFER: 1001
+>CURSOR: 3
+
+  zletest $'001\e2000\C-p'
+0:Decrementing an integer with leading zeros works when the result has more digits than the original
+>BUFFER: -1999
+>CURSOR: 4
+
+  zletest $'0b11\C-n'
+0:Increment a binary integer
+>BUFFER: 0b100
+>CURSOR: 5
+
+  zletest $'0B11\C-n'
+0:Increment a binary integer with an upper case prefix
+>BUFFER: 0B100
+>CURSOR: 5
+
+  zletest $'0b100\C-p'
+0:Decrement a binary integer
+>BUFFER: 0b11
+>CURSOR: 4
+
+  zletest $'0b0011\C-n'
+0:Increment a binary integer preserves leading zeros
+>BUFFER: 0b0100
+>CURSOR: 6
+
+  zletest $'0b001\e8\C-n'
+0:Incrementing a binary integer work when the result has more zeros than the original
+>BUFFER: 0b1001
+>CURSOR: 5
+
+  zletest $'0b0\C-p'
+0:Decrementing a binary integer to a negative value will fail
+>BUFFER: 0b0
+>CURSOR: 3
+
+  zletest $'0o7\C-n'
+0:Increment an octal integer
+>BUFFER: 0o10
+>CURSOR: 4
+
+  zletest $'0O7\C-n'
+0:Increment an octal integer with an upper case prefix
+>BUFFER: 0O10
+>CURSOR: 4
+
+  zletest $'0o10\C-p'
+0:Decrement an octal integer
+>BUFFER: 0o7
+>CURSOR: 3
+
+  zletest $'0o0\C-p'
+0:Decrementing an octal integer to a negative value will fail
+>BUFFER: 0o0
+>CURSOR: 3
+
+  zletest $'0x9\C-n'
+0:Increment a hexadecimal integer
+>BUFFER: 0xa
+>CURSOR: 3
+
+  zletest $'0X9\C-n'
+0:Increment a hexadecimal integer with an upper case prefix
+>BUFFER: 0XA
+>CURSOR: 3
+
+  zletest $'0xf\C-n'
+0:Increment a hexadecimal integer with no numeric digit
+>BUFFER: 0x10
+>CURSOR: 4
+
+  zletest $'0x10\C-p'
+0:Decrement a hexadecimal integer
+>BUFFER: 0xf
+>CURSOR: 3
+
+  zletest $'0x0\C-p'
+0:Decrementing an octal integer to a negative value will fail
+>BUFFER: 0x0
+>CURSOR: 3
+
+  zletest $'0x0b1\C-n'
+0:a number that starts with 0x0b is interpreted as a hexadecimal integer
+>BUFFER: 0x0b2
+>CURSOR: 5
+
+  zletest $'10x9\e0\C-n'
+0:[0-9]0x[0-9a-f] will become [0-9]1x[0-9a-f] when incremented from the left of x
+>BUFFER: 11x9
+>CURSOR: 1
+
+  zletest $'10x9\eFx\C-n'
+0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-n'
+0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-n'
+0:[0-9]0b[01] will become [0-9]1b[01] when incremented from the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-n'
+0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-n'
+0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-n'
+0:[0-9]0o[0-7] will become [0-9]1o[0-7] when incremented from the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-n'
+0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-n'
+0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-n'
+0:0b0x[0-9a-f] will increment the binary 0b0 when the cursor is on the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-n'
+0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on top of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-n'
+0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'echo 012ab\eF i\C-n'
+0:incarg does nothing when the cursor is placed just to the left of an integer
+>BUFFER: echo 012ab
+>CURSOR: 4
+
+  zletest $'echo 012ab\eF0i\C-n'
+0:incarg works when the cursor is placed at the leftmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 8
+
+  zletest $'echo 012ab\eF1i\C-n'
+0:incarg works when the cursor is placed at the inner digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 8
+
+  zletest $'echo 012ab\eF2i\C-n'
+0:incarg works when the cursor is placed at the rightmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 8
+
+  zletest $'echo 012ab\eFai\C-n'
+0:incarg works when the cursor is placed just to the right of an integer
+>BUFFER: echo 013ab
+>CURSOR: 8
+
+  zletest $'echo 012ab\ei\C-n'
+0:incarg does nothing when the cursor is placed more than a single letter away to the right
+>BUFFER: echo 012ab
+>CURSOR: 9
+
+  zletest $'echo 012ab\eF \C-n'
+0:vim-incarg works when the cursor is placed to the left of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF0\C-n'
+0:vim-incarg works when the cursor is placed at the leftmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF1\C-n'
+0:vim-incarg works when the cursor is placed at the inner digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF2\C-n'
+0:incarg works when the cursor is placed at the rightmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eFa\C-n'
+0:vim-incarg does nothing when the cursor is placed to the right of an integer
+>BUFFER: echo 012ab
+>CURSOR: 8
+
+  zletest $'echo 012ab\ei\C-n'
+0:vim-incarg does nothing when the cursor is placed more than a single letter away to the right
+>BUFFER: echo 012ab
+>CURSOR: 9
+
+%clean
+
+  zmodload -ui zsh/zpty