about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSven Wischnowsky <wischnow@users.sourceforge.net>2001-07-25 08:52:34 +0000
committerSven Wischnowsky <wischnow@users.sourceforge.net>2001-07-25 08:52:34 +0000
commiteba59194d72250402bdbb97a866ffea89ec9d7a7 (patch)
tree607dc8f64428fb9f749405c96e7c3e7969739cce
parent6d81779954a9a5ef64c87a21ce131190a1214d7c (diff)
downloadzsh-eba59194d72250402bdbb97a866ffea89ec9d7a7.tar.gz
zsh-eba59194d72250402bdbb97a866ffea89ec9d7a7.tar.xz
zsh-eba59194d72250402bdbb97a866ffea89ec9d7a7.zip
make display for groups in _describe nicer; improve packing with list_packed; leave space for type character (list_types) only in groups with at least one file name (15477)
-rw-r--r--ChangeLog9
-rw-r--r--Completion/Base/Utility/_describe40
-rw-r--r--Doc/Zsh/compwid.yo12
-rw-r--r--Src/Zle/comp.h3
-rw-r--r--Src/Zle/compcore.c69
-rw-r--r--Src/Zle/complete.c18
-rw-r--r--Src/Zle/complist.c7
-rw-r--r--Src/Zle/compresult.c50
-rw-r--r--Src/Zle/computil.c456
9 files changed, 506 insertions, 158 deletions
diff --git a/ChangeLog b/ChangeLog
index b3c2e034c..3697bc10e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2001-07-25  Sven Wischnowsky  <wischnow@zsh.org>
+
+	* 15477: Completion/Base/Utility/_describe, Doc/Zsh/compwid.yo,
+	Src/Zle/comp.h, Src/Zle/compcore.c, Src/Zle/complete.c,
+	Src/Zle/complist.c, Src/Zle/compresult.c, Src/Zle/computil.c:
+	make display for groups in _describe nicer; improve packing
+	with list_packed; leave space for type character (list_types)
+	only in groups with at least one file name
+
 2001-07-24  Sven Wischnowsky  <wischnow@zsh.org>
 
 	* 15470: Src/parse.c: remove nulargs in here strings
diff --git a/Completion/Base/Utility/_describe b/Completion/Base/Utility/_describe
index 45b8c17d6..2783c25d1 100644
--- a/Completion/Base/Utility/_describe
+++ b/Completion/Base/Utility/_describe
@@ -2,8 +2,9 @@
 
 # This can be used to add options or values with descriptions as matches.
 
-local _opt _expl _tmps _tmpd _tmph _tmpmd _tmpms _tmpmh
-local _type=values _descr _ret=1 _showd _nm _hide _args _grp
+local _opt _expl _tmpm _tmpd
+local _type=values _descr _ret=1 _showd _nm _hide _args _grp _sep
+local csl="$compstate[list]" csl2
 
 # Get the option.
 
@@ -27,6 +28,7 @@ if zstyle -T ":completion:${curcontext}:$_type" list-grouped; then
 
   _argv=( "$@" )
   _grp=(-g)
+  _sep='-- '
   _new=( "$1" )
   shift
 
@@ -66,43 +68,33 @@ if zstyle -T ":completion:${curcontext}:$_type" list-grouped; then
   set - "$_argv[@]"
 else
   _grp=()
+  _sep=' -- '
 fi
 
 _descr="$1"
 shift
 
 [[ "$_type" = options ]] &&
-    zstyle -t ":completion:${curcontext}:options" prefix-hidden && _hide=yes
+    zstyle -t ":completion:${curcontext}:options" prefix-hidden &&
+        _hide="${(M)PREFIX##(--|[-+])}"
 
 _tags "$_type"
 while _tags; do
   while _next_label "$_type" _expl "$_descr"; do
 
     if [[ -n "$_showd" ]]; then
-      compdescribe -I ' -- ' "$_grp[@]" "$@"
+      compdescribe -I "$_hide" "$_sep" _expl "$_grp[@]" "$@"
     else
-      compdescribe -i "$@"
+      compdescribe -i "$_hide" "$@"
     fi
 
-    while compdescribe -g _args _tmpd _tmpmd _tmph _tmpmh _tmps _tmpms; do
-
-      # See if we should remove the option prefix characters.
-
-      if [[ -n "$_hide" ]]; then
-        if [[ "$PREFIX" = --* ]]; then
-          _tmpd=( "${(@)_tmpd#--}" )
-          _tmph=( "${(@)_tmph#--}" )
-          _tmps=( "${(@)_tmps#--}" )
-        elif [[ "$PREFIX" = [-+]* ]]; then
-          _tmpd=( "${(@)_tmpd#[-+]}" )
-          _tmph=( "${(@)_tmph#[-+]}" )
-          _tmps=( "${(@)_tmps#[-+]}" )
-        fi
-      fi
-
-      compadd "$_args[@]" "$_expl[@]" -ld _tmpd -a _tmpmd && _ret=0
-      compadd -n "$_args[@]" "$_expl[@]" -ld _tmph -a _tmpmh && _ret=0
-      compadd "$_args[@]" "$_expl[@]" -d _tmps  -a _tmpms && _ret=0
+    compstate[list]="$csl"
+
+    while compdescribe -g csl2 _args _tmpm _tmpd; do
+
+      compstate[list]="$csl $csl2"
+
+      compadd "$_args[@]" -d _tmpd -a _tmpm && _ret=0
     done
   done
   (( _ret )) || return 0
diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo
index 8b3537100..e3f43e382 100644
--- a/Doc/Zsh/compwid.yo
+++ b/Doc/Zsh/compwid.yo
@@ -441,6 +441,7 @@ xitem([ tt(-W) var(file-prefix) ] [ tt(-d) var(array) ])
 xitem([ tt(-J) var(name) ] [ tt(-V) var(name) ] [ tt(-X) var(explanation) ] [ tt(-x) var(message) ])
 xitem([ tt(-r) var(remove-chars) ] [ tt(-R) var(remove-func) ])
 xitem([ tt(-D) var(array) ] [ tt(-O) var(array) ] [ tt(-A) var(array) ])
+xitem([ tt(-E) var(number) ])
 item([ tt(-M) var(match-spec) ] [ tt(--) ] [ var(words) ... ])(
 
 This builtin command can be used to add matches directly and control
@@ -664,6 +665,17 @@ match.  If no string is given, it will be shown as a string containing
 the strings that would be inserted for the other matches, truncated to 
 the width of the screen.
 )
+item(tt(-E))(
+This option adds var(number) empty matches after the var(words) have
+been added.  An empty match takes up space in completion listings but
+will never be inserted in the line and can't be selected with menu
+completion or menu selection.  This makes empty matches only useful to
+format completion lists and to make explanatory string be shown in
+completion lists (since empty matches can be given display strings
+with the tt(-d) option).  And because all but one empty string would
+otherwise be removed, this option implies the tt(-V) and tt(-1)
+options (even if an explicit tt(-J) option is given).
+)
 xitem(tt(-))
 item(tt(-)tt(-))(
 This flag ends the list of flags and options. All arguments after it
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 22029d511..c06e7aa7e 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -85,6 +85,7 @@ struct cmgroup {
 #define CGF_UNIQCON 16		/* remove consecutive duplicates */
 #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 */
 
 /* This is the struct used to hold matches. */
 
@@ -125,6 +126,7 @@ struct cmatch {
 #define CMF_MULT     (1<<11)	/* string appears more than once */
 #define CMF_FMULT    (1<<12)	/* first of multiple equal strings */
 #define CMF_ALL      (1<<13)	/* a match representing all other matches */
+#define CMF_DUMMY    (1<<14)	/* unselectable dummy match */
 
 /* Stuff for completion matcher control. */
 
@@ -264,6 +266,7 @@ struct cadata {
     char *dpar;			/* array to delete non-matches in (-D) */
     char *disp;			/* array with display lists (-d) */
     char *mesg;			/* message to show unconditionally (-x) */
+    int dummies;               /* add that many dummy matches */
 };
 
 /* List data. */
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 97ed6d58f..00dfea935 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -1558,6 +1558,40 @@ get_data_arr(char *name, int keys)
     return ret;
 }
 
+static void
+addmatch(char *str, int flags, char ***dispp, int line)
+{
+    Cmatch cm = (Cmatch) zhalloc(sizeof(struct cmatch));
+    char **disp = *dispp;
+
+    memset(cm, 0, sizeof(struct cmatch));
+    cm->str = dupstring(str);
+    cm->flags = (flags |
+                 (complist ?
+                  ((strstr(complist, "packed") ? CMF_PACKED : 0) |
+                   (strstr(complist, "rows")   ? CMF_ROWS   : 0)) : 0));
+    if (disp) {
+        if (!*++disp)
+            disp = NULL;
+        if (disp)
+            cm->disp = dupstring(*disp);
+    } else if (line) {
+        cm->disp = dupstring("");
+        cm->flags |= CMF_DISPLINE;
+    }
+    mnum++;
+    ainfo->count++;
+    if (curexpl)
+        curexpl->count++;
+
+    addlinknode(matches, cm);
+
+    newmatches = 1;
+    mgroup->new = 1;
+
+    *dispp = disp;
+}
+
 /* This is used by compadd to add a couple of matches. The arguments are
  * the strings given via options. The last argument is the array with
  * the matches. */
@@ -1583,7 +1617,7 @@ addmatches(Cadata dat, char **argv)
     Brinfo bp, bpl = brbeg, obpl, bsl = brend, obsl;
     Heap oldheap;
 
-    if (!*argv && !(dat->aflags & CAF_ALL)) {
+    if (!*argv && !dat->dummies && !(dat->aflags & CAF_ALL)) {
 	SWITCHHEAPS(oldheap, compheap) {
 	    /* Select the group in which to store the matches. */
 	    gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT  : 0) |
@@ -1602,6 +1636,8 @@ addmatches(Cadata dat, char **argv)
 
 	return 1;
     }
+    if (dat->dummies)
+        dat->aflags = dat->aflags | CAF_NOSORT | CAF_UNIQALL;
     for (bp = brbeg; bp; bp = bp->next)
 	bp->curpos = ((dat->aflags & CAF_QUOTE) ? bp->pos : bp->qpos);
     for (bp = brend; bp; bp = bp->next)
@@ -2022,35 +2058,12 @@ addmatches(Cadata dat, char **argv)
 	if (dat->exp)
 	    addexpl();
 	if (!hasallmatch && (dat->aflags & CAF_ALL)) {
-	    Cmatch cm = (Cmatch) zhalloc(sizeof(struct cmatch));
-
-	    memset(cm, 0, sizeof(struct cmatch));
-	    cm->str = dupstring("<all>");
-	    cm->flags = (dat->flags | CMF_ALL |
-			 (complist ?
-			  ((strstr(complist, "packed") ? CMF_PACKED : 0) |
-			   (strstr(complist, "rows")   ? CMF_ROWS   : 0)) : 0));
-	    if (disp) {
-		if (!*++disp)
-		    disp = NULL;
-		if (disp)
-		    cm->disp = dupstring(*disp);
-	    } else {
-		cm->disp = dupstring("");
-		cm->flags |= CMF_DISPLINE;
-	    }
-	    mnum++;
-	    ainfo->count++;
-	    if (curexpl)
-		curexpl->count++;
-
-	    addlinknode(matches, cm);
-
-	    newmatches = 1;
-	    mgroup->new = 1;
-
+            addmatch("<all>", dat->flags | CMF_ALL, &disp, 1);
 	    hasallmatch = 1;
 	}
+        while (dat->dummies--)
+            addmatch("", dat->flags | CMF_DUMMY, &disp, 0);
+
     } SWITCHBACKHEAPS(oldheap);
 
     /* We switched back to the current heap, now restore the stack of
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 574e638ac..106e0ddab 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -435,6 +435,7 @@ bin_compadd(char *name, char **argv, char *ops, int func)
     dat.match = NULL;
     dat.flags = 0;
     dat.aflags = CAF_MATCH;
+    dat.dummies = 0;
 
     for (; *argv && **argv ==  '-'; argv++) {
 	if (!(*argv)[1]) {
@@ -565,6 +566,23 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 	    case 'l':
 		dat.flags |= CMF_DISPLINE;
 		break;
+	    case 'E':
+                if (p[1]) {
+                    dat.dummies = atoi(p + 1);
+                    p = "" - 1;
+                } else if (argv[1]) {
+                    argv++;
+                    dat.dummies = atoi(*argv);
+                    p = "" - 1;
+                } else {
+                    zwarnnam(name, "number expected after -%c", NULL, *p);
+                    return 1;
+                }
+                if (dat.dummies < 0) {
+                    zwarnnam(name, "invalid number: %d", NULL, dat.dummies);
+                    return 1;
+                }
+		break;
 	    case '-':
 		argv++;
 		goto ca_args;
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 94fd2cc56..14cb16d4c 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -1394,7 +1394,7 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 
     mlastm = m->gnum;
     if (m->disp && (m->flags & CMF_DISPLINE)) {
-	if (mselect >= 0) {
+	if (mselect >= 0 && !(m->flags & CMF_DUMMY)) {
 	    int mm = (mcols * ml), i;
 
 	    for (i = mcols; i--; ) {
@@ -1441,7 +1441,7 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	} else
 	    mx = mc * g->width;
 
-	if (mselect >= 0) {
+	if (mselect >= 0 && !(m->flags & CMF_DUMMY)) {
 	    int mm = mcols * ml, i;
 
 	    for (i = (width ? width : mcols); i--; ) {
@@ -1482,7 +1482,7 @@ clprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	len = niceztrlen(m->disp ? m->disp : m->str);
 	mlprinted = len / columns;
 
-	if (isset(LISTTYPES) && buf) {
+	if ((g->flags & CGF_FILES) && buf) {
 	    if (m->gnum != mselect) {
 		zcoff();
 		zcputs(&mcolors, g->name, COL_TC);
@@ -1684,6 +1684,7 @@ domenuselect(Hookdef dummy, Chdata dat)
     noselect = 1;
     while ((menuacc &&
 	    !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr)) ||
+	   ((*minfo.cur)->flags & CMF_DUMMY) ||
 	   (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
 	    (!(*minfo.cur)->str || !*(*minfo.cur)->str)))
 	do_menucmp(0);
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index 0e7bbedba..6b5d7df38 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -1160,6 +1160,7 @@ do_menucmp(int lst)
 	}
     } while ((menuacc &&
 	      !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr)) ||
+             ((*minfo.cur)->flags & CMF_DUMMY) ||
 	     (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
 	      (!(*minfo.cur)->str || !*(*minfo.cur)->str)));
     /* ... and insert it into the command line. */
@@ -1183,6 +1184,7 @@ reverse_menu(Hookdef dummy, void *dummy2)
 	    minfo.cur--;
     } while ((menuacc &&
 	      !hasbrpsfx(*(minfo.cur), minfo.prebr, minfo.postbr)) ||
+	     ((*minfo.cur)->flags & CMF_DUMMY) ||
 	     (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
 	      (!(*minfo.cur)->str || !*(*minfo.cur)->str)));
     metafy_line();
@@ -1378,7 +1380,7 @@ calclist(int showall)
     Cmgroup g;
     Cmatch *p, m;
     Cexpl *e;
-    int hidden = 0, nlist = 0, nlines = 0, add = 2 + isset(LISTTYPES);
+    int hidden = 0, nlist = 0, nlines = 0, add;
     int max = 0, i;
     VARARR(int, mlens, nmatches + 1);
 
@@ -1392,6 +1394,7 @@ calclist(int showall)
     for (g = amatches; g; g = g->next) {
 	char **pp = g->ylist;
 	int nl = 0, l, glong = 1, gshort = columns, ndisp = 0, totl = 0;
+        int hasf = 0;
 
 	g->flags |= CGF_PACKED | CGF_ROWS;
 
@@ -1437,6 +1440,8 @@ calclist(int showall)
 	    }
 	} else if (!onlyexpl) {
 	    for (p = g->matches; (m = *p); p++) {
+                if (m->flags & CMF_FILE)
+                    hasf = 1;
 		if (menuacc && !hasbrpsfx(m, minfo.prebr, minfo.postbr)) {
 		    m->flags |= CMF_HIDE;
 		    continue;
@@ -1496,6 +1501,11 @@ calclist(int showall)
 		e++;
 	    }
 	}
+        if (isset(LISTTYPES) && hasf) {
+            g->flags |= CGF_FILES;
+            add = 3;
+        } else
+            add = 2;
 	g->totl = totl + (ndisp * add);
 	g->dcount = ndisp;
 	g->width = glong + add;
@@ -1513,6 +1523,7 @@ calclist(int showall)
 	int *ws, tlines, tline, tcols, maxlen, nth, width, glines;
 
 	for (g = amatches; g; g = g->next) {
+            add = 2 + !!(g->flags & CGF_FILES);
 	    glines = 0;
 
 	    zfree(g->widths, 0);
@@ -1523,7 +1534,8 @@ calclist(int showall)
 		    if (g->cols) {
 			glines += (arrlen(pp) + g->cols - 1) / g->cols;
 			if (g->cols > 1)
-			    g->width += (max - (g->width * g->cols - add)) / g->cols;
+			    g->width += ((max - (g->width * g->cols - add)) /
+                                         g->cols);
 		    } else {
 			g->cols = 1;
 			g->width = 1;
@@ -1559,6 +1571,8 @@ calclist(int showall)
 	    if (!(g->flags & CGF_PACKED))
 		continue;
 
+            add = 2 + !!(g->flags & CGF_FILES);
+
 	    ws = g->widths = (int *) zalloc(columns * sizeof(int));
 	    memset(ws, 0, columns * sizeof(int));
 	    tlines = g->lins;
@@ -1666,7 +1680,7 @@ calclist(int showall)
 	    } else if (g->width) {
 		if (g->flags & CGF_ROWS) {
 		    int addlen, count, tcol, maxlines = 0, llines, i;
-		    int beg = columns / g->shortest, end = g->cols;
+		    int beg = columns / g->shortest, end = g->cols, fe = 1;
 		    Cmatch *first;
 
 		    while (1) {
@@ -1677,7 +1691,8 @@ calclist(int showall)
 				 count = g->dcount;
 			     count > 0; count--) {
 			    m = *p;
-			    addlen = mlens[m->gnum] + add;
+			    addlen = (mlens[m->gnum] +
+                                      (tcol == tcols - 1 ? 0 : add));
 			    if (addlen > maxlen)
 				maxlen = addlen;
 			    for (i = tcols; i && *p; i--)
@@ -1706,15 +1721,21 @@ calclist(int showall)
 			    break;
 
 			if (beg == end) {
-			    beg--;
-			    end--;
+                            if (fe) {
+                                beg += 2;
+                                end += 2;
+                                fe = 0;
+                            } else {
+                                beg--;
+                                end--;
+                            }
 			} else if (width < columns) {
 			    if ((end = tcols) == beg - 1)
 				end++;
 			} else {
 			    if ((beg = tcols) - 1 == end)
 				end++;
-			}
+                        }
 		    }
 		    if (tcols > g->cols)
 			tlines = maxlines;
@@ -1723,7 +1744,7 @@ calclist(int showall)
 		    int smask = ((showall ? 0 : (CMF_NOLIST | CMF_MULT)) |
 				 CMF_HIDE);
 		    int beg = ((g->totl + columns) / columns);
-		    int end = g->lins;
+		    int end = g->lins, fe = 1;
 
 		    while (1) {
 			tlines = (beg + end) >> 1;
@@ -1755,8 +1776,14 @@ calclist(int showall)
 			    break;
 
 			if (beg == end) {
-			    beg++;
-			    end++;
+                            if (fe) {
+                                beg -= 2;
+                                end -= 2;
+                                fe = 0;
+                            } else {
+                                beg++;
+                                end++;
+                            }
 			} else if (width < columns) {
 			    if ((end = tlines) == beg + 1)
 				end--;
@@ -1783,6 +1810,7 @@ calclist(int showall)
 	    }
 	}
 	for (g = amatches; g; g = g->next) {
+            add = 2 + !!(g->flags & CGF_FILES);
 	    if (g->widths) {
 		int *p, a = (max - g->totl + add) / g->cols;
 
@@ -2152,7 +2180,7 @@ iprintm(Cmgroup g, Cmatch *mp, int mc, int ml, int lastc, int width,
 	nicezputs(m->str, shout);
 	len = niceztrlen(m->str);
 
-	if (isset(LISTTYPES) && buf) {
+	if ((g->flags & CGF_FILES) && buf) {
 	    putc(file_type(buf->st_mode), shout);
 	    len++;
 	}
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index 5c4fc3ed5..7548a87bf 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -35,6 +35,7 @@
 
 typedef struct cdset *Cdset;
 typedef struct cdstr *Cdstr;
+typedef struct cdrun *Cdrun;
 
 struct cdstate {
     int showd;			/* != 0 if descriptions should be shown */
@@ -43,6 +44,11 @@ struct cdstate {
     Cdset sets;			/* the sets of matches */
     int pre;                    /* longest prefix (before description) */
     int suf;                    /* longest suffix (description) */
+    int maxg;                   /* size of largest group */
+    int groups;                 /* number of groups */
+    int descs;                  /* number of non-group matches with desc */
+    int gpre;                   /* prefix length for group display */
+    Cdrun runs;                 /* runs to report to shell code */
 };
 
 struct cdstr {
@@ -50,10 +56,25 @@ struct cdstr {
     char *str;                  /* the string to display */
     char *desc;                 /* the description or NULL */
     char *match;                /* the match to add */
+    int len;                    /* length of str or match */
     Cdstr other;                /* next string with the same description */
     int kind;                   /* 0: not in a group, 1: the first, 2: other */
+    Cdset set;                  /* the set this string is in */
+    Cdstr run;                  /* next in this run */
 };
 
+struct cdrun {
+    Cdrun next;                 /* ... */
+    int type;                   /* see CRT_* below */
+    Cdstr strs;                 /* strings in this run */
+    int count;                  /* number of strings in this run */
+};
+
+#define CRT_SIMPLE 0
+#define CRT_DESC   1
+#define CRT_SPEC   2
+#define CRT_DUMMY  3
+
 struct cdset {
     Cdset next;			/* guess what */
     char **opts;		/* the compadd-options */
@@ -62,11 +83,6 @@ struct cdset {
     int desc;                   /* number of matches with description */
 };
 
-/* Maximum string length when used with descriptions. */
-
-#define CD_MAXLEN 30
-
-
 static struct cdstate cd_state;
 static int cd_parsed = 0;
 
@@ -75,6 +91,7 @@ freecdsets(Cdset p)
 {
     Cdset n;
     Cdstr s, sn;
+    Cdrun r, rn;
 
     for (; p; p = n) {
 	n = p->next;
@@ -88,6 +105,10 @@ freecdsets(Cdset p)
                 zsfree(s->match);
             zfree(s, sizeof(*s));
         }
+        for (r = cd_state.runs; r; r = rn) {
+            rn = r->next;
+            zfree(r, sizeof(*r));
+        }
 	zfree(p, sizeof(*p));
     }
 }
@@ -99,14 +120,14 @@ cd_group()
 {
     Cdset set1, set2;
     Cdstr str1, str2, *strp;
-    int yep = 0;
-    char *buf;
+    int num;
 
     for (set1 = cd_state.sets; set1; set1 = set1->next) {
         for (str1 = set1->strs; str1; str1 = str1->next) {
             if (!str1->desc || str1->kind != 0)
                 continue;
 
+            num = 1;
             strp = &(str1->other);
 
             for (set2 = set1; set2; set2 = set2->next)
@@ -115,33 +136,18 @@ cd_group()
                     if (str2->desc && !strcmp(str1->desc, str2->desc)) {
                         str1->kind = 1;
                         str2->kind = 2;
-                        zsfree(str2->desc);
-                        str2->desc = ztrdup("|");
+                        num++;
                         *strp = str2;
                         strp = &(str2->other);
-                        yep = 1;
                     }
             *strp = NULL;
-        }
-    }
-    if (!yep)
-        return;
-
-    for (set1 = cd_state.sets; set1; set1 = set1->next) {
-        for (str1 = set1->strs; str1; str1 = str1->next) {
-            if (str1->kind != 1)
-                continue;
-
-            buf = str1->str;
-            for (str2 = str1->other; str2; str2 = str2->other)
-                buf = zhtricat(buf, ", ", str2->str);
+            if (num > 1)
+                cd_state.groups++;
+            else
+                cd_state.descs++;
 
-            for (str2 = str1; str2; str2 = str2->other) {
-                if (str2->str == str2->match)
-                    str2->match = ztrdup(str2->match);
-                zsfree(str2->str);
-                str2->str = ztrdup(buf);
-            }
+            if (num > cd_state.maxg)
+                cd_state.maxg = num;
         }
     }
 }
@@ -171,14 +177,172 @@ cd_calc()
             }
         }
     }
-    if (cd_state.pre > CD_MAXLEN)
-        cd_state.pre = CD_MAXLEN;
+}
+
+static int
+cd_sort(const void *a, const void *b)
+{
+    return strcmp((*((Cdstr *) a))->str, (*((Cdstr *) b))->str);
+}
+
+static void
+cd_prep()
+{
+    Cdrun run, *runp;
+    Cdset set;
+    Cdstr str, *strp;
+
+    runp = &(cd_state.runs);
+
+    if (cd_state.groups) {
+        int lines = cd_state.groups + cd_state.descs;
+        VARARR(Cdstr, grps, lines);
+        VARARR(int, wids, cd_state.maxg);
+        Cdstr gs, gp, gn, *gpp;
+        int i, j;
+
+        memset(wids, 0, cd_state.maxg * sizeof(int));
+        strp = grps;
+
+        for (set = cd_state.sets; set; set = set->next)
+            for (str = set->strs; str; str = str->next) {
+                if (str->kind != 1) {
+                    if (!str->kind && str->desc) {
+                        str->other = NULL;
+                        *strp++ = str;
+                    }
+                    continue;
+                }
+                gs = str;
+                gs->kind = 2;
+                gp = str->other;
+                gs->other = NULL;
+                for (; gp; gp = gn) {
+                    gn = gp->other;
+                    gp->other = NULL;
+                    for (gpp = &gs; *gpp && (*gpp)->len > gp->len;
+                         gpp = &((*gpp)->other));
+                    gp->other = *gpp;
+                    *gpp = gp;
+                }
+                for (gp = gs, i = 0; gp; gp = gp->other, i++)
+                    if (gp->len > wids[i])
+                        wids[i] = gp->len;
+
+                *strp++ = gs;
+            }
+
+        qsort(grps, lines, sizeof(Cdstr), cd_sort);
+
+        for (i = lines, strp = grps; i; i--, strp++) {
+            for (j = 0, gs = *strp; gs->other; gs = gs->other, j++) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_SPEC;
+                run->strs = gs;
+                gs->run = NULL;
+                run->count = 1;
+            }
+            *runp = run = (Cdrun) zalloc(sizeof(*run));
+            runp = &(run->next);
+            run->type = CRT_DUMMY + cd_state.maxg - j - 1;
+            run->strs = gs;
+            gs->run = NULL;
+            run->count = 1;
+        }
+        for (set = cd_state.sets; set; set = set->next) {
+            for (i = 0, gs = NULL, gpp = &gs, str = set->strs;
+                 str; str = str->next) {
+                if (str->kind || str->desc)
+                    continue;
+
+                i++;
+                *gpp = str;
+                gpp = &(str->run);
+            }
+            *gpp = NULL;
+            if (i) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_SIMPLE;
+                run->strs = gs;
+                run->count = i;
+            }
+        }
+        cd_state.gpre = 0;
+        for (i = 0; i < cd_state.maxg; i++)
+            cd_state.gpre += wids[i] + 2;
+    } else if (cd_state.showd) {
+        for (set = cd_state.sets; set; set = set->next) {
+            if (set->desc) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_DESC;
+                strp = &(run->strs);
+                for (str = set->strs; str; str = str->next)
+                    if (str->desc) {
+                        *strp = str;
+                        strp = &(str->run);
+                    }
+                *strp = NULL;
+                run->count = set->desc;
+            }
+            if (set->desc != set->count) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_SIMPLE;
+                strp = &(run->strs);
+                for (str = set->strs; str; str = str->next)
+                    if (!str->desc) {
+                        *strp = str;
+                        strp = &(str->run);
+                    }
+                *strp = NULL;
+                run->count = set->count - set->desc;
+            }
+        }
+    } else {
+        for (set = cd_state.sets; set; set = set->next)
+            if (set->count) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_SIMPLE;
+                run->strs = set->strs;
+                for (str = set->strs; str; str = str->next)
+                    str->run = str->next;
+                run->count = set->count;
+            }
+    }
+    *runp = NULL;
+}
+
+/* Duplicate and concatenate two arrays.  Return the result. */
+
+static char **
+cd_arrcat(char **a, char **b)
+{
+    if (!b)
+        return zarrdup(a);
+    else {
+        char **r = (char **) zalloc((arrlen(a) + arrlen(b) + 1) *
+                                    sizeof(char *));
+        char **p = r;
+
+        for (; *a; a++)
+            *p++ = ztrdup(*a);
+        for (; *b; b++)
+            *p++ = ztrdup(*b);
+
+        *p = NULL;
+
+        return r;
+    }
 }
 
 /* Initialisation. Store and calculate the string and matches and so on. */
 
 static int
-cd_init(char *nam, char *sep, char **args, int disp)
+cd_init(char *nam, char *hide, char *sep, char **opts, char **args, int disp)
 {
     Cdset *setp, set;
     Cdstr *strp, str;
@@ -195,6 +359,7 @@ cd_init(char *nam, char *sep, char **args, int disp)
     cd_state.slen = ztrlen(sep);
     cd_state.sets = NULL;
     cd_state.showd = disp;
+    cd_state.maxg = cd_state.groups = cd_state.descs = 0;
 
     if (*args && !strcmp(*args, "-g")) {
         args++;
@@ -219,6 +384,7 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
             str->kind = 0;
             str->other = NULL;
+            str->set = set;
 
             for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
                 if (*tmp == '\\' && tmp[1])
@@ -230,6 +396,7 @@ cd_init(char *nam, char *sep, char **args, int disp)
                 str->desc = NULL;
             *tmp = '\0';
             str->str = str->match = ztrdup(rembslash(*ap));
+            str->len = strlen(str->str);
         }
         if (str)
             str->next = NULL;
@@ -246,13 +413,23 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
 	    args++;
 	}
+        if (hide && *hide) {
+            for (str = set->strs; str; str = str->next) {
+                if (str->str == str->match)
+                    str->str = ztrdup(str->str);
+                if (hide[1] && str->str[0] == '-' && str->str[1] == '-')
+                    strcpy(str->str, str->str + 2);
+                else if (str->str[0] == '-' || str->str[0] == '+')
+                    strcpy(str->str, str->str + 1);
+            }
+        }
 	for (ap = args; *args &&
 		 (args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
 	     args++);
 
 	tmp = *args;
 	*args = NULL;
-	set->opts = zarrdup(ap);
+	set->opts = cd_arrcat(ap, opts);
 	if ((*args = tmp))
 	    args++;
     }
@@ -260,79 +437,157 @@ cd_init(char *nam, char *sep, char **args, int disp)
         cd_group();
 
     cd_calc();
+    cd_prep();
 
     cd_parsed = 1;
     return 0;
 }
 
+/* Copy an array with one element in reserve (at the beginning). */
+
+static char **
+cd_arrdup(char **a)
+{
+    char **r = (char **) zalloc((arrlen(a) + 2) * sizeof(char *));
+    char **p = r + 1;
+
+    while (*a)
+        *p++ = ztrdup(*a++);
+    *p = NULL;
+
+    return r;
+}
+
 /* Get the next set. */
 
 static int
 cd_get(char **params)
 {
-    Cdset set;
+    Cdrun run;
 
-    if ((set = cd_state.sets)) {
-	char **sd, **sdp, **md, **mdp, **sh, **shp, **mh, **mhp;
-        char **ss, **ssp, **ms, **msp;
+    if ((run = cd_state.runs)) {
         Cdstr str;
-        VARARR(char, buf, cd_state.pre + cd_state.suf + cd_state.slen + 1);
-        char *sufp = NULL, *disp;
+        char **mats, **mp, **dpys, **dp, **opts, *csl = "";
 
-        if (cd_state.showd) {
-            memcpy(buf + cd_state.pre, cd_state.sep, cd_state.slen);
-            sufp = buf + cd_state.pre + cd_state.slen;
-        }
-	sd = (char **) zalloc((set->desc + 1) * sizeof(char *));
-	md = (char **) zalloc((set->desc + 1) * sizeof(char *));
-	sh = (char **) zalloc((set->desc + 1) * sizeof(char *));
-	mh = (char **) zalloc((set->desc + 1) * sizeof(char *));
-	ss = (char **) zalloc((set->count + 1) * sizeof(char *));
-	ms = (char **) zalloc((set->count + 1) * sizeof(char *));
-
-        for (sdp = sd, mdp = md, shp = sh, mhp = mh,
-             ssp = ss, msp = ms, str = set->strs;
-             str;
-             str = str->next) {
-            if (cd_state.showd && str->desc) {
-                if (strlen(str->str) > CD_MAXLEN)
-                    disp = tricat(str->str, cd_state.sep, str->desc);
-                else {
-                    strcpy(sufp, str->desc);
-                    memset(buf, ' ', cd_state.pre);
-                    memcpy(buf, str->str, strlen(str->str));
-                    disp = ztrdup(buf);
+        cd_state.runs = run->next;
+
+        switch (run->type) {
+        case CRT_SIMPLE:
+            mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
+            dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
+
+            for (str = run->strs; str; str = str->run) {
+                *mp++ = ztrdup(str->match);
+                *dp++ = ztrdup(str->str ? str->str : str->match);
+            }
+            *mp = *dp = NULL;
+            opts = zarrdup(run->strs->set->opts);
+            if (cd_state.groups) {
+                /* We are building a columnised list with dummy matches
+                 * but there are also matches without descriptions.
+                 * Those end up in a different group, so make sure that
+                 * groupd doesn't have an explanation. */
+
+                for (mp = dp = opts; *mp; mp++) {
+                    if (dp[0][0] == '-' && dp[0][1] == 'X') {
+                        if (!dp[0][2] && dp[1])
+                            mp++;
+                    } else
+                        *dp++ = *mp;
                 }
-                if (strlen(disp) >= columns)
-                    disp[columns - 1] = '\0';
+                *dp = NULL;
+            }
+            break;
 
-                if (str->kind == 2) {
-                    *shp++ = disp;
-                    *mhp++ = ztrdup(str->match);
-                } else {
-                    *sdp++ = disp;
-                    *mdp++ = ztrdup(str->match);
+        case CRT_DESC:
+            {
+                VARARR(char, buf,
+                       cd_state.pre + cd_state.suf + cd_state.slen + 1);
+                char *sufp = NULL;
+
+                memcpy(buf + cd_state.pre, cd_state.sep, cd_state.slen);
+                sufp = buf + cd_state.pre + cd_state.slen;
+
+                mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
+                dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
+
+                for (str = run->strs; str; str = str->run) {
+                    *mp++ = ztrdup(str->match);
+                    memset(buf, ' ', cd_state.pre);
+                    memcpy(buf, str->str, str->len);
+                    strcpy(sufp, str->desc);
+                    if (strlen(buf) >= columns)
+                        buf[columns] = '\0';
+                    *dp++ = ztrdup(buf);
                 }
-            } else {
-                *ssp++ = ztrdup(str->str);
-                *msp++ = ztrdup(str->match);
+                *mp = *dp = NULL;
+                opts = cd_arrdup(run->strs->set->opts);
+                opts[0] = ztrdup("-l");
+                break;
+            }
+        case CRT_SPEC:
+            mats = (char **) zalloc(2 * sizeof(char *));
+            dpys = (char **) zalloc(2 * sizeof(char *));
+            mats[0] = ztrdup(run->strs->match);
+            dpys[0] = ztrdup(run->strs->str);
+            mats[1] = dpys[1] = NULL;
+            opts = cd_arrdup(run->strs->set->opts);
+            for (dp = opts + 1; *dp; dp++)
+                if (dp[0][0] == '-' && dp[0][1] == 'J')
+                    break;
+            if (*dp) {
+                char *s = tricat("-1V", "", dp[0] + 2);
+
+                zsfree(*dp);
+                *dp = s;
+
+                memmove(opts, opts + 1,
+                        (arrlen(opts + 1) + 1) * sizeof(char *));
+                
+            } else
+                opts[0] = ztrdup("-1V-default-");
+            csl = "packed rows";
+            break;
+
+        default:
+            {
+                int dlen = columns - cd_state.gpre - cd_state.slen;
+                VARARR(char, dbuf, dlen + cd_state.slen);
+                char buf[20];
+                int i = run->type - CRT_DUMMY;
+
+                sprintf(buf, "-E%d", i + 1);
+
+                mats = (char **) zalloc(2 * sizeof(char *));
+                dpys = (char **) zalloc((3 + i) * sizeof(char *));
+                mats[0] = ztrdup(run->strs->match);
+                dpys[0] = ztrdup(run->strs->str);
+                for (dp = dpys + 1; i; i--, dp++)
+                    *dp = ztrdup("");
+                memset(dbuf + cd_state.slen, ' ', dlen - 1);
+                dbuf[dlen + cd_state.slen - 1] = '\0';
+                strcpy(dbuf, cd_state.sep);
+                memcpy(dbuf + cd_state.slen,
+                       run->strs->desc,
+                       (strlen(run->strs->desc) >= dlen ? dlen - 1 :
+                        strlen(run->strs->desc)));
+                *dp++ = ztrdup(dbuf);
+                mats[1] = *dp = NULL;
+
+                opts = cd_arrdup(run->strs->set->opts);
+                opts[0] = ztrdup(buf);
+
+                csl = "packed rows";
             }
         }
-        *sdp = *mdp = *shp = *mhp = *ssp = *msp = NULL;
-
-	setaparam(params[0], zarrdup(set->opts));
-	setaparam(params[1], sd);
-	setaparam(params[2], md);
-	setaparam(params[3], sh);
-	setaparam(params[4], mh);
-	setaparam(params[5], ss);
-	setaparam(params[6], ms);
+        setsparam(params[0], ztrdup(csl));
+        setaparam(params[1], opts);
+        setaparam(params[2], mats);
+        setaparam(params[3], dpys);
 
-	cd_state.sets = set->next;
-	set->next = NULL;
-	freecdsets(set);
+        zfree(run, sizeof(*run));
 
-	return 0;
+        return 0;
     }
     return 1;
 }
@@ -341,6 +596,8 @@ cd_get(char **params)
 static int
 bin_compdescribe(char *nam, char **args, char *ops, int func)
 {
+    int n = arrlen(args);
+
     if (incompfunc != 1) {
 	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
@@ -351,15 +608,30 @@ bin_compdescribe(char *nam, char **args, char *ops, int func)
     }
     switch (args[0][1]) {
     case 'i':
-	return cd_init(nam, "", args + 1, 0);
+        if (n < 2) {
+            zwarnnam(nam, "not enough arguments", NULL, 0);
+
+            return 1;
+        }
+	return cd_init(nam, args[1], "", NULL, args + 2, 0);
     case 'I':
-	return cd_init(nam, args[1], args + 2, 1);
+        if (n < 5) {
+            zwarnnam(nam, "not enough arguments", NULL, 0);
+
+            return 1;
+        } else {
+            char **opts;
+
+            if (!(opts = getaparam(args[3]))) {
+		zwarnnam(nam, "unknown parameter: %s", args[2], 0);
+		return 1;
+            }
+            return cd_init(nam, args[1], args[2], opts, args + 4, 1);
+        }
     case 'g':
 	if (cd_parsed) {
-	    int n = arrlen(args);
-
-	    if (n != 8) {
-		zwarnnam(nam, (n < 8 ? "not enough arguments" :
+	    if (n != 5) {
+		zwarnnam(nam, (n < 5 ? "not enough arguments" :
 			      "too many arguments"), NULL, 0);
 		return 1;
 	    }