mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-06-15 20:38:10 +02:00
303 lines
7.4 KiB
Text
303 lines
7.4 KiB
Text
emulate -L zsh
|
|
|
|
# A ZLE widget to increment an integer.
|
|
#
|
|
# In addition to decimals, it can handle hexadecimals prefixed with "0x",
|
|
# binaries with "0b", and octals with "0o".
|
|
#
|
|
# By default, the target integer will be incremented by one. With a numeric
|
|
# argument, the integer is incremented by the amount of the argument. The shell
|
|
# parameter "incarg" may be set to change the default increment to something
|
|
# other than one.
|
|
#
|
|
# The behavior of this widget changes depending on how it is named.
|
|
#
|
|
# - incarg / decarg
|
|
#
|
|
# incarg will increment an integer either under the cursor or just to the left
|
|
# of it. decarg, on the other hand, will decrement it.
|
|
#
|
|
# For example,
|
|
#
|
|
# echo 41
|
|
# ^^^ cursor anywhere here
|
|
#
|
|
# with incarg gives
|
|
#
|
|
# echo 42
|
|
# ^ cursor will move here
|
|
#
|
|
# - sync-incarg / sync-decarg
|
|
#
|
|
# The sync- variant is used for creating a sequence of numbers on split
|
|
# terminals with synchronized key input. The first pane won't be incremented
|
|
# at all, but each pane after that will have the number incremented once more
|
|
# than the previous pane.
|
|
#
|
|
# Currently supports tmux and iTerm2.
|
|
#
|
|
# - vim-incarg / vim-decarg
|
|
#
|
|
# This behaves like Vim's CTRL-A / CTRL-X. It moves the cursor to the nearest
|
|
# number after the cursor and increments or decrements it.
|
|
#
|
|
# - vim-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
|
|
|
|
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
|
|
|
|
if (( base == 0 )); then
|
|
base=10
|
|
fi
|
|
|
|
# find the start of the number
|
|
integer first="$pos"
|
|
case "$base" in
|
|
10)
|
|
while [[ "$BUFFER[first-1]" == [0-9] ]]; do
|
|
(( first-- ))
|
|
done
|
|
if [[ $BUFFER[first-1] = - ]]; then
|
|
(( first-- ))
|
|
fi
|
|
;;
|
|
2)
|
|
while [[ "$BUFFER[first-1]" == [01] ]]; do
|
|
(( first-- ))
|
|
done
|
|
;;
|
|
8)
|
|
while [[ "$BUFFER[first-1]" == [0-7] ]]; do
|
|
(( first-- ))
|
|
done
|
|
;;
|
|
16)
|
|
while [[ "$BUFFER[first-1]" == [0-9a-fA-F] ]]; do
|
|
(( first-- ))
|
|
done
|
|
;;
|
|
esac
|
|
|
|
# find the end of the number
|
|
integer last="$pos"
|
|
case "$base" in
|
|
10)
|
|
while [[ "$BUFFER[last+1]" == [0-9] ]]; do
|
|
(( last++ ))
|
|
done
|
|
;;
|
|
2)
|
|
while [[ "$BUFFER[last+1]" == [01] ]]; do
|
|
(( last++ ))
|
|
done
|
|
;;
|
|
8)
|
|
while [[ "$BUFFER[last+1]" == [0-7] ]]; do
|
|
(( last++ ))
|
|
done
|
|
;;
|
|
16)
|
|
while [[ "$BUFFER[last+1]" == [0-9a-fA-F] ]]; do
|
|
(( last++ ))
|
|
done
|
|
;;
|
|
esac
|
|
|
|
# calculate the number of digits
|
|
integer ndigits=0
|
|
case "$BUFFER[first,first+1]" in
|
|
0*|-0) ndigits=$(( last - first + 1 )) ;;
|
|
esac
|
|
|
|
# determine the amount to increment
|
|
integer delta=${NUMERIC:-${incarg:-1}}
|
|
if [[ "$WIDGET" = *decarg ]]; then
|
|
(( delta = -delta ))
|
|
fi
|
|
if [[ "$WIDGET" = *sync-* ]]; then
|
|
integer pane_index=0
|
|
if [[ -n "$TMUX_PANE" ]]; then
|
|
pane_index="$(tmux display-message -pt "$TMUX_PANE" '#{pane_index}')"
|
|
elif [[ "$ITERM_SESSION_ID" =~ '^w[0-9]+t[0-9]+p([0-9]+)' ]]; then
|
|
pane_index="$match[1]"
|
|
else
|
|
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 )); 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
|
|
# $(( )) 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
|
|
|
|
# try to detect integer truncation
|
|
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
|