Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
PATCH: new utility function for mixing in extra values
- X-seq: zsh-workers 54665
- From: Oliver Kiddle <opk@xxxxxxx>
- To: Zsh workers <zsh-workers@xxxxxxx>
- Subject: PATCH: new utility function for mixing in extra values
- Date: Thu, 04 Jun 2026 03:01:20 +0200
- Archived-at: <https://zsh.org/workers/54665>
- List-id: <zsh-workers.zsh.org>
This is a change I've used privately for a while and Mikael's recent
post about utility functions taking compadd options reminded me.
It's fairly common to find cases where a command accepts special
values like "forever", "auto", "never" or "now" as an alternative to
real values. Handling these can mean resorting to states or separate
functions or ugly contortions such as those removed from _zfs and _ipadm
in the patch below. I know that at times, I've simply not bothered.
We've also sometimes included support in functions, e.g. _sys_calls
which takes -a/-n for all/none.
The usage of this proposed utility function is similar to _sequence in
that it wraps another completion function, e.g:
_phony none -- _files
When searching for places where this might be useful, many of the cases
I found are in combination with such things as numbers that can't be
usefully generated. So it can also be used alone, e.g. in _ssh:
'-e+[set escape character]:escape character:_phony none'
Both there and when used with _numbers or _guard, it will set
compstate[insert] to disable the empty string completing to "none"
meaning that you'll get to see the descriptions unless you've made a
minimal start of typing "none".
If you want descriptions for the special values, the normal compadd -d
and -l options can be applied. I considered syntax like that offered by
_arguments' (( ... )) specs but the values are usually self-explanatory
and compadd options may compose better with other things.
This exposes some of the limitations in grouping, tag loops, list-colors
and how we present the descriptions and lists of matches. I'd be
interested in thoughts on improvements to the presentation and
especially ideas on how these might be achieved.
As always I'm open to alternative suggestions on naming as that's always
hard.
The _shutdown change addresses one of the outstanding github PRs.
_sleep is also new in this patch. Not the most useful command to have a
completion for but on Solaris and Linux you can pass "infinity" as the
delay.
Oliver
diff --git a/Completion/Base/Utility/_phony b/Completion/Base/Utility/_phony
new file mode 100644
index 000000000..27c124b2c
--- /dev/null
+++ b/Completion/Base/Utility/_phony
@@ -0,0 +1,51 @@
+#autoload
+
+# Mix literal matches in with those produced by another function.
+# This is useful where certain phony values, like "all", "any" or
+# "none" are permitted alongside the real matches.
+
+# _phony [compadd options] [word] [words... --] [ _function [args] ]
+# -a, -k, -l and -d options are the same as for compadd but are not
+# passed on to the child function
+# -E is only used last
+# -A and -O have their results merged
+
+local curcontext="$curcontext" minus
+local -i minus strip nm skip ret=1
+local -a phony opts cont amerge omerge phony_amat phony_omat last
+
+zparseopts -D -a opts a=phony k=phony d:=phony l=phony \
+ n F: p: i: P: I: e f s: S: q r: R: M+: J+: V+: 1 2 o+: X+: x+: D: \
+ A+:=amerge O+:=omerge \
+ E:=last
+
+if (( $argv[(I)--] )); then
+ (( strip = $argv[(i)--] ))
+ phony+=( "${(@)argv[1,strip-1]}" )
+ shift $strip
+else
+ phony+=( "$argv[1]" )
+ shift
+fi
+
+if (( ! $# )); then # no function passed
+ opts=( ${opts:/-X/-x} )
+ skip=1
+else
+ (( minus = argv[(ib:2:)-] ))
+ (( minus > $# )) && minus=2 && argv[1]+=( - )
+ argv[minus]=( "$opts[@]" ${=amerge:+-A phony_amat} ${=omerge:+-O phony_omat} "$last[@]" )
+fi
+
+compadd "$opts[@]" "$amerge[@]" "$omerge[@]" "$phony[@]" && ret=0
+nm=$compstate[nmatches]
+if (( skip )) || "$@" || [[ -n "$_comp_mesg" ]]; then
+ [[ ret -eq 0 && compstate[nmatches] -eq nm && -z "$PREFIX$SUFFIX" ]] &&
+ compstate[insert]=''
+ ret=0
+fi
+
+(( $+amerge[2] )) && set -A ${amerge[2]} "${(@P)amerge[2]}" "$phony_amat[@]"
+(( $+omerge[2] )) && set -A ${omerge[2]} "${(@P)omerge[2]}" "$phony_omat[@]"
+
+return ret
diff --git a/Completion/Darwin/Command/_fink b/Completion/Darwin/Command/_fink
index 04a067ec1..258e4ecee 100644
--- a/Completion/Darwin/Command/_fink
+++ b/Completion/Darwin/Command/_fink
@@ -123,7 +123,7 @@ _fink(){
'(-s --section)'{-s=,--section=}'[sections]:section name' \
'(-m --maintainer)'{-m=,--maintainer=}'[maintainer]:maintainer name' \
--tree='[tree]:tree name' \
- '(-w --width)'{-w=,--width=}'[width of display]:number or "auto"' \
+ '(-w --width)'{-w=,--width=}'[width of display]:number:_phony auto' \
'(1 : -)'{-h,--help}'[display help text]' \
'1: :->pkgs' && return 0
@@ -134,7 +134,7 @@ _fink(){
apropos)
_arguments \
'(-t --tab)'{-t,--tab}'[output the list with tabs as field delimiter]' \
- '(-w --width)'{-w=,--width=}'[width of display]:number or "auto"' \
+ '(-w --width)'{-w=,--width=}'[width of display]:number:_phony auto' \
'(1 : -)'{-h,--help}'[display help text]' \
'1: :->pkgs' && return
diff --git a/Completion/Solaris/Command/_ipadm b/Completion/Solaris/Command/_ipadm
index c59fc8108..c3ddd26ec 100644
--- a/Completion/Solaris/Command/_ipadm
+++ b/Completion/Solaris/Command/_ipadm
@@ -274,7 +274,7 @@ _ipadm() {
("dhcp:"*)
_arguments -A "-*" \
- '(-w --wait)'{-w,--wait}'[Seconds to wait for completion]:number or "forever":{if [[ -prefix [0-9]## ]]; then _message -e "number of seconds"; else _wanted forever expl "number or \"forever\"" compadd forever; fi}' \
+ '(-w --wait)'{-w,--wait}'[time to wait for completion]:time (seconds):_phony forever' \
'-h[Request a specific hostname]:hostname:' \
':address object name:_ipadm_addrobjs_or_ifs'
;;
diff --git a/Completion/Unix/Command/_abcde b/Completion/Unix/Command/_abcde
index 6b09d87b5..be18d7f05 100644
--- a/Completion/Unix/Command/_abcde
+++ b/Completion/Unix/Command/_abcde
@@ -34,7 +34,7 @@ _arguments -s -S -A "-*" \
'(- :)-v[show the version and exit]' \
'-V[be more verbose]' \
'-x[eject the CD when all tracks have been read]' \
- '-X+[use an alternative "cue2discid" implementation]:cue2discid:_command_names -e' \
+ '-X+[use an alternative "cue2discid" implementation]:cue2discid:_phony builtin _command_names -e' \
'-w+[add a comment to the tracks ripped from the CD]:comment' \
"-W+[concatenate CD's]:cd-number" \
'-z[debug mode]' \
diff --git a/Completion/Unix/Command/_postfix b/Completion/Unix/Command/_postfix
index 006c950e3..8c172b424 100644
--- a/Completion/Unix/Command/_postfix
+++ b/Completion/Unix/Command/_postfix
@@ -67,7 +67,7 @@ case $service in
'-d[delete]:queue id:_postfix_queue_id' \
'-h[hold]:queue id:_postfix_queue_id' \
'-H[release]:queue id:_postfix_queue_id' \
- '*-r[requeue]:queue id, or "ALL":_postfix_queue_id' \
+ '*-r[requeue]:queue id:_phony ALL _postfix_queue_id' \
'1:queue:(hold incoming active deferred)'
;;
(postqueue)
diff --git a/Completion/Unix/Command/_shutdown b/Completion/Unix/Command/_shutdown
index a237b14e0..2020b9d17 100644
--- a/Completion/Unix/Command/_shutdown
+++ b/Completion/Unix/Command/_shutdown
@@ -23,7 +23,7 @@ case $OSTYPE in
'-r[reboot the system]'
'-k[kick everybody off]'
'-n[prevent file system cache from being flushed]'
- '1: :_guard "^-*" "time (now/hh\:mm/+mins)"'
+ '1:time (hh\:mm/+mins):_phony now -- _guard "^-*" "time (hh\:mm/+mins)"' \
'*:warning message'
)
;|
diff --git a/Completion/Unix/Command/_sleep b/Completion/Unix/Command/_sleep
new file mode 100644
index 000000000..54c132434
--- /dev/null
+++ b/Completion/Unix/Command/_sleep
@@ -0,0 +1,25 @@
+#autoload sleep
+
+local sum inf fraction
+local -a expl units
+
+case $OSTYPE in
+ linux-gnu)
+ args=(
+ -S
+ '--help[display help information]'
+ '--version[display version information]'
+ )
+ ;& # fall-through
+ solaris*)
+ inf="_phony infinity --"
+ ;& # fall-through
+ darwin*|freebsd*)
+ units=( :s:seconds m:minutes h:hours d:days )
+ sum='*'
+ fraction='-f'
+ ;;
+esac
+
+_arguments $args \
+ "${sum}:interval:$inf _numbers $fraction -u seconds interval $units"
diff --git a/Completion/Unix/Command/_ssh b/Completion/Unix/Command/_ssh
index 9ab1a1139..c95849e84 100644
--- a/Completion/Unix/Command/_ssh
+++ b/Completion/Unix/Command/_ssh
@@ -13,7 +13,7 @@ _ssh () {
'-A[enable forwarding of the authentication agent connection]'
'-C[compress data]'
'-c+[select encryption cipher]:encryption cipher:->ciphers'
- '-F+[specify alternate config file]:config file:_files'
+ '-F+[specify alternate config file]:config file:_phony none _files'
'*-i+[select identity file]:SSH identity file:_files -g "*(-.^AR)"'
'*-o+[specify extra options]:option string:->option'
)
@@ -41,7 +41,7 @@ _ssh () {
'-B+[bind to specified interface before attempting to connect]:interface:_net_interfaces' \
'(-P)-b+[specify interface to transmit on]:bind address:_bind_addresses' \
'-D+[specify a dynamic port forwarding]:dynamic port forwarding:->dynforward' \
- '-e+[set escape character]:escape character (or `none'\''):' \
+ '-e+[set escape character]:escape character:_phony none' \
'-E+[append log output to file instead of stderr]:log file:_files' \
'(-n)-f[go to background]' \
'-g[allow remote hosts to connect to local forwarded ports]' \
@@ -61,7 +61,7 @@ _ssh () {
'-p+[specify port on remote host]:port number on remote host' \
'(-v)*-q[quiet operation]' \
'*-R+[specify remote port forwarding]:remote port forwarding:->forward' \
- '-S+[specify location of control socket for connection sharing]:path to control socket:_files' \
+ '-S+[specify location of control socket for connection sharing]:path to control socket:_phony none _files' \
'(- 1 *)-Q+[query parameters]:query option:((cipher\:"supported symmetric ciphers" cipher-auth\:"supported symmetric ciphers that support authenticated encryption" compression mac\:"supported message integrity codes" kex\:"key exchange algorithms" kex-gss\:"GSSAPI key exchange algorithms" key\:"key types" key-cert\:"certificate key types" key-plain\:"non-certificate key types" key-sig\:"all key types and signature algorithms" protocol-version\:"supported SSH protocol versions" sig\:"supported signature algorithms" help\:"show supported queries" HostbasedAcceptedAlgorithms HostKeyAlgorithms KexAlgorithms MACs PubkeyAcceptedAlgorithms))' \
'-s[invoke subsystem]' \
'(-t)-T[disable pseudo-tty allocation]' \
@@ -263,7 +263,7 @@ _ssh () {
"$p1($cmn -f -k -u -D)-U[indicate that CA key is held by ssh-agent]" \
"$p1($cmn -f -k -u -U)-D+[indicate the CA key is stored in a PKCS#11 token]:PKCS11 shared library:_files -g '*.(so|dylib)(|.<->)(-.)'" \
"$p1($cmn -f -k -u)-n+[specify user/host principal names to include in certificate]:principals" \
- "$p1($cmn -f -u)-V+[specify certificate validity interval]:interval" \
+ "$p1($cmn -f -u)-V+[specify certificate validity interval]:interval:_phony -S\: always" \
"($cmn -I -h -n -D -O -U -V)-k[generate a KRL file]" \
"$p1($cmn -I -h -n -D -O -U -V)-u[update a KRL]" \
- signature \
@@ -402,8 +402,8 @@ _ssh () {
'values:truth value:(yes no)' && ret=0
;;
(#i)escapechar=*)
- _message -e 'escape character (or `none'\'')'
- ret=0
+ _description escape-characters expl 'escape character'
+ _phony "$expl[@]" none && ret=0
;;
(#i)fingerprinthash=*)
_values 'fingerprint hash algorithm' \
diff --git a/Completion/Unix/Command/_zfs b/Completion/Unix/Command/_zfs
index 3a884439b..3e2882536 100644
--- a/Completion/Unix/Command/_zfs
+++ b/Completion/Unix/Command/_zfs
@@ -169,8 +169,8 @@ case $service:$implementation in
'userobjquota@'
'vscan:value:(on off)'
'xattr:value:(on off dir sa)'
- "filesystem_limit: :{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ -prefix n ]]; then compadd none; else _message -e limits 'number or none'; fi}"
- "snapshot_limit: :{if [[ -prefix [0-9]## ]]; then _message -e 'number'; elif [[ -prefix n ]]; then compadd none; else _message -e limits 'number or none'; fi}"
+ "filesystem_limit:unlimited:_phony none _numbers limit"
+ "snapshot_limit:unlimited:_phony none _numbers limit"
'volmode:mode:((
default\:use\ system-wide\ tunable
full\:expose\ as\ block\ devices
@@ -1023,7 +1023,7 @@ case $service:$words[1] in
'-f[force use of in-use devices]' \
'-n[display configuration without creating pool]' \
'-R+[use alternate root]:alternate root:_directories' \
- '-m+[set mountpoint for root dataset]:mountpoint' \
+ '-m+[set mountpoint for root dataset]:mountpoint:_phony none _path_files -/ -P / -W /' \
'-t+[use a temporary pool name]:pool name' \
':pool :_guard "^-*" "pool name"' \
'*: :->virtual-devices'
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 7f748ad73..38eb5b75c 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -4907,6 +4907,24 @@ Finally, the tt(_path_files) function uses the styles tt(expand),
tt(ambiguous), tt(special-dirs), tt(list-suffixes) and tt(file-sort)
described above.
)
+findex(_phony)
+item(tt(_phony) ... [ var(word) ... ] [ [ tt(--) ] var(function) [ tt(-) ] ... ])(
+This function allows extra matches to be mixed in with the matches
+generated by a function. Sometimes a command accepts special values like
+`tt(any)', `tt(all)', or `tt(none)' to be specified but the applicable
+completion function only generates real values.
+
+tt(_phony) takes all the same arguments as tt(compadd). With the exception
+of `tt(-a)', `tt(-d)', `tt(-k)' and `tt(-l)', these are passed on to the
+following function. Unless there is only one word, a `tt(--)' separator is
+needed to indicate where the words finish and the function begins. You can
+also omit the function in which case an empty input won't match the phony
+values, allowing descriptions to be displayed. This is useful where real
+values can't be generated and tt(_message -e) would otherwise be used.
+
+To ensure that real and phony matches are grouped together, any options
+defining the match group should be passed to tt(_phony).
+)
findex(_pick_variant)
redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ @ @ @ @ @ @ ))ifnztexi( )))
xitem(tt(_pick_variant )[ tt(-b) var(builtin-label) ] [ tt(-c) var(command) ] [ tt(-r) var(name) ])
Messages sorted by:
Reverse Date,
Date,
Thread,
Author