zsh-workers
 help / color / mirror / code / Atom feed
* [PATCH] add new features and improvements to the "incarg" ZLE widget
@ 2024-02-04 19:19 midchildan
  2024-02-05  0:36 ` Bart Schaefer
  2024-02-15 15:30 ` Oliver Kiddle
  0 siblings, 2 replies; 9+ messages in thread
From: midchildan @ 2024-02-04 19:19 UTC (permalink / raw)
  To: zsh-workers; +Cc: midchildan

This brings numerous improvements to the incarg ZLE widget that's used
to increment an integer under the cursor. I find myself incrementing
numbers very often when editing commands from shell history. I
originally used incarg for this, but I missed a couple of features from
Vim's increment command. The contents of this PR is what I've eventually
settled on.

Here are the list of features:

* Decrement integers without defining a new widget

Previously, the user had to define their own widget if they wanted to
decrement instead of increment.

    decarg() {
      autoload -Uz incarg
      (( NUMERIC = -${NUMERIC:-${incarg:-1}} ))
      incarg
    }
    zle -N decarg

After this change, it's simply:

    zle -N decarg incarg

* Preserve the number of leading zeros

Previously, incarg omitted leading zeros.

    Before: 001
    After:  2

After this change, leading zeros are preserved.

    Before: 001
    After:  002

* Increment binaries, octals, and hexadecimals

Previously, incarg only recognized decimals. After this change, it'll
recognize 0b, 0o, and 0x prefixes. Both lower case and upper case
prefixes are recognized.

* Move the cursor to the end of the incremented integer

Previously the cursor position didn't change after incrementing. This
lead to annoyances when the number of digits increased:

    Before: 99
              ^ cursor
    After:  100
              ^ cursor

After this change, the cursor will be moved to the immediate right of
the integer.

    Before: 99
              ^ cursor
    After:  100
               ^ cursor

This is probably a nice property even when the number of digits don't
change. If I want to keep on editing the current line right after
incrementing, I typically want to change something on the right of the
integer.

* Create a sequence of integers across terminal panes

This adds a variation of the incarg widget that can be used by prefixing
the widget name with sync-. It's intended to be used on multi-pane
terminals with the keyboard input broadcast to all panes. It increments
the integer in each pane by a different amount, with the next pane
having its integer incremented once more than the previous one.
Supported terminals are tmux and iTerm2.

    zle -N sync-incarg incarg
    zle -N sync-decarg incarg

I often use this to interactively run the same commands on multiple
sequentially numbered servers all at once. It's shown in the demo down
below.

* Add a Vim variant

Vim's increment command jumps to the nearest integer after the cursor
and increments it. I find this behavior very useful when combined with
history-beginning-search-backward. So I made it possible to get this
behavior by prefixing vi to the widget name.

    zle -N vim-incarg incarg
    zle -N vim-decarg decarg
    bindkey -a \
      '^A' vim-incarg \
      '^X' vim-decarg

It can also be combined with the sync- prefix.

    zle -N vim-sync-incarg incarg
    zle -N vim-sync-decarg incarg
    bindkey -a \
      'g^A' vim-sync-incarg \
      'g^X' vim-sync-decarg

I also created a short demo showing how it can be used:

https://asciinema.org/a/ijvvGnXnqsgQn476JIplUxlp8
---
 Doc/Zsh/contrib.yo     |  30 +++-
 Functions/Zle/incarg   | 279 ++++++++++++++++++++++++++++----
 Test/X05zleincarg.ztst | 360 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 629 insertions(+), 40 deletions(-)
 create mode 100644 Test/X05zleincarg.ztst

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index e1781a5e1..6f13a9ebb 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 numberic
+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
-#   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).
-
 emulate -L zsh
-setopt extendedglob
 
-local rrest lrest num
+# 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
+#   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
 
-rrest=${RBUFFER##[0-9]#}
-if [[ $RBUFFER = [0-9]* ]]; then
-  if [[ -z $rrest ]]; then
-    num=$RBUFFER
-  else
-    num=${RBUFFER[1,-$#rrest-1]}
+setopt localoptions extended_glob
+local match mbegin mend MATCH MBEGIN MEND i
+
+# find the number and determine the base
+integer pos=$(( CURSOR + 1 )) base=0
+
+# 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
 
-lrest=${LBUFFER%%[0-9]#}
-if [[ $LBUFFER = *[0-9] ]]; then
-  if [[ -z $lrest ]]; then
-    num="$LBUFFER$num"
-  else
-    num="${LBUFFER[$#lrest+1,-1]}$num"
-  fi
+if (( base == 0 )); then
+  base=10
 fi
 
-[[ -n $num ]] && (( num += ${NUMERIC:-${incarg:-1}} ))
+# 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
 
-BUFFER="$lrest$num$rrest"
+# 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
+    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"
+
+integer offset=0
+if [[ "$WIDGET" == vi* ]]; then
+  offset=-1
+fi
+(( CURSOR = last + $#BUFFER - oldlen + offset ))
+
+return 0
diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst
new file mode 100644
index 000000000..73191278f
--- /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 numberic 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
-- 
2.39.3 (Apple Git-145)



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] add new features and improvements to the "incarg" ZLE widget
  2024-02-04 19:19 [PATCH] add new features and improvements to the "incarg" ZLE widget midchildan
@ 2024-02-05  0:36 ` Bart Schaefer
  2024-02-05 16:16   ` midchildan
  2024-02-15 15:30 ` Oliver Kiddle
  1 sibling, 1 reply; 9+ messages in thread
From: Bart Schaefer @ 2024-02-05  0:36 UTC (permalink / raw)
  To: zsh-workers; +Cc: midchildan

On Sun, Feb 4, 2024 at 11:37 AM midchildan <git@midchildan.org> wrote:
>
> This brings numerous improvements to the incarg ZLE widget

Thanks for sending.  Remarks below, all minor.

> Vim's increment command jumps to the nearest integer after the cursor
> and increments it. I find this behavior very useful when combined with
> history-beginning-search-backward. So I made it possible to get this
> behavior by prefixing vi to the widget name.

Here you say "vi" and in the doc specifically "vim" but in fact any
prefix beginning with "vi" is accepted by the code, I think?

> +  if [[ "$WIDGET" == vi* ]]; then
> +    # jump to the nearest number after the cursor

Should probably be more precise.

> +++ b/Doc/Zsh/contrib.yo
> @@ -2620,12 +2620,30 @@ zle -N history-pattern-search-forward history-pattern-search)
> +By default, the target integer will be incremented by one. With a numberic

Typo: "numeric"

Thanks for including a test.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] add new features and improvements to the "incarg" ZLE widget
  2024-02-05  0:36 ` Bart Schaefer
@ 2024-02-05 16:16   ` midchildan
  0 siblings, 0 replies; 9+ messages in thread
From: midchildan @ 2024-02-05 16:16 UTC (permalink / raw)
  To: zsh-workers; +Cc: Bart Schaefer, midchildan

> Here you say "vi" and in the doc specifically "vim" but in fact any
> prefix beginning with "vi" is accepted by the code, I think?

> Typo: "numeric"

Thanks. Here's the new patch with the fixes.

> Thanks for including a test.

No problem. The existing ZLE testing infrastructure made it very easy to
write one.
---
 Doc/Zsh/contrib.yo     |  31 +++-
 Functions/Zle/incarg   | 279 ++++++++++++++++++++++++++++----
 Test/X05zleincarg.ztst | 360 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 630 insertions(+), 40 deletions(-)
 create mode 100644 Test/X05zleincarg.ztst

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index e1781a5e1..ea00f0ccc 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2620,12 +2620,31 @@ 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(vi),
+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(vi) and tt(sync-) can be combined, for example, into
+tt(vim-sync-). In this case, the tt(vi) prefix should come first.
 
 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
-#   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).
-
 emulate -L zsh
-setopt extendedglob
 
-local rrest lrest num
+# 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
+#   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
 
-rrest=${RBUFFER##[0-9]#}
-if [[ $RBUFFER = [0-9]* ]]; then
-  if [[ -z $rrest ]]; then
-    num=$RBUFFER
-  else
-    num=${RBUFFER[1,-$#rrest-1]}
+setopt localoptions extended_glob
+local match mbegin mend MATCH MBEGIN MEND i
+
+# find the number and determine the base
+integer pos=$(( CURSOR + 1 )) base=0
+
+# 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
 
-lrest=${LBUFFER%%[0-9]#}
-if [[ $LBUFFER = *[0-9] ]]; then
-  if [[ -z $lrest ]]; then
-    num="$LBUFFER$num"
-  else
-    num="${LBUFFER[$#lrest+1,-1]}$num"
-  fi
+if (( base == 0 )); then
+  base=10
 fi
 
-[[ -n $num ]] && (( num += ${NUMERIC:-${incarg:-1}} ))
+# 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
 
-BUFFER="$lrest$num$rrest"
+# 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
+    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"
+
+integer offset=0
+if [[ "$WIDGET" == vi* ]]; then
+  offset=-1
+fi
+(( CURSOR = last + $#BUFFER - oldlen + offset ))
+
+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

base-commit: 698af7bc1387462c8e87767d7eaeb7e30c6f0b2b


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] add new features and improvements to the "incarg" ZLE widget
  2024-02-04 19:19 [PATCH] add new features and improvements to the "incarg" ZLE widget midchildan
  2024-02-05  0:36 ` Bart Schaefer
@ 2024-02-15 15:30 ` Oliver Kiddle
  2024-02-28 19:11   ` [PATCH v3] " midchildan
  1 sibling, 1 reply; 9+ messages in thread
From: Oliver Kiddle @ 2024-02-15 15:30 UTC (permalink / raw)
  To: midchildan; +Cc: zsh-workers

On 5 Feb, midchildan wrote:
> This brings numerous improvements to the incarg ZLE widget that's used
> to increment an integer under the cursor. I find myself incrementing
> numbers very often when editing commands from shell history. I
> originally used incarg for this, but I missed a couple of features from
> Vim's increment command. The contents of this PR is what I've eventually
> settled on.

I have just applied this after doing some testing. 

I have a couple of minor comments that can perhaps be addressed in a
follow-up patch.

If it fails to find a number going forwards in the buffer, it'd be
useful if it could look backwards too. In contrast to an editor, the
cursor is more often at the very end of the buffer in a shell.

I think that just adding zle -f vichange to the beginning of the
function it will work together with vi-repeat-change (.) which brings it
closer to ^A/^X in vim.

Oliver


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3] add new features and improvements to the "incarg" ZLE widget
  2024-02-15 15:30 ` Oliver Kiddle
@ 2024-02-28 19:11   ` midchildan
  2024-02-28 20:07     ` [PATCH] incarg: add a backward variant and make it repeatable midchildan
  0 siblings, 1 reply; 9+ messages in thread
From: midchildan @ 2024-02-28 19:11 UTC (permalink / raw)
  To: zsh-workers; +Cc: Oliver Kiddle, midchildan

> If it fails to find a number going forwards in the buffer, it'd be
> useful if it could look backwards too. In contrast to an editor, the
> cursor is more often at the very end of the buffer in a shell.

I now added a new variant of the widget that consistently jumps
backward. This can be enabled by including "backward-" in the widget
name. Whether a user needs a forward or backward variant likely depends
on where the history widget places the cursor. I always need forward
jumps because the history widget I use,
history-beginning-search-backward, doesn't move the cursor. In contrast,
people using up-line-or-history need backward jumps because the cursor
goes to the end of the line.

I didn't add logic to reverse the direction when it fails to find a
number because it would complicate the implementation. The complication
comes from the fact that different logic are required for backward and
forward searching due to support for binary, octal, hexadecimal, and
negative numbers. To keep it relatively simple, the choice was to
either provide a backward variant or to only provide a forward variant
that falls back to backward jumps. I chose the former because I thought
it provided more convenience.

> I think that just adding zle -f vichange to the beginning of the
> function it will work together with vi-repeat-change (.) which brings it
> closer to ^A/^X in vim.

Thanks for the tip. I updated the patch to include this.
---
 Doc/Zsh/contrib.yo     |  33 ++-
 Functions/Zle/incarg   | 327 ++++++++++++++++++++++---
 Test/X05zleincarg.ztst | 528 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 848 insertions(+), 40 deletions(-)
 create mode 100644 Test/X05zleincarg.ztst

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index e1781a5e1..e682c800a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2620,12 +2620,33 @@ 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(vi),
+the cursor will jump to the nearest integer after the cursor before incrementing
+it. The tt(vi) prefix can also be combined with a tt(backward-) prefix to make
+the widget search backwards for numbers.
+
+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(vi), tt(backward-), and tt(sync-) can be combined, for example,
+into tt(vim-sync-) or tt(vim-backward-sync-). The tt(vi) prefix needs to be
+at the very beginning.
 
 example(bindkey '^X+' incarg)
 )
diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg
index cff0cfe4c..1f5861b79 100644
--- a/Functions/Zle/incarg
+++ b/Functions/Zle/incarg
@@ -1,43 +1,302 @@
-# Shell function to increment an integer either under the cursor or just
-# to the left of it.  Use
-#   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).
-
 emulate -L zsh
-setopt extendedglob
 
-local rrest lrest num
+# 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-backward-incarg / vim-backward-decarg
+#
+#   This behaves like vim-incarg & vim-decarg, but it searches backwards for a
+#   number.
+#
+# - 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.
+#
+# - vim-backward-sync-incarg / vim-backward-sync-decarg
+#
+#   This combines the behavior of the vim-backward- and sync- variants.
+#
+# Example Usage:
+#
+#   autoload -Uz incarg
+#   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
 
-rrest=${RBUFFER##[0-9]#}
-if [[ $RBUFFER = [0-9]* ]]; then
-  if [[ -z $rrest ]]; then
-    num=$RBUFFER
-  else
-    num=${RBUFFER[1,-$#rrest-1]}
+zle -f vichange
+
+setopt localoptions extended_glob
+local match mbegin mend MATCH MBEGIN MEND i
+
+[[ -z "$BUFFER" ]] && return 1
+
+# find the number and determine the base
+integer pos=$(( CURSOR + 1 )) base=0
+
+# 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
+    if [[ "$WIDGET" == *backward-* ]]; then
+      # search backwards for a number
+      while true; 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 ;;
+          *[0-9]) base=10 ;;
+          *-)
+            case "$BUFFER[pos,-1]" in
+              -0[xX][0-9a-fA-F]*) ;;
+              -0[oO][0-7]*) ;;
+              -0[bB][01]*) ;;
+              -[0-9]*) base=10 ;;
+            esac
+            ;;
+        esac
+        (( base != 0 )) && break
+
+        (( pos-- ))
+        (( pos <= 0 )) && return 1
+      done
+    else
+      # jump to the nearest number after the cursor
+      while [[ "$BUFFER[pos]" == [^0-9] ]]; do
+        (( pos++ ))
+        (( pos > $#BUFFER )) && return 1
+      done
+    fi
+  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
 
-lrest=${LBUFFER%%[0-9]#}
-if [[ $LBUFFER = *[0-9] ]]; then
-  if [[ -z $lrest ]]; then
-    num="$LBUFFER$num"
-  else
-    num="${LBUFFER[$#lrest+1,-1]}$num"
-  fi
+if (( base == 0 )); then
+  base=10
 fi
 
-[[ -n $num ]] && (( num += ${NUMERIC:-${incarg:-1}} ))
+# 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
 
-BUFFER="$lrest$num$rrest"
+# 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
+    zle -M "[$WIDGET] unsupported terminal"
+    return 1
+  fi
+  (( delta *= pane_index ))
+fi
+
+local old="$BUFFER[first,last]"
+integer oldlen=$#BUFFER
+integer oldnum="$base#$old" 2> /dev/null
+
+# -00 should increment to 01 instead of 001
+if [[ "$BUFFER[first]" == '-' ]] && (( oldnum == 0 && delta > 0 )); then
+  (( ndigits-- ))
+fi
+
+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 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
+
+if (( 0 < first && first <= last && last <= $#BUFFER )); then
+  BUFFER[first,last]="$new"
+else
+  zle -M "[$WIDGET] The detected location of the integer was invalid. [location=BUFFER[$first,$last]]"
+  return 1
+fi
+
+integer offset=0
+if [[ "$WIDGET" == vi* ]]; then
+  offset=-1
+fi
+(( CURSOR = last + $#BUFFER - oldlen + offset ))
+
+return 0
diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst
new file mode 100644
index 000000000..50a450734
--- /dev/null
+++ b/Test/X05zleincarg.ztst
@@ -0,0 +1,528 @@
+# 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-,vim-backward-}{,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
+    bindkey -a "^E" vim-backward-incarg
+    bindkey -a "^Y" vim-backward-decarg
+    unset TMUX_PANE ITERM_SESSION_ID
+    tmux() {
+      echo "$TMUX_PANE"
+    }
+  '
+
+%test
+
+# Basic increment & decrement
+
+  zletest $'0\C-n'
+0:incarg increments an integer
+>BUFFER: 1
+>CURSOR: 1
+
+  zletest $'0\C-p'
+0:decarg decrements an integer
+>BUFFER: -1
+>CURSOR: 2
+
+  zletest $'echo 0\e0\C-n'
+0:vim-incarg increments an integer
+>BUFFER: echo 1
+>CURSOR: 5
+
+  zletest $'echo 0\e0\C-p'
+0:vim-decarg decrements an integer
+>BUFFER: echo -1
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e\C-e'
+0:vim-backward-incarg increments an integer
+>BUFFER: echo 1 foo
+>CURSOR: 5
+
+  zletest $'echo 0 foo\e\C-y'
+0:vim-backward-decarg decrements an integer
+>BUFFER: echo -1 foo
+>CURSOR: 6
+
+# sync- variants
+
+  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 does nothing 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 increments by 1 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 increments by 2 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 does nothing 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 increments by 1 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 increments by 2 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:sync-incarg prioritizes tmux pane number over iTerm2's
+>BUFFER: 1
+>CURSOR: 1
+
+  zletest $'0\e2\C-n'
+0:incarg changes the incremented amount based on the numeric argument
+>BUFFER: 2
+>CURSOR: 0
+
+  zpty_run 'incarg=3'
+  zletest $'0\e\C-n'
+  zpty_run 'unset incarg'
+0:incarg changes the default incremented amount based on the incarg variable
+>BUFFER: 3
+>CURSOR: 0
+
+  zpty_run 'incarg=3'
+  zletest $'0\e2\C-n'
+  zpty_run 'unset incarg'
+0:incarg prioritizes the numeric argument over the incarg variable
+>BUFFER: 2
+>CURSOR: 0
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'0\e2\C-f'
+  zpty_run 'unset TMUX_PANE'
+0:The sync- variants of incarg takes the numeric argument into account
+>BUFFER: 4
+>CURSOR: 0
+
+# Leading zeros
+
+  zletest $'000\C-n'
+0:incarg preserves leading zeros of decimal integers
+>BUFFER: 001
+>CURSOR: 3
+
+  zletest $'-001\C-n\C-n'
+0:incarg preserves leading zeros when the digit turns from negative to positive
+>BUFFER: 001
+>CURSOR: 3
+
+  zletest $'001\C-p\C-p'
+0:incarg preserves leading zeros when the digit turns from positive to negative
+>BUFFER: -001
+>CURSOR: 4
+
+  zletest $'001\e1000\C-n'
+0:incarg works when the result has more number of digits than the original
+>BUFFER: 1001
+>CURSOR: 3
+
+  zletest $'001\e2000\C-p'
+0:decargs works on integers with leading zeros when the result has more digits than the original
+>BUFFER: -1999
+>CURSOR: 4
+
+# Binaries
+
+  zletest $'0b11\C-n'
+0:incarg can increment a binary integer
+>BUFFER: 0b100
+>CURSOR: 5
+
+  zletest $'0B11\C-n'
+0:incarg can increment a binary integer with an upper case prefix
+>BUFFER: 0B100
+>CURSOR: 5
+
+  zletest $'0b100\C-p'
+0:decarg can decrement a binary integer
+>BUFFER: 0b11
+>CURSOR: 4
+
+  zletest $'0b0011\C-n'
+0:incarg can preserve leading zeros of binaries
+>BUFFER: 0b0100
+>CURSOR: 6
+
+  zletest $'0b001\e8\C-n'
+0:incarg works on binaries when the result has more zeros than the original
+>BUFFER: 0b1001
+>CURSOR: 5
+
+  zletest $'0b0\C-p'
+0:decarg fails to produce a negative binary value
+>BUFFER: 0b0
+>CURSOR: 3
+
+# Octals
+
+  zletest $'0o7\C-n'
+0:incarg can increment an octal integer
+>BUFFER: 0o10
+>CURSOR: 4
+
+  zletest $'0O7\C-n'
+0:incarg can increment an octal integer with an upper case prefix
+>BUFFER: 0O10
+>CURSOR: 4
+
+  zletest $'0o10\C-p'
+0:decarg can decrement an octal integer
+>BUFFER: 0o7
+>CURSOR: 3
+
+  zletest $'0o0\C-p'
+0:decarg fails to produce a negative octal value
+>BUFFER: 0o0
+>CURSOR: 3
+
+# Hexadecimals
+
+  zletest $'0x9\C-n'
+0:incarg can increment a hexadecimal integer
+>BUFFER: 0xa
+>CURSOR: 3
+
+  zletest $'0X9\C-n'
+0:incarg can increment a hexadecimal integer with an upper case prefix
+>BUFFER: 0XA
+>CURSOR: 3
+
+  zletest $'0xf\C-n'
+0:incarg can increment a hexadecimal integer with no numeric digit
+>BUFFER: 0x10
+>CURSOR: 4
+
+  zletest $'0x10\C-p'
+0:decarg can decrement a hexadecimal integer
+>BUFFER: 0xf
+>CURSOR: 3
+
+  zletest $'0x0\C-p'
+0:decarg fails to produce a negative hexadecimal value
+>BUFFER: 0x0
+>CURSOR: 3
+
+  zletest $'0x0b1\C-n'
+0:incarg interprets integers starting with 0x0b as a hexadecimal
+>BUFFER: 0x0b2
+>CURSOR: 5
+
+  zletest $'0x0b1\e\C-e'
+0:vim-backward-incarg interprets integers starting with 0x0b as a hexadecimal
+>BUFFER: 0x0b2
+>CURSOR: 4
+
+# Cursor position - incarg
+
+  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 $'10x9\e0\C-n'
+0:incarg turns [0-9]0x[0-9a-f] into [0-9]1x[0-9a-f] when the cursor is at the left of x
+>BUFFER: 11x9
+>CURSOR: 1
+
+  zletest $'10x9\eFx\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-n'
+0:incarg turns [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-n'
+0:incarg takes [0-9]0b[01] and increments the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-n'
+0:incarg takes [0-9]0b[01] and increments binary part when the cursor is at the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-n'
+0:incarg turns [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the binary part when the cursor is at the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Cursor position - vim-incarg
+
+  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
+
+# Cursor position - vim-backward-incarg
+
+  zletest $'echo 012ab\eF \C-e'
+0:vim-backward-incarg does nothing when the cursor is placed just to the left of an integer
+>BUFFER: echo 012ab
+>CURSOR: 4
+
+  zletest $'echo 012ab\eF0\C-e'
+0:vim-backward-incarg works when the cursor is placed at the leftmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF1\C-e'
+0:vim-backward-incarg works when the cursor is placed at the inner digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF2\C-e'
+0:vim-backward-incarg works when the cursor is placed at the rightmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eFa\C-e'
+0:vim-backward-incarg works when the cursor is placed just to the right of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\e\C-e'
+0:vim-backward-incarg works when the cursor is placed more than a single letter away to the right
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'10x9\eFx\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the binary 0b0 when the cursor is on the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Repeats
+
+  zletest $'echo 0\e0\C-n.'
+0:vim-incarg is compatible with the repeat command
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zletest $'echo 0\e0\C-p.'
+0:vim-decarg is compatible with the repeat command
+>BUFFER: echo -2
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e\C-e.'
+0:vim-backward-incarg is compatible with the repeat command
+>BUFFER: echo 2 foo
+>CURSOR: 5
+
+  zletest $'echo 0\e010\C-n.'
+0:Repeats of vim-incarg takes the numeric argument into account
+>BUFFER: echo 20
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e10\C-e.'
+0:Repeats of vim-backward-incarg takes the numeric argument into account
+>BUFFER: echo 20 foo
+>CURSOR: 6
+
+  zpty_run 'TMUX_PANE=0'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 0
+>BUFFER: echo 0
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=1'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 1
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 2
+>BUFFER: echo 4
+>CURSOR: 5
+
+%clean
+
+  zmodload -ui zsh/zpty

base-commit: 698af7bc1387462c8e87767d7eaeb7e30c6f0b2b


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] incarg: add a backward variant and make it repeatable
  2024-02-28 19:11   ` [PATCH v3] " midchildan
@ 2024-02-28 20:07     ` midchildan
  2024-02-29 13:34       ` [PATCH v2] " midchildan
  0 siblings, 1 reply; 9+ messages in thread
From: midchildan @ 2024-02-28 20:07 UTC (permalink / raw)
  To: zsh-workers; +Cc: midchildan

I misunderstood the part about the previous patch being applied, and
just realized it already made into master. So I rebased the latest
follow-up patch against the current master branch.
---
 Doc/Zsh/contrib.yo     |   8 +-
 Functions/Zle/incarg   |  64 +++++++-
 Test/X05zleincarg.ztst | 356 ++++++++++++++++++++++++++++++-----------
 3 files changed, 324 insertions(+), 104 deletions(-)

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index ea00f0ccc..e682c800a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2635,7 +2635,8 @@ 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(vi),
 the cursor will jump to the nearest integer after the cursor before incrementing
-it.
+it. The tt(vi) prefix can also be combined with a tt(backward-) prefix to make
+the widget search backwards for numbers.
 
 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
@@ -2643,8 +2644,9 @@ 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(vi) and tt(sync-) can be combined, for example, into
-tt(vim-sync-). In this case, the tt(vi) prefix should come first.
+The prefixes tt(vi), tt(backward-), and tt(sync-) can be combined, for example,
+into tt(vim-sync-) or tt(vim-backward-sync-). The tt(vi) prefix needs to be
+at the very beginning.
 
 example(bindkey '^X+' incarg)
 )
diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg
index 9d56b21f6..f8b0e6b3b 100644
--- a/Functions/Zle/incarg
+++ b/Functions/Zle/incarg
@@ -41,11 +41,20 @@ emulate -L zsh
 #   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-backward-incarg / vim-backward-decarg
+#
+#   This behaves like vim-incarg & vim-decarg, but it searches backwards for a
+#   number.
+#
 # - 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.
 #
+# - vim-backward-sync-incarg / vim-backward-sync-decarg
+#
+#   This combines the behavior of the vim-backward- and sync- variants.
+#
 # Example Usage:
 #
 #   autoload -Uz incarg
@@ -58,9 +67,13 @@ emulate -L zsh
 #     'g^A' vim-sync-incarg \
 #     'g^X' vim-sync-decarg
 
+zle -f vichange
+
 setopt localoptions extended_glob
 local match mbegin mend MATCH MBEGIN MEND i
 
+[[ -z "$BUFFER" ]] && return 1
+
 # find the number and determine the base
 integer pos=$(( CURSOR + 1 )) base=0
 
@@ -104,11 +117,35 @@ 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
+    if [[ "$WIDGET" == *backward-* ]]; then
+      # search backwards for a number
+      while true; 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 ;;
+          *[0-9]) base=10 ;;
+          *-)
+            case "$BUFFER[pos,-1]" in
+              -0[xX][0-9a-fA-F]*) ;;
+              -0[oO][0-7]*) ;;
+              -0[bB][01]*) ;;
+              -[0-9]*) base=10 ;;
+            esac
+            ;;
+        esac
+        (( base != 0 )) && break
+
+        (( pos-- ))
+        (( pos <= 0 )) && return 1
+      done
+    else
+      # jump to the nearest number after the cursor
+      while [[ "$BUFFER[pos]" == [^0-9] ]]; do
+        (( pos++ ))
+        (( pos > $#BUFFER )) && return 1
+      done
+    fi
   fi
 
   # check for a prefix right after the cursor and jump right after it, if any
@@ -204,6 +241,12 @@ fi
 
 local old="$BUFFER[first,last]"
 integer oldlen=$#BUFFER
+integer oldnum="$base#$old" 2> /dev/null
+
+# -00 should increment to 01 instead of 001
+if [[ "$BUFFER[first]" == '-' ]] && (( oldnum == 0 && delta > 0 )); then
+  (( ndigits-- ))
+fi
 
 local fmt1 fmt2
 case "$base" in
@@ -214,10 +257,12 @@ case "$base" in
 esac
 
 local raw_result padded
+# $(( )) outputs an error message to stderr when integer truncation occurs
 printf -v raw_result "%0$ndigits$fmt1" $(( [$fmt2] "$base#$old" + delta )) 2> /dev/null
 padded="${raw_result// /0}"
+integer newnum="$base#$padded" 2> /dev/null
 
-integer oldnum="$base#$old" newnum="$base#$padded" 2> /dev/null
+# try to detect integer truncation
 if (( base != 10 && newnum < 0
         || delta > 0 && newnum < oldnum
         || delta < 0 && newnum > oldnum  )); then
@@ -242,7 +287,12 @@ if zstyle -t ":zle:$WIDGET" debug; then
   zle -M "[$WIDGET] base: $base delta: $delta old: '$old' new: '$new'"
 fi
 
-BUFFER[first,last]="$new"
+if (( 0 < first && first <= last && last <= $#BUFFER )); then
+  BUFFER[first,last]="$new"
+else
+  zle -M "[$WIDGET] The detected location of the integer was invalid. [location=BUFFER[$first,$last]]"
+  return 1
+fi
 
 integer offset=0
 if [[ "$WIDGET" == vi* ]]; then
diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst
index 2a6aa2d3f..50a450734 100644
--- a/Test/X05zleincarg.ztst
+++ b/Test/X05zleincarg.ztst
@@ -10,7 +10,7 @@
   fi
   zpty_run '
     autoload -Uz incarg
-    for name in {,vim-}{,sync-}{inc,dec}arg; do
+    for name in {,vim-,vim-backward-}{,sync-}{inc,dec}arg; do
       zle -N "$name" incarg
     done
     bindkey -v "^N" incarg
@@ -21,6 +21,8 @@
     bindkey -a "^P" vim-decarg
     bindkey -a "^F" vim-sync-incarg
     bindkey -a "^B" vim-sync-decarg
+    bindkey -a "^E" vim-backward-incarg
+    bindkey -a "^Y" vim-backward-decarg
     unset TMUX_PANE ITERM_SESSION_ID
     tmux() {
       echo "$TMUX_PANE"
@@ -29,26 +31,40 @@
 
 %test
 
+# Basic increment & decrement
+
   zletest $'0\C-n'
-0:increment an integer with incarg
+0:incarg increments an integer
 >BUFFER: 1
 >CURSOR: 1
 
   zletest $'0\C-p'
-0:decrement an integer with decarg
+0:decarg decrements an integer
 >BUFFER: -1
 >CURSOR: 2
 
   zletest $'echo 0\e0\C-n'
-0:increment an integer with vim-incarg
+0:vim-incarg increments an integer
 >BUFFER: echo 1
 >CURSOR: 5
 
   zletest $'echo 0\e0\C-p'
-0:decrement an integer with vim-decarg
+0:vim-decarg decrements an integer
 >BUFFER: echo -1
 >CURSOR: 6
 
+  zletest $'echo 0 foo\e\C-e'
+0:vim-backward-incarg increments an integer
+>BUFFER: echo 1 foo
+>CURSOR: 5
+
+  zletest $'echo 0 foo\e\C-y'
+0:vim-backward-decarg decrements an integer
+>BUFFER: echo -1 foo
+>CURSOR: 6
+
+# sync- variants
+
   zletest $'0\C-f'
 0:sync-incarg does nothing on unsupported terminals
 >BUFFER: 0
@@ -57,42 +73,42 @@
   zpty_run 'TMUX_PANE=0'
   zletest $'0\C-f'
   zpty_run 'unset TMUX_PANE'
-0:sync-incarg on tmux in pane 0
+0:sync-incarg does nothing 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
+0:sync-incarg increments by 1 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
+0:sync-incarg increments by 2 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
+0:sync-incarg does nothing 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
+0:sync-incarg increments by 1 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
+0:sync-incarg increments by 2 on tmux in pane 2
 >BUFFER: 2
 >CURSOR: 1
 
@@ -100,200 +116,155 @@
   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
+0:sync-incarg prioritizes tmux pane number over iTerm2's
 >BUFFER: 1
 >CURSOR: 1
 
   zletest $'0\e2\C-n'
-0:Providing a numeric argument will change the incremented amount
+0:incarg changes the incremented amount based on the numeric argument
 >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
+0:incarg changes the default incremented amount based on the incarg variable
 >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
+0:incarg prioritizes the numeric argument 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
+0:The sync- variants of incarg takes the numeric argument into account
 >BUFFER: 4
 >CURSOR: 0
 
+# Leading zeros
+
   zletest $'000\C-n'
-0:Incrementing a decimal integer preserves leading zeros
+0:incarg preserves leading zeros of decimal integers
 >BUFFER: 001
 >CURSOR: 3
 
   zletest $'-001\C-n\C-n'
-0:Leading zeros are preserved when the digit turns from negative to positive
+0:incarg preserves leading zeros 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
+0:incarg preserves leading zeros 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
+0:incarg works when the result has more number of digits 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
+0:decargs works on integers with leading zeros when the result has more digits than the original
 >BUFFER: -1999
 >CURSOR: 4
 
+# Binaries
+
   zletest $'0b11\C-n'
-0:Increment a binary integer
+0:incarg can increment a binary integer
 >BUFFER: 0b100
 >CURSOR: 5
 
   zletest $'0B11\C-n'
-0:Increment a binary integer with an upper case prefix
+0:incarg can increment a binary integer with an upper case prefix
 >BUFFER: 0B100
 >CURSOR: 5
 
   zletest $'0b100\C-p'
-0:Decrement a binary integer
+0:decarg can decrement a binary integer
 >BUFFER: 0b11
 >CURSOR: 4
 
   zletest $'0b0011\C-n'
-0:Increment a binary integer preserves leading zeros
+0:incarg can preserve leading zeros of binaries
 >BUFFER: 0b0100
 >CURSOR: 6
 
   zletest $'0b001\e8\C-n'
-0:Incrementing a binary integer work when the result has more zeros than the original
+0:incarg works on binaries 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
+0:decarg fails to produce a negative binary value
 >BUFFER: 0b0
 >CURSOR: 3
 
+# Octals
+
   zletest $'0o7\C-n'
-0:Increment an octal integer
+0:incarg can increment an octal integer
 >BUFFER: 0o10
 >CURSOR: 4
 
   zletest $'0O7\C-n'
-0:Increment an octal integer with an upper case prefix
+0:incarg can increment an octal integer with an upper case prefix
 >BUFFER: 0O10
 >CURSOR: 4
 
   zletest $'0o10\C-p'
-0:Decrement an octal integer
+0:decarg can decrement an octal integer
 >BUFFER: 0o7
 >CURSOR: 3
 
   zletest $'0o0\C-p'
-0:Decrementing an octal integer to a negative value will fail
+0:decarg fails to produce a negative octal value
 >BUFFER: 0o0
 >CURSOR: 3
 
+# Hexadecimals
+
   zletest $'0x9\C-n'
-0:Increment a hexadecimal integer
+0:incarg can increment a hexadecimal integer
 >BUFFER: 0xa
 >CURSOR: 3
 
   zletest $'0X9\C-n'
-0:Increment a hexadecimal integer with an upper case prefix
+0:incarg can 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
+0:incarg can increment a hexadecimal integer with no numeric digit
 >BUFFER: 0x10
 >CURSOR: 4
 
   zletest $'0x10\C-p'
-0:Decrement a hexadecimal integer
+0:decarg can decrement a hexadecimal integer
 >BUFFER: 0xf
 >CURSOR: 3
 
   zletest $'0x0\C-p'
-0:Decrementing an octal integer to a negative value will fail
+0:decarg fails to produce a negative hexadecimal value
 >BUFFER: 0x0
 >CURSOR: 3
 
   zletest $'0x0b1\C-n'
-0:a number that starts with 0x0b is interpreted as a hexadecimal integer
+0:incarg interprets integers starting with 0x0b as a hexadecimal
 >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
+  zletest $'0x0b1\e\C-e'
+0:vim-backward-incarg interprets integers starting with 0x0b as a hexadecimal
+>BUFFER: 0x0b2
 >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
+# Cursor position - incarg
 
   zletest $'echo 012ab\eF i\C-n'
 0:incarg does nothing when the cursor is placed just to the left of an integer
@@ -325,6 +296,68 @@
 >BUFFER: echo 012ab
 >CURSOR: 9
 
+  zletest $'10x9\e0\C-n'
+0:incarg turns [0-9]0x[0-9a-f] into [0-9]1x[0-9a-f] when the cursor is at the left of x
+>BUFFER: 11x9
+>CURSOR: 1
+
+  zletest $'10x9\eFx\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-n'
+0:incarg turns [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-n'
+0:incarg takes [0-9]0b[01] and increments the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-n'
+0:incarg takes [0-9]0b[01] and increments binary part when the cursor is at the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-n'
+0:incarg turns [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the binary part when the cursor is at the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Cursor position - vim-incarg
+
   zletest $'echo 012ab\eF \C-n'
 0:vim-incarg works when the cursor is placed to the left of an integer
 >BUFFER: echo 013ab
@@ -355,6 +388,141 @@
 >BUFFER: echo 012ab
 >CURSOR: 9
 
+# Cursor position - vim-backward-incarg
+
+  zletest $'echo 012ab\eF \C-e'
+0:vim-backward-incarg does nothing when the cursor is placed just to the left of an integer
+>BUFFER: echo 012ab
+>CURSOR: 4
+
+  zletest $'echo 012ab\eF0\C-e'
+0:vim-backward-incarg works when the cursor is placed at the leftmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF1\C-e'
+0:vim-backward-incarg works when the cursor is placed at the inner digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF2\C-e'
+0:vim-backward-incarg works when the cursor is placed at the rightmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eFa\C-e'
+0:vim-backward-incarg works when the cursor is placed just to the right of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\e\C-e'
+0:vim-backward-incarg works when the cursor is placed more than a single letter away to the right
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'10x9\eFx\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the binary 0b0 when the cursor is on the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Repeats
+
+  zletest $'echo 0\e0\C-n.'
+0:vim-incarg is compatible with the repeat command
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zletest $'echo 0\e0\C-p.'
+0:vim-decarg is compatible with the repeat command
+>BUFFER: echo -2
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e\C-e.'
+0:vim-backward-incarg is compatible with the repeat command
+>BUFFER: echo 2 foo
+>CURSOR: 5
+
+  zletest $'echo 0\e010\C-n.'
+0:Repeats of vim-incarg takes the numeric argument into account
+>BUFFER: echo 20
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e10\C-e.'
+0:Repeats of vim-backward-incarg takes the numeric argument into account
+>BUFFER: echo 20 foo
+>CURSOR: 6
+
+  zpty_run 'TMUX_PANE=0'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 0
+>BUFFER: echo 0
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=1'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 1
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 2
+>BUFFER: echo 4
+>CURSOR: 5
+
 %clean
 
   zmodload -ui zsh/zpty

base-commit: 69c58874611a586c68be14ce7965029dc00f41b7


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] incarg: add a backward variant and make it repeatable
  2024-02-28 20:07     ` [PATCH] incarg: add a backward variant and make it repeatable midchildan
@ 2024-02-29 13:34       ` midchildan
  2024-03-04 23:20         ` Oliver Kiddle
  0 siblings, 1 reply; 9+ messages in thread
From: midchildan @ 2024-02-29 13:34 UTC (permalink / raw)
  To: zsh-workers; +Cc: midchildan

The previous patch failed to handle negative zeros when the increment
amount was zero or less. So I updated the patch to fix this and added a
couple more test cases.
---
 Doc/Zsh/contrib.yo     |   8 +-
 Functions/Zle/incarg   |  64 ++++++-
 Test/X05zleincarg.ztst | 390 +++++++++++++++++++++++++++++++----------
 3 files changed, 358 insertions(+), 104 deletions(-)

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index ea00f0ccc..e682c800a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2635,7 +2635,8 @@ 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(vi),
 the cursor will jump to the nearest integer after the cursor before incrementing
-it.
+it. The tt(vi) prefix can also be combined with a tt(backward-) prefix to make
+the widget search backwards for numbers.
 
 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
@@ -2643,8 +2644,9 @@ 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(vi) and tt(sync-) can be combined, for example, into
-tt(vim-sync-). In this case, the tt(vi) prefix should come first.
+The prefixes tt(vi), tt(backward-), and tt(sync-) can be combined, for example,
+into tt(vim-sync-) or tt(vim-backward-sync-). The tt(vi) prefix needs to be
+at the very beginning.
 
 example(bindkey '^X+' incarg)
 )
diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg
index 9d56b21f6..0cfaf9ff5 100644
--- a/Functions/Zle/incarg
+++ b/Functions/Zle/incarg
@@ -41,11 +41,20 @@ emulate -L zsh
 #   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-backward-incarg / vim-backward-decarg
+#
+#   This behaves like vim-incarg & vim-decarg, but it searches backwards for a
+#   number.
+#
 # - 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.
 #
+# - vim-backward-sync-incarg / vim-backward-sync-decarg
+#
+#   This combines the behavior of the vim-backward- and sync- variants.
+#
 # Example Usage:
 #
 #   autoload -Uz incarg
@@ -58,9 +67,13 @@ emulate -L zsh
 #     'g^A' vim-sync-incarg \
 #     'g^X' vim-sync-decarg
 
+zle -f vichange
+
 setopt localoptions extended_glob
 local match mbegin mend MATCH MBEGIN MEND i
 
+[[ -z "$BUFFER" ]] && return 1
+
 # find the number and determine the base
 integer pos=$(( CURSOR + 1 )) base=0
 
@@ -104,11 +117,35 @@ 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
+    if [[ "$WIDGET" == *backward-* ]]; then
+      # search backwards for a number
+      while true; 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 ;;
+          *[0-9]) base=10 ;;
+          *-)
+            case "$BUFFER[pos,-1]" in
+              -0[xX][0-9a-fA-F]*) ;;
+              -0[oO][0-7]*) ;;
+              -0[bB][01]*) ;;
+              -[0-9]*) base=10 ;;
+            esac
+            ;;
+        esac
+        (( base != 0 )) && break
+
+        (( pos-- ))
+        (( pos <= 0 )) && return 1
+      done
+    else
+      # jump to the nearest number after the cursor
+      while [[ "$BUFFER[pos]" == [^0-9] ]]; do
+        (( pos++ ))
+        (( pos > $#BUFFER )) && return 1
+      done
+    fi
   fi
 
   # check for a prefix right after the cursor and jump right after it, if any
@@ -204,6 +241,12 @@ fi
 
 local old="$BUFFER[first,last]"
 integer oldlen=$#BUFFER
+integer oldnum="$base#$old" 2> /dev/null
+
+# -00 should increment to 01 instead of 001
+if [[ "$BUFFER[first]" == '-' ]] && (( oldnum == 0 )); then
+  (( ndigits-- ))
+fi
 
 local fmt1 fmt2
 case "$base" in
@@ -214,10 +257,12 @@ case "$base" in
 esac
 
 local raw_result padded
+# $(( )) outputs an error message to stderr when integer truncation occurs
 printf -v raw_result "%0$ndigits$fmt1" $(( [$fmt2] "$base#$old" + delta )) 2> /dev/null
 padded="${raw_result// /0}"
+integer newnum="$base#$padded" 2> /dev/null
 
-integer oldnum="$base#$old" newnum="$base#$padded" 2> /dev/null
+# try to detect integer truncation
 if (( base != 10 && newnum < 0
         || delta > 0 && newnum < oldnum
         || delta < 0 && newnum > oldnum  )); then
@@ -242,7 +287,12 @@ if zstyle -t ":zle:$WIDGET" debug; then
   zle -M "[$WIDGET] base: $base delta: $delta old: '$old' new: '$new'"
 fi
 
-BUFFER[first,last]="$new"
+if (( 0 < first && first <= last && last <= $#BUFFER )); then
+  BUFFER[first,last]="$new"
+else
+  zle -M "[$WIDGET] The detected location of the integer was invalid. [location=BUFFER[$first,$last]]"
+  return 1
+fi
 
 integer offset=0
 if [[ "$WIDGET" == vi* ]]; then
diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst
index 2a6aa2d3f..cd9817c82 100644
--- a/Test/X05zleincarg.ztst
+++ b/Test/X05zleincarg.ztst
@@ -10,7 +10,7 @@
   fi
   zpty_run '
     autoload -Uz incarg
-    for name in {,vim-}{,sync-}{inc,dec}arg; do
+    for name in {,vim-,vim-backward-}{,sync-}{inc,dec}arg; do
       zle -N "$name" incarg
     done
     bindkey -v "^N" incarg
@@ -21,6 +21,8 @@
     bindkey -a "^P" vim-decarg
     bindkey -a "^F" vim-sync-incarg
     bindkey -a "^B" vim-sync-decarg
+    bindkey -a "^E" vim-backward-incarg
+    bindkey -a "^Y" vim-backward-decarg
     unset TMUX_PANE ITERM_SESSION_ID
     tmux() {
       echo "$TMUX_PANE"
@@ -29,26 +31,40 @@
 
 %test
 
+# Basic increment & decrement
+
   zletest $'0\C-n'
-0:increment an integer with incarg
+0:incarg increments an integer
 >BUFFER: 1
 >CURSOR: 1
 
   zletest $'0\C-p'
-0:decrement an integer with decarg
+0:decarg decrements an integer
 >BUFFER: -1
 >CURSOR: 2
 
   zletest $'echo 0\e0\C-n'
-0:increment an integer with vim-incarg
+0:vim-incarg increments an integer
 >BUFFER: echo 1
 >CURSOR: 5
 
   zletest $'echo 0\e0\C-p'
-0:decrement an integer with vim-decarg
+0:vim-decarg decrements an integer
 >BUFFER: echo -1
 >CURSOR: 6
 
+  zletest $'echo 0 foo\e\C-e'
+0:vim-backward-incarg increments an integer
+>BUFFER: echo 1 foo
+>CURSOR: 5
+
+  zletest $'echo 0 foo\e\C-y'
+0:vim-backward-decarg decrements an integer
+>BUFFER: echo -1 foo
+>CURSOR: 6
+
+# sync- variants
+
   zletest $'0\C-f'
 0:sync-incarg does nothing on unsupported terminals
 >BUFFER: 0
@@ -57,42 +73,42 @@
   zpty_run 'TMUX_PANE=0'
   zletest $'0\C-f'
   zpty_run 'unset TMUX_PANE'
-0:sync-incarg on tmux in pane 0
+0:sync-incarg does nothing 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
+0:sync-incarg increments by 1 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
+0:sync-incarg increments by 2 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
+0:sync-incarg does nothing 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
+0:sync-incarg increments by 1 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
+0:sync-incarg increments by 2 on tmux in pane 2
 >BUFFER: 2
 >CURSOR: 1
 
@@ -100,200 +116,189 @@
   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
+0:sync-incarg prioritizes tmux pane number over iTerm2's
 >BUFFER: 1
 >CURSOR: 1
 
   zletest $'0\e2\C-n'
-0:Providing a numeric argument will change the incremented amount
+0:incarg changes the incremented amount based on the numeric argument
 >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
+0:incarg changes the default incremented amount based on the incarg variable
 >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
+0:incarg prioritizes the numeric argument 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
+0:The sync- variants of incarg takes the numeric argument into account
 >BUFFER: 4
 >CURSOR: 0
 
+# Leading zeros
+
   zletest $'000\C-n'
-0:Incrementing a decimal integer preserves leading zeros
+0:incarg preserves leading zeros of decimal integers
 >BUFFER: 001
 >CURSOR: 3
 
   zletest $'-001\C-n\C-n'
-0:Leading zeros are preserved when the digit turns from negative to positive
+0:incarg preserves leading zeros 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
+0:incarg preserves leading zeros 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
+0:incarg works when the result has more number of digits 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
+0:decargs works on integers with leading zeros when the result has more digits than the original
 >BUFFER: -1999
 >CURSOR: 4
 
+  zletest $'-000\C-n'
+0:incarg produces the correct number of zeros when incrementing integers starting with -0
+>BUFFER: 001
+>CURSOR: 3
+
+  zletest $'-000\C-p'
+0:decarg produces the correct number of zeros when incrementing integers starting with -0
+>BUFFER: -001
+>CURSOR: 4
+
+  zpty_run 'incarg=0'
+  zletest $'-000\C-n'
+  zpty_run 'unset incarg'
+0:incarg removes the sign when the target integer starts with -0 and the increment amount is 0
+>BUFFER: 000
+>CURSOR: 3
+
+  zletest $'-0\C-n'
+0:incarg turns -0 into 1
+>BUFFER: 1
+>CURSOR: 1
+
+  zletest $'-0\C-p'
+0:decarg turns -0 into -1
+>BUFFER: -1
+>CURSOR: 2
+
+  zpty_run 'incarg=0'
+  zletest $'-0\C-n'
+  zpty_run 'unset incarg'
+0:incarg turns -0 into 0 when the increment amount is 0
+>BUFFER: 0
+>CURSOR: 1
+
+# Binaries
+
   zletest $'0b11\C-n'
-0:Increment a binary integer
+0:incarg can increment a binary integer
 >BUFFER: 0b100
 >CURSOR: 5
 
   zletest $'0B11\C-n'
-0:Increment a binary integer with an upper case prefix
+0:incarg can increment a binary integer with an upper case prefix
 >BUFFER: 0B100
 >CURSOR: 5
 
   zletest $'0b100\C-p'
-0:Decrement a binary integer
+0:decarg can decrement a binary integer
 >BUFFER: 0b11
 >CURSOR: 4
 
   zletest $'0b0011\C-n'
-0:Increment a binary integer preserves leading zeros
+0:incarg can preserve leading zeros of binaries
 >BUFFER: 0b0100
 >CURSOR: 6
 
   zletest $'0b001\e8\C-n'
-0:Incrementing a binary integer work when the result has more zeros than the original
+0:incarg works on binaries 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
+0:decarg fails to produce a negative binary value
 >BUFFER: 0b0
 >CURSOR: 3
 
+# Octals
+
   zletest $'0o7\C-n'
-0:Increment an octal integer
+0:incarg can increment an octal integer
 >BUFFER: 0o10
 >CURSOR: 4
 
   zletest $'0O7\C-n'
-0:Increment an octal integer with an upper case prefix
+0:incarg can increment an octal integer with an upper case prefix
 >BUFFER: 0O10
 >CURSOR: 4
 
   zletest $'0o10\C-p'
-0:Decrement an octal integer
+0:decarg can decrement an octal integer
 >BUFFER: 0o7
 >CURSOR: 3
 
   zletest $'0o0\C-p'
-0:Decrementing an octal integer to a negative value will fail
+0:decarg fails to produce a negative octal value
 >BUFFER: 0o0
 >CURSOR: 3
 
+# Hexadecimals
+
   zletest $'0x9\C-n'
-0:Increment a hexadecimal integer
+0:incarg can increment a hexadecimal integer
 >BUFFER: 0xa
 >CURSOR: 3
 
   zletest $'0X9\C-n'
-0:Increment a hexadecimal integer with an upper case prefix
+0:incarg can 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
+0:incarg can increment a hexadecimal integer with no numeric digit
 >BUFFER: 0x10
 >CURSOR: 4
 
   zletest $'0x10\C-p'
-0:Decrement a hexadecimal integer
+0:decarg can decrement a hexadecimal integer
 >BUFFER: 0xf
 >CURSOR: 3
 
   zletest $'0x0\C-p'
-0:Decrementing an octal integer to a negative value will fail
+0:decarg fails to produce a negative hexadecimal value
 >BUFFER: 0x0
 >CURSOR: 3
 
   zletest $'0x0b1\C-n'
-0:a number that starts with 0x0b is interpreted as a hexadecimal integer
+0:incarg interprets integers starting with 0x0b as a hexadecimal
 >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
+  zletest $'0x0b1\e\C-e'
+0:vim-backward-incarg interprets integers starting with 0x0b as a hexadecimal
+>BUFFER: 0x0b2
 >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
+# Cursor position - incarg
 
   zletest $'echo 012ab\eF i\C-n'
 0:incarg does nothing when the cursor is placed just to the left of an integer
@@ -325,6 +330,68 @@
 >BUFFER: echo 012ab
 >CURSOR: 9
 
+  zletest $'10x9\e0\C-n'
+0:incarg turns [0-9]0x[0-9a-f] into [0-9]1x[0-9a-f] when the cursor is at the left of x
+>BUFFER: 11x9
+>CURSOR: 1
+
+  zletest $'10x9\eFx\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-n'
+0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-n'
+0:incarg turns [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-n'
+0:incarg takes [0-9]0b[01] and increments the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-n'
+0:incarg takes [0-9]0b[01] and increments binary part when the cursor is at the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-n'
+0:incarg turns [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-n'
+0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the binary part when the cursor is at the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-n'
+0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Cursor position - vim-incarg
+
   zletest $'echo 012ab\eF \C-n'
 0:vim-incarg works when the cursor is placed to the left of an integer
 >BUFFER: echo 013ab
@@ -355,6 +422,141 @@
 >BUFFER: echo 012ab
 >CURSOR: 9
 
+# Cursor position - vim-backward-incarg
+
+  zletest $'echo 012ab\eF \C-e'
+0:vim-backward-incarg does nothing when the cursor is placed just to the left of an integer
+>BUFFER: echo 012ab
+>CURSOR: 4
+
+  zletest $'echo 012ab\eF0\C-e'
+0:vim-backward-incarg works when the cursor is placed at the leftmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF1\C-e'
+0:vim-backward-incarg works when the cursor is placed at the inner digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eF2\C-e'
+0:vim-backward-incarg works when the cursor is placed at the rightmost digit of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\eFa\C-e'
+0:vim-backward-incarg works when the cursor is placed just to the right of an integer
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'echo 012ab\e\C-e'
+0:vim-backward-incarg works when the cursor is placed more than a single letter away to the right
+>BUFFER: echo 013ab
+>CURSOR: 7
+
+  zletest $'10x9\eFx\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10x9\e\C-e'
+0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on the right of x
+>BUFFER: 10xa
+>CURSOR: 3
+
+  zletest $'10b1\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b
+>BUFFER: 11b1
+>CURSOR: 1
+
+  zletest $'10b1\eFb\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10b1\e\C-e'
+0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on the right of b
+>BUFFER: 10b10
+>CURSOR: 4
+
+  zletest $'10o7\e0\C-e'
+0:vim-backward-incarg will turn [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o
+>BUFFER: 11o7
+>CURSOR: 1
+
+  zletest $'10o7\eFo\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is on o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'10o7\e\C-e'
+0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is at the right of o
+>BUFFER: 10o10
+>CURSOR: 4
+
+  zletest $'0b0x9\eF0\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the binary 0b0 when the cursor is on the left of x
+>BUFFER: 0b1x9
+>CURSOR: 2
+
+  zletest $'0b0x9\eFx\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is on x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+  zletest $'0b0x9\e\C-e'
+0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is at the right of x
+>BUFFER: 0b0xa
+>CURSOR: 4
+
+# Repeats
+
+  zletest $'echo 0\e0\C-n.'
+0:vim-incarg is compatible with the repeat command
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zletest $'echo 0\e0\C-p.'
+0:vim-decarg is compatible with the repeat command
+>BUFFER: echo -2
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e\C-e.'
+0:vim-backward-incarg is compatible with the repeat command
+>BUFFER: echo 2 foo
+>CURSOR: 5
+
+  zletest $'echo 0\e010\C-n.'
+0:Repeats of vim-incarg takes the numeric argument into account
+>BUFFER: echo 20
+>CURSOR: 6
+
+  zletest $'echo 0 foo\e10\C-e.'
+0:Repeats of vim-backward-incarg takes the numeric argument into account
+>BUFFER: echo 20 foo
+>CURSOR: 6
+
+  zpty_run 'TMUX_PANE=0'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 0
+>BUFFER: echo 0
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=1'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 1
+>BUFFER: echo 2
+>CURSOR: 5
+
+  zpty_run 'TMUX_PANE=2'
+  zletest $'echo 0\e0\C-f.'
+  zpty_run 'unset TMUX_PANE'
+0:Repeats of vim-sync-incarg work in pane 2
+>BUFFER: echo 4
+>CURSOR: 5
+
 %clean
 
   zmodload -ui zsh/zpty

base-commit: 69c58874611a586c68be14ce7965029dc00f41b7


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] incarg: add a backward variant and make it repeatable
  2024-02-29 13:34       ` [PATCH v2] " midchildan
@ 2024-03-04 23:20         ` Oliver Kiddle
  2024-03-28 23:07           ` Sho Yuhara
  0 siblings, 1 reply; 9+ messages in thread
From: Oliver Kiddle @ 2024-03-04 23:20 UTC (permalink / raw)
  To: midchildan; +Cc: zsh-workers

On 29 Feb, midchildan wrote:
> The previous patch failed to handle negative zeros when the increment
> amount was zero or less. So I updated the patch to fix this and added a
> couple more test cases.

Thanks. I have now applied this. Thanks for sending a fresh patch.

>  # - 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.

vim help for g_Ctrl-A says "print memory usage statistics.". Is this
perhaps a feature of some plugin? Or do you mean the visual mode
g_Ctrl-A?

Oliver


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] incarg: add a backward variant and make it repeatable
  2024-03-04 23:20         ` Oliver Kiddle
@ 2024-03-28 23:07           ` Sho Yuhara
  0 siblings, 0 replies; 9+ messages in thread
From: Sho Yuhara @ 2024-03-28 23:07 UTC (permalink / raw)
  To: Oliver Kiddle; +Cc: zsh-workers

> Thanks. I have now applied this. Thanks for sending a fresh patch.

Thank you too!

> vim help for g_Ctrl-A says "print memory usage statistics.". Is this perhaps a feature of some plugin? Or do you mean the visual mode g_Ctrl-A?

I meant the one in visual mode. I wasn't aware of the normal mode variant.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2024-03-28 23:08 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-04 19:19 [PATCH] add new features and improvements to the "incarg" ZLE widget midchildan
2024-02-05  0:36 ` Bart Schaefer
2024-02-05 16:16   ` midchildan
2024-02-15 15:30 ` Oliver Kiddle
2024-02-28 19:11   ` [PATCH v3] " midchildan
2024-02-28 20:07     ` [PATCH] incarg: add a backward variant and make it repeatable midchildan
2024-02-29 13:34       ` [PATCH v2] " midchildan
2024-03-04 23:20         ` Oliver Kiddle
2024-03-28 23:07           ` Sho Yuhara

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).