about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
authorPaul Ackersviller <packersv@users.sourceforge.net>2007-05-07 03:23:05 +0000
committerPaul Ackersviller <packersv@users.sourceforge.net>2007-05-07 03:23:05 +0000
commitc68e8d226ae4d024c3a0b2d9e05ff03b704a79c2 (patch)
tree838b9519986ca36078a413d7e6aea4b7893976a7 /Src
parentcf7d86e7b27f85728c9cc44ee95a8bdc48fe8d5e (diff)
downloadzsh-c68e8d226ae4d024c3a0b2d9e05ff03b704a79c2.tar.gz
zsh-c68e8d226ae4d024c3a0b2d9e05ff03b704a79c2.tar.xz
zsh-c68e8d226ae4d024c3a0b2d9e05ff03b704a79c2.zip
Merge of 21428: fix null pointer deref on wrong number of arguments to compvalues.
Diffstat (limited to 'Src')
-rw-r--r--Src/Zle/computil.c3148
1 files changed, 2564 insertions, 584 deletions
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index 088d89931..e56972cee 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -31,57 +31,59 @@
 #include "computil.pro"
 
 
-/* Help for `_display'. */
-
-/* Calculation state. */
-
-typedef struct cdisp *Cdisp;
-
-struct cdisp {
-    int pre;			/* prefix length */
-    int suf;			/* suffix length */
-    int colon;			/* number of strings with descriptions */
-};
-
-/* Calculate longest prefix and suffix and count the strings with
- * descriptions. */
-
-static void
-cdisp_calc(Cdisp disp, char **args)
-{
-    char *cp;
-    int i, nbc;
-
-    for (; *args; args++) {
-	for (nbc = 0, cp = *args; *cp && *cp != ':'; cp++)
-	    if (*cp == '\\' && cp[1])
-		cp++, nbc++;
-	if (*cp == ':' && cp[1]) {
-	    disp->colon++;
-	    if ((i = cp - *args - nbc) > disp->pre)
-		disp->pre = i;
-	    if ((i = strlen(cp + 1)) > disp->suf)
-		disp->suf = i;
-	}
-    }
-}
-
-/* Help fuer `_describe'. */
+/* Help for `_describe'. */
 
 typedef struct cdset *Cdset;
+typedef struct cdstr *Cdstr;
+typedef struct cdrun *Cdrun;
 
 struct cdstate {
     int showd;			/* != 0 if descriptions should be shown */
     char *sep;			/* the separator string */
+    int slen;			/* its length */
+    int maxmlen;                /* maximum length to allow for the matches */
     Cdset sets;			/* the sets of matches */
-    struct cdisp disp;		/* used to calculate the alignment */
+    int pre;                    /* longest prefix (before description) */
+    int suf;                    /* longest suffix (description) */
+    int maxg;                   /* size of largest group */
+    int maxglen;                /* columns for matches 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 {
+    Cdstr next;                 /* the next one in this set */
+    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
+#define CRT_EXPL   4
+
 struct cdset {
     Cdset next;			/* guess what */
     char **opts;		/* the compadd-options */
-    char **strs;		/* the display-strings */
-    char **matches;		/* the matches (or NULL) */
+    Cdstr strs;                 /* the strings/matches */
+    int count;                  /* number of matches in this set */
+    int desc;                   /* number of matches with description */
 };
 
 static struct cdstate cd_state;
@@ -91,164 +93,634 @@ static void
 freecdsets(Cdset p)
 {
     Cdset n;
+    Cdstr s, sn;
+    Cdrun r, rn;
 
     for (; p; p = n) {
 	n = p->next;
 	if (p->opts)
 	    freearray(p->opts);
-	if (p->strs)
-	    freearray(p->strs);
-	if (p->matches)
-	    freearray(p->matches);
+        for (s = p->strs; s; s = sn) {
+            sn = s->next;
+            zsfree(s->str);
+            zsfree(s->desc);
+            if (s->match != s->str)
+                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));
     }
 }
 
+/* Find matches with same descriptions and group them. */
+
+static void
+cd_group(int maxg)
+{
+    Cdset set1, set2;
+    Cdstr str1, str2, *strp;
+    int num, len;
+
+    cd_state.groups = cd_state.descs = cd_state.maxglen = 0;
+    cd_state.maxg = 0;
+
+    for (set1 = cd_state.sets; set1; set1 = set1->next)
+        for (str1 = set1->strs; str1; str1 = str1->next) {
+            str1->kind = 0;
+            str1->other = NULL;
+        }
+
+    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;
+            len = str1->len + cd_state.slen;
+            if (len > cd_state.maxglen)
+                cd_state.maxglen = len;
+            strp = &(str1->other);
+
+            for (set2 = set1; set2; set2 = set2->next) {
+                for (str2 = (set2 == set1 ? str1->next : set2->strs);
+                     str2; str2 = str2->next)
+                    if (str2->desc && !strcmp(str1->desc, str2->desc)) {
+                        len += 2 + str2->len;
+                        if (len > cd_state.maxmlen || num == maxg)
+                            break;
+                        if (len > cd_state.maxglen)
+                            cd_state.maxglen = len;
+                        str1->kind = 1;
+                        str2->kind = 2;
+                        num++;
+                        *strp = str2;
+                        strp = &(str2->other);
+                    }
+                if (str2)
+                    break;
+            }
+            *strp = NULL;
+
+            if (num > 1)
+                cd_state.groups++;
+            else
+                cd_state.descs++;
+
+            if (num > cd_state.maxg)
+                cd_state.maxg = num;
+        }
+    }
+}
+
+/* Calculate longest prefix and suffix and count the strings with
+ * descriptions. */
+
+static void
+cd_calc()
+{
+    Cdset set;
+    Cdstr str;
+    int l;
+
+    cd_state.pre = cd_state.suf = 0;
+
+    for (set = cd_state.sets; set; set = set->next) {
+        set->count = set->desc = 0;
+        for (str = set->strs; str; str = str->next) {
+            set->count++;
+            if ((l = strlen(str->str)) > cd_state.pre)
+                cd_state.pre = l;
+            if (str->desc) {
+                set->desc++;
+                if ((l = strlen(str->desc)) > cd_state.suf)
+                    cd_state.suf = l;
+            }
+        }
+    }
+}
+
+static int
+cd_sort(const void *a, const void *b)
+{
+    return strcmp((*((Cdstr *) a))->str, (*((Cdstr *) b))->str);
+}
+
+static int
+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, d;
+        Cdrun expl;
+        Cdstr *strp2;
+
+        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) {
+                        if (str->len > wids[0])
+                            wids[0] = str->len;
+                        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;
+            }
+
+        cd_state.gpre = 0;
+        for (i = 0; i < cd_state.maxg; i++)
+            cd_state.gpre += wids[i] + 2;
+
+        if (cd_state.gpre > cd_state.maxmlen && cd_state.maxglen > 1)
+            return 1;
+
+        qsort(grps, lines, sizeof(Cdstr), cd_sort);
+
+        for (i = lines, strp = grps; i > 1; i--, strp++) {
+            strp2 = strp + 1;
+            if (!strcmp((*strp)->desc, (*strp2)->desc))
+                continue;
+            for (j = i - 2, strp2++; j > 0; j--, strp2++)
+                if (!strcmp((*strp)->desc, (*strp2)->desc)) {
+                    Cdstr tmp = *strp2;
+
+                    memmove(strp + 2, strp + 1,
+                            (strp2 - strp - 1) * sizeof(Cdstr));
+
+                    *++strp = tmp;
+                    i--;
+                }
+        }
+        expl =  (Cdrun) zalloc(sizeof(*run));
+        expl->type = CRT_EXPL;
+        expl->strs = grps[0];
+        expl->count = lines;
+
+        for (i = lines, strp = grps, strp2 = NULL; i; i--, strp++) {
+            str = *strp;
+            *strp = str->other;
+            if (strp2)
+                *strp2 = str;
+            strp2 = &(str->run);
+
+            *runp = run = (Cdrun) zalloc(sizeof(*run));
+            runp = &(run->next);
+            run->type = CRT_SPEC;
+            run->strs = str;
+            run->count = 1;
+        }
+        *strp2 = NULL;
+
+        for (i = cd_state.maxg - 1; i; i--) {
+            for (d = 0, j = lines, strp = grps; j; j--, strp++) {
+                if ((str = *strp)) {
+                    if (d) {
+                        *runp = run = (Cdrun) zalloc(sizeof(*run));
+                        runp = &(run->next);
+                        run->type = CRT_DUMMY;
+                        run->strs = expl->strs;
+                        run->count = d;
+                        d = 0;
+                    }
+                    *runp = run = (Cdrun) zalloc(sizeof(*run));
+                    runp = &(run->next);
+                    run->type = CRT_SPEC;
+                    run->strs = str;
+                    run->strs->run = NULL;
+                    run->count = 1;
+
+                    *strp = str->other;
+                } else
+                    d++;
+            }
+            if (d) {
+                *runp = run = (Cdrun) zalloc(sizeof(*run));
+                runp = &(run->next);
+                run->type = CRT_DUMMY;
+                run->strs = expl->strs;
+                run->count = d;
+            }
+        }
+        *runp = expl;
+        runp = &(expl->next);
+
+        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;
+            }
+        }
+    } 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;
+
+    return 0;
+}
+
+/* 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 *mlen, char *sep,
+        char **opts, char **args, int disp)
 {
     Cdset *setp, set;
+    Cdstr *strp, str;
     char **ap, *tmp;
+    int grp = 0, itmp;
 
     if (cd_parsed) {
 	zsfree(cd_state.sep);
 	freecdsets(cd_state.sets);
+	cd_parsed = 0;
     }
     setp = &(cd_state.sets);
     cd_state.sep = ztrdup(sep);
+    cd_state.slen = ztrlen(sep);
     cd_state.sets = NULL;
-    cd_state.disp.pre = cd_state.disp.suf = cd_state.disp.colon = 0;
     cd_state.showd = disp;
-
+    cd_state.maxg = cd_state.groups = cd_state.descs = 0;
+    cd_state.maxmlen = atoi(mlen);
+    itmp = columns - cd_state.slen - 4;
+    if (cd_state.maxmlen > itmp)
+        cd_state.maxmlen = itmp;
+    if (cd_state.maxmlen < 4)
+        cd_state.maxmlen = 4;
+    if (*args && !strcmp(*args, "-g")) {
+        args++;
+        grp = 1;
+    }
     while (*args) {
-	*setp = set = (Cdset) zcalloc(sizeof(*set));
+	*setp = set = (Cdset) zshcalloc(sizeof(*set));
 	setp = &(set->next);
+        *setp = NULL;
+        set->opts = NULL;
+        set->strs = NULL;
 
 	if (!(ap = get_user_var(*args))) {
 	    zwarnnam(nam, "invalid argument: %s", *args, 0);
+            zsfree(cd_state.sep);
+            freecdsets(cd_state.sets);
 	    return 1;
 	}
-	set->strs = zarrdup(ap);
-
-	if (disp)
-	    cdisp_calc(&(cd_state.disp), set->strs);
+        for (str = NULL, strp = &(set->strs); *ap; ap++) {
+            *strp = str = (Cdstr) zalloc(sizeof(*str));
+            strp = &(str->next);
+
+            str->kind = 0;
+            str->other = NULL;
+            str->set = set;
+
+            for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
+                if (*tmp == '\\' && tmp[1])
+                    tmp++;
+
+            if (*tmp)
+                str->desc = ztrdup(rembslash(tmp + 1));
+            else
+                str->desc = NULL;
+            *tmp = '\0';
+            str->str = str->match = ztrdup(rembslash(*ap));
+            str->len = strlen(str->str);
+        }
+        if (str)
+            str->next = NULL;
 
 	if (*++args && **args != '-') {
 	    if (!(ap = get_user_var(*args))) {
 		zwarnnam(nam, "invalid argument: %s", *args, 0);
+                zsfree(cd_state.sep);
+                freecdsets(cd_state.sets);
 		return 1;
 	    }
-	    set->matches = zarrdup(ap);
+            for (str = set->strs; str && *ap; str = str->next, ap++)
+                str->match = ztrdup(*ap);
+
 	    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++;
     }
+    if (disp && grp) {
+        int mg = columns;
+
+        do {
+            cd_group(mg);
+            mg = cd_state.maxg - 1;
+            cd_calc();
+        } while (cd_prep());
+
+    } else {
+        cd_calc();
+        cd_prep();
+    }
+    cd_parsed = 1;
     return 0;
 }
 
-/* Get the next set. */
+/* Copy an array with one element in reserve (at the beginning). */
 
-static int
-cd_get(char **params)
+static char **
+cd_arrdup(char **a)
 {
-    Cdset set;
-
-    if ((set = cd_state.sets)) {
-	char **sd, **sdp, **md, **mdp, **ss, **ssp, **ms, **msp;
-	char **p, **mp, *cp, *copy, *cpp, oldc;
-	int dl = 1, sl = 1, sepl = strlen(cd_state.sep);
-	int pre = cd_state.disp.pre, suf = cd_state.disp.suf;
-	VARARR(char, buf, pre + suf + sepl + 1);
-
-	for (p = set->strs; *p; p++)
-	    if (cd_state.showd) {
-		for (cp = *p; *cp && *cp != ':'; cp++)
-		    if (*cp == '\\' && cp[1])
-			cp++;
-		if (*cp == ':' && cp[1])
-		    dl++;
-		else
-		    sl++;
-	    } else
-		sl++;
-
-	sd = (char **) zalloc(dl * sizeof(char *));
-	ss = (char **) zalloc(sl * sizeof(char *));
-	md = (char **) zalloc(dl * sizeof(char *));
-	ms = (char **) zalloc(sl * sizeof(char *));
-
-	if (cd_state.showd) {
-	    memcpy(buf + pre, cd_state.sep, sepl);
-	    suf = pre + sepl;
-	}
-
-	/* Build the aligned display strings. */
-
-	for (sdp = sd, ssp = ss, mdp = md, msp = ms,
-		 p = set->strs, mp = set->matches; *p; p++) {
-	    copy = dupstring(*p);
-	    for (cp = cpp = copy; *cp && *cp != ':'; cp++) {
-		if (*cp == '\\' && cp[1])
-		    cp++;
-		*cpp++ = *cp;
-	    }
-	    oldc = *cpp;
-	    *cpp = '\0';
-	    if (((cpp == cp && oldc == ':') || *cp == ':') && cp[1] &&
-		cd_state.showd) {
-		memset(buf, ' ', pre);
-		memcpy(buf, copy, (cpp - copy));
-		strcpy(buf + suf, cp + 1);
-		*sdp++ = ztrdup(buf);
-		if (mp) {
-		    *mdp++ = ztrdup(*mp);
-		    if (*mp)
-			mp++;
-		} else
-		    *mdp++ = ztrdup(copy);
-	    } else {
-		*ssp++ = ztrdup(copy);
-		if (mp) {
-		    *msp++ = ztrdup(*mp);
-		    if (*mp)
-			mp++;
-		} else
-		    *msp++ = ztrdup(copy);
-	    }
-	}
-	*sdp = *ssp = *mdp = *msp = NULL;
+    char **r = (char **) zalloc((arrlen(a) + 2) * sizeof(char *));
+    char **p = r + 1;
 
-	p = zarrdup(set->opts);
+    while (*a)
+        *p++ = ztrdup(*a++);
+    *p = NULL;
 
-	setaparam(params[0], p);
-	setaparam(params[1], sd);
-	setaparam(params[2], md);
-	setaparam(params[3], ss);
-	setaparam(params[4], ms);
+    return r;
+}
 
-	cd_state.sets = set->next;
-	set->next = NULL;
-	freecdsets(set);
+/* Get the next set. */
 
-	return 0;
+static int
+cd_get(char **params)
+{
+    Cdrun run;
+
+    if ((run = cd_state.runs)) {
+        Cdstr str;
+        char **mats, **mp, **dpys, **dp, **opts, *csl = "";
+
+        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
+                 * group 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;
+                }
+                *dp = NULL;
+            }
+            break;
+
+        case CRT_DESC:
+            {
+                VARARR(char, buf,
+                       cd_state.pre + cd_state.suf + cd_state.slen + 3);
+                char *sufp = NULL;
+
+                memcpy(buf + cd_state.pre + 2, cd_state.sep, cd_state.slen);
+                buf[cd_state.pre] = buf[cd_state.pre + 1] = ' ';
+                sufp = buf + cd_state.pre + cd_state.slen + 2;
+
+                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 - 1)
+                        buf[columns - 1] = '\0';
+                    *dp++ = ztrdup(buf);
+                }
+                *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("-2V", "", dp[0] + 2);
+
+                zsfree(*dp);
+                *dp = s;
+
+                memmove(opts, opts + 1,
+                        (arrlen(opts + 1) + 1) * sizeof(char *));
+                
+            } else
+                opts[0] = ztrdup("-2V-default-");
+            csl = "packed";
+            break;
+  
+        case CRT_DUMMY:
+            {
+                char buf[20];
+
+                sprintf(buf, "-E%d", run->count);
+
+                mats = (char **) zalloc(sizeof(char *));
+                dpys = (char **) zalloc(sizeof(char *));
+                mats[0] = dpys[0] = NULL;
+
+                opts = cd_arrdup(run->strs->set->opts);
+                opts[0] = ztrdup(buf);
+
+                csl = "packed";
+            }
+            break;
+
+	default: /* This silences the "might be used uninitialized" warnings */
+        case CRT_EXPL:
+            {
+                int dlen = columns - cd_state.gpre - cd_state.slen;
+                VARARR(char, dbuf, dlen + cd_state.slen);
+                char buf[20];
+                int i = run->count;
+
+                sprintf(buf, "-E%d", i);
+
+                mats = (char **) zalloc(sizeof(char *));
+                dpys = (char **) zalloc((i + 1) * sizeof(char *));
+
+                for (dp = dpys, str = run->strs; str; str = str->run) {
+                    if (str->run && !strcmp(str->desc, str->run->desc)) {
+                        *dp++ = ztrdup("");
+                        continue;
+                    }
+                    memset(dbuf + cd_state.slen, ' ', dlen - 1);
+                    dbuf[dlen + cd_state.slen - 1] = '\0';
+                    strcpy(dbuf, cd_state.sep);
+                    memcpy(dbuf + cd_state.slen,
+                           str->desc,
+                           ((int)strlen(str->desc) >= dlen ? dlen - 1 :
+                            (int)strlen(str->desc)));
+                    *dp++ = ztrdup(dbuf);
+                }
+                mats[0] = *dp = NULL;
+
+                opts = cd_arrdup(run->strs->set->opts);
+                opts[0] = ztrdup(buf);
+
+                csl = "packed";
+            }
+            break;
+        }
+        setsparam(params[0], ztrdup(csl));
+        setaparam(params[1], opts);
+        setaparam(params[2], mats);
+        setaparam(params[3], dpys);
+
+        zfree(run, sizeof(*run));
+
+        return 0;
     }
     return 1;
 }
 
 /**/
 static int
-bin_compdescribe(char *nam, char **args, char *ops, int func)
+bin_compdescribe(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
+    int n = arrlen(args);
+
     if (incompfunc != 1) {
 	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
@@ -259,17 +731,30 @@ bin_compdescribe(char *nam, char **args, char *ops, int func)
     }
     switch (args[0][1]) {
     case 'i':
-	cd_parsed = 1;
-	return cd_init(nam, "", args + 1, 0);
+        if (n < 3) {
+            zwarnnam(nam, "not enough arguments", NULL, 0);
+
+            return 1;
+        }
+	return cd_init(nam, args[1], args[2], "", NULL, args + 3, 0);
     case 'I':
-	cd_parsed = 1;
-	return cd_init(nam, args[1], args + 2, 1);
+        if (n < 6) {
+            zwarnnam(nam, "not enough arguments", NULL, 0);
+
+            return 1;
+        } else {
+            char **opts;
+
+            if (!(opts = getaparam(args[4]))) {
+		zwarnnam(nam, "unknown parameter: %s", args[4], 0);
+		return 1;
+            }
+            return cd_init(nam, args[1], args[2], args[3], opts, args + 5, 1);
+        }
     case 'g':
 	if (cd_parsed) {
-	    int n = arrlen(args);
-
-	    if (n != 6) {
-		zwarnnam(nam, (n < 6 ? "not enough arguments" :
+	    if (n != 5) {
+		zwarnnam(nam, (n < 5 ? "not enough arguments" :
 			      "too many arguments"), NULL, 0);
 		return 1;
 	    }
@@ -293,6 +778,7 @@ typedef struct caarg *Caarg;
 
 struct cadef {
     Cadef next;			/* next in cache */
+    Cadef snext;		/* next set */
     Caopt opts;			/* the options */
     int nopts, ndopts, nodopts;	/* number of options/direct/optional direct */
     Caarg args;			/* the normal arguments */
@@ -304,8 +790,14 @@ struct cadef {
     char *match;		/* -M spec to use */
     int argsactive;		/* if arguments are still allowed */
 				/* used while parsing a command line */
+    char *set;			/* set name prefix (<name>-), shared */
+    char *sname;		/* set name */
+    int flags;			/* see CDF_* below */
+    char *nonarg;		/* pattern for non-args (-A argument) */
 };
 
+#define CDF_SEP 1
+
 /* Description for an option. */
 
 struct caopt {
@@ -317,12 +809,15 @@ struct caopt {
     Caarg args;			/* option arguments */
     int active;			/* still allowed on command line */
     int num;			/* it's the num'th option */
+    char *set;			/* set name, shared */
+    int not;			/* don't complete this option (`!...') */
 };
 
 #define CAO_NEXT    1
 #define CAO_DIRECT  2
 #define CAO_ODIRECT 3
 #define CAO_EQUAL   4
+#define CAO_OEQUAL  5
 
 /* Description for an argument */
 
@@ -335,7 +830,10 @@ struct caarg {
     char *end;			/* end-pattern for ::<pat>:... */
     char *opt;			/* option name if for an option */
     int num;			/* it's the num'th argument */
+    int min;			/* it's also this argument, using opt. args */
+    int direct;			/* number was given directly */
     int active;			/* still allowed on command line */
+    char *set;			/* set name, shared */
 };
 
 #define CAA_NORMAL 1
@@ -389,10 +887,14 @@ freecaargs(Caarg a)
 static void
 freecadef(Cadef d)
 {
-    if (d) {
-	Caopt p, n;
+    Cadef s;
+    Caopt p, n;
 
+    while (d) {
+	s = d->snext;
 	zsfree(d->match);
+	zsfree(d->set);
+	zsfree(d->sname);
 	if (d->defs)
 	    freearray(d->defs);
 
@@ -407,9 +909,11 @@ freecadef(Cadef d)
 	}
 	freecaargs(d->args);
 	freecaargs(d->rest);
+	zsfree(d->nonarg);
 	if (d->single)
 	    zfree(d->single, 256 * sizeof(Caopt));
 	zfree(d, sizeof(*d));
+	d = s;
     }
 }
 
@@ -454,7 +958,8 @@ bslashcolon(char *s)
 /* Parse an argument definition. */
 
 static Caarg
-parse_caarg(int mult, int type, int num, char *oname, char **def)
+parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
+	    char *set)
 {
     Caarg ret = (Caarg) zalloc(sizeof(*ret));
     char *p = *def, *d, sav;
@@ -463,8 +968,11 @@ parse_caarg(int mult, int type, int num, char *oname, char **def)
     ret->descr = ret->action = ret->end = NULL;
     ret->xor = NULL;
     ret->num = num;
+    ret->min = num - opt;
     ret->type = type;
     ret->opt = ztrdup(oname);
+    ret->direct = 0;
+    ret->set = set;
 
     /* Get the description. */
 
@@ -489,22 +997,69 @@ parse_caarg(int mult, int type, int num, char *oname, char **def)
 		*p = ':';
 	} else
 	    ret->action = ztrdup(rembslashcolon(p + 1));
-    }
+    } else
+	ret->action = ztrdup("");
     *def = p;
 
     return ret;
 }
 
+static Cadef
+alloc_cadef(char **args, int single, char *match, char *nonarg, int flags)
+{
+    Cadef ret;
+
+    ret = (Cadef) zalloc(sizeof(*ret));
+    ret->next = ret->snext = NULL;
+    ret->opts = NULL;
+    ret->args = ret->rest = NULL;
+    ret->nonarg = ztrdup(nonarg);
+    if (args) {
+	ret->defs = zarrdup(args);
+	ret->ndefs = arrlen(args);
+    } else {
+	ret->defs = NULL;
+	ret->ndefs = 0;
+    }
+    ret->lastt = time(0);
+    ret->set = ret->sname = NULL;
+    if (single) {
+	ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
+	memset(ret->single, 0, 256 * sizeof(Caopt));
+    } else
+	ret->single = NULL;
+    ret->match = ztrdup(match);
+    ret->flags = flags;
+
+    return ret;
+}
+
+static void
+set_cadef_opts(Cadef def)
+{
+    Caarg argp;
+    int xnum;
+
+    for (argp = def->args, xnum = 0; argp; argp = argp->next) {
+	if (!argp->direct)
+	    argp->min = argp->num - xnum;
+	if (argp->type == CAA_OPT)
+	    xnum++;
+    }
+}
+
 /* Parse an array of definitions. */
 
 static Cadef
 parse_cadef(char *nam, char **args)
 {
-    Cadef ret;
+    Cadef all, ret;
     Caopt *optp;
-    char **oargs = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor;
-    char *adpre, *adsuf;
-    int single = 0, anum = 1, xnum, nopts, ndopts, nodopts;
+    char **oargs = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
+    char *adpre, *adsuf, *axor = NULL, *doset = NULL, **setp = NULL;
+    char *nonarg = NULL;
+    int single = 0, anum = 1, xnum, nopts, ndopts, nodopts, flags = 0;
+    int state = 0, not = 0;
 
     nopts = ndopts = nodopts = 0;
 
@@ -520,49 +1075,104 @@ parse_cadef(char *nam, char **args)
     } else
 	adpre = adsuf = NULL;
 
-    /* Now get the -s and -M options. */
+    /* Now get the -s, -A, -S and -M options. */
 
     args++;
-    while ((p = *args)) {
-	if (!strcmp(p, "-s"))
-	    single = 1;
-	else if (p[0] == '-' && p[1] == 'M') {
-	    if (p[2])
-		match = p + 2;
-	    else if (args[1])
-		match = *++args;
-	    else {
-		args++;
+    while ((p = *args) && *p == '-' && p[1]) {
+	for (q = ++p; *q; q++)
+	    if (*q == 'M' || *q == 'A') {
+		q = "";
 		break;
+	    } else if (*q != 's' && *q != 'S')
+		break;
+
+	if (*q)
+	    break;
+
+	for (; *p; p++) {
+	    if (*p == 's')
+		single = 1;
+	    else if (*p == 'S')
+		flags |= CDF_SEP;
+	    else if (*p == 'A') {
+		if (p[1]) {
+		    nonarg = p + 1;
+		    p = "" - 1;
+		} else if (args[1])
+		    nonarg = *++args;
+		else
+		    break;
+	    } else if (*p == 'M') {
+		if (p[1]) {
+		    match = p + 1;
+		    p = "" - 1;
+		} else if (args[1])
+		    match = *++args;
+		else
+		    break;
 	    }
-	} else
+	}
+	if (*p)
 	    break;
+
 	args++;
     }
+    if (*args && !strcmp(*args, ":"))
+        args++;
     if (!*args)
 	return NULL;
 
+    if (nonarg)
+	tokenize(nonarg = dupstring(nonarg));
+
     /* Looks good. Optimistically allocate the cadef structure. */
 
-    ret = (Cadef) zalloc(sizeof(*ret));
-    ret->next = NULL;
-    ret->opts = NULL;
-    ret->args = ret->rest = NULL;
-    ret->defs = zarrdup(oargs);
-    ret->ndefs = arrlen(oargs);
-    ret->lastt = time(0);
-    if (single) {
-	ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
-	memset(ret->single, 0, 256 * sizeof(Caopt));
-    } else
-	ret->single = NULL;
-    ret->match = ztrdup(match);
+    all = ret = alloc_cadef(oargs, single, match, nonarg, flags);
+    optp = &(ret->opts);
+    anum = 1;
+
+    sargs = args;
 
     /* Get the definitions. */
 
-    for (optp = &(ret->opts); *args; args++) {
+    for (; *args; args++) {
+        if (args[0][0] == '-' && !args[0][1] && args[1]) {
+	    if (!state) {
+		char *p;
+		int l;
+
+		if (setp)
+		    args = setp;
+		p = *++args;
+		l = strlen(p) - 1;
+		if (*p == '(' && p[l] == ')') {
+		    axor = p = dupstring(p + 1);
+		    p[l - 1] = '\0';
+		} else
+		    axor = NULL;
+		ret->set = doset = tricat(p, "-", "");
+		ret->sname = ztrdup(p);
+		state = 1;
+	    } else {
+		setp = args;
+		state = 0;
+		args = sargs - 1;
+		doset = NULL;
+		ret->nopts = nopts;
+		ret->ndopts = ndopts;
+		ret->nodopts = nodopts;
+		set_cadef_opts(ret);
+		ret = ret->snext = alloc_cadef(NULL, single, NULL, nonarg, flags);
+		optp = &(ret->opts);
+		nopts = ndopts = nodopts = 0;
+		anum = 1;
+	    }
+	    continue;
+	}
 	p = dupstring(*args);
 	xnum = 0;
+	if ((not = (*p == '!')))
+	    p++;
 	if (*p == '(') {
 	    /* There is a xor list, get it. */
 
@@ -588,16 +1198,25 @@ parse_cadef(char *nam, char **args)
 	    }
 	    /* Oops, end-of-string. */
 	    if (*p != ')') {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
+	    if (doset && axor)
+		xnum++;
 	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
 	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
 		*xp = ztrdup((char *) getdata(node));
+	    if (doset && axor)
+		*xp++ = ztrdup(axor);
 	    xp[0] = xp[1] = NULL;
 
 	    p++;
+	} else if (doset && axor) {
+	    xnum = 1;
+	    xor = (char **) zalloc(3 * sizeof(char *));
+	    xor[0] = ztrdup(axor);
+	    xor[1] = xor[2] = NULL;
 	} else
 	    xor = NULL;
 
@@ -607,7 +1226,7 @@ parse_cadef(char *nam, char **args)
 	    Caopt opt;
 	    Caarg oargs = NULL;
 	    int multi, otype = CAO_NEXT, again = 0;
-	    char *name, *descr, c;
+	    char *name, *descr, c, *againp = NULL;
 
 	    rec:
 
@@ -621,9 +1240,10 @@ parse_cadef(char *nam, char **args)
 		p[2] != '=' && p[2] != '-' && p[2] != '+') {
 		/* It's a -+ or +- definition. We just execute the whole
 		 * stuff twice for such things. */
+		againp = dupstring(p);
 		name = ++p;
 		*p = (again ? '-' : '+');
-		again = 1 - again;
+		again++;
 	    } else {
 		name = p;
 		/* If it's a long option skip over the first `-'. */
@@ -631,15 +1251,17 @@ parse_cadef(char *nam, char **args)
 		    p++;
 	    }
 	    if (!p[1]) {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 
 	    /* Skip over the name. */
 	    for (p++; *p && *p != ':' && *p != '[' &&
-		     ((*p != '-' && *p != '+' && *p != '=') ||
-		      (p[1] != ':' && p[1] != '[')); p++)
+		     ((*p != '-' && *p != '+') ||
+		      (p[1] != ':' && p[1] != '[')) &&
+		     (*p != '=' ||
+		      (p[1] != ':' && p[1] != '[' && p[1] != '-')); p++)
 		if (*p == '\\' && p[1])
 		    p++;
 
@@ -653,8 +1275,11 @@ parse_cadef(char *nam, char **args)
 		otype = CAO_ODIRECT;
 		c = *++p;
 	    } else if (c == '=') {
-		otype = CAO_EQUAL;
-		c = *++p;
+		otype = CAO_OEQUAL;
+		if ((c = *++p) == '-') {
+		    otype = CAO_EQUAL;
+		    c = *++p;
+		}
 	    }
 	    /* Get the optional description, if any. */
 	    if (c == '[') {
@@ -663,7 +1288,7 @@ parse_cadef(char *nam, char **args)
 			p++;
 
 		if (!*p) {
-		    freecadef(ret);
+		    freecadef(all);
 		    zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		    return NULL;
 		}
@@ -673,7 +1298,7 @@ parse_cadef(char *nam, char **args)
 		descr = NULL;
 
 	    if (c && c != ':') {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		return NULL;
 	    }
@@ -681,15 +1306,16 @@ parse_cadef(char *nam, char **args)
 	    if (!multi) {
 		if (!xor) {
 		    xor = (char **) zalloc(2 * sizeof(char *));
-		    xor[1] = NULL;
+		    xor[0] = xor[1] = NULL;
 		}
-		xor[xnum] = ztrdup(name);
+                zsfree(xor[xnum]);
+		xor[xnum] = ztrdup(rembslashcolon(name));
 	    }
 	    if (c == ':') {
 		/* There's at least one argument. */
 
 		Caarg *oargp = &oargs;
-		int atype, rest, oanum = 1;
+		int atype, rest, oanum = 1, onum = 0;
 		char *end;
 
 		/* Loop over the arguments. */
@@ -716,7 +1342,7 @@ parse_cadef(char *nam, char **args)
 			    *p = sav;
 			}
 			if (*p != ':') {
-			    freecadef(ret);
+			    freecadef(all);
 			    freecaargs(oargs);
 			    zwarnnam(nam, "invalid option definition: %s",
 				    *args, 0);
@@ -736,7 +1362,10 @@ parse_cadef(char *nam, char **args)
 
 		    /* And the definition. */
 
-		    *oargp = parse_caarg(!rest, atype, oanum++, name, &p);
+		    *oargp = parse_caarg(!rest, atype, oanum++, onum,
+					 name, &p, doset);
+		    if (atype == CAA_OPT)
+			onum++;
 		    if (end)
 			(*oargp)->end = ztrdup(end);
 		    oargp = &((*oargp)->next);
@@ -751,6 +1380,7 @@ parse_cadef(char *nam, char **args)
 	    optp = &((*optp)->next);
 
 	    opt->next = NULL;
+	    opt->set = doset;
 	    opt->name = ztrdup(rembslashcolon(name));
 	    if (descr)
 		opt->descr = ztrdup(descr);
@@ -767,14 +1397,15 @@ parse_cadef(char *nam, char **args)
 		    opt->descr = NULL;
 	    } else
 		opt->descr = NULL;
-	    opt->xor = xor;
+	    opt->xor = (again == 1 && xor ? zarrdup(xor) : xor);
 	    opt->type = otype;
 	    opt->args = oargs;
 	    opt->num = nopts++;
+	    opt->not = not;
 
-	    if (otype == CAO_DIRECT)
+	    if (otype == CAO_DIRECT || otype == CAO_EQUAL)
 		ndopts++;
-	    else if (otype == CAO_ODIRECT || otype == CAO_EQUAL)
+	    else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL)
 		nodopts++;
 
 	    /* If this is for single-letter option we also store a
@@ -783,9 +1414,9 @@ parse_cadef(char *nam, char **args)
 	    if (single && name[1] && !name[2])
 		ret->single[STOUC(name[1])] = opt;
 
-	    if (again) {
+	    if (again == 1) {
 		/* Do it all again for `*-...'. */
-		p = dupstring(*args);
+		p = againp;
 		goto rec;
 	    }
 	} else if (*p == '*') {
@@ -793,13 +1424,16 @@ parse_cadef(char *nam, char **args)
 
 	    int type = CAA_REST;
 
+	    if (not)
+		continue;
+
 	    if (*++p != ':') {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "invalid rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    if (ret->rest) {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "doubled rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
@@ -810,15 +1444,18 @@ parse_cadef(char *nam, char **args)
 		} else
 		    type = CAA_RARGS;
 	    }
-	    ret->rest = parse_caarg(0, type, -1, NULL, &p);
+	    ret->rest = parse_caarg(0, type, -1, 0, NULL, &p, doset);
 	    ret->rest->xor = xor;
 	} else {
 	    /* It's a normal argument definition. */
 
-	    int type = CAA_NORMAL;
+	    int type = CAA_NORMAL, direct;
 	    Caarg arg, tmp, pre;
 
-	    if (idigit(*p)) {
+	    if (not)
+		continue;
+
+	    if ((direct = idigit(*p))) {
 		/* Argment number is given. */
 		int num = 0;
 
@@ -831,7 +1468,7 @@ parse_cadef(char *nam, char **args)
 		anum++;
 
 	    if (*p != ':') {
-		freecadef(ret);
+		freecadef(all);
 		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
@@ -840,8 +1477,9 @@ parse_cadef(char *nam, char **args)
 		type = CAA_OPT;
 		p++;
 	    }
-	    arg = parse_caarg(0, type, anum - 1, NULL, &p);
+	    arg = parse_caarg(0, type, anum - 1, 0, NULL, &p, doset);
 	    arg->xor = xor;
+	    arg->direct = direct;
 
 	    /* Sort the new definition into the existing list. */
 
@@ -850,7 +1488,7 @@ parse_cadef(char *nam, char **args)
 		 pre = tmp, tmp = tmp->next);
 
 	    if (tmp && tmp->num == anum - 1) {
-		freecadef(ret);
+		freecadef(all);
 		freecaargs(arg);
 		zwarnnam(nam, "doubled argument definition: %s", *args, 0);
 		return NULL;
@@ -865,8 +1503,9 @@ parse_cadef(char *nam, char **args)
     ret->nopts = nopts;
     ret->ndopts = ndopts;
     ret->nodopts = nodopts;
-    
-    return ret;
+    set_cadef_opts(ret);
+
+    return all;
 }
 
 /* Given an array of definitions, return the cadef for it. From the cache
@@ -878,7 +1517,7 @@ get_cadef(char *nam, char **args)
     Cadef *p, *min, new;
     int i, na = arrlen(args);
 
-    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; *p && i; p++, i--)
+    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; i && *p; p++, i--)
 	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
 	    (*p)->lastt = time(0);
 
@@ -920,7 +1559,8 @@ ca_get_opt(Cadef d, char *line, int full, char **end)
 		    /* Return a pointer to the end of the option. */
 		    int l = strlen(p->name);
 
-		    if (p->type == CAO_EQUAL && line[l] == '=')
+		    if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
+			line[l] == '=')
 			l++;
 
 		    *end = line + l;
@@ -934,36 +1574,39 @@ ca_get_opt(Cadef d, char *line, int full, char **end)
 /* Same as above, only for single-letter-style. */
 
 static Caopt
-ca_get_sopt(Cadef d, char *line, int full, char **end)
+ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp)
 {
-    Caopt p;
+    Caopt p, pp = NULL;
     char pre = *line++;
-
-    if (full) {
-	for (p = NULL; *line; line++)
-	    if (!(p = d->single[STOUC(*line)]) || !p->active ||
-		(line[1] && p->args))
-		return NULL;
-	return p;
-    } else {
-	for (p = NULL; *line; line++)
-	    if ((p = d->single[STOUC(*line)]) && p->active &&
-		p->args && p->type != CAO_NEXT && p->name[0] == pre) {
+    LinkList l = NULL;
+
+    *lp = NULL;
+    for (p = NULL; *line; line++) {
+	if ((p = d->single[STOUC(*line)]) && p->active &&
+	    p->args && p->name[0] == pre) {
+	    if (p->type == CAO_NEXT) {
+		if (!l)
+		    *lp = l = newlinklist();
+		addlinknode(l, p);
+	    } else {
 		if (end) {
 		    line++;
-		    if (p->type == CAO_EQUAL && *line == '=')
+		    if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
+			*line == '=')
 			line++;
 		    *end = line;
 		}
+		pp = p;
 		break;
-	    } else if (!p || !p->active || (line[1] && p->args) ||
-		       p->name[0] != pre)
-		return NULL;
-	if (p && end)
-	    *end = line;
-	return p;
+	    }
+	} else if (!p || (p && !p->active))
+	    return NULL;
+	pp = (p->name[0] == pre ? p : NULL);
+	p = NULL;
     }
-    return NULL;
+    if (pp && end)
+	*end = line;
+    return pp;
 }
 
 /* Return the n'th argument definition. */
@@ -974,10 +1617,12 @@ ca_get_arg(Cadef d, int n)
     if (d->argsactive) {
 	Caarg a = d->args;
 
-	while (a && a->num < n)
+	while (a && (!a->active || n < a->min || n > a->num)) {
+            if (!a->active)
+                n++;
 	    a = a->next;
-
-	if (a && a->num == n && a->active)
+        }
+	if (a && a->min <= n && a->num >= n && a->active)
 	    return a;
 
 	return (d->rest && d->rest->active ? d->rest : NULL);
@@ -987,42 +1632,88 @@ ca_get_arg(Cadef d, int n)
 
 /* Use a xor list, marking options as inactive. */
 
-static void
-ca_inactive(Cadef d, char **xor)
+static LinkList ca_xor;
+
+static int
+ca_inactive(Cadef d, char **xor, int cur, int opts, char *optname)
 {
-    if (xor) {
+    if ((xor || opts) && cur <= compcurrent) {
 	Caopt opt;
-
-	for (; *xor; xor++) {
-	    if (xor[0][0] == ':' && !xor[0][1])
-		d->argsactive = 0;
-	    else if (xor[0][0] == '*' && !xor[0][1]) {
-		if (d->rest)
+	char *x;
+	int sl = (d->set ? (int)strlen(d->set) : -1), set = 0;
+
+	for (; (x = (opts ? "-" : *xor)); xor++) {
+            if (optname && optname[0] == x[0] && strcmp(optname, x))
+                continue;
+	    if (ca_xor)
+		addlinknode(ca_xor, x);
+	    set = 0;
+	    if (sl > 0) {
+		if (strpfx(d->set, x)) {
+		    x += sl;
+		    set = 1;
+		} else if (!strncmp(d->set, x, sl - 1)) {
+		    Caopt p;
+
+		    for (p = d->opts; p; p = p->next)
+			if (p->set)
+			    p->active = 0;
+			
+		    x = ":";
+		    set = 1;
+		}
+	    }
+	    if (x[0] == ':' && !x[1]) {
+		if (set) {
+		    Caarg a;
+
+		    for (a = d->args; a; a = a->next)
+			if (a->set)
+			    a->active = 0;
+		    if (d->rest && (!set || d->rest->set))
+			d->rest->active = 0;
+		} else
+		    d->argsactive = 0;
+	    } else if (x[0] == '-' && !x[1]) {
+		Caopt p;
+
+		for (p = d->opts; p; p = p->next)
+		    if (!set || p->set)
+			p->active = 0;
+	    } else if (x[0] == '*' && !x[1]) {
+		if (d->rest && (!set || d->rest->set))
 		    d->rest->active = 0;
-	    } else if (xor[0][0] >= '0' && xor[0][0] <= '9') {
-		int n = atoi(xor[0]);
+	    } else if (x[0] >= '0' && x[0] <= '9') {
+		int n = atoi(x);
 		Caarg a = d->args;
 
 		while (a && a->num < n)
 		    a = a->next;
 
-		if (a && a->num == n)
+		if (a && a->num == n && (!set || a->set))
 		    a->active = 0;
-	    } else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
+	    } else if ((opt = ca_get_opt(d, x, 1, NULL)) && (!set || opt->set))
 		opt->active = 0;
+
+	    if (opts)
+		break;
 	}
     }
+    return 0;
 }
 
 /* State when parsing a command line. */
 
+typedef struct castate *Castate;
+
 struct castate {
+    Castate snext;
     Cadef d;
     int nopts;
     Caarg def, ddef;
-    Caopt curopt;
-    int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos;
-    int inopt, inrest, inarg, nth, doff, singles;
+    Caopt curopt, dopt;
+    int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos, argend;
+    int inopt, inrest, inarg, nth, doff, singles, oopt, actopts;
     LinkList args;
     LinkList *oargs;
 };
@@ -1030,30 +1721,72 @@ struct castate {
 static struct castate ca_laststate;
 static int ca_parsed = 0, ca_alloced = 0;
 
-/* Pars a command line. */
-
 static void
-ca_parse_line(Cadef d)
+freecastate(Castate s)
+{
+    int i;
+    LinkList *p;
+
+    freelinklist(s->args, freestr);
+    for (i = s->nopts, p = s->oargs; i--; p++)
+	if (*p)
+	    freelinklist(*p, freestr);
+    zfree(s->oargs, s->d->nopts * sizeof(LinkList));
+}
+
+/* Return a copy of an option's argument, ignoring possible quoting
+ * in the option name. */
+
+static char *
+ca_opt_arg(Caopt opt, char *line)
+{
+    char *o = opt->name;
+
+    while (1) {
+        if (*o == '\\')
+            o++;
+        if (*line == '\\' || *line == '\'' || *line == '"')
+            line++;
+        if (!*o || *o != *line)
+            break;
+        o++;
+        line++;
+    }
+    if (*line && (opt->type == CAO_EQUAL || opt->type == CAO_OEQUAL)) {
+        if (*line == '\\')
+            line++;
+        if (*line == '=')
+            line++;
+    }
+    return ztrdup(line);
+}
+
+/* Parse a command line. */
+
+static int
+ca_parse_line(Cadef d, int multi, int first)
 {
     Caarg adef, ddef;
-    Caopt ptr, wasopt;
+    Caopt ptr, wasopt = NULL, dopt;
     struct castate state;
-    char *line, *pe, **argxor = NULL;
-    int cur, doff;
-    Patprog endpat = NULL;
+    char *line, *oline, *pe, **argxor = NULL;
+    int cur, doff, argend, arglast, ne;
+    Patprog endpat = NULL, napat = NULL;
+    LinkList sopts = NULL;
 
     /* Free old state. */
 
-    if (ca_alloced) {
-	int i = ca_laststate.nopts;
-	LinkList *p = ca_laststate.oargs;
+    if (first && ca_alloced) {
+	Castate s = &ca_laststate, ss;
+	int f = 1;
 
-	freelinklist(ca_laststate.args, freestr);
-	while (i--)
-	    if (*p++)
-		freelinklist(p[-1], freestr);
-
-	zfree(ca_laststate.oargs, ca_laststate.d->nopts * sizeof(LinkList));
+	while (s) {
+	    ss = s->snext;
+	    freecastate(s);
+	    if (!f)
+		zfree(s, sizeof(*s));
+	    s = ss;
+	}
     }
     /* Mark everything as active. */
 
@@ -1067,13 +1800,15 @@ ca_parse_line(Cadef d)
 
     /* Default values for the state. */
 
+    state.snext = NULL;
     state.d = d;
     state.nopts = d->nopts;
     state.def = state.ddef = NULL;
-    state.curopt = NULL;
-    state.argbeg = state.optbeg = state.nargbeg = state.restbeg =
+    state.curopt = state.dopt = NULL;
+    state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts =
 	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
-    state.inrest = state.doff = state.singles = state.doff = 0;
+    state.argend = argend = arrlen(compwords) - 1;
+    state.inrest = state.doff = state.singles = state.doff = state.oopt = 0;
     state.curpos = compcurrent;
     state.args = znewlinklist();
     state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
@@ -1086,25 +1821,44 @@ ca_parse_line(Cadef d)
     if (!compwords[1]) {
 	ca_laststate.opt = ca_laststate.arg = 0;
 
-	return;
+	goto end;
     }
+    if (d->nonarg)
+	napat = patcompile(d->nonarg, 0, NULL);
+
     /* Loop over the words from the line. */
 
     for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
 	 line; line = compwords[cur++]) {
 	ddef = adef = NULL;
-	doff = state.singles = 0;
-
-	ca_inactive(d, argxor);
-
-	/* We've a definition for an argument, skip to the next. */
+	dopt = NULL;
+	doff = state.singles = arglast = 0;
+
+        /* remove quotes */
+        oline = line;
+        line = dupstring(line);
+        ne = noerrs;
+        noerrs = 2;
+        parse_subst_string(line);
+        noerrs = ne;
+        remnulargs(line);
+        untokenize(line);
+
+	if (ca_inactive(d, argxor, cur, 0, NULL) ||
+	    ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--"))) {
+	    if (ca_inactive(d, NULL, cur, 1, NULL))
+		return 1;
+	    continue;
+	}
+	/* We've got a definition for an argument, skip to the next. */
 
 	if (state.def) {
 	    state.arg = 0;
 	    if (state.curopt)
-		zaddlinknode(state.oargs[state.curopt->num], ztrdup(line));
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(oline));
 
-	    state.opt = (state.def->type == CAA_OPT);
+	    if ((state.opt = (state.def->type == CAA_OPT)) && state.def->opt)
+		state.oopt++;
 
 	    if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
 		state.def->type == CAA_RREST) {
@@ -1112,11 +1866,24 @@ ca_parse_line(Cadef d)
 		    state.def = NULL;
 		    state.curopt = NULL;
 		    state.opt = state.arg = 1;
-		    continue;
+		    state.argend = ca_laststate.argend = cur - 1;
+		    goto cont;
 		}
-	    } else if ((state.def = state.def->next))
+	    } else if ((state.def = state.def->next)) {
 		state.argbeg = cur;
-	    else {
+		state.argend = argend;
+	    } else if (sopts && nonempty(sopts)) {
+		state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
+		state.def = state.curopt->args;
+		state.opt = 0;
+		state.argbeg = state.optbeg = state.inopt = cur;
+		state.argend = argend;
+		doff = state.doff = 0;
+		state.singles = 1;
+		if (!state.oargs[state.curopt->num])
+		    state.oargs[state.curopt->num] = znewlinklist();
+		goto cont;
+	    } else {
 		state.curopt = NULL;
 		state.opt = 1;
 	    }
@@ -1134,32 +1901,44 @@ ca_parse_line(Cadef d)
 	/* See if it's an option. */
 
 	if (state.opt == 2 && (state.curopt = ca_get_opt(d, line, 0, &pe)) &&
-	    (state.curopt->type != CAO_EQUAL || 
-	     compwords[cur] || pe[-1] == '=')) {
+	    (state.curopt->type == CAO_OEQUAL ?
+	     (compwords[cur] || pe[-1] == '=') :
+	     (state.curopt->type == CAO_EQUAL ?
+	      (pe[-1] == '=' || !pe[0]) : 1))) {
+
+	    if ((ddef = state.def = ((state.curopt->type != CAO_EQUAL ||
+				      pe[-1] == '=') ?
+				     state.curopt->args : NULL)))
+		dopt = state.curopt;
 
-	    ddef = state.def = state.curopt->args;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
+	    state.argend = argend;
 	    state.singles = (d->single && (!pe || !*pe) &&
 			     state.curopt->name[1] && !state.curopt->name[2]);
 
-	    state.oargs[state.curopt->num] = znewlinklist();
+	    if (!state.oargs[state.curopt->num])
+		state.oargs[state.curopt->num] = znewlinklist();
 
-	    ca_inactive(d, state.curopt->xor);
+	    if (ca_inactive(d, state.curopt->xor, cur, 0,
+                            (cur == compcurrent ? state.curopt->name : NULL)))
+		return 1;
 
 	    /* Collect the argument strings. Maybe. */
 
 	    if (state.def &&
 		(state.curopt->type == CAO_DIRECT ||
+		 state.curopt->type == CAO_EQUAL ||
 		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
-		 (state.curopt->type == CAO_EQUAL &&
+		 (state.curopt->type == CAO_OEQUAL &&
 		  (pe[0] || pe[-1] == '=')))) {
 		if (state.def->type != CAA_REST &&
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
 
-		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		zaddlinknode(state.oargs[state.curopt->num],
+                             ca_opt_arg(state.curopt, oline));
 	    }
 	    if (state.def)
 		state.opt = 0;
@@ -1169,63 +1948,104 @@ ca_parse_line(Cadef d)
 		state.curopt = NULL;
 	    }
 	} else if (state.opt == 2 && d->single &&
-		   (state.curopt = ca_get_sopt(d, line, 0, &pe))) {
+		   ((state.curopt = ca_get_sopt(d, line, &pe, &sopts)) ||
+		    (cur != compcurrent && sopts && nonempty(sopts)))) {
 	    /* Or maybe it's a single-letter option? */
 
 	    char *p;
 	    Caopt tmpopt;
 
-	    ddef = state.def = state.curopt->args;
+	    if (cur != compcurrent && sopts && nonempty(sopts))
+		state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
+
+	    if (!state.oargs[state.curopt->num])
+		state.oargs[state.curopt->num] = znewlinklist();
+
+	    state.def = state.curopt->args;
+	    ddef = (state.curopt->type == CAO_NEXT && cur == compcurrent ?
+		    NULL : state.def);
+	    dopt = state.curopt;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
+	    state.argend = argend;
 	    state.singles = (!pe || !*pe);
 
 	    for (p = line + 1; p < pe; p++) {
 		if ((tmpopt = d->single[STOUC(*p)])) {
-		    state.oargs[tmpopt->num] = znewlinklist();
+		    if (!state.oargs[tmpopt->num])
+			state.oargs[tmpopt->num] = znewlinklist();
 
-		    ca_inactive(d, tmpopt->xor);
+		    if (ca_inactive(d, tmpopt->xor, cur, 0,
+                                    (cur == compcurrent ? tmpopt->name : NULL)))
+			return 1;
 		}
 	    }
 	    if (state.def &&
 		(state.curopt->type == CAO_DIRECT ||
+		 state.curopt->type == CAO_EQUAL ||
 		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
-		 (state.curopt->type == CAO_EQUAL &&
+		 (state.curopt->type == CAO_OEQUAL &&
 		  (pe[0] || pe[-1] == '=')))) {
 		if (state.def->type != CAA_REST &&
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
 
-		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		zaddlinknode(state.oargs[state.curopt->num],
+                             ca_opt_arg(state.curopt, line));
 	    }
 	    if (state.def)
 		state.opt = 0;
 	    else
 		state.curopt = NULL;
-	} else if (state.arg) {
+	} else if (multi && (*line == '-' || *line == '+') && cur != compcurrent
+#if 0
+		   /**** Ouch. Using this will disable the mutual exclusion
+			 of different sets. Not using it will make the -A
+			 pattern be effectively ignored with multiple sets. */
+		   && (!napat || !pattry(napat, line))
+#endif
+		   )
+	    return 1;
+	else if (state.arg && (!napat || !pattry(napat, line))) {
 	    /* Otherwise it's a normal argument. */
+	    if (napat && ca_inactive(d, NULL, cur + 1, 1, NULL))
+		return 1;
+
+	    arglast = 1;
 	    if (state.inopt) {
 		state.inopt = 0;
 		state.nargbeg = cur - 1;
+		state.argend = argend;
+	    }
+	    if (!d->args && !d->rest && *line && *line != '-' && *line != '+') {
+		if (!multi && cur > compcurrent)
+		    break;
+		return 1;
 	    }
 	    if ((adef = state.def = ca_get_arg(d, state.nth)) &&
 		(state.def->type == CAA_RREST ||
 		 state.def->type == CAA_RARGS)) {
 		state.inrest = 0;
-		state.opt = (cur == state.nargbeg + 1);
+		state.opt = (cur == state.nargbeg + 1 &&
+			     (!multi || !*line || 
+			      *line == '-' || *line == '+'));
 		state.optbeg = state.nargbeg;
 		state.argbeg = cur - 1;
+		state.argend = argend;
 
 		for (; line; line = compwords[cur++])
 		    zaddlinknode(state.args, ztrdup(line));
 
 		memcpy(&ca_laststate, &state, sizeof(state));
 		ca_laststate.ddef = NULL;
+		ca_laststate.dopt = NULL;
 		ca_laststate.doff = 0;
 		break;
 	    }
 	    zaddlinknode(state.args, ztrdup(line));
+            if (adef)
+                state.oopt = adef->num - state.nth;
 
 	    if (state.def)
 		argxor = state.def->xor;
@@ -1256,6 +2076,7 @@ ca_parse_line(Cadef d)
 		    zaddlinknode(l, ztrdup(line));
 
 		ca_laststate.ddef = NULL;
+		ca_laststate.dopt = NULL;
 		ca_laststate.doff = 0;
 		break;
 	    }
@@ -1264,15 +2085,19 @@ ca_parse_line(Cadef d)
 
 	/* Copy the state into the global one. */
 
+    cont:
+
 	if (cur + 1 == compcurrent) {
 	    memcpy(&ca_laststate, &state, sizeof(state));
 	    ca_laststate.ddef = NULL;
+	    ca_laststate.dopt = NULL;
 	    ca_laststate.doff = 0;
 	} else if (cur == compcurrent && !ca_laststate.def) {
 	    if ((ca_laststate.def = ddef)) {
 		ca_laststate.singles = state.singles;
 		if (state.curopt && state.curopt->type == CAO_NEXT) {
 		    ca_laststate.ddef = ddef;
+		    ca_laststate.dopt = dopt;
 		    ca_laststate.def = NULL;
 		    ca_laststate.opt = 1;
 		    state.curopt->active = 1;
@@ -1282,15 +2107,28 @@ ca_parse_line(Cadef d)
 		}
 	    } else {
 		ca_laststate.def = adef;
+		ca_laststate.opt = (!arglast || !multi || !*line || 
+				    *line == '-' || *line == '+');
 		ca_laststate.ddef = NULL;
+		ca_laststate.dopt = NULL;
 		ca_laststate.optbeg = state.nargbeg;
 		ca_laststate.argbeg = state.restbeg;
+		ca_laststate.argend = state.argend;
 		ca_laststate.singles = state.singles;
+		ca_laststate.oopt = state.oopt;
 		if (wasopt)
 		    wasopt->active = 1;
 	    }
 	}
     }
+ end:
+
+    ca_laststate.actopts = 0;
+    for (ptr = d->opts; ptr; ptr = ptr->next)
+	if (ptr->active)
+	    ca_laststate.actopts++;
+
+    return 0;
 }
 
 /* Build a colon-list from a list. */
@@ -1327,10 +2165,104 @@ ca_colonlist(LinkList l)
 	return ztrdup("");
 }
 
+static void
+ca_set_data(LinkList descr, LinkList act, LinkList subc,
+	    char *opt, Caarg arg, int single)
+{
+    LinkNode dnode, anode;
+    char nbuf[40], *buf;
+    int restr = 0, onum, miss = 0, rest, oopt = 1, lopt = 0, addopt;
+
+ rec:
+
+    addopt = (opt ? 0 : ca_laststate.oopt);
+
+    for (; arg && (opt || (arg->num < 0 ||
+			   (arg->min <= ca_laststate.nth + addopt &&
+			    arg->num >= ca_laststate.nth)));) {
+	lopt = (arg->type == CAA_OPT);
+	if (!opt && !lopt && oopt > 0)
+	    oopt = 0;
+
+	for (dnode = firstnode(descr), anode = firstnode(act);
+	     dnode; incnode(dnode), incnode(anode))
+	    if (!strcmp((char *) getdata(dnode), arg->descr) &&
+		!strcmp((char *) getdata(anode), arg->action))
+		break;
+
+	if (!dnode) {
+	    addlinknode(descr, arg->descr);
+	    addlinknode(act, arg->action);
+
+	    if (!restr) {
+		if ((restr = (arg->type == CAA_RARGS)))
+		    restrict_range(ca_laststate.optbeg, ca_laststate.argend);
+		else if ((restr = (arg->type == CAA_RREST)))
+		    restrict_range(ca_laststate.argbeg, ca_laststate.argend);
+	    }
+	    if (arg->opt) {
+		buf = (char *) zhalloc((arg->set ? strlen(arg->set) : 0) +
+				       strlen(arg->opt) + 40);
+		if (arg->num > 0 && arg->type < CAA_REST)
+		    sprintf(buf, "%soption%s-%d",
+			    (arg->set ? arg->set : ""), arg->opt, arg->num);
+		else
+		    sprintf(buf, "%soption%s-rest",
+			    (arg->set ? arg->set : ""), arg->opt);
+	    } else if (arg->num > 0) {
+		sprintf(nbuf, "argument-%d", arg->num);
+		buf = (arg->set ? dyncat(arg->set, nbuf) : dupstring(nbuf));
+	    } else
+		buf = (arg->set ? dyncat(arg->set, "argument-rest") :
+		       dupstring("argument-rest"));
+
+	    addlinknode(subc, buf);
+	}
+	if (single)
+	    break;
+
+	if (!opt) {
+	    if (arg->num >= 0 && !arg->next && miss)
+		arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
+		       ca_laststate.d->rest : NULL);
+	    else {
+		onum = arg->num;
+		rest = (onum != arg->min && onum == ca_laststate.nth);
+		if ((arg = arg->next)) {
+		    if (arg->num != onum + 1)
+			miss = 1;
+		} else if (rest || (oopt > 0 && !opt)) {
+		    arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
+			   ca_laststate.d->rest : NULL);
+		    oopt = -1;
+		}
+	    }
+	} else {
+	    if (!lopt)
+		break;
+	    arg = arg->next;
+	}
+    }
+    if (!single && opt && (lopt || ca_laststate.oopt)) {
+	opt = NULL;
+	arg = ca_get_arg(ca_laststate.d, ca_laststate.nth);
+
+	goto rec;
+    }
+    if (!opt && oopt > 0) {
+	oopt = -1;
+	arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
+	       ca_laststate.d->rest : NULL);
+
+	goto rec;
+    }
+}
+
 static int
-bin_comparguments(char *nam, char **args, char *ops, int func)
+bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int min, max, n;
+    Castate lstate = &ca_laststate;
 
     if (incompfunc != 1) {
 	zwarnnam(nam, "can only be called from completion function", NULL, 0);
@@ -1340,14 +2272,13 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
-    if (args[0][1] != 'i' && !ca_parsed) {
+    if (args[0][1] != 'i' && args[0][1] != 'I' && !ca_parsed) {
 	zwarnnam(nam, "no parsed state", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
     case 'i': min = 2; max = -1; break;
-    case 'D': min = 2; max =  2; break;
-    case 'C': min = 1; max =  1; break;
+    case 'D': min = 3; max =  3; break;
     case 'O': min = 4; max =  4; break;
     case 'L': min = 3; max =  4; break;
     case 's': min = 1; max =  1; break;
@@ -1368,169 +2299,281 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
     }
     switch (args[0][1]) {
     case 'i':
+        /* This initialises the internal data structures. Arguments are the
+         * auto-description string, the optional -s, -S, -A and -M options
+         * given to _arguments and the specs. */
 	if (compcurrent > 1 && compwords[0]) {
-	    Cadef def = get_cadef(nam, args + 1);
-	    int cap = ca_parsed;
+	    Cadef def;
+	    int cap = ca_parsed, multi, first = 1, use, ret = 0;
+	    LinkList cax = ca_xor, nx;
+	    LinkNode node;
+	    Castate states = NULL, sp;
+	    char *xor[2];
 
 	    ca_parsed = 0;
+	    xor[1] = NULL;
 
-	    if (!def)
+	    if (!(def = get_cadef(nam, args + 1)))
 		return 1;
 
+	    multi = !!def->snext;
 	    ca_parsed = cap;
-	    ca_parse_line(def);
+	    ca_xor = (multi ? newlinklist() : NULL);
+
+	    while (def) {
+		use = !ca_parse_line(def, multi, first);
+		nx = ca_xor;
+		ca_xor = NULL;
+		while ((def = def->snext)) {
+		    if (nx) {
+			for (node = firstnode(nx); node; incnode(node)) {
+			    xor[0] = (char *) getdata(node);
+			    if (!strcmp(xor[0], def->sname) ||
+				ca_inactive(def, xor, compcurrent, 0, NULL))
+				break;
+			}
+			if (!node)
+			    break;
+		    }
+		}
+		ca_xor = nx;
+		if (use && def) {
+		    sp = (Castate) zalloc(sizeof(*sp));
+		    memcpy(sp, &ca_laststate, sizeof(*sp));
+		    sp->snext = states;
+		    states = sp;
+		} else if (!use && !def) {
+		    if (states) {
+			freecastate(&ca_laststate);
+			memcpy(&ca_laststate, states, sizeof(*sp));
+			sp = states->snext;
+			zfree(states, sizeof(*states));
+			states = sp;
+		    } else
+			ret = 1;
+		}
+		first = 0;
+	    }
+	    ca_xor = cax;
 	    ca_parsed = 1;
+	    ca_laststate.snext = states;
 
-	    return 0;
+	    return ret;
 	}
 	return 1;
 
     case 'D':
+        /* This returns the descriptions, actions and sub-contexts for the
+         * things _arguments has to execute at this place on the line (the
+         * sub-contexts are used as tags).
+         * The return value is particularly important here, it says if 
+         * there are arguments to completely at all. */
 	{
-	    Caarg arg = ca_laststate.def;
-
-	    if (arg) {
-		setsparam(args[1], ztrdup(arg->descr));
-		setsparam(args[2], ztrdup(arg->action));
-
-		if (ca_laststate.doff > 0)
-		    ignore_prefix(ca_laststate.doff);
-		if (arg->type == CAA_RARGS)
-		    restrict_range(ca_laststate.optbeg,
-				   arrlen(compwords) - 1);
-		else if (arg->type == CAA_RREST)
-		    restrict_range(ca_laststate.argbeg,
-				   arrlen(compwords) - 1);
-		return 0;
+	    LinkList descr, act, subc;
+	    Caarg arg;
+	    int ign = 0, ret = 1;
+
+	    descr = newlinklist();
+	    act = newlinklist();
+	    subc = newlinklist();
+
+	    while (lstate) {
+		arg = lstate->def;
+
+		if (arg) {
+		    ret = 0;
+		    if (!ign && lstate->doff > 0) {
+			ign = 1;
+			ignore_prefix(lstate->doff);
+		    }
+		    ca_set_data(descr, act, subc, arg->opt, arg,
+				(lstate->doff > 0));
+		}
+		lstate = lstate->snext;
 	    }
-	    return 1;
-	}
-    case 'C':
-	{
-	    Caarg arg = ca_laststate.def;
-
-	    if (arg) {
-		char buf[20];
-
-		if (arg->num > 0)
-		    sprintf(buf, "%d", arg->num);
-		else
-		    strcpy(buf, "rest");
-
-		setsparam(args[1], (arg->opt ? tricat(arg->opt, "-", buf) :
-				    tricat("argument-", buf, "")));
-		return 0;
+	    if (!ret) {
+		set_list_array(args[1], descr);
+		set_list_array(args[2], act);
+		set_list_array(args[3], subc);
 	    }
-	    return 1;
+	    return ret;
 	}
     case 'O':
-	if ((ca_laststate.opt || (ca_laststate.doff && ca_laststate.def) ||
-	     (ca_laststate.def &&
-	      (ca_laststate.def->type == CAA_OPT ||
-	       ca_laststate.def->type >= CAA_RARGS))) &&
-	    (!ca_laststate.def || ca_laststate.def->type < CAA_RARGS ||
-	     (ca_laststate.def->type == CAA_RARGS ?
-	      (ca_laststate.curpos == ca_laststate.argbeg + 1) :
-	      (compcurrent == 1)))) {
+        /* This returns the descriptions for the options in the arrays whose
+         * names are given as arguments.  The descriptions are strings in a
+         * form usable by _describe.  The return value says if there are any
+         * options to be completed. */
+	{
 	    LinkList next = newlinklist();
 	    LinkList direct = newlinklist();
 	    LinkList odirect = newlinklist();
 	    LinkList equal = newlinklist(), l;
+            LinkNode node;
 	    Caopt p;
 	    char *str;
+	    int ret = 1;
+
+	    for (; lstate; lstate = lstate->snext) {
+		if (lstate->actopts &&
+		    (lstate->opt || (lstate->doff && lstate->def) ||
+		     (lstate->def && lstate->def->opt &&
+		      (lstate->def->type == CAA_OPT ||
+		       (lstate->def->type >= CAA_RARGS &&
+			lstate->def->num < 0)))) &&
+		    (!lstate->def || lstate->def->type < CAA_RARGS ||
+		     (lstate->def->type == CAA_RARGS ?
+		      (lstate->curpos == lstate->argbeg + 1) :
+		      (compcurrent == 1)))) {
+		    ret = 0;
+		    for (p = lstate->d->opts; p; p = p->next) {
+			if (p->active && !p->not) {
+			    switch (p->type) {
+			    case CAO_NEXT:    l = next;    break;
+			    case CAO_DIRECT:  l = direct;  break;
+			    case CAO_ODIRECT: l = odirect; break;
+			    default:          l = equal;   break;
+			    }
+			    if (p->descr) {
+				char *n = bslashcolon(p->name);
+				int len = strlen(n) + strlen(p->descr) + 2;
+
+				str = (char *) zhalloc(len);
+				strcpy(str, n);
+				strcat(str, ":");
+				strcat(str, p->descr);
+			    } else
+				str = bslashcolon(p->name);
 
-	    for (p = ca_laststate.d->opts; p; p = p->next) {
-		if (p->active) {
-		    switch (p->type) {
-		    case CAO_NEXT:    l = next;    break;
-		    case CAO_DIRECT:  l = direct;  break;
-		    case CAO_ODIRECT: l = odirect; break;
-		    default:          l = equal;   break;
-		    }
-		    if (p->descr) {
-			char *n = bslashcolon(p->name);
-			int len = strlen(n) + strlen(p->descr) + 2;
+                            for (node = firstnode(l); node; incnode(node))
+                                if (!strcmp(str, (char *) getdata(node)))
+                                    break;
 
-			str = (char *) zhalloc(len);
-			strcpy(str, n);
-			strcat(str, ":");
-			strcat(str, p->descr);
-		    } else
-			str = bslashcolon(p->name);
-		    addlinknode(l, str);
+                            if (!node)
+                                addlinknode(l, str);
+			}
+		    }
 		}
 	    }
-	    set_list_array(args[1], next);
-	    set_list_array(args[2], direct);
-	    set_list_array(args[3], odirect);
-	    set_list_array(args[4], equal);
+	    if (!ret) {
+		set_list_array(args[1], next);
+		set_list_array(args[2], direct);
+		set_list_array(args[3], odirect);
+		set_list_array(args[4], equal);
 
-	    return 0;
+		return 0;
+	    }
+	    return (ca_laststate.singles ? 2 : 1);
 	}
-	return 1;
     case 'L':
+        /* This tests if the beginning of the current word matches an option.
+         * It is for cases like `./configure --pre=/<TAB>' which should
+         * complete to `--prefix=/...'.  The options name isn't fully typed
+         * and _arguments finds out that there is no option `--pre' and that
+         * it should complete some argument to an option.  It then uses -L
+         * to find the option the argument is for. */
 	{
-	    Caopt opt = ca_get_opt(ca_laststate.d, args[1], 1, NULL);
+	    LinkList descr, act, subc;
+	    Caopt opt;
+	    int ret = 1;
 
-	    if (opt && opt->args) {
-		setsparam(args[2], ztrdup(opt->args->descr));
-		setsparam(args[3], ztrdup(opt->args->action));
+	    descr = newlinklist();
+	    act = newlinklist();
+	    subc = newlinklist();
 
-		if (args[4])
-		    setsparam(args[4], tricat(opt->name, "-1", ""));
+	    while (lstate) {
+		opt = ca_get_opt(lstate->d, args[1], 1, NULL);
 
-		return 0;
+		if (opt && opt->args) {
+		    ret = 0;
+		    ca_set_data(descr, act, subc, opt->name, opt->args, 1);
+		}
+		lstate = lstate->snext;
 	    }
-	    return 1;
+	    if (!ret) {
+		set_list_array(args[2], descr);
+		set_list_array(args[3], act);
+		set_list_array(args[4], subc);
+	    }
+	    return ret;
 	}
     case 's':
-	if (ca_laststate.d->single && ca_laststate.singles &&
-	    ca_laststate.opt) {
-	    setsparam(args[1],
-		      ztrdup(ca_laststate.ddef ?
-			     (ca_laststate.ddef->type == CAO_DIRECT ?
-			      "direct" :
-			      (ca_laststate.ddef->type == CAO_EQUAL ?
-			       "equal" : "next")) : ""));
-	    return 0;
-	}
+        /* This returns zero if we are completing single letter options.
+         * It also uses its argument as the name of a parameter and sets
+         * that to a string describing the argument behaviour of the last
+         * option in the current word so that we can get the auto-suffix
+         * right. */
+	for (; lstate; lstate = lstate->snext)
+	    if (lstate->d->single && lstate->singles &&
+		lstate->actopts
+#if 0
+                /* let's try without, for the -W option of _arguments */
+                && lstate->opt
+#endif
+                ) {
+		setsparam(args[1],
+			  ztrdup((lstate->ddef && lstate->dopt) ?
+				 (lstate->dopt->type == CAO_DIRECT ?
+				  "direct" :
+				  ((lstate->dopt->type == CAO_OEQUAL ||
+				    lstate->dopt->type == CAO_EQUAL) ?
+				   "equal" : "next")) : ""));
+		return 0;
+	    }
 	return 1;
     case 'M':
+        /* This returns the match specs defined for the set of specs we are
+         * using.  Returned, as usual in a parameter whose name is given as
+         * the argument. */
 	setsparam(args[1], ztrdup(ca_laststate.d->match));
 	return 0;
     case 'a':
-	return !(ca_laststate.d->args || ca_laststate.d->rest);
+        /* This just sets the return value.  To zero if there would be or
+         * were any normal arguments to be completed.  Used to decide if
+         * _arguments should say `no arguments' or `no more arguments'. */
+	for (; lstate; lstate = lstate->snext)
+	    if (lstate->d->args || lstate->d->rest)
+		return 0;
+	return 1;
     case 'W':
+        /* This gets two parameter names as arguments.  The first is set to
+         * the current word sans any option prefixes handled by comparguments.
+         * The second parameter is set to an array containing the options on
+         * the line and their arguments.  I.e. the stuff _arguments returns
+         * to its caller in the `line' and `opt_args' parameters. */
 	{
+	    Castate s;
 	    char **ret, **p;
 	    LinkNode n;
 	    LinkList *a;
 	    Caopt o;
 	    int num;
 
-	    ret = p = zalloc((countlinknodes(ca_laststate.args) + 1) *
-			     sizeof(char *));
+	    for (num = 0, s = lstate; s; s = s->snext)
+		num += countlinknodes(s->args);
 
-	    for (n = firstnode(ca_laststate.args); n; incnode(n))
-		*p++ = ztrdup((char *) getdata(n));
+	    ret = p = zalloc((num + 1) * sizeof(char *));
+
+	    for (s = lstate; s; s = s->snext)
+		for (n = firstnode(s->args); n; incnode(n))
+		    *p++ = ztrdup((char *) getdata(n));
 	    *p = NULL;
 
 	    setaparam(args[1], ret);
 
-	    for (num = 0, o = ca_laststate.d->opts, a = ca_laststate.oargs; o;
-		 o = o->next, a++)
-		if (*a)
-		    num += 2;
+	    for (num = 0, s = lstate; s; s = s->snext)
+		for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
+		    if (*a)
+			num += 2;
 
 	    ret = p = zalloc((num + 1) * sizeof(char *));
 
-	    for (o = ca_laststate.d->opts, a = ca_laststate.oargs; o;
-		 o = o->next, a++) {
-		if (*a) {
-		    *p++ = ztrdup(o->name);
-		    *p++ = ca_colonlist(*a);
-		}
-	    }
+	    for (s = lstate; s; s = s->snext)
+		for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
+		    if (*a) {
+			*p++ = (o->set ? tricat(o->set, o->name, "") :
+				ztrdup(o->name));
+			*p++ = ca_colonlist(*a);
+		    }
 	    *p = NULL;
 
 	    sethparam(args[2], ret);
@@ -1551,11 +2594,13 @@ struct cvdef {
     char *descr;		/* global description */
     int hassep;			/* multiple values allowed */
     char sep;			/* separator character */
+    char argsep;                /* argument separator */
     Cvdef next;			/* next in cache */
     Cvval vals;			/* value definitions */
     char **defs;		/* original strings */
     int ndefs;			/* number of ... */
     int lastt;			/* last time used */
+    int words;                  /* if to look at other words */
 };
 
 /* One value definition. */
@@ -1612,17 +2657,25 @@ parse_cvdef(char *nam, char **args)
     Cvdef ret;
     Cvval val, *valp;
     Caarg arg;
-    char **oargs = args, sep = '\0', *name, *descr, *p, *q, **xor, c;
-    int xnum, multi, vtype, hassep = 0;
-
-    if (args[0][0] == '-' && args[0][1] == 's' && !args[0][2]) {
-	if (args[1][0] && args[1][1]) {
-	    zwarnnam(nam, "invalid separator: %s", args[1], 0);
-	    return NULL;
-	}
-	hassep = 1;
-	sep = args[1][0];
-	args += 2;
+    char **oargs = args, sep = '\0', asep = '=', *name, *descr, *p, *q, **xor, c;
+    int xnum, multi, vtype, hassep = 0, words = 0;
+
+    while (args && args[0] && args[1] &&
+           args[0][0] == '-' &&
+           (args[0][1] == 's' || args[0][1] == 'S' || args[0][1] == 'w') &&
+           !args[0][2]) {
+
+        if (args[0][1] == 's') {
+            hassep = 1;
+            sep = args[1][0];
+            args += 2;
+        } else if (args[0][1] == 'S') {
+            asep = args[1][0];
+            args += 2;
+        } else {
+            words = 1;
+            args++;
+        }
     }
     if (!args[0] || !args[1]) {
 	zwarnnam(nam, "not enough arguments", NULL, 0);
@@ -1634,13 +2687,16 @@ parse_cvdef(char *nam, char **args)
     ret->descr = ztrdup(descr);
     ret->hassep = hassep;
     ret->sep = sep;
+    ret->argsep = asep;
     ret->next = NULL;
     ret->vals = NULL;
     ret->defs = zarrdup(oargs);
     ret->ndefs = arrlen(oargs);
     ret->lastt = time(0);
+    ret->words = words;
 
     for (valp = &(ret->vals); *args; args++) {
+	int bs = 0;
 	p = dupstring(*args);
 	xnum = 0;
 
@@ -1688,9 +2744,9 @@ parse_cvdef(char *nam, char **args)
 
 	for (name = p; *p && *p != ':' && *p != '['; p++)
 	    if (*p == '\\' && p[1])
-		p++;
+		p++, bs = 1;
 
-	if (hassep && !sep && name + 1 != p) {
+	if (hassep && !sep && name + bs + 1 < p) {
 	    freecvdef(ret);
 	    zwarnnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
 	    return NULL;
@@ -1739,7 +2795,7 @@ parse_cvdef(char *nam, char **args)
 		vtype = CVV_OPT;
 	    } else
 		vtype = CVV_ARG;
-	    arg = parse_caarg(0, 0, 0, name, &p);
+	    arg = parse_caarg(0, 0, 0, 0, name, &p, NULL);
 	} else {
 	    vtype = CVV_NOARG;
 	    arg = NULL;
@@ -1795,6 +2851,23 @@ cv_get_val(Cvdef d, char *name)
     return NULL;
 }
 
+static Cvval
+cv_quote_get_val(Cvdef d, char *name)
+{
+    int ne;
+
+    /* remove quotes */
+    name = dupstring(name);
+    ne = noerrs;
+    noerrs = 2;
+    parse_subst_string(name);
+    noerrs = ne;
+    remnulargs(name);
+    untokenize(name);
+
+    return cv_get_val(d, name);
+}
+
 /* Handle a xor list. */
 
 static void
@@ -1821,20 +2894,122 @@ struct cvstate {
 static struct cvstate cv_laststate;
 static int cv_parsed = 0, cv_alloced = 0;
 
+/* Get the next value in the string.  Return it's definition and update the
+ * sp pointer to point to the end of the value (plus argument, if any).
+ * If there is no next value, the string pointer is set to null.  In any
+ * case ap will point to the beginning of the argument or will be a null
+ * pointer if there is no argument.
+ */
+
+static Cvval
+cv_next(Cvdef d, char **sp, char **ap)
+{
+    Cvval r = NULL;
+    char *s = *sp;
+
+    if (!*s) {
+        *sp = *ap = NULL;
+
+        return NULL;
+    }
+    if ((d->hassep && !d->sep) || !d->argsep) {
+        char sav, ec, *v = s, *os;
+
+        ec = ((d->hassep && d->sep) ? d->sep : d->argsep);
+
+        do {
+            sav = *++s;
+            *s = '\0';
+            if ((r = cv_quote_get_val(d, v))) {
+                *s = sav;
+
+                break;
+            }
+            *s = sav;
+        } while (*s && *s != ec);
+
+        os = s;
+
+        if (d->hassep && d->sep) {
+            if ((s = strchr(s, d->sep)))
+                *sp = s + 1;
+            else
+                *sp = NULL;
+        } else
+            *sp = s;
+        if (d->argsep && *os == d->argsep) {
+            *ap = os + 1;
+            *sp = NULL;
+        } else if (r && r->type != CVV_NOARG)
+            *ap = os;
+        else
+            *ap = NULL;
+
+        return r;
+
+    } else if (d->hassep) {
+        char *ns = strchr(s, d->sep), *as = 0, *sap, sav = 0;
+        int skip = 0;
+
+        if (d->argsep && (as = strchr(s, d->argsep)) && (!ns || as <= ns)) {
+            *ap = as + 1;
+            ns = strchr(as + 1, d->sep);
+            skip = 1;
+            sap = as;
+        } else {
+            *ap = NULL;
+            sap = ns;
+        }
+        if (sap) {
+            sav = *sap;
+            *sap = '\0';
+        }
+        if ((!(r = cv_quote_get_val(d, s)) || r->type == CVV_NOARG) && skip)
+            ns = as;
+
+        if (sap)
+            *sap = sav;
+
+        *sp = ((!ns || (ns == as && r && r->type != CVV_NOARG)) ? NULL : ns + 1);
+
+        return r;
+    } else {
+        char *as = strchr(s, d->argsep), *sap, sav = 0;
+
+        *sp = NULL;
+
+        if (as) {
+            *ap = as + 1;
+            sap = as;
+            sav = *as;
+            *sap = '\0';
+        } else
+            *ap = sap = NULL;
+
+        r = cv_quote_get_val(d, s);
+
+        if (sap)
+            *sap = sav;
+
+        return r;
+    }
+}
+
 /* Parse the current word. */
 
 static void
 cv_parse_word(Cvdef d)
 {
-    Cvval ptr;
+    Cvval val;
     struct cvstate state;
-    char *str, *eq;
+    char *str, *arg = NULL, *pign = compprefix;
+    int nosfx = 0;
 
     if (cv_alloced)
 	freelinklist(cv_laststate.vals, freestr);
 
-    for (ptr = d->vals; ptr; ptr = ptr->next)
-	ptr->active = 1;
+    for (val = d->vals; val; val = val->next)
+	val->active = 1;
 
     state.d = d;
     state.def = NULL;
@@ -1843,108 +3018,126 @@ cv_parse_word(Cvdef d)
 
     cv_alloced = 1;
 
-    if (d->hassep) {
-	if (d->sep) {
-	    char *end;
-	    int heq;
-
-	    for (str = compprefix, end = strchr(str, d->sep); end;) {
-		*end = '\0';
-
-		if ((heq = !!(eq = strchr(str, '='))))
-		    *eq++ = '\0';
-		else
-		    eq = "";
-
-		if ((ptr = cv_get_val(d, str))) {
-		    zaddlinknode(state.vals, ztrdup(str));
-		    zaddlinknode(state.vals, ztrdup(eq));
-
-		    cv_inactive(d, ptr->xor);
-		}
-		if (heq)
-		    eq[-1] = '=';
-
-		*end = d->sep;
-		str = end + 1;
-		end = strchr(str, d->sep);
-	    }
-	    ignore_prefix(str - compprefix);
-
-	    if ((str = strchr(compsuffix, d->sep))) {
-		char *beg = str;
-
-		for (str++; str; str = end) {
-		    if ((end = strchr(str, d->sep)))
-			*end = '\0';
-
-		    if ((heq = !!(eq = strchr(str, '='))))
-			*eq++ = '\0';
-		    else
-			eq = "";
-
-		    if ((ptr = cv_get_val(d, str))) {
-			zaddlinknode(state.vals, ztrdup(str));
-			zaddlinknode(state.vals, ztrdup(eq));
-
-			cv_inactive(d, ptr->xor);
-		    }
-		    if (heq)
-			eq[-1] = '=';
-		    if (end)
-			*end++ = d->sep;
-		}
-		ignore_suffix(strlen(beg));
-	    }
-	} else {
-	    char tmp[2];
-
-	    tmp[1] = '\0';
-
-	    for (str = compprefix; *str; str++) {
-		tmp[0] = *str;
-		if ((ptr = cv_get_val(d, tmp))) {
-		    zaddlinknode(state.vals, ztrdup(tmp));
-		    zaddlinknode(state.vals, ztrdup(""));
-
-		    cv_inactive(d, ptr->xor);
-		}
-	    }
-	    for (str = compsuffix; *str; str++) {
-		tmp[0] = *str;
-		if ((ptr = cv_get_val(d, tmp))) {
-		    zaddlinknode(state.vals, ztrdup(tmp));
-		    zaddlinknode(state.vals, ztrdup(""));
-
-		    cv_inactive(d, ptr->xor);
-		}
-	    }
-	    ignore_prefix(strlen(compprefix));
-	    ignore_suffix(strlen(compsuffix));
-	}
+    if (d->words && compwords[0]) {
+        int i;
+
+        for (i = 1; compwords[i]; i++)
+            if (i != compcurrent - 1)
+                for (str = compwords[i]; str && *str; ) {
+                    if ((val = cv_next(d, &str, &arg))) {
+                        zaddlinknode(state.vals, ztrdup(val->name));
+                        if (arg) {
+                            char sav = '\0';
+
+                            if (str) {
+                                sav = str[-1];
+                                str[-1] = '\0';
+                            }
+                            zaddlinknode(state.vals, ztrdup(arg));
+                            if (str)
+                                str[-1] = sav;
+                        } else
+                            zaddlinknode(state.vals, ztrdup(""));
+
+                        if (i + 1 < compcurrent)
+                            cv_inactive(d, val->xor);
+                    }
+                }
+
+        val = NULL;
+        arg = NULL;
     }
-    str = tricat(compprefix, compsuffix, "");
-    zsfree(compprefix);
-    zsfree(compsuffix);
-    compprefix = str;
-    compsuffix = ztrdup("");
-
-    if ((eq = strchr(str, '='))) {
-	*eq++ = '\0';
-
-	if ((ptr = cv_get_val(d, str)) && ptr->type != CVV_NOARG) {
-	    eq[-1] = '=';
-	    ignore_prefix(eq - str);
-	    state.def = ptr->arg;
-	    state.val = ptr;
-	} else
-	    eq[-1] = '=';
+    for (str = compprefix; str && *str; ) {
+        if ((val = cv_next(d, &str, &arg))) {
+            zaddlinknode(state.vals, ztrdup(val->name));
+            if (arg) {
+                if (str) {
+                    char sav = str[-1];
+
+                    str[-1] = '\0';
+                    zaddlinknode(state.vals, ztrdup(arg));
+                    str[-1] = sav;
+                } else {
+                    zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
+                    nosfx = 1;
+                }
+            } else
+                zaddlinknode(state.vals, ztrdup(""));
+
+            cv_inactive(d, val->xor);
+
+            if (str)
+                pign = str;
+            else
+                val->active = 1;
+        }
     }
+    state.val = val;
+    if (val && arg && !str)
+        state.def = val->arg;
+
+    if (!nosfx && d->hassep) {
+        int ign = 0;
+        char *more = NULL;
+
+        ignore_prefix(pign - compprefix);
+
+        if (!d->sep && (!val || val->type == CVV_NOARG)) {
+            ign = strlen(compsuffix);
+            more = compsuffix;
+        } else {
+            if (d->sep) {
+                char *ns = strchr(compsuffix, d->sep), *as;
+
+                if (d->argsep && (as = strchr(compsuffix, d->argsep)) &&
+                    (!ns || as <= ns)) {
+                    ign = strlen(as);
+                } else
+                    ign = (ns ? strlen(ns) : 0);
+
+                more = (ns ? ns + 1 : NULL);
+            } else if (d->argsep) {
+                char *as;
+
+                if ((as = strchr(compsuffix, d->argsep)))
+                    ign = strlen(as);
+            }
+        }
+        more = dupstring(more);
+
+        if (ign)
+            ignore_suffix(ign);
+
+        while (more && *more) {
+            if ((val = cv_next(d, &more, &arg))) {
+                zaddlinknode(state.vals, ztrdup(val->name));
+                if (arg) {
+                    if (more) {
+                        char sav = more[-1];
+
+                        more[-1] = '\0';
+                        zaddlinknode(state.vals, ztrdup(arg));
+                        more[-1] = sav;
+                    } else {
+                        zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
+                        nosfx = 1;
+                    }
+                } else
+                    zaddlinknode(state.vals, ztrdup(""));
+
+                cv_inactive(d, val->xor);
+            }
+        }
+    } else if (arg)
+        ignore_prefix(arg - compprefix);
+    else
+        ignore_prefix(pign - compprefix);
+
     memcpy(&cv_laststate, &state, sizeof(state));
 }
 
 static int
-bin_compvalues(char *nam, char **args, char *ops, int func)
+bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int min, max, n;
 
@@ -1966,6 +3159,7 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     case 'C': min = 1; max =  1; break;
     case 'V': min = 3; max =  3; break;
     case 's': min = 1; max =  1; break;
+    case 'S': min = 1; max =  1; break;
     case 'd': min = 1; max =  1; break;
     case 'L': min = 3; max =  4; break;
     case 'v': min = 1; max =  1; break;
@@ -1983,6 +3177,8 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     }
     switch (args[0][1]) {
     case 'i':
+        /* This initialises the internal data structures.  The arguments are
+         * just the arguments that were given to _values itself. */
 	{
 	    Cvdef def = get_cvdef(nam, args + 1);
 	    int cvp = cv_parsed;
@@ -2001,6 +3197,9 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	return 1;
 
     case 'D':
+        /* This returns the description and action to use if we are at
+         * a place where some action has to be used at all.  In that case
+         * zero is returned and non-zero otherwise. */
 	{
 	    Caarg arg = cv_laststate.def;
 
@@ -2013,6 +3212,8 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    return 1;
 	}
     case 'C':
+        /* This returns the sub-context (i.e.: the tag) to use when executing
+         * an action. */
 	{
 	    Caarg arg = cv_laststate.def;
 
@@ -2024,6 +3225,10 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    return 1;
 	}
     case 'V':
+        /* This is what -O is for comparguments: it returns (in three arrays)
+         * the values for values without arguments, with arguments and with
+         * optional arguments (so that we can get the auto-suffixes right).
+         * As for comparguments, the strings returned are usable for _describe. */
 	{
 	    LinkList noarg = newlinklist();
 	    LinkList arg = newlinklist();
@@ -2057,6 +3262,8 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    return 0;
 	}
     case 's':
+        /* This returns the value separator, if any, and sets the return
+         * value to say if there is such a separator. */
 	if (cv_laststate.d->hassep) {
 	    char tmp[2];
 
@@ -2067,10 +3274,26 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    return 0;
 	}
 	return 1;
+    case 'S':
+        /* Like -s, but for the separator between values and their arguments. */
+	{
+	    char tmp[2];
+
+	    tmp[0] = cv_laststate.d->argsep;
+	    tmp[1] = '\0';
+	    setsparam(args[1], ztrdup(tmp));
+	}
+	return 0;
     case 'd':
+        /* This returns the description string (first argument to _values)
+         * which is passed down to _describe. */
 	setsparam(args[1], ztrdup(cv_laststate.d->descr));
 	return 0;
     case 'L':
+        /* Almost the same as for comparguments.  This gets a value name
+         * and returns the description and action of its first argument, if
+         * any.  The rest (prefix matching) is in _values.  Return non-zero
+         * if there is no such option. */
 	{
 	    Cvval val = cv_get_val(cv_laststate.d, args[1]);
 
@@ -2086,6 +3309,8 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    return 1;
 	}
     case 'v':
+        /* Again, as for comparguments.  This returns the values and their
+         * arguments as an array which will be stored in val_args in _values. */
 	if (cv_laststate.vals) {
 	    char **ret, **p;
 	    LinkNode n;
@@ -2106,13 +3331,35 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     return 1;
 }
 
+static char *
+comp_quote(char *str, int prefix)
+{
+    int x;
+    char *ret;
+
+    if ((x = (prefix && *str == '=')))
+	*str = 'x';
+
+    ret = bslashquote(str, NULL, (*compqstack == '\'' ? 1 :
+				  (*compqstack == '"' ? 2 : 0)));
+
+    if (x)
+	*str = *ret = '=';
+
+    return ret;
+}
+
 static int
-bin_compquote(char *nam, char **args, char *ops, int func)
+bin_compquote(char *nam, char **args, Options ops, UNUSED(int func))
 {
     char *name;
     struct value vbuf;
     Value v;
 
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
     /* Anything to do? */
 
     if (!compqstack || !*compqstack)
@@ -2122,31 +3369,22 @@ bin_compquote(char *nam, char **args, char *ops, int func)
 
     while ((name = *args++)) {
 	name = dupstring(name);
+	queue_signals();
 	if ((v = getvalue(&vbuf, &name, 0))) {
 	    switch (PM_TYPE(v->pm->flags)) {
 	    case PM_SCALAR:
-		{
-		    char *val = getstrvalue(v);
-
-		    val = bslashquote(val, NULL,
-				      (*compqstack == '\'' ? 1 :
-				       (*compqstack == '"' ? 2 : 0)));
-
-		    setstrvalue(v, ztrdup(val));
-		}
+		setstrvalue(v, ztrdup(comp_quote(getstrvalue(v), 
+						 OPT_ISSET(ops,'p'))));
 		break;
 	    case PM_ARRAY:
 		{
-		    char **val = v->pm->gets.afn(v->pm);
+		    char **val = v->pm->gsu.a->getfn(v->pm);
 		    char **new = (char **) zalloc((arrlen(val) + 1) *
 						  sizeof(char *));
 		    char **p = new;
 
 		    for (; *val; val++, p++)
-			*p = ztrdup(bslashquote(*val, NULL,
-						(*compqstack == '\'' ? 1 :
-						 (*compqstack == '"' ? 2 :
-						  0))));
+			*p = ztrdup(comp_quote(*val, OPT_ISSET(ops,'p')));
 		    *p = NULL;
 
 		    setarrvalue(v, new);
@@ -2157,6 +3395,7 @@ bin_compquote(char *nam, char **args, char *ops, int func)
 	    }
 	} else
 	    zwarnnam(nam, "unknown parameter: %s", args[-1], 0);
+	unqueue_signals();
     }
     return 0;
 }
@@ -2242,6 +3481,7 @@ settags(int level, char **tags)
 
 /* Check if an array contains a string. */
 
+/**/
 static int
 arrcontains(char **a, char *s, int colon)
 {
@@ -2261,7 +3501,7 @@ arrcontains(char **a, char *s, int colon)
 }
 
 static int
-bin_comptags(char *nam, char **args, char *ops, int func)
+bin_comptags(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int min, max, n, level;
 
@@ -2290,7 +3530,7 @@ bin_comptags(char *nam, char **args, char *ops, int func)
     case 'N': min = 0; max =  0; break;
     case 'R': min = 1; max =  1; break;
     case 'S': min = 1; max =  1; break;
-    case 'A': min = 2; max =  2; break;
+    case 'A': min = 2; max =  3; break;
     default:
 	zwarnnam(nam, "invalid option: %s", args[0], 0);
 	return 1;
@@ -2363,7 +3603,15 @@ bin_comptags(char *nam, char **args, char *ops, int func)
 		    return 1;
 		}
 		s->ptr = q + 1;
-		setsparam(args[2], ztrdup(v));
+		setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
+		if (args[3]) {
+		    char *r = dupstring(*q), *p;
+
+		    for (p = r + (v - *q); *p && *p != ':'; p++);
+		    *p = '\0';
+
+		    setsparam(args[3], ztrdup(r));
+		}
 		return 0;
 	    }
 	    return 1;
@@ -2383,7 +3631,7 @@ bin_comptags(char *nam, char **args, char *ops, int func)
 }
 
 static int
-bin_comptry(char *nam, char **args, char *ops, int func)
+bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     if (incompfunc != 1) {
 	zwarnnam(nam, "can only be called from completion function", NULL, 0);
@@ -2394,84 +3642,815 @@ bin_comptry(char *nam, char **args, char *ops, int func)
 	return 1;
     }
     if (*args) {
-	char **p, **q, **all;
+	if (!strcmp(*args, "-m")) {
+	    char *s, *p, *q, *c, **all = comptags[lasttaglevel]->all;
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    int num = 0;
+	    Ctset set;
+
+	    while ((s = *++args)) {
+		while (*s) {
+		    while (*s && iblank(*s))
+			s++;
+		    for (p = q = s, c = NULL; *s && !inblank(*s); s++) {
+			if (!c && *s == ':')
+			    c = p;
+			if (*s == '\\' && s[1])
+			    s++;
+			*p++ = *s;
+		    }
+		    if (*s)
+			s++;
+		    *p = '\0';
+		    if (*q) {
+			char *qq, *qqq;
+
+			if (c)
+			    *c = '\0';
+
+			qqq = qq = dupstring(q);
+			while (*qqq) {
+			    if (qqq == qq || qqq[-1] != '\\') {
+				if (*qqq == '{')
+				    *qqq = Inbrace;
+				else if (*qqq == '}')
+				    *qqq = Outbrace;
+				else if (*qqq == ',')
+				    *qqq = Comma;
+			    }
+			    qqq++;
+			}
+			tokenize(qq);
+			if (haswilds(qq) || hasbraces(qq)) {
+			    Patprog prog;
+			    LinkNode bnode, node;
+			    LinkList blist = newlinklist();
+
+			    addlinknode(blist, qq);
+			    for (bnode = firstnode(blist); bnode; incnode(bnode))
+				while (hasbraces(getdata(bnode)))
+				    xpandbraces(blist, &bnode);
+
+			    for (bnode = firstnode(blist); bnode; incnode(bnode)) {
+				qq = (char *) getdata(bnode);
+				if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
+				    char **a, *n;
+				    int l = (c ? strlen(c + 1) + 2 : 1), al;
+
+				    for (a = all; *a; a++) {
+					for (node = firstnode(list); node;
+					     incnode(node)) {
+					    char *as, *ls;
+
+					    for (as = *a, ls = (char *) getdata(node);
+						 *as && *ls && *ls != ':'; as++, ls++)
+						if (*as != *ls)
+						    break;
+					    if (!*as && (!*ls || *ls == ':'))
+						break;
+					}
+					if (node)
+					    continue;
+					if (pattry(prog, *a)) {
+					    n = (char *) zhalloc((al = strlen(*a)) + l);
+					    strcpy(n, *a);
+					    if (c) {
+						n[al] = ':';
+						strcpy(n + al + 1, c + 1);
+					    }
+					    addlinknode(list, n);
+					    num++;
+					}
+				    }
+				}
+			    }
+			} else if (arrcontains(all, q, 0)) {
+			    for (set = comptags[lasttaglevel]->sets; set;
+				 set = set->next)
+				if (arrcontains(set->tags, q, 0))
+				    break;
+			    if (!set) {
+				addlinknode(list, q);
+				num++;
+			    }
+			}
+			if (c)
+			    *c = ':';
+		    }
+		}
+		if (num) {
+		    char **a;
+		    Ctset l;
 
-	args = arrdup(args);
+		    set = (Ctset) zalloc(sizeof(*set));
 
-	for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
-	    if (arrcontains(all, *p, 1)) {
-		Ctset s;
+		    a = set->tags = (char **) zalloc((num + 1) * sizeof(char *));
+		    for (node = firstnode(list); node; incnode(node))
+			*a++ = ztrdup((char *) getdata(node));
 
-		for (s = comptags[lasttaglevel]->sets; s; s = s->next)
-		    if (arrcontains(s->tags, *p, 0))
-			break;
+		    *a = NULL;
+		    set->next = NULL;
+		    set->ptr = NULL;
+		    set->tag = NULL;
+
+		    if ((l = comptags[lasttaglevel]->sets)) {
+			while (l->next)
+			    l = l->next;
 
-		if (!s)
-		    *q++ = *p;
+			l->next = set;
+		    } else
+			comptags[lasttaglevel]->sets = set;
+		}
 	    }
-	*q = NULL;
+	} else {
+	    char **p, **q, **all;
+	    int sep = 0;
 
-	if (*args) {
-	    Ctset s = (Ctset) zalloc(sizeof(*s)), l;
+	    if ((sep = !strcmp(*args, "-s")))
+		args++;
 
-	    s->tags = zarrdup(args);
-	    s->next = NULL;
-	    s->ptr = NULL;
-	    s->tag = NULL;
+	    for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
+		if (arrcontains(all, *p, 1)) {
+		    Ctset s;
 
-	    if ((l = comptags[lasttaglevel]->sets)) {
-		while (l->next)
-		    l = l->next;
+		    for (s = comptags[lasttaglevel]->sets; s; s = s->next)
+			if (arrcontains(s->tags, *p, 0))
+			    break;
 
-		l->next = s;
-	    } else
-		comptags[lasttaglevel]->sets = s;
+		    if (!s)
+			*q++ = *p;
+		}
+	    *q = NULL;
+
+	    if (*args) {
+		char *dummy[2];
+
+		do {
+		    Ctset s = (Ctset) zalloc(sizeof(*s)), l;
+
+		    if (sep) {
+			dummy[0] = *args++;
+			dummy[1] = NULL;
+			s->tags = zarrdup(dummy);
+		    } else
+			s->tags = zarrdup(args);
+		    s->next = NULL;
+		    s->ptr = NULL;
+		    s->tag = NULL;
+
+		    if ((l = comptags[lasttaglevel]->sets)) {
+			while (l->next)
+			    l = l->next;
+
+			l->next = s;
+		    } else
+			comptags[lasttaglevel]->sets = s;
+		} while (sep && *args);
+	    }
 	}
     }
     return 0;
 }
 
+#define PATH_MAX2 (PATH_MAX * 2)
+
+static LinkList
+cfp_test_exact(LinkList names, char **accept, char *skipped)
+{
+    char buf[PATH_MAX2 + 1], *suf, *p;
+    int l, sl, found = 0;
+    struct stat st;
+    LinkNode node;
+    LinkList ret = newlinklist(), alist = NULL;
+
+    if ((!(compprefix && *compprefix) && !(compsuffix && *compsuffix)) ||
+	(!accept || !*accept ||
+	 ((!strcmp(*accept, "false") || !strcmp(*accept, "no") ||
+	   !strcmp(*accept, "off") || !strcmp(*accept, "0")) && !accept[1])))
+	return NULL;
+
+    if (accept[1] ||
+	(strcmp(*accept, "true") && strcmp(*accept, "yes") &&
+	 strcmp(*accept, "on") && strcmp(*accept, "1"))) {
+	Patprog prog;
+
+	alist = newlinklist();
+
+	for (; (p = *accept); accept++) {
+	    if (*p == '*' && !p[1]) {
+		alist = NULL;
+		break;
+	    }
+	    tokenize(p = dupstring(p));
+	    if ((prog = patcompile(p, 0, NULL)))
+		addlinknode(alist, prog);
+	}
+    }
+    sl = strlen(skipped) + (compprefix ? strlen(compprefix) : 0) +
+	(compsuffix ? strlen(compsuffix) : 0);
+
+    if (sl > PATH_MAX2)
+	return NULL;
+
+    suf = dyncat(skipped, rembslash(dyncat(compprefix, compsuffix)));
+
+    for (node = firstnode(names); node; incnode(node)) {
+	l = strlen(p = (char *) getdata(node));
+	if (l + sl < PATH_MAX2) {
+	    strcpy(buf, p);
+	    strcpy(buf + l, suf);
+
+	    if (!ztat(buf, &st, 0)) {
+		if (alist) {
+		    LinkNode anode;
+
+		    for (anode = firstnode(alist); anode; incnode(anode))
+			if (pattry((Patprog) getdata(anode), buf))
+			    break;
+
+		    if (!anode)
+			continue;
+		}
+		found = 1;
+		addlinknode(ret, dupstring(buf));
+	    }
+	}
+    }
+    return (found ? ret : NULL);
+}
+
 static char *
-fmtstr(char *str, char c, char *repl)
+cfp_matcher_pats(char *matcher, char *add)
 {
-    int len, num, rlen;
-    char *s, *ret, *rp;
+    Cmatcher m = parse_cmatcher(NULL, matcher);
+
+    if (m && m != pcm_err) {
+	char *tmp;
+	int al = strlen(add), tl;
+	VARARR(Cmatcher, ms, al);
+	Cmatcher *mp;
+	Cpattern stopp;
+	int stopl = 0;
+
+	memset(ms, 0, al * sizeof(Cmatcher));
+
+	for (; m && *add; m = m->next) {
+	    stopp = NULL;
+	    if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) {
+		if (m->llen == 1 && m->wlen == 1) {
+		    for (tmp = add, tl = al, mp = ms; tl; tl--, tmp++, mp++) {
+			if (pattern_match(m->line, tmp, NULL, NULL)) {
+			    if (*mp) {
+				*tmp = '\0';
+				al = tmp - add;
+				break;
+			    } else
+				*mp = m;
+			}
+		    }
+		} else {
+		    stopp = m->line;
+		    stopl = m->llen;
+		}
+	    } else if (m->flags & CMF_RIGHT) {
+		if (m->wlen < 0 && !m->llen && m->ralen == 1) {
+		    for (tmp = add, tl = al, mp = ms; tl; tl--, tmp++, mp++) {
+			if (pattern_match(m->right, tmp, NULL, NULL)) {
+			    if (*mp || (tmp == add && *tmp == '.')) {
+				*tmp = '\0';
+				al = tmp - add;
+				break;
+			    } else
+				*mp = m;
+			}
+		    }
+		} else if (m->llen) {
+		    stopp = m->line;
+		    stopl = m->llen;
+		} else {
+		    stopp = m->right;
+		    stopl = m->ralen;
+		}
+	    } else {
+		if (!m->lalen)
+		    return "";
 
-    len = strlen(str);
-    rlen = strlen(repl);
+		stopp = m->left;
+		stopl = m->lalen;
+	    }
+	    if (stopp)
+		for (tmp = add, tl = al; tl >= stopl; tl--, tmp++)
+		    if (pattern_match(stopp, tmp, NULL, NULL)) {
+			*tmp = '\0';
+			al = tmp - add;
+			break;
+		    }
+	}
+	if (*add) {
+	    char *ret = "", buf[259];
+
+	    for (mp = ms; *add; add++, mp++) {
+		if (!(m = *mp)) {
+		    buf[0] = *add;
+		    buf[1] = '\0';
+		} else if (m->flags & CMF_RIGHT) {
+		    buf[0] = '*';
+		    buf[1] = *add;
+		    buf[2] = '\0';
+		} else {
+		    unsigned char *t, c;
+		    char *p = buf;
+		    int i;
 
-    for (num = 0, s = str; *s; s++)
-	if (*s == '%' && s[1] == c)
-	    num++, s++;
+		    for (i = 256, t = m->word->tab; i--; t++)
+			if (*t)
+			    break;
+		    if (i) {
+			t = m->word->tab;
+			*p++ = '[';
+			if (m->line->equiv && m->word->equiv) {
+			    *p++ = *add;
+			    c = m->line->tab[STOUC(*add)];
+			    for (i = 0; i < 256; i++)
+				if (m->word->tab[i] == c) {
+				    *p++ = (char) i;
+				    break;
+				}
+			} else {
+			    if (*add == ']' || t[STOUC(']')])
+				*p++ = ']';
+			    for (i = 0; i < 256; i++, t++)
+				if (*t && ((char) i) != *add &&
+				    i != ']' && i != '-' &&
+				    i != '^' && i != '!')
+				    *p++ = (char) i;
+			    *p++ = *add;
+			    t = m->word->tab;
+			    if (*add != '^' && t[STOUC('^')])
+				*p++ = '^';
+			    if (*add != '!' && t[STOUC('!')])
+				*p++ = '!';
+			    if (*add != '-' && t[STOUC('-')])
+				*p++ = '-';
+			}
+			*p++ = ']';
+			*p = '\0';
+		    } else {
+			*p = '?';
+			p[1] = '\0';
+		    }
+		}
+		ret = dyncat(ret, buf);
+	    }
+	    return ret;
+	}
+    }
+    return add;
+}
 
-    ret = (char *) zhalloc((num * (rlen - 2)) + len + 1);
+static void
+cfp_opt_pats(char **pats, char *matcher)
+{
+    char *add, **p, *q, *t, *s;
 
-    for (s = str, rp = ret; *s; s++) {
-	if (*s == '%' && s[1] == c) {
-	    strcpy(rp, repl);
-	    rp += rlen;
-	    s++;
-	} else
-	    *rp++ = *s;
+    if (!compprefix || !*compprefix)
+	return;
+
+    if (comppatmatch && *comppatmatch) {
+	tokenize(t = rembslash(dyncat(compprefix, compsuffix)));
+	remnulargs(t);
+	if (haswilds(t))
+	    return;
+    }
+    add = (char *) zhalloc(strlen(compprefix) * 2 + 1);
+    for (s = compprefix, t = add; *s; s++) {
+	if (*s != '\\' || !s[1] || s[1] == '*' || s[1] == '?' ||
+	    s[1] == '<' || s[1] == '>' || s[1] == '(' || s[1] == ')' ||
+	    s[1] == '[' || s[1] == ']' || s[1] == '|' || s[1] == '#' ||
+	    s[1] == '^' || s[1] == '~' || s[1] == '=') {
+	    if ((s == compprefix || s[-1] != '\\') &&
+		(*s == '*' || *s == '?' || *s == '<' || *s == '>' ||
+		 *s == '(' || *s == ')' || *s == '[' || *s == ']' ||
+		 *s == '|' || *s == '#' || *s == '^' || *s == '~' ||
+		 *s == '='))
+		*t++ = '\\';
+	    *t++ = *s;
+	}
     }
-    *rp = '\0';
+    *t = '\0';
+    for (p = pats; *add && (q = *p); p++) {
+	if (*q) {
+	    q = dupstring(q);
+	    t = q + strlen(q) - 1;
+	    if (*t == ')') {
+		for (s = t--; t > q; t--)
+		    if (*t == ')' || *t == '|' || *t == '~' || *t == '(')
+			break;
+		if (t != q && *t == '(')
+		    *t = '\0';
+	    }
+	    for (; *q && *add; q++) {
+		if (*q == '\\' && q[1]) {
+		    for (s = add, q++; *s && *s != *q; s++);
+		    *s = '\0';
+		} else if (*q == '<') {
+		    for (s = add; *s && !idigit(*s); s++);
+		    *s = '\0';
+		} else if (*q == '[') {
+		    int not, first = 1;
+		    char *x = ++q;
+
+		    if ((not = (*x == '!' || *x == '^')))
+			x++;
+		    for (; *x && (first || *x != ']'); x++) {
+			if (x[1] == '-' && x[2]) {
+			    char c1 = *x, c2 = x[2];
+
+			    for (s = add; *s && (*x < c1 || *x > c2); s++);
+			    *s = '\0';
+			} else {
+			    for (s = add; *s && *s != *x; s++);
+			    *s = '\0';
+			}
+		    }
+		} else if (*q != '?' && *q != '*' && *q != '(' && *q != ')' &&
+			   *q != '|' && *q != '~' && *q != '#') {
+		    for (s = add; *s && *s != *q; s++);
+		    *s = '\0';
+		}
+	    }
+	}
+    }
+    if (*add) {
+	if (*matcher && !(add = cfp_matcher_pats(matcher, add)))
+	    return;
 
+	for (p = pats; *p; p++)
+	    if (**p == '*')
+		*p = dyncat(add, *p);
+    }
+}
+
+static LinkList
+cfp_bld_pats(UNUSED(int dirs), LinkList names, char *skipped, char **pats)
+{
+    LinkList ret = newlinklist();
+    LinkNode node;
+    int ol, sl = strlen(skipped), pl, dot;
+    char **p, *o, *str;
+
+    dot = (unset(GLOBDOTS) && compprefix && *compprefix == '.');
+    for (node = firstnode(names); node; incnode(node)) {
+	ol = strlen(o = (char *) getdata(node));
+	for (p = pats; *p; p++) {
+	    pl = strlen(*p);
+	    str = (char *) zhalloc(ol + sl + pl + 1);
+	    strcpy(str, o);
+	    strcpy(str + ol, skipped);
+	    strcpy(str + ol + sl, *p);
+	    addlinknode(ret, str);
+	    if (dot && **p != '.') {
+		str = (char *) zhalloc(ol + sl + pl + 2);
+		strcpy(str, o);
+		strcpy(str + ol, skipped);
+		str[ol + sl] = '.';
+		strcpy(str + ol + sl + 1, *p);
+		addlinknode(ret, str);
+	    }
+	}
+    }
     return ret;
 }
 
+static LinkList
+cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
+	      char *sdirs, char **fake)
+{
+    int add = 0;
+
+    if (*sdirs && (isset(GLOBDOTS) || (compprefix && *compprefix == '.'))) {
+	if (!strcmp(sdirs, "yes") || !strcmp(sdirs, "true") ||
+	    !strcmp(sdirs, "on") || !strcmp(sdirs, "1"))
+	    add = 2;
+	else if (!strcmp(sdirs, ".."))
+	    add = 1;
+    }
+    if (add) {
+	LinkNode node;
+	char *s1 = dyncat(skipped, "..");
+	char *s2 = (add == 2 ? dyncat(skipped, ".") : NULL), *m;
+
+	for (node = firstnode(orig); node; incnode(node)) {
+	    if ((m = (char *) getdata(node))) {
+		addlinknode(final, dyncat(m, s1));
+		if (s2)
+		    addlinknode(final, dyncat(m, s2));
+	    }
+	}
+    }
+    if (fake && *fake) {
+	LinkNode node;
+	char *m, *f, *p, *t, *a, c;
+	int sl = strlen(skipped) + 1;
+	struct stat st1, st2;
+
+	for (; (f = *fake); fake++) {
+	    f = dupstring(f);
+	    for (p = t = f; *p; p++) {
+		if (*p == ':')
+		    break;
+		else if (*p == '\\' && p[1])
+		    p++;
+		*t++ = *p;
+	    }
+	    if (*p) {
+		*t = *p++ = '\0';
+		if (!*p)
+		    continue;
+
+		for (node = firstnode(orig); node; incnode(node)) {
+		    if ((m = (char *) getdata(node)) &&
+			(!strcmp(f, m) ||
+			 (!stat(f, &st1) && !stat((*m ? m : "."), &st2) &&
+			  st1.st_dev == st2.st_dev &&
+			  st1.st_ino == st2.st_ino))) {
+			while (*p) {
+			    while (*p && inblank(*p))
+				p++;
+			    if (!*p)
+				break;
+			    for (f = t = p; *p; p++) {
+				if (inblank(*p))
+				    break;
+				else if (*p == '\\' && p[1])
+				    p++;
+				*t++ = *p;
+			    }
+			    c = *t;
+			    *t = '\0';
+			    a = (char *) zhalloc(strlen(m) + sl + strlen(f));
+			    strcpy(a, m);
+			    strcat(a, skipped);
+			    strcat(a, f);
+			    addlinknode(final, a);
+			    *t = c;
+			}
+		    }
+		}
+	    }
+	}
+    }
+    return final;
+}
+
+static LinkList
+cf_pats(int dirs, int noopt, LinkList names, char **accept, char *skipped,
+	char *matcher, char *sdirs, char **fake, char **pats)
+{
+    LinkList ret;
+    char *dpats[2];
+
+    if ((ret = cfp_test_exact(names, accept, skipped)))
+	return cfp_add_sdirs(ret, names, skipped, sdirs, fake);
+
+    if (dirs) {
+	dpats[0] = "*(-/)";
+	dpats[1] = NULL;
+	pats = dpats;
+    }
+    if (!noopt)
+	cfp_opt_pats(pats, matcher);
+
+    return cfp_add_sdirs(cfp_bld_pats(dirs, names, skipped, pats),
+			 names, skipped, sdirs, fake);
+}
+
+static void
+cf_ignore(char **names, LinkList ign, char *style, char *path)
+{
+    int pl = strlen(path), tpar, tpwd, found;
+    struct stat nst, est, st;
+    char *n, *c, *e;
+
+    tpar = !!strstr(style, "parent");
+    if ((tpwd = !!strstr(style, "pwd")) && stat(pwd, &est))
+	tpwd = 0;
+
+    if (!tpar && !tpwd)
+	return;
+
+    for (; (n = *names); names++) {
+	if (!ztat(n, &nst, 0) && S_ISDIR(nst.st_mode)) {
+	    if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
+		addlinknode(ign, bslashquote(n, NULL, 0));
+		continue;
+	    }
+	    if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
+		found = 0;
+		while ((e = strrchr(c, '/')) && e > c + pl) {
+		    *e = '\0';
+		    if (!ztat(c, &st, 0) &&
+			st.st_dev == nst.st_dev && st.st_ino == nst.st_ino) {
+			found = 1;
+			break;
+		    }
+		}
+		if (found || ((e = strrchr(c, '/')) && e > c + pl &&
+			      !ztat(c, &st, 0) && st.st_dev == nst.st_dev &&
+			      st.st_ino == nst.st_ino))
+		    addlinknode(ign, bslashquote(n, NULL, 0));
+	    }
+	}
+    }
+}
+
+static LinkList
+cf_remove_other(char **names, char *pre, int *amb)
+{
+    char *p;
+
+    if ((p = strchr(pre, '/'))) {
+	char **n;
+
+	*p = '\0';
+	pre = dyncat(pre, "/");
+	*p = '/';
+
+	for (n = names; *n; n++)
+	    if (strpfx(pre, *n))
+		break;
+
+	if (*n) {
+	    LinkList ret = newlinklist();
+
+	    for (; *names; names++)
+		if (strpfx(pre, *names))
+		    addlinknode(ret, dupstring(*names));
+
+	    *amb = 0;
+
+	    return ret;
+	} else {
+	    if (!(p = *names++))
+		*amb = 0;
+	    else {
+		char *q;
+
+		if ((q = strchr((p = dupstring(p)), '/')))
+		    *q = '\0';
+
+                p = dyncat(p, "/");
+
+		for (; *names; names++)
+		    if (!strpfx(p, *names)) {
+			*amb = 1;
+			return NULL;
+		    }
+	    }
+	}
+    } else {
+	if (!(p = *names++))
+	    *amb = 0;
+	else
+	    for (; *names; names++)
+		if (strcmp(p, *names)) {
+		    *amb = 1;
+		    return NULL;
+		}
+    }
+    return NULL;
+}
+
 static int
-bin_compfmt(char *nam, char **args, char *ops, int func)
+bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
-    char *param = args[0], *str = args[1];
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (**args != '-') {
+	zwarnnam(nam, "missing option: %s", *args, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'p':
+    case 'P':
+	if (args[0][2] && (args[0][2] != '-' || args[0][3])) {
+	    zwarnnam(nam, "invalid option: %s", *args, 0);
+	    return 1;
+	} else {
+	    char **tmp;
+	    LinkList l;
 
-    for (args += 2; *args; args++) {
-	if (args[0][1] != ':') {
-	    zwarnnam(nam, "invalid argument `%s'", args[0], 0);
+	    if (!args[1] || !args[2] || !args[3] || !args[4] || !args[5] ||
+		!args[6] || (args[0][1] == 'p' && !args[7])) {
+		zwarnnam(nam, "too few arguments", NULL, 0);
+		return 1;
+	    }
+	    queue_signals();
+	    if (!(tmp = getaparam(args[1]))) {
+		zwarnnam(nam, "unknown parameter: %s", args[1], 0);
+		return 0;
+	    }
+	    for (l = newlinklist(); *tmp; tmp++)
+		addlinknode(l, *tmp);
+	    set_list_array(args[1], cf_pats((args[0][1] == 'P'), !!args[0][2],
+					    l, getaparam(args[2]), args[3],
+					    args[4], args[5],
+					    getaparam(args[6]), args + 7));
+	    unqueue_signals();
+	    return 0;
+	}
+    case 'i':
+	if (args[0][2]) {
+	    zwarnnam(nam, "invalid option: %s", *args, 0);
 	    return 1;
+	} else {
+	    char **tmp;
+	    LinkList l;
+
+	    if (!args[1] || !args[2] || !args[3] || !args[4]) {
+		zwarnnam(nam, "too few arguments", NULL, 0);
+		return 1;
+	    }
+	    if (args[5]) {
+		zwarnnam(nam, "too many arguments", NULL, 0);
+		return 1;
+	    }
+	    queue_signals();
+	    tmp = getaparam(args[2]);
+	    l = newlinklist();
+	    if (tmp)
+		for (; *tmp; tmp++)
+		    addlinknode(l, *tmp);
+	    if (!(tmp = getaparam(args[1]))) {
+		unqueue_signals();
+		zwarnnam(nam, "unknown parameter: %s", args[1], 0);
+		return 0;
+	    }
+	    cf_ignore(tmp, l, args[3], args[4]);
+	    unqueue_signals();
+	    set_list_array(args[2], l);
+	    return 0;
+	}
+    case 'r':
+	{
+	    char **tmp;
+	    LinkList l;
+	    int ret = 0;
+
+	    if (!args[1] || !args[2]) {
+		zwarnnam(nam, "too few arguments", NULL, 0);
+		return 1;
+	    }
+	    if (args[3]) {
+		zwarnnam(nam, "too many arguments", NULL, 0);
+		return 1;
+	    }
+	    queue_signals();
+	    if (!(tmp = getaparam(args[1]))) {
+		unqueue_signals();
+		zwarnnam(nam, "unknown parameter: %s", args[1], 0);
+		return 0;
+	    }
+	    if ((l = cf_remove_other(tmp, args[2], &ret)))
+		set_list_array(args[1], l);
+	    unqueue_signals();
+	    return ret;
 	}
-	str = fmtstr(str, **args, *args + 2);
     }
-    setsparam(param, ztrdup(str));
+    zwarnnam(nam, "invalid option: %s", *args, 0);
+    return 1;
+}
+
+static int
+bin_compgroups(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
+{
+    Heap oldheap;
+    char *n;
+
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    SWITCHHEAPS(oldheap, compheap) {
+	while ((n = *args++)) {
+	    endcmgroup(NULL);
+	    begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
+	    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);
+	    endcmgroup(NULL);
+	    begcmgroup(n, 0);
+	}
+    } SWITCHBACKHEAPS(oldheap);
+
     return 0;
 }
 
@@ -2479,16 +4458,17 @@ static struct builtin bintab[] = {
     BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
     BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
     BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL),
-    BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, NULL, NULL),
+    BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, "p", NULL),
     BUILTIN("comptags", 0, bin_comptags, 1, -1, 0, NULL, NULL),
     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),
 };
 
 
 /**/
 int
-setup_(Module m)
+setup_(UNUSED(Module m))
 {
     memset(cadef_cache, 0, sizeof(cadef_cache));
     memset(cvdef_cache, 0, sizeof(cvdef_cache));
@@ -2517,7 +4497,7 @@ cleanup_(Module m)
 
 /**/
 int
-finish_(Module m)
+finish_(UNUSED(Module m))
 {
     int i;