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

Re: PATCH: list units in brackets at the end of completion group descriptions



Attached is an updated version of the patch from 49532 to add a helper
for completing numbers with unit suffixes and for separating out
defaults, ranges and units in descriptions.

I think I've handled everything from Jun T's comments. Documentation is
added. The uppercase format specifiers are gone as this builds on the
earlier zformat -F patch. I've made _numbers slightly more aggressive in
emptying compstate[insert] because it is not uncommon for _alternative
to combine numbers with certain fixed values like "all", "any" or "none"
and completing them unconditionally is not really helpful.

Completions for head and tail also now make use of _numbers along with a
few other commands.

Oliver

diff --git a/Completion/BSD/Command/_ipfw b/Completion/BSD/Command/_ipfw
index 2354a70fe..49d0ef1e8 100644
--- a/Completion/BSD/Command/_ipfw
+++ b/Completion/BSD/Command/_ipfw
@@ -249,8 +249,8 @@ actions=(
     $'/(pipe|queue|sched)[ \t\0]/' -'pqs=${match%?}' ':dummynet-commands:dummynet configuration:$ca pipe queue sched'
     $word ': _message -e numbers number'
     $word ':options:config:$ca config'
-    \( $'/bw[ \t\0]/' \( $'/<->/' ': _guard "[0-9]#" bandwidth'
-         $word ':units:unit:compadd -M "m:{a-z}={A-Z}" {K,M,G}{bit,Byte}/s'
+    \( $'/bw[ \t\0]/'
+      \( $word ':bandwidths: :_numbers -M "m:{a-z}={A-Z}" bandwidth {K,M,G}{bit,Byte}/s'
       \| $word ':devices:device:_net_interfaces -qS " "' \)
     \| $'/delay[ \t\0]/' $word ': _message -e numbers "propagation delay (ms)"'
     \| $'/burst[ \t\0]/' $word ': _message -e numbers "size (bytes)"'
diff --git a/Completion/Base/Core/_description b/Completion/Base/Core/_description
index 5b54484c7..368b41ee2 100644
--- a/Completion/Base/Core/_description
+++ b/Completion/Base/Core/_description
@@ -2,6 +2,7 @@
 
 local name nopt xopt format gname hidden hide match opts tag
 local -a ign gropt sort
+local -a match mbegin mend
 
 opts=()
 
@@ -78,6 +79,13 @@ shift 2
 if [[ -z "$1" && $# -eq 1 ]]; then
   format=
 elif [[ -n "$format" ]]; then
+  if [[ -z $2 ]]; then
+    argv+=( h:${1%%( ##\((#b)([^\)]#[^0-9-][^\)]#)(#B)\)|)( ##\((#b)([0-9-]##)(#B)\)|)( ##\[(#b)([^\]]##)(#B)\]|)} )
+    [[ -n $match[1] ]] && argv+=( m:$match[1] )
+    [[ -n $match[2] ]] && argv+=( r:$match[2] )
+    [[ -n $match[3] ]] && argv+=( o:$match[3] )
+  fi
+
   zformat -F format "$format" "d:$1" "${(@)argv[2,-1]}"
 fi
 
diff --git a/Completion/Base/Utility/_numbers b/Completion/Base/Utility/_numbers
new file mode 100644
index 000000000..97bb8b4c8
--- /dev/null
+++ b/Completion/Base/Utility/_numbers
@@ -0,0 +1,87 @@
+#autoload
+
+# Usage: _numbers [compadd options] [-t tag] [-f|-N] [-u units] [-l min] [-m max] \
+#                 [-d default] ["description"] [unit-suffix...]
+
+#   -t : specify a tag (defaults to 'numbers')
+#   -u : indicate the units, e.g. seconds
+#   -l : lowest possible value
+#   -m : maximum possible value
+#   -d : default value
+#   -N : allow negative numbers (implied by range including a negative)
+#   -f : allow decimals (float)
+
+# For a unit-suffix, an initial colon indicates a unit that asserts the default
+# otherwise, colons allow for descriptions, e.g:
+
+#   :s:seconds m:minutes h:hours
+
+# unit-suffixes are not sorted by the completion system when listed
+# Specify them in order of magnitude, this tends to be ascending unless
+# the default is of a higher magnitude, in which case, descending.
+# So for, example
+#   bytes kB MB GB
+#   s ms us ns
+# Where the compadd options include matching control or suffixes, these
+# are applied to the units
+
+# For each unit-suffix, the format style is looked up with the
+# unit-suffixes tag and the results concatenated. Specs used are:
+#   x : the suffix
+#   X : suffix description
+#   d : indicate suffix is for the default unit
+#   i : list index
+#   r : reverse list index
+# The latter three of these are useful with ternary expressions.
+
+# _description is called with the x token set to make the completed
+# list of suffixes available to the normal format style
+
+local desc tag range suffixes suffix suffixfmt pat='<->' partial=''
+local -a expl formats
+local -a default max min keep tags units
+local -i i
+local -A opts
+
+zparseopts -K -D -A opts M+:=keep q:=keep s+:=keep S+:=keep J+: V+: 1 2 o+: n F: x+: X+: \
+  t:=tags u:=units l:=min m:=max d:=default f=type e=type N=type
+
+desc="${1:-number}" tag="${tags[2]:-numbers}"
+(( $# )) && shift
+
+[[ -n ${(M)type:#-f} ]] && pat='(<->.[0-9]#|[0-9]#.<->|<->)' partial='(|.)'
+[[ -n ${(M)type:#-N} || $min[2] = -* || $max[2] = -* ]] && \
+    pat="(|-)$pat" partial="(|-)$partial"
+
+if (( $#argv )) && compset -P "$pat"; then
+  zstyle -s ":completion:${curcontext}:units" list-separator sep || sep=--
+  _description -V units expl unit
+  disp=( ${${argv#:}/:/ $sep } )
+  compadd -M 'r:|/=* r:|=*' -d disp "$keep[@]" "$expl[@]" - ${${argv#:}%%:*}
+  return
+elif [[ -prefix $~pat || $PREFIX = $~partial ]]; then
+  formats=( "h:$desc" )
+  (( $#units )) && formats+=( m:${units[2]} ) desc+=" ($units[2])"
+  (( $#min )) && range="$min[2]-"
+  (( $#max )) && range="${range:--}$max[2]"
+  [[ -n $range ]] && formats+=( r:$range ) desc+=" ($range)"
+  (( $#default )) && formats+=( o:${default[2]} ) desc+=" [$default[2]]"
+
+  zstyle -s ":completion:${curcontext}:unit-suffixes" format suffixfmt || \
+      suffixfmt='%(d.%U.)%x%(d.%u.)%(r..|)'
+  for ((i=0;i<$#;i++)); do
+    zformat -f suffix "$suffixfmt" "x:${${argv[i+1]#:}%%:*}" \
+        "X:${${argv[i+1]#:}#*:}" "d:${#${argv[i+1]}[1]#:}" \
+	i:i r:$(( $# - i - 1))
+    suffixes+="$suffix"
+  done
+  [[ -n $suffixes ]] && formats+=( x:$suffixes )
+
+  _comp_mesg=yes
+  _description -x $tag expl "$desc" $formats
+  [[ $compstate[insert] = *unambiguous* ]] && compstate[insert]=
+  compadd "$expl[@]"
+  return 0
+fi
+
+return 1
diff --git a/Completion/Linux/Command/_btrfs b/Completion/Linux/Command/_btrfs
index bb0f724e6..65cf067aa 100644
--- a/Completion/Linux/Command/_btrfs
+++ b/Completion/Linux/Command/_btrfs
@@ -147,16 +147,16 @@ while (( $#state )); do
             '--tbytes[show sizes in TiB, or TB with --si]'
           )
         ;|
-        filesystem:resize) args+=( '1:size:_guard "(|+|-)[0-9]#[GKM]"' '2:path:->mounts' );;
+        filesystem:resize) args+=( '1: :_numbers -u bytes -N size K M G T P E' '2:path:->mounts' );;
         filesystem:defragment)
           args+=( '!-v'
             '-r[defragment files recursively]'
             '-c+[compress files while defragmenting]::compression algorithm:(zlib lzo zstd)'
             '-r[defragment files recursively]'
             '-f[flush after defragmenting]'
-            '-s[start position]:byte position'
-            '-l[defragment limited number of bytes]:length (bytes)'
-            '-t[defragment only files over a certain size]:minimum size (bytes) [32M]'
+            '-s[start position]: :_numbers -u bytes -d "beginning of file" offset K M G T P E'
+            '-l[defragment limited number of bytes]: :_numbers -u bytes length K M G T P E'
+            '-t[defragment only extents up to a certain size]: :_numbers -u bytes -d 32M "maximum extent size" K M G T P E'
             '*:file:_files'
           )
         ;;
diff --git a/Completion/Unix/Command/_dd b/Completion/Unix/Command/_dd
index e5c5e63ce..10682bc8e 100644
--- a/Completion/Unix/Command/_dd
+++ b/Completion/Unix/Command/_dd
@@ -1,18 +1,19 @@
 #compdef dd gdd
 
-local -a vals conv flags
+local -a vals conv flags units
 local variant
 
+units=( w:word b:block k:1024 m g t )
 _pick_variant -r variant gnu=GNU $OSTYPE --version
 
 vals=(
-  '(ibs obs)bs[block size]:block size (bytes)'
-  'cbs[conversion buffer size]:buffer size (bytes)'
+  '(ibs obs)bs[block size]: :_numbers -u bytes "block size" $units'
+  'cbs[conversion buffer size]: :_numbers -u bytes "buffer size" $units'
   'conv[specify conversions to apply]: :_values -s , conversion $conv'
   'count[number of input blocks to copy]:blocks'
-  '(bs)ibs[input block size]:block size (bytes)'
+  '(bs)ibs[input block size]: :_numbers -u bytes -d 512 "block size" $units'
   'if[specify input file]:input file:_tilde_files'
-  '(bs)obs[output block size]:block size (bytes)'
+  '(bs)obs[output block size]: :_numbers -u bytes -d 512 "block size" $units'
   'of[specify output file]:output file:_tilde_files'
   'seek[output blocks initially skipped]:blocks'
   'skip[input blocks initially skipped]:blocks'
@@ -63,7 +64,7 @@ case $variant in
   freebsd*)
     vals+=(
       'fillchar[specify padding character]:character'
-      'speed[limit copying speed]:speed (bytes/second)'
+      'speed[limit copying speed]: :_numbers -u bytes/second speed $units'
     )
     conv+=(
       '(pareven parnone parodd parset)'{pareven,parnone,parodd,parset}
@@ -75,6 +76,7 @@ case $variant in
     )
     flags+=( fullblock noatime nocache count_bytes skip_bytes seek_bytes )
     conv+=( excl nocreat fdatasync fsync )
+    units=( c:1 w:2 b:512 kB:1000 K:1024 MB:1000^2 M:1024\^2 GB G TB T PB P EB E ZB Z YB Y )
   ;;
   netbsd*)
     vals+=(
diff --git a/Completion/Unix/Command/_git b/Completion/Unix/Command/_git
index 7c7fb22bc..c757376b0 100644
--- a/Completion/Unix/Command/_git
+++ b/Completion/Unix/Command/_git
@@ -3720,8 +3720,8 @@ _git-fast-import () {
                                                            now\:"use current time and timezone"' \
     '--done[terminate with error if there is no "done" command at the end of the stream]' \
     '--force[force updating modified existing branches]' \
-    '--max-pack-size=-[maximum size of each packfile]: : __git_guard_bytes' \
-    '--big-file-threshold=-[maximum size of blob to create deltas for]: : __git_guard_bytes' \
+    '--max-pack-size=-[maximum size of each packfile]: : __git_guard_bytes -d unlimited size' \
+    '--big-file-threshold=-[maximum size of blob to create deltas for]: : __git_guard_bytes -d 512m size' \
     '--depth=-[maximum delta depth for blob and tree deltification]: :__git_guard_number "maximum delta depth"' \
     '--active-branches=-[maximum number of branches to maintain active at once]: :__git_guard_number "maximum number of branches"' \
     '--export-marks=-[dump internal marks table when complete]: :_files' \
@@ -7506,7 +7506,7 @@ __git_guard_number () {
 
 (( $+functions[__git_guard_bytes] )) ||
 __git_guard_bytes () {
-  _guard '[[:digit:]]#([kKmMgG]|)' ${*:-size}
+  _numbers -u bytes ${*:-size} k m g
 }
 
 (( $+functions[__git_datetimes] )) ||
diff --git a/Completion/Unix/Command/_head b/Completion/Unix/Command/_head
index f25c97c83..0771b1e4d 100644
--- a/Completion/Unix/Command/_head
+++ b/Completion/Unix/Command/_head
@@ -32,20 +32,14 @@ _arguments -C -s -S $opts : $args '*:file:_files' && return 0
 
 case $state in
   (number)
-    local mlt sign digit
-    mlt='multiplier:multiplier:((b\:512 K\:1024 KB\:1000 M\:1024\^2'
-    mlt+=' MB\:1000\^2 G\:1024\^3 GB\:1000\^3 T\:1024\^4 TB\:1000\^4))'
-    sign='sign:sign:((-\:"print all but the last specified bytes/lines"'
-    sign+=' +\:"print the first specified bytes/lines (default)"))'
-    digit='digits:digit:(0 1 2 3 4 5 6 7 8 9)'
-    if compset -P '(-|+|)[0-9]##'; then
-      _alternative $mlt $digit && ret=0
-    elif [[ -z $PREFIX ]]; then
-      _alternative $sign $digit && ret=0
-    elif compset -P '(+|-)'; then
-      _alternative $digit && ret=0
-    fi
-    ;;
+    local alts
+    [[ -z $PREFIX ]] && alts=(
+      'sign:sign:((-\:"print all but the last specified bytes/lines" +\:"print the first specified bytes/lines (default)"))'
+    )
+    compset -P '+'
+    alts+=( 'numbers: :_numbers -N $state_descr b\:512 K\:1024 KB\:1000 M\:1024\^2 MB\:1000\^2 G\:1024\^3 GB\:1000\^3 T\:1024\^4 TB\:1000\^4' )
+    _alternative $alts && ret=0
+  ;;
 esac
 
 return ret
diff --git a/Completion/Unix/Command/_killall b/Completion/Unix/Command/_killall
index 36accb2e0..3ddd36341 100644
--- a/Completion/Unix/Command/_killall
+++ b/Completion/Unix/Command/_killall
@@ -38,15 +38,9 @@ if _pick_variant psmisc=PSmisc unix --version; then
 
   case $state in
     (time)
-      local -a units=( 's:seconds' 'm:minutes' 'h:hours' 'd:days'
-			'w:weeks' 'M:months' 'y:years' )
-      if compset -P '[0-9]##(|.[0-9]#)'; then
-	_alternative 'float-numbers:: _message "float number"' \
-		    'units:: _describe unit units' && ret=0
-      else
-	_message 'float number and unit' && ret=0
-      fi
-      ;;
+      _numbers -fN age 's:seconds' 'm:minutes' 'h:hours' 'd:days' \
+          'w:weeks' 'M:months' 'y:years'
+    ;;
   esac
 
   return ret
diff --git a/Completion/Unix/Command/_pv b/Completion/Unix/Command/_pv
index 68f8e8586..d02d3a35d 100644
--- a/Completion/Unix/Command/_pv
+++ b/Completion/Unix/Command/_pv
@@ -25,7 +25,7 @@ _arguments -s -S $args \
   '(-q --quiet)'{-q,--quiet}"[don't output any transfer information at all, useful with -L]" \
   '(-W --wait)'{-W,--wait}'[display nothing until first byte transferred]' \
   '(-D --delay-start -R --remote)'{-D+,--delay-start=}'[display nothing until delay has passed]:delay (seconds)' \
-  '(-s --size)'{-s+,--size=}'[set estimated data size]:size (bytes):->size-unit' \
+  '(-s --size)'{-s+,--size=}'[set estimated data size]: :_numbers -u bytes size K M G T' \
   '(-l --line-mode -R --remote)'{-l,--line-mode}'[count lines instead of bytes]' \
   '(-0 --null -l --line-mode)'{-0,--null}'[lines are null-terminated]' \
   '(-i --interval)'{-i+,--interval=}'[update every after specified interval]:interval (seconds) [1]' \
@@ -34,8 +34,8 @@ _arguments -s -S $args \
   '(-N --name)'{-N+,--name=}'[prefix visual information with given name]:name' \
   '(-f --force -R --remote)'{-f,--force}'[output even if standard error is not a terminal]' \
   '(-c --cursor -R --remote)'{-c,--cursor}'[use cursor positioning escape sequences]' \
-  '(-L --rate-limit)'{-L+,--rate-limit=}'[limit transfer rate]:rate (bytes per second):->size-unit' \
-  '(-B --buffer-size)'{-B+,--buffer-size=}'[use a buffer size of given size]:size (bytes):->size-unit' \
+  '(-L --rate-limit)'{-L+,--rate-limit=}'[limit transfer rate]: :_numbers -u "bytes per second" rate K M G T' \
+  '(-B --buffer-size)'{-B+,--buffer-size=}'[use a buffer size of given size]: :_numbers -u bytes size K M G T' \
   '(-C --no-splice)'{-C,--no-splice}'[never use splice(), always use read/write]' \
   '(-R --remote)*'{-E,--skip-errors}"[skip read errors in input${Edesc}]" \
   '(-S --stop-at-size -R --remote)'{-S,--stop-at-size}'[stop after --size bytes have been transferred]' \
@@ -70,18 +70,6 @@ case $state in
       _pids $suf && ret=0
     fi
   ;;
-  size-unit)
-    if compset -P '<->'; then
-      _tags values units
-    else
-      _tags values
-    fi
-    while _tags; do
-      _requested values && _message -e values "$state_descr" && ret=0
-      _requested units expl unit compadd -o nosort - K M G T && ret=0
-      (( ret )) || break
-    done
-  ;;
 esac
 
 return ret
diff --git a/Completion/Unix/Command/_rclone b/Completion/Unix/Command/_rclone
index 27b4dd926..a2e3429f5 100644
--- a/Completion/Unix/Command/_rclone
+++ b/Completion/Unix/Command/_rclone
@@ -62,7 +62,7 @@ _arguments -C \
   '--backup-dir[make backups into hierarchy based at specified directory]:directory:_directories' \
   '--bind[specify socal address to bind to for outgoing connections]:IPv4, IPv6 or name' \
   '--buffer-size[specify in memory buffer size when reading files for each --transfer]:size [16M]' \
-  '--bwlimit[specify bandwidth limit]:BwTimetable (kBytes/s or b|k|M|G suffix)' \
+  '--bwlimit[specify bandwidth limit]: :_numbers -u kBytes/s limit b k M G' \
   '--cache-dir[specify directory rclone will use for caching]:directory [~/.cache/rclone]:_directories' \
   '--checkers[specify number of checkers to run in parallel]:number [8]': \
   '(-c --checksum)'{-c,--checksum}'[skip based on checksum & size, not mod-time & size]' \
@@ -99,15 +99,15 @@ _arguments -C \
   '--log-format[specify comma separated list of log format options]:string ["date,time"]' \
   '--log-level[specify log level]:string [NOTICE]:(DEBUG INFO NOTICE ERROR)'  \
   '--low-level-retries[number of low level retries to do]:int [10]' \
-  '--max-age[only transfer files younger than this in s or suffix ms|s|m|h|d|w|M|y]:duration [default off]' \
+  '--max-age[only transfer files younger than specified age]: :_numbers -u seconds age ms\:milliseconds \:s\:seconds m\:minutes h\:hours d\:days w\:weeks M\:months y\:years' \
   '--max-backlog[maximum number of objects in sync or check backlog]:int [10000]' \
   '--max-delete[when synchronizing, limit the number of deletes]:delete limit [-1]' \
   '--max-depth[limit the recursion depth]:depth [-1]' \
-  '--max-size[only transfer files smaller than this in k or suffix b|k|M|G]:int [default off]' \
+  '--max-size[only transfer files smaller than specified size]: :_numbers -u kBytes size \:k M G' \
   '--max-transfer[maximum size of data to transfer]:int [default off]' \
   '--memprofile[write memory profile to file]:file:_files' \
-  '--min-age[only transfer files older than this in s or suffix ms|s|m|h|d|w|M|y]:duration [default off]' \
-  '--min-size[only transfer files bigger than this in k or suffix b|k|M|G]:int [default off]' \
+  '--min-age[only transfer files older than specified age]: :_numbers -u seconds age ms\:milliseconds \:s\:seconds m\:minutes h\:hours d\:days w\:weeks M\:months y\:years' \
+  '--min-size[only transfer files bigger than specified size]: :_numbers -u kBytes size \:k M G' \
   '--modify-window[specify max time delta to be considered the same]:duration [1ns]' \
   '--multi-thread-cutoff[use multi-threaded downloads for files above specified size]:size (250M)' \
   '--multi-thread-streams[specify max number of streams to use for multi-threaded downloads]:number (4)' \
diff --git a/Completion/Unix/Command/_rsync b/Completion/Unix/Command/_rsync
index b1a4f6046..eb906e974 100644
--- a/Completion/Unix/Command/_rsync
+++ b/Completion/Unix/Command/_rsync
@@ -99,7 +99,7 @@ _rsync() {
   _arguments -s \
     '*'{-v,--verbose}'[increase verbosity]' \
     {--no-v,--no-verbose}'[turn off --verbose]' \
-    '--bwlimit=[limit I/O bandwidth]:limit (KiB per second)' \
+    '--bwlimit=[limit I/O bandwidth]: :_numbers -f -u "KiB per second" -d 1g limit B K M G T P' \
     '--outbuf=[set output buffering]:buffering:(none line block)' \
     '--port=[specify alternate port number]:port:(873)' \
     '--address=[bind to the specified address]:bind address:_bind_addresses' \
@@ -181,7 +181,7 @@ _rsync() {
     {--no-W,--no-whole-file}'[turn off --whole-file]' \
     '(--cc --checksum-choice)'{--cc,--checksum-choice}'=[choose the checksum algorithms]:algorithm:_sequence -n 2 compadd - auto md4 md5 none' \
     '(-x --one-file-system)'{-x,--one-file-system}"[don't cross filesystem boundaries]" \
-    '(-B --block-size)'{-B+,--block-size=}'[force a fixed checksum block-size]:block size (bytes)' \
+    '(-B --block-size)'{-B+,--block-size=}'[force a fixed checksum block-size]: :_numbers -f -u bytes -d 1g "block size" B K M G T P' \
     '(-e --rsh)'{-e+,--rsh=}'[specify the remote shell to use]:remote-shell command:(rsh ssh)' \
     '--rsync-path=[specify path to rsync on the remote machine]:remote command' \
     '--ignore-existing[ignore files that already exist on receiving side]' \
@@ -199,10 +199,10 @@ _rsync() {
     '--force-change[affect user-/system-immutable files/dirs]' \
     '--force-uchange[affect user-immutable files/dirs]' \
     '--force-schange[affect system-immutable files/dirs]' \
-    '--max-delete=[do not delete more than NUM files]:number' \
-    '--max-size=[do not transfer any file larger than specified size]:number' \
+    "--max-delete=[don't delete more than NUM files]: :_numbers -f -u bytes size B K M G T P" \
+    "--max-size=[don't transfer any file larger than specified size]: :_numbers -f -u bytes size B K M G T P" \
     '--min-size=[do not transfer any file smaller than specified size]:number' \
-    '--max-alloc=[set limit to individual memory allocation]:size (bytes) [1g]' \
+    '--max-alloc=[set limit to individual memory allocation]: :_numbers -f -u bytes -d 1g size B K M G T P' \
     '(-P)--partial[keep partially transferred files]' \
     '--no-partial[turn off --partial]' \
     '--partial-dir=[put a partially transferred file into specified directory]:directory:_directories' \
diff --git a/Completion/Unix/Command/_stdbuf b/Completion/Unix/Command/_stdbuf
index a18938ee1..4b7d98ba0 100644
--- a/Completion/Unix/Command/_stdbuf
+++ b/Completion/Unix/Command/_stdbuf
@@ -7,7 +7,9 @@ short=( -e -i -o )
 long=( --error --input --output )
 buf=( err in out )
 
-opt='[set initial buffering for std${buf[i]}]:mode or size:((0\:unbuffered L\:line\ buffered'
+opt='[set initial buffering for std${buf[i]}]: : _alternative
+  "sizes\: \: _numbers -u bytes size k M G"
+  "modes\:mode\:((0\:unbuffered L\:line\ buffered'
 if _pick_variant gnu=GNU freebsd --version; then
   gnu=1
   args=(
@@ -17,7 +19,7 @@ if _pick_variant gnu=GNU freebsd --version; then
 else
   opt+=' B\:fully\ buffered'
 fi
-opt+='))'
+opt+='))"'
 
 for ((i=1;i<=3;i++)); do
   args+=( "(${long[i]})${short[i]}+${(e)opt}" )
diff --git a/Completion/Unix/Command/_tail b/Completion/Unix/Command/_tail
index 6d6e9b2d5..e54a0b06e 100644
--- a/Completion/Unix/Command/_tail
+++ b/Completion/Unix/Command/_tail
@@ -53,20 +53,14 @@ _arguments -C -s -S $opts : $args '*:file:_files' && return
 
 case $state in
   (number)
-    local mlt sign digit
-    mlt='multipliers:multiplier:((b\:512 K\:1024 KB\:1000 M\:1024\^2'
-    mlt+=' MB\:1000\^2 G\:1024\^3 GB\:1000\^3 T\:1024\^4 TB\:1000\^4))'
-    sign='signs:sign:((+\:"start at the specified byte/line"'
-    sign+=' -\:"output the last specified bytes/lines (default)"))'
-    digit='digits:digit:(0 1 2 3 4 5 6 7 8 9)'
-    if compset -P '(-|+|)[0-9]##'; then
-      _alternative $mlt $digit && ret=0
-    elif [[ -z $PREFIX ]]; then
-      _alternative $sign $digit && ret=0
-    elif compset -P '(+|-)'; then
-      _alternative $digit && ret=0
-    fi
-    ;;
+    local alts
+    [[ -z $PREFIX ]] && alts=(
+      'sign:sign:((-\:"print all but the last specified bytes/lines" +\:"print the first specified bytes/lines (default)"))'
+    )
+    compset -P '+'
+    alts+=( 'numbers: :_numbers -N $state_descr b\:512 K\:1024 KB\:1000 M\:1024\^2 MB\:1000\^2 G\:1024\^3 GB\:1000\^3 T\:1024\^4 TB\:1000\^4' )
+    _alternative $alts && ret=0
+  ;;
 esac
 
 return ret
diff --git a/Completion/Unix/Command/_timeout b/Completion/Unix/Command/_timeout
index 2235fa5ec..c041283ac 100644
--- a/Completion/Unix/Command/_timeout
+++ b/Completion/Unix/Command/_timeout
@@ -16,5 +16,5 @@ _arguments -S -A "-" $args \
   "--foreground[don't propagate timeout to the command children]" \
   '(-s --signal)'{-s,--signal}'[specify the signal to send on timeout]:signal:_signals' \
   '(-k --kill-after)'{-k,--kill-after}'[followup first signal with SIGKILL if command persists after specified time]:time' \
-  '1: :_guard "[0-9.]#([smhd]|)" duration' \
+  '1: :_numbers -f -u seconds duration :s:seconds m:minutes h:hours d:days' \
   '*:::command:_normal'
diff --git a/Completion/Unix/Command/_zfs b/Completion/Unix/Command/_zfs
index 452e1160d..51da9170b 100644
--- a/Completion/Unix/Command/_zfs
+++ b/Completion/Unix/Command/_zfs
@@ -162,12 +162,20 @@ _zfs() {
 		"multilevel:value:(on off)"
 		"nbmand:value:(on off)"
 		"primarycache:value:(all none metadata)"
-		"quota:number or 'none':{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ $PREFIX == quota= ]]; then _wanted none expl 'number or none' compadd none; else _wanted none expl 'quota' compadd none; fi}"
+		"quota: : _alternative \
+				'sizes: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes size :B {k,M,G,T,P,E,Z}{,B}' \
+				'properties:property:(none)'"
 		"readonly:value:(on off)"
 		"recordsize:value:(512 1K 2K 4K 8K 16K 32K 64K 128K 256K 512K 1M)"
-		"refquota:number or 'none':{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ $PREFIX == refquota= ]]; then _wanted none expl 'number or none' compadd none; else _wanted none expl 'refquota' compadd none; fi}"
-		"refreservation:number or 'none':{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ $PREFIX == refreservation= ]]; then _wanted none expl 'number or none' compadd none; else _wanted none expl 'refreservation' compadd none; fi}"
-		"reservation:value:{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ $PREFIX == reservation= ]]; then _wanted none expl 'number or none' compadd none; else _wanted none expl 'reservation' compadd none; fi}"
+		"refquota: : _alternative \
+				'sizes: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes size :B {k,M,G,T,P,E,Z}{,B}' \
+				'properties:property:(none)'"
+		"refreservation: : _alternative \
+				'sizes: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes size :B {k,M,G,T,P,E,Z}{,B}' \
+				'properties:property:(auto none)'"
+		"reservation: : _alternative \
+				'sizes: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes size :B {k,M,G,T,P,E,Z}{,B}' \
+				'properties:property:(none)'"
 		"rstchown:value:(on off)"
 		"secondarycache:value:(all none metadata)"
 		"setuid:value:(on off)"
@@ -238,8 +246,8 @@ _zfs() {
 			':filesystem:_zfs_dataset -t fs -e "parent dataset"' \
 			- set2 \
 			'-s[Create sparse volume]' \
-			'-b[Set volblocksize]:blocksize:' \
-			'-V[Set size]:size:' \
+			'-b+[set volblocksize]: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes blocksize :B {k,M,G,T,P,E,Z}{,B}' \
+			'-V+[set size]: :_numbers -M "m\:{a-zA-Z}={A-Za-z}" -u bytes size :B {k,M,G,T,P,E,Z}{,B}' \
 			':volume:_zfs_dataset -t fs -e "parent dataset"'
 		;;
 
diff --git a/Completion/X/Command/_xset b/Completion/X/Command/_xset
index b35a6466b..adda47f01 100644
--- a/Completion/X/Command/_xset
+++ b/Completion/X/Command/_xset
@@ -91,8 +91,8 @@ _regex_arguments _xset_parse \
     \( "/(blank|noblank|expose|noexpose|default|on|activate|reset)$nul/" \
        ':option-s:screen saver:(blank noblank expose noexpose default on activate reset off)' \
     \| "/off$nul/" \( "/off$nul/" ':option-s-off-period:period off:(off)' \| \) \
-    \| "/[0-9]##$nul/" ':option-s-timeout:length:' \
-      \( "/[0-9]##$nul/" ':option-s-period:period:' \
+    \| "/[0-9]##$nul/" ':option-s-timeout: :_numbers -u seconds length' \
+      \( "/[0-9]##$nul/" ':option-s-period: :_numbers -u seconds period' \
       \| \) \
     \| \) \
   \| "/-r$nul/" "$guard" \
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 8c6bf9c40..40238c4b9 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -4496,8 +4496,22 @@ not contain an explanation string to be displayed above the matches.
 If tt(_description) is called with more than three arguments,
 the additional var(spec)s should be of the form `var(char)tt(:)var(str)'.
 These supply escape sequence replacements for the tt(format) style:
-every appearance of `tt(%)var(char)' will be
-replaced by var(string).
+every appearance of `tt(%)var(char)' will be replaced by var(string).
+If no additional var(spec)s are given but the description in var(descr)
+conforms to a common form then further escape sequences are set for
+elements of that description.  These elements correspond to a default
+value (`tt(%o)'), the units (`tt(%m)') range of acceptable values
+(`tt(%r)') and the remaining initial part of the description (`tt(%h)').
+The form the description takes consists of specifying the units and
+range in parentheses and the default value in square brackets, for
+example:
+
+example(_description times expl 'timeout (seconds) (0-60) [20]')
+
+It is possible to use tt(zformat) conditional expressions when styling
+these elements. So, for example, to add `tt(default:)' as a tag but only
+when there is a default value to show, the tt(format) style might
+include `tt(%(o.default: %o.))'.
 
 If the tt(-x) option is given, the description will be passed to
 tt(compadd) using the tt(-x) option instead of the default tt(-X).  This
@@ -4773,6 +4787,69 @@ checked.  If it is set completion is terminated at that point even if
 no matches have been found.  This is the same effect as in the
 tt(-first-) context.
 )
+findex(_numbers)
+item(tt(_numbers) [ var(option) ... ] [ var(description) ] [ var(suffix) ... ])(
+This can be used where a number is followed by a suffix to indicate the units.
+The unit suffixes are completed and can also be included in the description
+used when completion is invoked for the preceding number.
+
+In addition to common tt(compadd) options, tt(_numbers) accepts the following
+options:
+
+startitem()
+item(tt(-t) var(tag))(
+Specify a tag to use instead of the default of tt(numbers).
+)
+item(tt(-u) var(units))(
+Indicate the default units for the number, e.g. tt(bytes).
+)
+item(tt(-l) var(min))(
+Specify the lowest possible value for the number.
+)
+item(tt(-m) var(max))(
+Specify the highest possible value for the number.
+)
+item(tt(-d) var(default))(
+Specify the default value.
+)
+item(tt(-N))(
+Allow negative numbers.  This is implied if the range includes a negative.
+)
+item(tt(-f))(
+Allow decimal numbers.
+)
+enditem()
+
+Where a particular suffix represents the default units for a number, it
+should be prefixed with a colon.  Additionally, suffixes can be followed
+by a colon and a description.  So for example, the following allows the
+age of something to be specified, either in seconds or with an optional
+suffix with a longer unit of time:
+
+example(_numbers -u seconds age :s:seconds m:minutes h:hours d:days)
+
+It is typically helpful for units to be presented in order of magnitude
+when completed.  To facilitate this, the order in which they are given
+is preserved.
+
+When the tt(format) style is looked up with the tt(descriptions) tag or
+the tag specified with tt(-t), the list of suffixes is available as a
+`tt(%x)' escape sequence. This is in addition to the usual sequences
+documented under the tt(format) style. The form this list takes can also
+be configured. To this end, the tt(format) style is first looked up with
+the tag tt(unit-suffixes). The retrieved format is applied to each
+suffix in turn and the results are then concatenated to form the
+completed list. For the tt(unit-suffixes) format, `tt(%x)' expands to
+the individual suffix and `tt(%X)' to its description. tt(%d)' indicates
+a default suffix and can be used in a condition. The index and reverse
+index are set in `tt(%i)' and `tt(%r)' respectively and are useful for
+text included only with the first and last suffixes in the list. So for
+example, the following joins the suffixes together as a comma-separated
+list:
+
+example(zstyle ':completion:*:unit-suffixes' format '%x%(r::,)')
+)
+
 findex(_options)
 item(tt(_options))(
 This can be used to complete the names of shell options.  It provides a




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