zsh-workers
 help / color / mirror / code / Atom feed
From: Oliver Kiddle <okiddle@yahoo.co.uk>
To: Zsh workers <zsh-workers@zsh.org>
Subject: PATCH: completion match ordering
Date: Mon, 06 May 2019 23:16:11 +0200	[thread overview]
Message-ID: <93343-1557177371.866119@fZLB.yW79.IWgX> (raw)
In-Reply-To: <76839-1543195550.251964@c6AU.RX4q.p78d>

I finally got around to finishing off the changes to the options for completion
match ordering via compadd -o. Thanks to dana for reminding me about this.

The argument to -o is now optional and can be abbreviated, e.g. -omat,rev
The sort style also accepts values like "numeric" and documentation and
tests have been added.

_description, _all_labels etc all take -V, -1, -2 options and in the previous
patch, I had added -o to that. However, as far as I can tell that only has any
use in the case of -V because the functions need to decide whether to add -J
group or -V group. So it was only needed because the interface conflates the
use of -V - specifying the group name and making it unsorted. I guess -1 and -2
were handled because it is common to combine them as -1V, -2J etc. Given that
it'd be useless, I decided not to add it.

Back when sort was added to _description in 18859, it was decided to have it
explicitly ignore the style if -V was passed. While, I would tend towards the
view that it is better to give users control anyway, it remains that way for
now. You don't need to pass -V to _wanted as such. To allow the style to enable
sorting, we would need to add a compadd -o value corresponding to normal sort
order: perhaps `sort' or `lexical' (i.e. not numeric). We could then rely on
compadd honouring only the first -o option passed to it.

The combination nosort,reverse could perhaps preserve (but reverse) the
original order.

We could perhaps also add the backslash ignoring that dana recently restored as
an option to -o. In my testing, that patch looks good by the way - thanks dana.

Oliver

diff --git a/Completion/Base/Core/_description b/Completion/Base/Core/_description
index 304c747a6..c2a0e080b 100644
--- a/Completion/Base/Core/_description
+++ b/Completion/Base/Core/_description
@@ -1,13 +1,13 @@
 #autoload
 
-local name gropt nopt xopt format gname hidden hide match opts tag sort
+local name nopt xopt format gname hidden hide match opts tag
+local -a gropt sort
 
 opts=()
 
-gropt=(-J)
 xopt=(-X)
 nopt=()
-zparseopts -K -D -a nopt 1 2 V=gropt J=gropt x=xopt
+zparseopts -K -D -a nopt 1 2 V=gropt J x=xopt
 
 3="${${3##[[:blank:]]#}%%[[:blank:]]#}"
 [[ -n "$3" ]] && _lastdescr=( "$_lastdescr[@]" "$3" )
@@ -33,14 +33,18 @@ zstyle -s ":completion:${curcontext}:$1" matcher match &&
 
 # Use sort style, but ignore `menu' value to help _expand.
 # Also don't override explicit use of -V.
-if { zstyle -s ":completion:${curcontext}:$1" sort sort ||
-     zstyle -s ":completion:${curcontext}:" sort sort; } &&
-    [[ "$gropt" = -J && $sort != menu ]]; then
-    if [[ "$sort" = (yes|true|1|on) ]]; then
-	gropt=(-J)
-    else
-	gropt=(-V)
+if [[ -z "$gropt" ]]; then
+  if zstyle -a ":completion:${curcontext}:$1" sort sort ||
+     zstyle -a ":completion:${curcontext}:" sort sort
+  then
+    if [[ -z "${(@)sort:#(match|numeric|reverse)}" ]]; then
+      gropt=( -o ${(j.,.)sort} )
+    elif [[ "$sort" != (yes|true|1|on|menu) ]]; then
+      gropt=( -o nosort )
     fi
+  fi
+else
+  gropt=( -o nosort )
 fi
 
 if [[ -z "$_comp_no_ignore" ]]; then
@@ -79,15 +83,15 @@ fi
 
 if [[ -n "$gname" ]]; then
   if [[ -n "$format" ]]; then
-    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt" "$gname" "$xopt" "$format"
+    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt[@]" -J "$gname" "$xopt" "$format"
   else
-    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt" "$gname"
+    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt[@]" -J "$gname"
   fi
 else
   if [[ -n "$format" ]]; then
-    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt" -default- "$xopt" "$format"
+    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt[@]" -J -default- "$xopt" "$format"
   else
-    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt" -default-
+    set -A "$name" "$opts[@]" "$nopt[@]" "$gropt[@]" -J -default-
   fi
 fi
 
diff --git a/Completion/Base/Utility/_describe b/Completion/Base/Utility/_describe
index 76ab1d995..c12eb0eab 100644
--- a/Completion/Base/Utility/_describe
+++ b/Completion/Base/Utility/_describe
@@ -108,10 +108,10 @@ while _tags; do
         fi
     
         if [[ -n $_mats ]]; then
-          compadd "$_opts[@]" "${(@)_expl:/-J/-2V}" -D $_strs -O $_mats - \
+          compadd "$_opts[@]" -2 -o nosort "${_expl[@]}" -D $_strs -O $_mats - \
                   "${(@)${(@M)${(@P)_mats}##([^:\\]|\\?)##}//\\(#b)(?)/$match[1]}"
         else
-          compadd "$_opts[@]" "${(@)_expl:/-J/-2V}" -D $_strs - \
+          compadd "$_opts[@]" -2 -o nosort "${_expl[@]}" -D $_strs - \
                   "${(@)${(@M)${(@P)_strs}##([^:\\]|\\?)##}//\\(#b)(?)/$match[1]}"
         fi
       done
diff --git a/Completion/Base/Utility/_guard b/Completion/Base/Utility/_guard
index ff62981ce..1cbd4f39b 100644
--- a/Completion/Base/Utility/_guard
+++ b/Completion/Base/Utility/_guard
@@ -2,7 +2,7 @@
 
 local garbage
 
-zparseopts -K -D -a garbage M: J: V: 1 2 n F: X:
+zparseopts -K -D -a garbage M+: J+: V+: 1 2 o+: n F: X+:
 
 [[ "$PREFIX$SUFFIX" != $~1 ]] && return 1
 
diff --git a/Completion/Base/Utility/_multi_parts b/Completion/Base/Utility/_multi_parts
index 12ff965ed..00e3e26cc 100644
--- a/Completion/Base/Utility/_multi_parts
+++ b/Completion/Base/Utility/_multi_parts
@@ -14,8 +14,8 @@ typeset -U tmp1 matches
 # Get the options.
 
 zparseopts -D -a sopts \
-    'J+:=group' 'V+:=group' 'X+:=expl' 'P:=opts' 'F:=opts' \
-    S: r: R: q 1 2 n f 'M+:=matcher' 'i=imm'
+    'J+:=group' 'V+:=group' 'x+:=expl' 'X+:=expl' 'P:=opts' 'F:=opts' \
+    S: r: R: q 1 2 o+: n f 'M+:=matcher' 'i=imm'
 
 sopts=( "$sopts[@]" "$opts[@]" )
 if (( $#matcher )); then
diff --git a/Completion/Base/Utility/_sep_parts b/Completion/Base/Utility/_sep_parts
index de836a696..6fcf54ec4 100644
--- a/Completion/Base/Utility/_sep_parts
+++ b/Completion/Base/Utility/_sep_parts
@@ -22,8 +22,8 @@ local matchflags opt group expl nm=$compstate[nmatches] opre osuf opts matcher
 
 # Get the options.
 
-zparseopts -D -a opts \
-    'J+:=group' 'V+:=group' P: F: S: r: R: q 1 2 n 'X+:=expl' 'M+:=matcher'
+zparseopts -D -a opts 'J+:=group' 'V+:=group' P: F: S: r: R: q 1 2 o+: n \
+    'x+:=expl' 'X+:=expl' 'M+:=matcher'
 
 # Get the string from the line.
 
diff --git a/Completion/Base/Utility/_sequence b/Completion/Base/Utility/_sequence
index f52955f46..101934490 100644
--- a/Completion/Base/Utility/_sequence
+++ b/Completion/Base/Utility/_sequence
@@ -10,7 +10,8 @@
 local curcontext="$curcontext" nm="$compstate[nmatches]" pre qsep nosep minus
 local -a sep num pref suf end uniq dedup
 
-zparseopts -D -a opts s:=sep n:=num p:=pref i:=pref P:=pref I:=suf S:=suf q=suf r:=suf R:=suf C:=cont d=uniq M: J: X: x:
+zparseopts -D -a opts s:=sep n:=num p:=pref i:=pref P:=pref I:=suf S:=suf \
+    q=suf r:=suf R:=suf C:=cont d=uniq M+: J+: V+: 1 2 o+: X+: x+:
 (( $#cont )) && curcontext="${curcontext%:*}:$cont[2]"
 (( $#sep )) || sep[2]=,
 
diff --git a/Completion/Darwin/Type/_mac_files_for_application b/Completion/Darwin/Type/_mac_files_for_application
index 299d8ff5c..44516b4db 100644
--- a/Completion/Darwin/Type/_mac_files_for_application
+++ b/Completion/Darwin/Type/_mac_files_for_application
@@ -35,7 +35,7 @@ _mac_parse_info_plist() {
 # Try to complete files for the specified application.
 _mac_files_for_application() {
   local -a opts
-  zparseopts -D -a opts q n 1 2 P: S: r: R: W: X+: M+: F: J+: V+:
+  zparseopts -D -a opts q n 1 2 o+: P: S: r: R: W: x+: X+: M+: F: J+: V+:
 
   local app_path
   _retrieve_mac_apps
diff --git a/Completion/Redhat/Command/_yum b/Completion/Redhat/Command/_yum
index 34a337109..8abd23ebb 100644
--- a/Completion/Redhat/Command/_yum
+++ b/Completion/Redhat/Command/_yum
@@ -212,7 +212,7 @@ _yum_ids() {
   # `${(@)@[...]}' selects a subrange from $@
   # `${(@)@[1,-2]}' are all except the last argument
   # `$@[$#]' is the last argument, e.g. the first suggestable ID
-  compadd "${(@)@[1,-2]:/-J/-V}" -M "B:0=" {$@[$#]..$maxid}
+  compadd "${(@)@[1,-2]}" -o numeric -M "B:0=" {$@[$#]..$maxid}
 }
 
 _yum_ranges() {
diff --git a/Completion/Unix/Command/_git b/Completion/Unix/Command/_git
index 4eb07e921..112098d3a 100644
--- a/Completion/Unix/Command/_git
+++ b/Completion/Unix/Command/_git
@@ -5688,7 +5688,7 @@ __git_ignore_line () {
 __git_ignore_line_inside_arguments () {
   declare -a compadd_opts
 
-  zparseopts -D -E -a compadd_opts V: J: 1 2 n f X: M: P: S: r: R: q F:
+  zparseopts -D -E -a compadd_opts V+: J+: 1 2 o+: n f x+: X+: M+: P: S: r: R: q F:
 
   __git_ignore_line $* $compadd_opts
 }
@@ -6160,7 +6160,7 @@ __git_ref_fields () {
   local match mbegin mend
   local -a cfields fields append opts all
 
-  zparseopts -D -E -a opts x: X: J: V: a=all
+  zparseopts -D -E -a opts M+: x+: X+: J+: V+: o+: 1 2 a=all
 
   if compset -P 1 '(#b)(*):'; then
     case $match[1] in
@@ -6731,7 +6731,7 @@ __git_tags_of_type () {
   tags=(${${(M)${(f)"$(_call_program ${(q)type}-tag-refs "git for-each-ref --format='%(*objecttype)%(objecttype) %(refname)' refs/tags 2>/dev/null")"}:#$type(tag|) *}#$type(tag|) refs/tags/})
   __git_command_successful $pipestatus || return 1
 
-  _wanted $type-tags expl "$type tag" compadd -M 'r:|/=* r:|=*' "$@" -a - tags
+  _wanted $type-tags expl "$type tag" compadd -M 'r:|/=* r:|=*' "$@" -o numeric -a - tags
 }
 
 # Reference Argument Types
@@ -6826,7 +6826,7 @@ __git_files_relative () {
 __git_files () {
   local compadd_opts opts tag description gitcdup gitprefix files expl
 
-  zparseopts -D -E -a compadd_opts V: J: 1 2 n f X: M: P: S: r: R: q F:
+  zparseopts -D -E -a compadd_opts V+: J+: 1 2 o+: n f x+: X+: M+: P: S: r: R: q F:
   zparseopts -D -E -a opts -- -cached -deleted -modified -others -ignored -unmerged -killed x+: --exclude+:
   tag=$1 description=$2; shift 2
 
@@ -6957,7 +6957,7 @@ __git_tree_files () {
     shift
   fi
 
-  zparseopts -D -E -a compadd_opts V: J: 1 2 n f X: M: P: S: r: R: q F:
+  zparseopts -D -E -a compadd_opts V+: J+: 1 2 o+: n f x+: X+: M+: P: S: r: R: q F:
 
   [[ "$1" == */ ]] && Path="$1" || Path="${1:h}/"
   shift
@@ -7037,7 +7037,7 @@ __git_any_repositories_or_references () {
 __git_guard () {
   declare -A opts
 
-  zparseopts -K -D -A opts M: J: V: 1 2 n F: X:
+  zparseopts -K -D -A opts M+: J+: V+: 1 2 o+: n F: x+: X+:
 
   [[ "$PREFIX$SUFFIX" != $~1 ]] && return 1
 
@@ -7075,7 +7075,7 @@ __git_guard_diff-stat-width () {
 __git_guard_number () {
   declare -A opts
 
-  zparseopts -K -D -A opts M: J: V: 1 2 n F: X:
+  zparseopts -K -D -A opts M+: J+: V+: 1 2 o+: n F: x+: X+:
 
   _guard '[[:digit:]]#' ${1:-number}
 }
diff --git a/Completion/Unix/Type/_baudrates b/Completion/Unix/Type/_baudrates
index add359d13..6e9ba4d97 100644
--- a/Completion/Unix/Type/_baudrates
+++ b/Completion/Unix/Type/_baudrates
@@ -72,7 +72,6 @@ if (( ${+opts[-f]} )); then
   done
 fi
 
-# -1V removes dupes (which there shouldn't be) and otherwise leaves the
-# order in the $rates array intact.
-_description -1V baud-rates expl 'baud rate'
-compadd "${(@)argv/#-J/-V}" "$expl[@]" -- "${rates[@]}"
+# -1 removes dupes (which there shouldn't be)
+_description -1 -o numeric baud-rates expl 'baud rate'
+compadd "${argv[@]}" "$expl[@]" -- "${rates[@]}"
diff --git a/Completion/Unix/Type/_canonical_paths b/Completion/Unix/Type/_canonical_paths
index 6eab7b677..cddc3b405 100644
--- a/Completion/Unix/Type/_canonical_paths
+++ b/Completion/Unix/Type/_canonical_paths
@@ -5,13 +5,14 @@
 # (relative path when an absolute path is given, and vice versa; when ..'s are
 # present in the word to be completed, and some paths got from symlinks).
 
-# Usage: _canonical_paths [-A var] [-N] [-MJV12nfX] tag desc [paths...]
+# Usage: _canonical_paths [-A var] [-N] [-MJV12onfX] tag desc [paths...]
 
 # -A, if specified, takes the paths from the array variable specified. Paths
 # can also be specified on the command line as shown above. -N, if specified,
 # prevents canonicalizing the paths given before using them for completion, in
 # case they are already so. `tag' and `desc' arguments are well, obvious :) In
-# addition, the options -M, -J, -V, -1, -2, -n, -F, -X are passed to compadd.
+# addition, the options -M, -J, -V, -1, -2, -o, -n, -F, -x, -X are passed to
+# compadd.
 
 _canonical_paths_add_paths () {
   # origpref = original prefix
@@ -59,7 +60,7 @@ _canonical_paths() {
   local __index
   typeset -a __gopts __opts
 
-  zparseopts -D -a __gopts M: J: V: 1 2 n F: X: A:=__opts N=__opts
+  zparseopts -D -a __gopts M+: J+: V+: o+: 1 2 n F: x+: X+: A:=__opts N=__opts
 
   : ${1:=canonical-paths} ${2:=path}
 
diff --git a/Completion/Unix/Type/_files b/Completion/Unix/Type/_files
index 467ed747c..6adaa8154 100644
--- a/Completion/Unix/Type/_files
+++ b/Completion/Unix/Type/_files
@@ -27,7 +27,7 @@ local opts tmp glob pat pats expl tag i def descr end ign tried
 local type sdef ignvars ignvar prepath oprefix rfiles rfile
 
 zparseopts -a opts \
-    '/=tmp' 'f=tmp' 'g+:-=tmp' q n 1 2 P: S: r: R: W: X+: M+: F: J+: V+:
+    '/=tmp' 'f=tmp' 'g+:-=tmp' q n 1 2 P: S: r: R: W: x+: X+: M+: F: J+: V+: o+:
 
 type="${(@j::M)${(@)tmp#-}#?}"
 if (( $tmp[(I)-g*] )); then
diff --git a/Completion/Unix/Type/_list_files b/Completion/Unix/Type/_list_files
index 6c52bc1f4..0ea02a55a 100644
--- a/Completion/Unix/Type/_list_files
+++ b/Completion/Unix/Type/_list_files
@@ -64,6 +64,6 @@ for f in ${(PQ)1}; do
  ${(r:8:)stat[gid]} ${(l:8:)stat[size]} $stat[mtime] $f")
 done
 
-(( ${#listfiles} )) && listopts=(-d listfiles -l -o)
+(( ${#listfiles} )) && listopts=(-d listfiles -l -o match)
 
 return 0
diff --git a/Completion/Unix/Type/_path_files b/Completion/Unix/Type/_path_files
index 1021c34e6..19ae59629 100644
--- a/Completion/Unix/Type/_path_files
+++ b/Completion/Unix/Type/_path_files
@@ -59,7 +59,7 @@ exppaths=()
 zparseopts -a mopts \
     'P:=pfx' 'S:=pfxsfx' 'q=pfxsfx' 'r:=pfxsfx' 'R:=pfxsfx' \
     'W:=prepaths' 'F:=ignore' 'M+:=matcher' \
-    J+: V+: X+: 1 2 n 'f=tmp1' '/=tmp1' 'g+:-=tmp1'
+    J+: V+: x+: X+: 1 2 o+: n 'f=tmp1' '/=tmp1' 'g+:-=tmp1'
 
 sopt="-${(@j::M)${(@)tmp1#-}#?}"
 (( $tmp1[(I)-[/g]*] )) && haspats=yes
@@ -168,7 +168,7 @@ if zstyle -s ":completion:${curcontext}:" file-sort tmp1; then
   if [[ "$sort" = on ]]; then
     sort=
   else
-    mopts=( "${(@)mopts/#-J/-V}" )
+    mopts=( -o nosort "${mopts[@]}" )
 
     tmp2=()
     for tmp1 in "$pats[@]"; do
diff --git a/Completion/Zsh/Command/_compadd b/Completion/Zsh/Command/_compadd
index e709e400e..781fa2af8 100644
--- a/Completion/Zsh/Command/_compadd
+++ b/Completion/Zsh/Command/_compadd
@@ -14,9 +14,13 @@ _arguments -C -s -S -A "-*" \
   '(-a)-k[matches are keys of specified associative arrays]' \
   '-d+[specify display strings]:array:_parameters -g "*array*"' \
   '-l[list display strings one per line, not in columns]' \
-  '-o[order matches by match string not by display string]' \
-  '(-1 -E)-J+[specify match group which will be sorted]:group' \
-  '-V+[specify pre-ordered match group]:group' \
+  '-o[specify order for matches by match string not by display string]:: : _values -s , order
+    "match[order by match not by display string]"
+    "nosort[matches are pre-ordered]"
+    "numeric[order numerically]"
+    "reverse[order backwards]"' \
+  '(-1 -E)-J+[specify match group]:group' \
+  '!-V+:group' \
   '(-J -E)-1[remove only consecutive duplicates from group]' \
   '-2[preserve all duplicates]' \
   '(-x)-X[specify explanation]:explanation' \
@@ -45,7 +49,7 @@ if [[ -n $state ]]; then
   elif (( $+opt_args[-k] )); then
     _parameters -g "*assoc*" && ret=0
   else
-    _message -e candidate candidates
+    _message -e candidates candidate
   fi
 fi
 
diff --git a/Completion/Zsh/Type/_file_descriptors b/Completion/Zsh/Type/_file_descriptors
index 3e9f4968b..be96dd0e4 100644
--- a/Completion/Zsh/Type/_file_descriptors
+++ b/Completion/Zsh/Type/_file_descriptors
@@ -56,4 +56,4 @@ fi
 fds=( 0 1 2 $fds )
 
 _description -V file-descriptors expl 'file descriptor'
-compadd $disp "${@/-J/-V}" "$expl[@]" -a fds
+compadd $disp -o nosort "$@" "$expl[@]" -a fds
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 8b00517b7..66c476fab 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -2534,20 +2534,20 @@ is started, making it easy to select either of them.
 )
 kindex(sort, completion style)
 item(tt(sort))(
-Many completion widgets call tt(_description) at some point which
-decides whether the matches are added sorted or unsorted (often
-indirectly via tt(_wanted) or tt(_requested)).  This style can be set
-explicitly to one of the usual `true' or `false' values as an override.
-If it is not set for the context, the standard behaviour of the
-calling widget is used.
+This allows the standard ordering of matches to be overridden.
+
+If its value is `tt(true)' or `tt(false)', sorting is enabled or disabled.
+Additionally the values associated with the `tt(-o)' option to tt(compadd) can
+also be listed: tt(match), tt(nosort), tt(numeric), tt(reverse).  If it is not
+set for the context, the standard behaviour of the calling widget is used.
 
 The style is tested first against the full context including the tag, and
 if that fails to produce a value against the context without the tag.
 
-If the calling widget explicitly requests unsorted matches, this is usually
-honoured.  However, the default (unsorted) behaviour of completion
-for the command history may be overridden by setting the style to
-`true'.
+In many cases where a calling widget explicitly selects a particular ordering
+in lieu of the default, a value of `tt(true)' is not honoured.  An example of
+where this is not the case is for command history where the default of sorting
+matches chronologically may be overridden by setting the style to `true'.
 
 In the tt(_expand) completer, if it is set to
 `true', the expansions generated will always be sorted.  If it is set
@@ -4404,11 +4404,11 @@ convention is not enforced).  The description for the corresponding set
 of matches is passed to the function in var(descr).
 
 The styles tested are: tt(format), tt(hidden), tt(matcher),
-tt(ignored-patterns) and tt(group-name).  The tt(format) style is first
-tested for the given var(tag) and then for the tt(descriptions) tag if
-no value was found, while the remainder are only tested for the tag
-given as the first argument.  The function also calls tt(_setup)
-which tests some more styles.
+tt(ignore-line), tt(ignored-patterns), tt(group-name) and tt(sort).
+The tt(format) style is first tested for the given var(tag) and then for
+the tt(descriptions) tag if no value was found, while the remainder are
+only tested for the tag given as the first argument.  The function also
+calls tt(_setup) which tests some more styles.
 
 The string returned by the tt(format) style (if any) will be modified so
 that the sequence `tt(%d)' is replaced by the var(descr) given as the third
diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo
index 1cc94bf95..0d8d4cc40 100644
--- a/Doc/Zsh/compwid.yo
+++ b/Doc/Zsh/compwid.yo
@@ -446,12 +446,13 @@ startitem()
 findex(compadd)
 cindex(completion widgets, adding specified matches)
 redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ ))ifnztexi(        )))
-xitem(tt(compadd )[ tt(-akqQfenUlo12C) ] [ tt(-F) var(array) ])
+xitem(tt(compadd )[ tt(-akqQfenUl12C) ] [ tt(-F) var(array) ])
 xitem(SPACES()[tt(-P) var(prefix) ] [ tt(-S) var(suffix) ])
 xitem(SPACES()[tt(-p) var(hidden-prefix) ] [ tt(-s) var(hidden-suffix) ])
 xitem(SPACES()[tt(-i) var(ignored-prefix) ] [ tt(-I) var(ignored-suffix) ])
 xitem(SPACES()[tt(-W) var(file-prefix) ] [ tt(-d) var(array) ])
-xitem(SPACES()[tt(-J) var(name) ] [ tt(-V) var(name) ] [ tt(-X) var(explanation) ] [ tt(-x) var(message) ])
+xitem(SPACES()[tt(-J) var(group-name) ] [ tt(-X) var(explanation) ] [ tt(-x) var(message) ])
+xitem(SPACES()[tt(-V) var(group-name) ] [ tt(-o) [ var(order) ] ])
 xitem(SPACES()[tt(-r) var(remove-chars) ] [ tt(-R) var(remove-func) ])
 xitem(SPACES()[tt(-D) var(array) ] [ tt(-O) var(array) ] [ tt(-A) var(array) ])
 xitem(SPACES()[tt(-E) var(number) ])
@@ -540,18 +541,40 @@ This option only has an effect if used together with the tt(-d)
 option. If it is given, the display strings are listed one per line,
 not arrayed in columns.
 )
-item(tt(-o))(
-This option only has an effect if used together with the tt(-d)
-option.  If it is given, the order of the output is determined by the
-match strings;  otherwise it is determined by the display strings
-(i.e. the strings given by the tt(-d) option).
+item(tt(-o) [ var(order) ])(
+This controls the order in which matches are sorted. var(order) is a
+comma-separated list comprising the following possible values.  These values
+can be abbreviated to their initial two or three characters.  Note that the
+order forms part of the group name space so matches with different orderings
+will not be in the same group.
+
+startitem()
+item(tt(match))(
+If given, the order of the output is determined by the match strings;
+otherwise it is determined by the display strings (i.e. the strings given
+by the tt(-d) option). This is the default if `tt(-o)' is specified but
+the var(order) argument is omitted.
 )
-item(tt(-J) var(name))(
+item(tt(nosort))(
+This specifies that the matches are pre-sorted and their order should be
+preserved.  This value only makes sense alone and cannot be combined with any
+others.
+)
+item(tt(numeric))(
+If the matches include numbers, sort them numerically rather than
+lexicographically.
+)
+item(tt(reverse))(
+Arrange the matches backwards by reversing the sort ordering.
+)
+enditem()
+)
+item(tt(-J) var(group-name))(
 Gives the name of the group of matches the words should be stored in.
 )
-item(tt(-V) var(name))(
-Like tt(-J) but naming an unsorted group. These are in a different name
-space than groups created with the tt(-J) flag.
+item(tt(-V) var(group-name))(
+Like tt(-J) but naming an unsorted group. This option is identical to
+the combination of tt(-J) and tt(-o nosort).
 )
 item(tt(-1))(
 If given together with the tt(-V) option, makes
diff --git a/NEWS b/NEWS
index 7e9ec05ff..ec20b4982 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,11 @@ functions may wish to familiarise themselves with `_normal -p` and
 The option CD_SILENT was added to suppress all output from cd (whether
 explicit or implicit with AUTO_CD). It is disabled by default.
 
+The compadd builtin's -o option now takes an optional argument to
+specify the order of completion matches. This affects the display
+of candidate matches and the order in which they are selected when
+cycling between them using menu completion.
+
 Changes from 5.6.2 to 5.7.1
 ---------------------------
 
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 3e9834560..743a2e3ac 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -90,6 +90,9 @@ struct cmgroup {
 #define CGF_PACKED  32		/* LIST_PACKED for this group */
 #define CGF_ROWS    64		/* LIST_ROWS_FIRST for this group */
 #define CGF_FILES   128		/* contains file names */
+#define CGF_MATSORT 256		/* sort by match rather than by display string */
+#define CGF_NUMSORT 512		/* sort numerically */
+#define CGF_REVSORT 1024	/* sort in reverse */
 
 /* This is the struct used to hold matches. */
 
@@ -300,6 +303,9 @@ struct menuinfo {
 #define CAF_ARRAYS  32    /* compadd -a or -k: array/assoc parameter names */
 #define CAF_KEYS    64    /* compadd -k: assoc parameter names */
 #define CAF_ALL    128    /* compadd -C: _all_matches */
+#define CAF_MATSORT 256   /* compadd -o match: sort by match rather than by display string */
+#define CAF_NUMSORT 512   /* compadd -o numeric: sort numerically */
+#define CAF_REVSORT 1024  /* compadd -o numeric: sort in reverse */
 
 /* Data for compadd and addmatches() */
 
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 0a454ad5f..12c70e46e 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -2080,6 +2080,9 @@ addmatches(Cadata dat, char **argv)
 
         /* Select the group in which to store the matches. */
         gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
+                  ((dat->aflags & CAF_MATSORT) ? CGF_MATSORT : 0) |
+                  ((dat->aflags & CAF_NUMSORT) ? CGF_NUMSORT : 0) |
+                  ((dat->aflags & CAF_REVSORT) ? CGF_REVSORT : 0) |
                   ((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
                   ((dat->aflags & CAF_UNIQCON) ? CGF_UNIQCON : 0));
         if (dat->group) {
@@ -3034,8 +3037,9 @@ begcmgroup(char *n, int flags)
 		HEAP_ERROR(p->heap_id);
 	    }
 #endif
-	    if (p->name &&
-		flags == (p->flags & (CGF_NOSORT|CGF_UNIQALL|CGF_UNIQCON)) &&
+	    if (p->name && flags ==
+		(p->flags & (CGF_NOSORT|CGF_UNIQALL|CGF_UNIQCON|
+			     CGF_MATSORT|CGF_NUMSORT|CGF_REVSORT)) &&
 		!strcmp(n, p->name)) {
 		mgroup = p;
 
@@ -3118,32 +3122,34 @@ addexpl(int always)
 
 /* The comparison function for matches (used for sorting). */
 
+static int matchorder;
+
 /**/
 static int
 matchcmp(Cmatch *a, Cmatch *b)
 {
-    if ((*a)->disp && !((*a)->flags & CMF_MORDER)) {
-	if ((*b)->disp) {
-	    if ((*a)->flags & CMF_DISPLINE) {
-		if ((*b)->flags & CMF_DISPLINE)
-		    return strcmp((*a)->disp, (*b)->disp);
-		else
-		    return -1;
-	    } else {
-		if ((*b)->flags & CMF_DISPLINE)
-		    return 1;
-		else
-		    return strcmp((*a)->disp, (*b)->disp);
-	    }
-	}
-	return -1;
-    }
-    if ((*b)->disp && !((*b)->flags & CMF_MORDER))
-	return 1;
+    const char *as, *bs;
+    int cmp = !!(*b)->disp - !!(*a)->disp;
+    int sortdir = (matchorder & CGF_REVSORT) ? -1 : 1;
 
-    return zstrcmp((*a)->str, (*b)->str, (SORTIT_IGNORING_BACKSLASHES|
-					  (isset(NUMERICGLOBSORT) ?
-					   SORTIT_NUMERICALLY : 0)));
+    /* if match sorting selected or we have no display strings */
+    if ((matchorder & CGF_MATSORT) || (!cmp && !(*a)->disp)) {
+	as = (*a)->str;
+	bs = (*b)->str;
+    } else {
+        if (cmp) /* matches with display strings come first */
+	    return cmp;
+
+	cmp = ((*b)->flags & CMF_DISPLINE) - ((*a)->flags & CMF_DISPLINE);
+        if (cmp) /* sort one-per-line display strings first */
+	    return cmp;
+
+	as = (*a)->disp;
+	bs = (*b)->disp;
+    }
+
+    return sortdir * zstrcmp(as, bs, (isset(NUMERICGLOBSORT) ||
+	    matchorder & CGF_NUMSORT) ? SORTIT_NUMERICALLY : 0);
 }
 
 /* This tests whether two matches are equal (would produce the same
@@ -3205,6 +3211,7 @@ makearray(LinkList l, int type, int flags, int *np, int *nlp, int *llp)
     } else {
 	if (!(flags & CGF_NOSORT)) {
 	    /* Now sort the array (it contains matches). */
+	    matchorder = flags;
 	    qsort((void *) rp, n, sizeof(Cmatch),
 		  (int (*) _((const void *, const void *)))matchcmp);
 
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 1dc2b01c2..c2f46c7f5 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -558,12 +558,53 @@ parse_class(Cpattern p, char *iptr)
     return iptr;
 }
 
+static struct { char *name; int abbrev; int oflag; } orderopts[] = {
+    { "nosort", 2, CAF_NOSORT },
+    { "match", 3, CAF_MATSORT },
+    { "numeric", 3, CAF_NUMSORT },
+    { "reverse", 3, CAF_REVSORT }
+};
+
+/* Parse the option to compadd -o, if flags is non-NULL set it
+ * returns -1 if the argument isn't a valid ordering, 0 otherwise */
+
+/**/
+static int
+parse_ordering(const char *arg, int *flags)
+{
+    int o, fl = 0;
+    const char *next, *opt = arg;
+    do {
+	int found = 0;
+	next = strchr(opt, ',');
+	if (!next)
+	    next = opt + strlen(opt);
+
+	for (o = sizeof(orderopts)/sizeof(*orderopts) - 1; o >= 0 &&
+		!found; --o)
+	{
+	    if ((found = next - opt >= orderopts[o].abbrev &&
+	            !strncmp(orderopts[o].name, opt, next - opt)))
+		fl |= orderopts[o].oflag;
+	}
+	if (!found) {
+	    if (flags) /* default to "match" */
+		*flags = CAF_MATSORT;
+	    return -1;
+	}
+    } while (*next && ((opt = next + 1)));
+    if (flags)
+	*flags |= fl;
+    return 0;
+}
+
 /**/
 static int
 bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 {
     struct cadata dat;
     char *mstr = NULL; /* argument of -M options, accumulated */
+    char *oarg = NULL; /* argument of -o option */
     int added; /* return value */
     Cmatcher match = NULL;
 
@@ -572,7 +613,7 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	return 1;
     }
     dat.ipre = dat.isuf = dat.ppre = dat.psuf = dat.prpre = dat.mesg =
-	dat.pre = dat.suf = dat.group = dat.rems = dat.remf = dat.disp = 
+	dat.pre = dat.suf = dat.group = dat.rems = dat.remf = dat.disp =
 	dat.ign = dat.exp = dat.apar = dat.opar = dat.dpar = NULL;
     dat.match = NULL;
     dat.flags = 0;
@@ -587,6 +628,7 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	}
 	for (p = *argv + 1; *p; p++) {
 	    char *m = NULL; /* argument of -M option (this one only) */
+	    int order = 0;  /* if -o found (argument to which is optional) */
 	    char **sp = NULL; /* the argument to an option should be copied
 				 to *sp. */
 	    const char *e; /* error message */
@@ -710,7 +752,11 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 		dat.flags |= CMF_DISPLINE;
 		break;
 	    case 'o':
-		dat.flags |= CMF_MORDER;
+		/* we honour just the first -o option but need to skip
+		 * over a valid argument to subsequent -o options */
+		order = oarg ? -1 : 1;
+		sp = &oarg;
+		/* no error string because argument is optional */
 		break;
 	    case 'E':
                 if (p[1]) {
@@ -741,15 +787,18 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	    if (sp) {
 		if (p[1]) {
 		    /* Pasted argument: -Xfoo. */
-		    if (!*sp)
+		    if (!*sp) /* take first option only */
 			*sp = p + 1;
-		    p += strlen(p+1);
+		    if (!order || !parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
+			p += strlen(p+1);
 		} else if (argv[1]) {
 		    /* Argument in a separate word: -X foo. */
 		    argv++;
 		    if (!*sp)
 			*sp = *argv;
-		} else {
+		    if (order && parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
+			--argv;
+		} else if (!order) {
 		    /* Missing argument: argv[N] == "-X", argv[N+1] == NULL. */
 		    zwarnnam(name, e, *p);
 		    zsfree(mstr);
diff --git a/Test/Y01completion.ztst b/Test/Y01completion.ztst
index b1c0e40e5..901556167 100644
--- a/Test/Y01completion.ztst
+++ b/Test/Y01completion.ztst
@@ -116,6 +116,46 @@ F:regression test workers/31611
 0:allow for suffixes when moving cursor to end of match (without ignored suffix)
 >line: {tst word:/}{}
 
+  comptesteval "_tst() { compadd -onum,rev -J versions r1.10 r1.1 r1.2 r2.3 r2.34 }"
+  comptest $'tst r\t'
+0:reverse numeric sorting of matches
+>line: {tst r}{}
+>NO:{r2.34}
+>NO:{r2.3}
+>NO:{r1.10}
+>NO:{r1.2}
+>NO:{r1.1}
+
+  comptesteval "_tst() { local expl; _wanted times expl time compadd -o match r1.10 r1.2 r2.3 r2.34 }"
+  comptesteval "zstyle ':completion:*:tst:*' sort reverse numeric"
+  comptest $'tst r\t'
+0:reverse numeric sorting of matches via a style
+>line: {tst r}{}
+>DESCRIPTION:{time}
+>NO:{r2.34}
+>NO:{r2.3}
+>NO:{r1.10}
+>NO:{r1.2}
+
+  comptesteval "_tst() { local disp=(a b c); compadd -o -J letters -d disp 3 2 1 }"
+  comptest $'tst \t'
+0:sort in match rather than display name order
+>line: {tst }{}
+>NO:{c}
+>NO:{b}
+>NO:{a}
+
+  comptesteval "_tst() { local expl; _wanted times expl time compadd 3am 12pm 3pm 10pm }"
+  comptesteval "zstyle ':completion:*:tst:*' sort false"
+  comptest $'tst \t'
+0:sorting disabled via the sort style
+>line: {tst }{}
+>DESCRIPTION:{time}
+>NO:{3am}
+>NO:{12pm}
+>NO:{3pm}
+>NO:{10pm}
+
 %clean
 
   zmodload -ui zsh/zpty

  parent reply	other threads:[~2019-05-06 21:17 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-11-04  0:13 [PATCH] Completion: Sort lz4 compression levels properly (+ a question) dana
2018-11-07 10:35 ` Oliver Kiddle
2018-11-07 17:52   ` dana
2018-11-26  1:25   ` completion match ordering Oliver Kiddle
2018-11-26  3:09     ` Daniel Shahaf
2018-11-26  5:18       ` dana
2018-11-26  9:37     ` Peter Stephenson
2018-11-26 23:07     ` dana
2019-05-06 21:16     ` Oliver Kiddle [this message]
2019-05-07  0:10       ` PATCH: " dana
2019-05-07 12:39         ` Oliver Kiddle
2019-08-22  8:39       ` Daniel Hahler
2019-08-23 19:05         ` Oliver Kiddle
2019-08-25 14:25           ` Daniel Hahler

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=93343-1557177371.866119@fZLB.yW79.IWgX \
    --to=okiddle@yahoo.co.uk \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).