about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSven Wischnowsky <wischnow@users.sourceforge.net>2000-06-22 08:42:36 +0000
committerSven Wischnowsky <wischnow@users.sourceforge.net>2000-06-22 08:42:36 +0000
commitb9a533f3823c3b6d69fad80a21f573670856823f (patch)
treeeca8b47b9101c1060f41500f9fb23e679ec8f94f
parent44b34667f844ce57b5b2eba0f2870c1ec7630348 (diff)
downloadzsh-b9a533f3823c3b6d69fad80a21f573670856823f.tar.gz
zsh-b9a533f3823c3b6d69fad80a21f573670856823f.tar.xz
zsh-b9a533f3823c3b6d69fad80a21f573670856823f.zip
allow subscripts for compadd -[ak]; new style for history completion; better list-colors handling (12029)
-rw-r--r--ChangeLog14
-rw-r--r--Completion/Builtins/_arrays6
-rw-r--r--Completion/Builtins/_zstyle2
-rw-r--r--Completion/Commands/_bash_completions4
-rw-r--r--Completion/Commands/_history_complete_word49
-rw-r--r--Completion/Core/_history33
-rw-r--r--Completion/Core/_main_complete8
-rw-r--r--Completion/Core/_parameters8
-rw-r--r--Completion/Core/_setup12
-rw-r--r--Completion/Core/_tags14
-rw-r--r--Completion/Debian/_apt2
-rw-r--r--Completion/X/_x_extension2
-rw-r--r--Doc/Zsh/compsys.yo29
-rw-r--r--Doc/Zsh/compwid.yo7
-rw-r--r--Src/Zle/compcore.c72
-rw-r--r--Src/Zle/compctl.c5
-rw-r--r--Src/Zle/complist.c2
-rw-r--r--Src/Zle/computil.c29
-rw-r--r--Src/zsh.h4
19 files changed, 209 insertions, 93 deletions
diff --git a/ChangeLog b/ChangeLog
index 3ae413e20..59cb0b23a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2000-06-22  Sven Wischnowsky  <wischnow@zsh.org>
+
+	* 12029: Completion/Builtins/_arrays, Completion/Builtins/_zstyle,
+ 	Completion/Commands/_bash_completions,
+ 	Completion/Commands/_history_complete_word,
+ 	Completion/Core/_history, Completion/Core/_main_complete,
+ 	Completion/Core/_parameters, Completion/Core/_setup,
+ 	Completion/Core/_tags, Completion/Debian/_apt,
+ 	Completion/X/_x_extension, Doc/Zsh/compsys.yo, Doc/Zsh/compwid.yo,
+ 	Src/zsh.h, Src/Zle/compcore.c, Src/Zle/compctl.c,
+ 	Src/Zle/complist.c, Src/Zle/computil.c: allow subscripts for
+ 	compadd -[ak]; new style for history completion; better
+ 	list-colors handling
+	
 2000-06-22  Clint Adams  <schizo@debian.org>
 
 	* 12027: Completion/User/_mailboxes: fix splitting problem in
diff --git a/Completion/Builtins/_arrays b/Completion/Builtins/_arrays
index cbeac7118..5ab6d41f0 100644
--- a/Completion/Builtins/_arrays
+++ b/Completion/Builtins/_arrays
@@ -1,3 +1,5 @@
-#defcomp shift
+#compdef shift
 
-complist -A
+local expl
+
+_wanted arrays expl array compadd -k "parameters[(R)*array*~*local*]"
diff --git a/Completion/Builtins/_zstyle b/Completion/Builtins/_zstyle
index 764afb5a1..fefa8af51 100644
--- a/Completion/Builtins/_zstyle
+++ b/Completion/Builtins/_zstyle
@@ -136,7 +136,7 @@ while [[ -n $state ]]; do
         ctop=cz
       fi
       _wanted styles expl style \
-         compadd -M 'r:|-=* r:|=*' - ${(k)styles[(R)[^:]#[$ctop][^:]#:*]}
+         compadd -M 'r:|-=* r:|=*' -k "styles[(R)[^:]#[$ctop][^:]#:*]"
       ;;
       
     style-arg)
diff --git a/Completion/Commands/_bash_completions b/Completion/Commands/_bash_completions
index 50600290d..fd2222797 100644
--- a/Completion/Commands/_bash_completions
+++ b/Completion/Commands/_bash_completions
@@ -33,8 +33,8 @@ local key=$KEYS[-1] expl
 case $key in
   '!') _main_complete _command_names
        ;;
-  '$') _main_complete - _wanted parameters expl 'exported parameters' \
-                            compadd - "${(@k)parameters[(R)*export*]}"
+  '$') _main_complete - parameters _wanted parameters expl 'exported parameters' \
+                                       compadd -k 'parameters[(R)*export*]'
        ;;
   '@') _main_complete _hosts
        ;;
diff --git a/Completion/Commands/_history_complete_word b/Completion/Commands/_history_complete_word
index 300100f59..01cd32fce 100644
--- a/Completion/Commands/_history_complete_word
+++ b/Completion/Commands/_history_complete_word
@@ -7,20 +7,26 @@
 #
 # Available styles:
 #
-#   :history-words:list -- display lists of available matches
-#   :history-words:stop -- prevent looping at beginning and end of matches
-#                          during menu-completion
-#   :history-words:sort -- sort matches lexically (default is to sort by age)
-#   :history-words:remove-all-dups --
-#                          remove /all/ duplicate matches rather than just
-#                          consecutives
-#
+#   list --  display lists of available matches
+#   stop --  prevent looping at beginning and end of matches during
+#            menu-completion
+#   sort --  sort matches lexically (default is to sort by age)
+#   remove-all-dups --
+#            remove /all/ duplicate matches rather than just consecutives
+#   range -- range of history words to complete
 
 _history_complete_word () {
   setopt localoptions nullglob rcexpandparam extendedglob
   unsetopt markdirs globsubst shwordsplit nounset ksharrays
 
-  local expl direction stop
+  local expl direction stop curcontext="$curcontext"
+  local max slice hmax=$#historywords
+
+  if [[ -z "$curcontext" ]]; then
+    curcontext=history-words:::
+  else
+    curcontext="history-words${curcontext#*:}"
+  fi
 
   if [[ $WIDGET = *newer ]]; then
     direction=newer
@@ -32,6 +38,19 @@ _history_complete_word () {
 
   zstyle -t ":completion:${curcontext}:history-words" list || compstate[list]=''
 
+  if zstyle -s ":completion:${curcontext}:history-words" range max; then
+    if [[ $max = *:* ]]; then
+      slice=${max#*:}
+      max=${max%:*}
+    else
+      slice=$max
+    fi
+    [[ max -gt hmax ]] && max=$hmax
+  else
+    max=$hmax
+    slice=$max
+  fi
+
   if [[ -n "$compstate[old_list]" &&
         ( -n "$stop" || "$compstate[insert]" = menu ) ]] ; then
     # array of matches is newest -> oldest (reverse of history order)
@@ -66,11 +85,11 @@ _history_complete_word () {
     _history_complete_word_gen_matches
   fi
 
-  [[ -n "$compstate[nmatches]" ]]
+  (( $compstate[nmatches] ))
 }
 
 _history_complete_word_gen_matches () {
-  local opt h_words
+  local opt beg=2
 
   [[ -n "$_hist_stop" ]] && PREFIX="$_hist_old_prefix"
 
@@ -90,9 +109,11 @@ _history_complete_word_gen_matches () {
   SUFFIX="$SUFFIX$ISUFFIX"
   ISUFFIX=
 
-  h_words=( "${(@)historywords[2,-1]}" )
-  _wanted "$opt" history-words expl 'history word' \
-      compadd -Q -a h_words
+  while [[ $compstate[nmatches] -eq 0 && beg -lt max ]]; do
+    _main_complete - history _wanted "$opt" history-words expl 'history word' \
+                                 compadd -Q -a 'historywords[beg,beg+slice]'
+    (( beg+=slice ))
+  done
 
   zstyle -t ":completion:${curcontext}:history-words" list ||
       compstate[list]=
diff --git a/Completion/Core/_history b/Completion/Core/_history
index 109bda91f..dafd61407 100644
--- a/Completion/Core/_history
+++ b/Completion/Core/_history
@@ -11,12 +11,12 @@
 #
 # Available styles:
 #
-#   :history-words:sort -- sort matches lexically (default is to sort by age)
-#   :history-words:remove-all-dups --
-#                          remove /all/ duplicate matches rather than just
-#                          consecutives
+#   sort --  sort matches lexically (default is to sort by age)
+#   remove-all-dups --
+#            remove /all/ duplicate matches rather than just consecutives
+#   range -- range of history words to complete
 
-local opt expl h_words
+local opt expl max slice hmax=$#historywords beg=2
 
 if zstyle -t ":completion:${curcontext}:" remove-all-dups; then
   opt=-
@@ -30,6 +30,19 @@ else
   opt="${opt}V"
 fi
 
+if zstyle -s ":completion:${curcontext}:" range max; then
+  if [[ $max = *:* ]]; then
+    slice=${max#*:}
+    max=${max%:*}
+  else
+    slice=$max
+  fi
+  [[ max -gt hmax ]] && max=$hmax
+else
+  max=$hmax
+  slice=$max
+fi
+
 PREFIX="$IPREFIX$PREFIX"
 IPREFIX=
 SUFFIX="$SUFFIX$ISUFFIX"
@@ -37,5 +50,11 @@ ISUFFIX=
 
 # We skip the first element of historywords so the current word doesn't
 # interfere with the completion
-h_words=( "${(@)historywords[2,-1]}" )
-_wanted "$opt" history-words expl 'history word' compadd -Q -a h_words
+
+while [[ $compstate[nmatches] -eq 0 && beg -lt max ]]; do
+  _wanted "$opt" history-words expl 'history word' \
+      compadd -Q -a 'historywords[beg,beg+slice]'
+  (( beg+=slice ))
+done
+
+(( $compstate[namtches] ))
diff --git a/Completion/Core/_main_complete b/Completion/Core/_main_complete
index b0798f67d..f238c88ac 100644
--- a/Completion/Core/_main_complete
+++ b/Completion/Core/_main_complete
@@ -34,7 +34,7 @@ local func funcs ret=1 tmp _compskip format nm call match \
       _saved_insert="${compstate[insert]}" \
       _saved_colors="$ZLS_COLORS"
 
-typeset -U _lastdescr _comp_ignore
+typeset -U _lastdescr _comp_ignore _comp_colors
 
 [[ -z "$curcontext" ]] && curcontext=:::
 
@@ -263,7 +263,11 @@ fi
    ( "$_comp_force_list" = ?*  && nm -ge _comp_force_list ) ]] &&
     compstate[list]="${compstate[list]//messages} force"
 
-[[ "$compstate[old_list]" = keep ]] && ZLS_COLORS="$_saved_colors"
+if [[ "$compstate[old_list]" = keep ]]; then
+  ZLS_COLORS="$_saved_colors"
+else
+  ZLS_COLORS="${(j.:.)_comp_colors}"
+fi
 
 # Now call the post-functions.
 
diff --git a/Completion/Core/_parameters b/Completion/Core/_parameters
index 0e8c548f7..d3a163b49 100644
--- a/Completion/Core/_parameters
+++ b/Completion/Core/_parameters
@@ -1,8 +1,8 @@
 #autoload
 
 # This should be used to complete parameter names if you need some of the
-# extra options of compadd. It first tries to complete only non-local
-# parameters. All arguments are given to compadd.
+# extra options of compadd. It completes only non-local parameters.
 
-compadd "$@" - "${(@)${(@)${(@)${(@f)$(typeset)}:#*local *\=*}%%\=*}##* }" ||
-    compadd "$@" - "${(@)${(@)${(@f)$(typeset)}%%\=*}##* }"
+local expl
+
+_wanted parameters expl parameter compadd "$@" -k 'parameters[(R)^*local*]'
diff --git a/Completion/Core/_setup b/Completion/Core/_setup
index 50e3dbfd8..1278fa1ba 100644
--- a/Completion/Core/_setup
+++ b/Completion/Core/_setup
@@ -7,16 +7,10 @@ local val nm="$compstate[nmatches]"
 if zstyle -a ":completion:${curcontext}:$1" list-colors val; then
   zmodload -i zsh/complist
   if [[ "$1" = default ]]; then
-    ZLS_COLORS="${(j.:.)${(@)val:gs/:/\\\:}}"
+    _comp_colors=( "$val[@]" )
   else
-    local simple grouped
-
-    simple=( "(${2})${(@)^val:#\(*\)*}" )
-    grouped=( "${(M@)val:#\(*\)*}" )
-    simple="${(j.:.)simple}:"
-    grouped="${(j.:.)grouped}:"
-    [[ "$ZLS_COLORS" != *${simple}*  ]] && ZLS_COLORS="${simple}$ZLS_COLORS"
-    [[ "$ZLS_COLORS" != *${grouped}* ]] && ZLS_COLORS="${grouped}$ZLS_COLORS"
+    _comp_colors=( "$_comp_colors[@]"
+                   "(${2})${(@)^val:#\(*\)*}" "${(M@)val:#\(*\)*}" )
   fi
 
 # Here is the problem mentioned in _main_complete.
diff --git a/Completion/Core/_tags b/Completion/Core/_tags
index c98990cec..5a1015356 100644
--- a/Completion/Core/_tags
+++ b/Completion/Core/_tags
@@ -30,18 +30,8 @@ if (( $# )); then
 
   [[ "$1" = -(|-) ]] && shift
 
-  if zstyle -a ":completion:${curcontext}:" group-order order; then
-    local name
-
-    for name in "$order[@]"; do
-      compadd -J "$name"
-      compadd -V "$name"
-      compadd -J "$name" -1
-      compadd -V "$name" -1
-      compadd -J "$name" -2
-      compadd -V "$name" -2
-    done
-  fi
+  zstyle -a ":completion:${curcontext}:" group-order order &&
+      compgroups "$order[@]"
 
   # Set and remember offered tags.
 
diff --git a/Completion/Debian/_apt b/Completion/Debian/_apt
index 8fa056594..cfc6880df 100644
--- a/Completion/Debian/_apt
+++ b/Completion/Debian/_apt
@@ -469,7 +469,7 @@ _apt-config () {
     -- \
     /$'shell\0'/ \
       \( \
-	/$'[^\0]#\0'/ ':parameters:shell variable to assign:compadd "$expl[@]" - "${(@k)parameters}"' \
+	/$'[^\0]#\0'/ ':parameters:shell variable to assign:_parameters' \
 	/$'[^\0]#\0'/ ':values:configuration key:compadd "$expl[@]" - ${${(f)"$(apt-config dump 2>&1)"}% *}' \
       \) \# \| \
     /$'dump\0'/ \| \
diff --git a/Completion/X/_x_extension b/Completion/X/_x_extension
index 690226975..d1a299e8b 100644
--- a/Completion/X/_x_extension
+++ b/Completion/X/_x_extension
@@ -15,5 +15,5 @@ else
   [[ "$1" = - ]] && shift
 
   _wanted extensions expl 'X extensions' \
-      compadd "$@" -M 'm:{a-z}={A-Z} r:|-=* r:|=*' - _xe_cache
+      compadd "$@" -M 'm:{a-z}={A-Z} r:|-=* r:|=*' -a _xe_cache
 fi
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 67d75bf3f..18eb388a8 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -386,6 +386,22 @@ ifnzman(noderef(The zsh/zutil Module))).
 When looking up styles the completion system uses full context names,
 including the tag.
 
+To have more control over when certain values for styles are used one
+can use the special parameters available in completion widgets (see
+ifzman(see zmanref(zshcompwid))\
+ifnzman(noderef(Completion Widgets)))\
+) and the tt(-e) option to tt(zstyle) that makes the value be
+evaluated when looked up.  For example, to make the tt(completer)
+style have a different value when completion for the tt(cvs) command,
+one could use the tt(words) special array:
+
+example(zstyle -e ':completion:*' completer '
+    if [[ $words[1] = cvs ]]; then
+      reply=(_complete)
+    else
+      reply=(_complete _approximate)
+    fi')
+
 Styles determine such things as how the matches are generated; some of them
 correspond to shell options (for example, the use of menu completion), but
 styles provide more specific control.  They can have any number of strings as
@@ -1732,6 +1748,19 @@ is any, and if it is different from the word on the line.
 )
 enditem()
 )
+kindex(range, completion style)
+item(tt(range))(
+This is used by the tt(_history) completer and the
+tt(_history_complete_word) bindable command to decide which words
+should be completed.  It may be set to a number, var(N), to say that
+only the last var(N) words from the history should be completed.  The
+value may also be of the form `var(max)tt(:)var(slice)'.  This means
+that first the last var(slice) words will be completed.  If that
+yields no matches, the var(slice) words before those will be tried and 
+so on, until either at least one match is generated or var(max) words
+have been tried.  The default is to complete all words from the
+history at once.
+)
 kindex(remove-all-dups, completion style)
 item(tt(remove-all-dups))(
 The tt(_history_complete_word) bindable command and the tt(_history)
diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo
index b63889573..8c86eef3c 100644
--- a/Doc/Zsh/compwid.yo
+++ b/Doc/Zsh/compwid.yo
@@ -485,11 +485,14 @@ Like tt(-i), but gives an ignored suffix.
 )
 item(tt(-a))(
 With this flag the var(words) are taken as names of arrays and the
-possible matches are their values.
+possible matches are their values.  If only some elements of the
+arrays are needed, the var(words) may also contain subscripts, as in
+`tt(foo[2,-1])'.
 )
 item(tt(-k))(
 With this flag the var(words) are taken as names of associative arrays
-and the possible matches are their keys.
+and the possible matches are their keys.  As for tt(-a), the
+var(words) may also contain subscripts, as in `tt(foo[(R)*bar*])'.
 )
 item(tt(-d) var(array))(
 This adds per-match display strings. The var(array) should contain one 
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 62d63000a..ad87fe619 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -1553,14 +1553,17 @@ get_user_var(char *nam)
 }
 
 static char **
-get_user_keys(char *nam)
+get_data_arr(char *name, int keys)
 {
-    char **ret;
+    struct value vbuf;
+    Value v;
 
-    if ((ret = gethkparam(nam)))
-	return (incompfunc ? arrdup(ret) : ret);
+    if (!(v = fetchvalue(&vbuf, &name, 1,
+			 (keys ? SCANPM_WANTKEYS : SCANPM_WANTVALS) |
+			 SCANPM_MATCHMANY)))
+	return NULL;
 
-    return NULL;
+    return getarrvalue(v);
 }
 
 /* This is used by compadd to add a couple of matches. The arguments are
@@ -1586,9 +1589,10 @@ addmatches(Cadata dat, char **argv)
     Patprog cp = NULL, *pign = NULL;
     LinkList aparl = NULL, oparl = NULL, dparl = NULL;
     Brinfo bp, bpl = brbeg, obpl, bsl = brend, obsl;
+    Heap oldheap;
 
     if (!*argv) {
-	SWITCHHEAPS(compheap) {
+	SWITCHHEAPS(oldheap, compheap) {
 	    /* Select the group in which to store the matches. */
 	    gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
 		      ((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
@@ -1602,7 +1606,7 @@ addmatches(Cadata dat, char **argv)
 	    }
 	    if (dat->mesg)
 		addmesg(dat->mesg);
-	} SWITCHBACKHEAPS;
+	} SWITCHBACKHEAPS(oldheap);
 
 	return 1;
     }
@@ -1638,7 +1642,7 @@ addmatches(Cadata dat, char **argv)
 
     /* Switch back to the heap that was used when the completion widget
      * was invoked. */
-    SWITCHHEAPS(compheap) {
+    SWITCHHEAPS(oldheap, compheap) {
 	if ((doadd = (!dat->apar && !dat->opar && !dat->dpar))) {
 	    if (dat->aflags & CAF_MATCH)
 		hasmatched = 1;
@@ -1887,17 +1891,22 @@ addmatches(Cadata dat, char **argv)
 	obpl = bpl;
 	obsl = bsl;
 	if (dat->aflags & CAF_ARRAYS) {
-	    arrays = argv;
-	    argv = NULL;
-	    while (*arrays && (!(argv = ((dat->aflags & CAF_KEYS) ?
-					 get_user_keys(*arrays) :
-					 get_user_var(*arrays))) || !*argv))
+	    Heap oldheap2;
+
+	    SWITCHHEAPS(oldheap2, oldheap) {
+		arrays = argv;
+		argv = NULL;
+		while (*arrays &&
+		       (!(argv = get_data_arr(*arrays,
+					      (dat->aflags & CAF_KEYS))) ||
+			!*argv))
+		    arrays++;
 		arrays++;
-	    arrays++;
-	    if (!argv) {
-		ms = NULL;
-		argv = &ms;
-	    }
+		if (!argv) {
+		    ms = NULL;
+		    argv = &ms;
+		}
+	    } SWITCHBACKHEAPS(oldheap2);
 	}
 	if (dat->ppre)
 	    ppl = strlen(dat->ppre);
@@ -1994,17 +2003,22 @@ addmatches(Cadata dat, char **argv)
 		free_cline(lc);
 	    }
 	    if ((dat->aflags & CAF_ARRAYS) && !argv[1]) {
-		argv = NULL;
-		while (*arrays && (!(argv = ((dat->aflags & CAF_KEYS) ?
-					     get_user_keys(*arrays) :
-					     get_user_var(*arrays))) || !*argv))
+		Heap oldheap2;
+
+		SWITCHHEAPS(oldheap2, oldheap) {
+		    argv = NULL;
+		    while (*arrays &&
+			   (!(argv = get_data_arr(*arrays,
+						  (dat->aflags & CAF_KEYS))) ||
+			    !*argv))
+			arrays++;
 		    arrays++;
-		arrays++;
-		if (!argv) {
-		    ms = NULL;
-		    argv = &ms;
-		}
-		argv--;
+		    if (!argv) {
+			ms = NULL;
+			argv = &ms;
+		    }
+		    argv--;
+		} SWITCHBACKHEAPS(oldheap2);
 	    }
 	}
 	if (dat->apar)
@@ -2015,7 +2029,7 @@ addmatches(Cadata dat, char **argv)
 	    set_list_array(dat->dpar, dparl);
 	if (dat->exp)
 	    addexpl();
-    } SWITCHBACKHEAPS;
+    } SWITCHBACKHEAPS(oldheap);
 
     /* We switched back to the current heap, now restore the stack of
      * matchers. */
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index c7356b69f..052209ee3 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -2268,13 +2268,14 @@ static int cdepth = 0;
 static int
 makecomplistctl(int flags)
 {
+    Heap oldheap;
     int ret;
 
     if (cdepth == MAX_CDEPTH)
 	return 0;
 
     cdepth++;
-    SWITCHHEAPS(compheap) {
+    SWITCHHEAPS(oldheap, compheap) {
 	int ooffs = offs, lip, lp;
 	char *str = comp_str(&lip, &lp, 0), *t;
 	char *os = cmdstr, **ow = clwords, **p, **q, qc;
@@ -2333,7 +2334,7 @@ makecomplistctl(int flags)
 	clwords = ow;
 	clwnum = on;
 	clwpos = op;
-    } SWITCHBACKHEAPS;
+    } SWITCHBACKHEAPS(oldheap);
     cdepth--;
 
     return ret;
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 445c89bb7..75a23f0e5 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -1750,8 +1750,6 @@ domenuselect(Hookdef dummy, Chdata dat)
 	}
 	setwish = wasnext = 0;
 
-    getk:
-
 	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
 	    zbeep();
 	    break;
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index da72a6902..a329d34d0 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -3200,7 +3200,7 @@ cfp_matcher_pats(char *matcher, char *add)
 		    }
 	}
 	if (*add) {
-	    char *ret = "", buf[259], *oadd = add;
+	    char *ret = "", buf[259];
 
 	    for (mp = ms; *add; add++, mp++) {
 		if (!(m = *mp)) {
@@ -3661,6 +3661,32 @@ bin_compfiles(char *nam, char **args, char *ops, int func)
     return 1;
 }
 
+static int
+bin_compgroups(char *nam, char **args, char *ops, int func)
+{
+    Heap oldheap;
+    char *n;
+
+    SWITCHHEAPS(oldheap, compheap) {
+	while ((n = *args++)) {
+	    endcmgroup(NULL);
+	    begcmgroup(n, 0);
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_NOSORT);
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_UNIQALL);
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_UNIQALL);
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
+	}
+    } SWITCHBACKHEAPS(oldheap);
+
+    return 0;
+}
+
 static struct builtin bintab[] = {
     BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
     BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
@@ -3670,6 +3696,7 @@ static struct builtin bintab[] = {
     BUILTIN("comptry", 0, bin_comptry, 0, -1, 0, NULL, NULL),
     BUILTIN("compfmt", 0, bin_compfmt, 2, -1, 0, NULL, NULL),
     BUILTIN("compfiles", 0, bin_compfiles, 1, -1, 0, NULL, NULL),
+    BUILTIN("compgroups", 0, bin_compgroups, 1, -1, 0, NULL, NULL),
 };
 
 
diff --git a/Src/zsh.h b/Src/zsh.h
index 293947463..49acf129c 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1629,8 +1629,8 @@ struct heap {
 # define NEWHEAPS(h)    do { Heap _switch_oldheaps = h = new_heaps(); do
 # define OLDHEAPS       while (0); old_heaps(_switch_oldheaps); } while (0);
 
-# define SWITCHHEAPS(h)  do { Heap _switch_oldheaps = switch_heaps(h); do
-# define SWITCHBACKHEAPS while (0); switch_heaps(_switch_oldheaps); } while (0);
+# define SWITCHHEAPS(o, h)  do { o = switch_heaps(h); do
+# define SWITCHBACKHEAPS(o) while (0); switch_heaps(o); } while (0);
 
 /****************/
 /* Debug macros */