about summary refs log tree commit diff
path: root/Src/Zle/computil.c
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2000-04-01 20:49:47 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2000-04-01 20:49:47 +0000
commit48525452555a24b9d41748f26b4b77f160f01220 (patch)
treed814ca2f017d9d843fec7d286fefbca78244beb5 /Src/Zle/computil.c
parente025336f2f6d9f107ee1e03b9900f04af0544ba9 (diff)
downloadzsh-48525452555a24b9d41748f26b4b77f160f01220.tar.gz
zsh-48525452555a24b9d41748f26b4b77f160f01220.tar.xz
zsh-48525452555a24b9d41748f26b4b77f160f01220.zip
Updated from list as far as 10376
Diffstat (limited to 'Src/Zle/computil.c')
-rw-r--r--Src/Zle/computil.c1498
1 files changed, 1119 insertions, 379 deletions
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index aed3d9808..a844ee1ef 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -33,22 +33,32 @@
 
 /* Help for `_display'. */
 
+/* Calculation state. */
+
 typedef struct cdisp *Cdisp;
 
 struct cdisp {
-    int pre, suf, colon;
+    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;
+    int i, nbc;
 
     for (; *args; args++) {
-	if ((cp = strchr(*args, ':')) && cp[1]) {
+	for (nbc = 0, cp = *args; *cp && *cp != ':'; cp++)
+	    if (*cp == '\\' && cp[1])
+		cp++, nbc++;
+	if (*cp == ':' && cp[1]) {
 	    disp->colon++;
-	    if ((i = cp - *args) > disp->pre)
+	    if ((i = cp - *args - nbc) > disp->pre)
 		disp->pre = i;
 	    if ((i = strlen(cp + 1)) > disp->suf)
 		disp->suf = i;
@@ -56,78 +66,29 @@ cdisp_calc(Cdisp disp, char **args)
     }
 }
 
-static char **
-cdisp_build(Cdisp disp, char *sep, char **args)
-{
-    int sl = strlen(sep), pre = disp->pre, suf;
-    VARARR(char, buf, disp->pre + disp->suf + sl + 1);
-    char **ret, **rp, *cp;
-
-    ret = (char **) zalloc((arrlen(args) + 1) * sizeof(char *));
-
-    memcpy(buf + pre, sep, sl);
-    suf = pre + sl;
-
-    for (rp = ret; *args; args++) {
-	if ((cp = strchr(*args, ':')) && cp[1]) {
-	    memset(buf, ' ', pre);
-	    memcpy(buf, *args, (cp - *args));
-	    strcpy(buf + suf, cp + 1);
-	    *rp++ = ztrdup(buf);
-	} else {
-	    if (cp)
-		*cp = '\0';
-	    *rp++ = ztrdup(*args);
-	    if (cp)
-		*cp = ':';
-	}
-    }
-    *rp = NULL;
-
-    return ret;
-}
-
-/**/
-static int
-bin_compdisplay(char *nam, char **args, char *ops, int func)
-{
-    struct cdisp disp;
-
-    if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
-	return 1;
-    }
-    disp.pre = disp.suf = disp.colon = 0;
-
-    cdisp_calc(&disp, args + 2);
-    setaparam(args[0], cdisp_build(&disp, args[1], args + 2));
-
-    return !disp.colon;
-}
-
 /* Help fuer `_describe'. */
 
 typedef struct cdset *Cdset;
 
 struct cdstate {
-    int showd;
-    char *sep;
-    Cdset sets;
-    struct cdisp disp;
+    int showd;			/* != 0 if descriptions should be shown */
+    char *sep;			/* the separator string */
+    Cdset sets;			/* the sets of matches */
+    struct cdisp disp;		/* used to calculate the alignment */
 };
 
 struct cdset {
-    Cdset next;
-    char **opts;
-    char **strs;
-    char **matches;
+    Cdset next;			/* guess what */
+    char **opts;		/* the compadd-options */
+    char **strs;		/* the display-strings */
+    char **matches;		/* the matches (or NULL) */
 };
 
 static struct cdstate cd_state;
 static int cd_parsed = 0;
 
 static void
-free_cdsets(Cdset p)
+freecdsets(Cdset p)
 {
     Cdset n;
 
@@ -143,6 +104,8 @@ free_cdsets(Cdset p)
     }
 }
 
+/* Initialisation. Store and calculate the string and matches and so on. */
+
 static int
 cd_init(char *nam, char *sep, char **args, int disp)
 {
@@ -151,7 +114,7 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
     if (cd_parsed) {
 	zsfree(cd_state.sep);
-	free_cdsets(cd_state.sets);
+	freecdsets(cd_state.sets);
     }
     setp = &(cd_state.sets);
     cd_state.sep = ztrdup(sep);
@@ -164,24 +127,20 @@ cd_init(char *nam, char *sep, char **args, int disp)
 	setp = &(set->next);
 
 	if (!(ap = get_user_var(*args))) {
-	    zerrnam(nam, "invalid argument: %s", *args, 0);
+	    zwarnnam(nam, "invalid argument: %s", *args, 0);
 	    return 1;
 	}
-	PERMALLOC {
-	    set->strs = arrdup(ap);
-	} LASTALLOC;
+	set->strs = zarrdup(ap);
 
 	if (disp)
 	    cdisp_calc(&(cd_state.disp), set->strs);
 
 	if (*++args && **args != '-') {
 	    if (!(ap = get_user_var(*args))) {
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return 1;
 	    }
-	    PERMALLOC {
-		set->matches = arrdup(ap);
-	    } LASTALLOC;
+	    set->matches = zarrdup(ap);
 	    args++;
 	}
 	for (ap = args; *args &&
@@ -190,15 +149,15 @@ cd_init(char *nam, char *sep, char **args, int disp)
 
 	tmp = *args;
 	*args = NULL;
-	PERMALLOC {
-	    set->opts = arrdup(ap);
-	} LASTALLOC;
+	set->opts = zarrdup(ap);
 	if ((*args = tmp))
 	    args++;
     }
     return 0;
 }
 
+/* Get the next set. */
+
 static int
 cd_get(char **params)
 {
@@ -206,15 +165,21 @@ cd_get(char **params)
 
     if ((set = cd_state.sets)) {
 	char **sd, **sdp, **md, **mdp, **ss, **ssp, **ms, **msp;
-	char **p, **mp, *cp;
+	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 && (cp = strchr(*p, ':')) && cp[1])
-		dl++;
-	    else
+	    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 *));
@@ -226,41 +191,44 @@ cd_get(char **params)
 	    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++) {
-	    if ((cp = strchr(*p, ':')) && cp[1] && cd_state.showd) {
+	    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, *p, (cp - *p));
+		memcpy(buf, copy, (cpp - copy));
 		strcpy(buf + suf, cp + 1);
 		*sdp++ = ztrdup(buf);
 		if (mp) {
 		    *mdp++ = ztrdup(*mp);
 		    if (*mp)
 			mp++;
-		} else {
-		    *cp = '\0';
-		    *mdp++ = ztrdup(*p);
-		    *cp = ':';
-		}
+		} else
+		    *mdp++ = ztrdup(copy);
 	    } else {
-		if (cp)
-		    *cp = '\0';
-		*ssp++ = ztrdup(*p);
+		*ssp++ = ztrdup(copy);
 		if (mp) {
 		    *msp++ = ztrdup(*mp);
 		    if (*mp)
 			mp++;
 		} else
-		    *msp++ = ztrdup(*p);
-		if (cp)
-		    *cp = ':';
+		    *msp++ = ztrdup(copy);
 	    }
 	}
 	*sdp = *ssp = *mdp = *msp = NULL;
 
-	PERMALLOC {
-	    p = arrdup(set->opts);
-	} LASTALLOC;
+	p = zarrdup(set->opts);
 
 	setaparam(params[0], p);
 	setaparam(params[1], sd);
@@ -270,7 +238,7 @@ cd_get(char **params)
 
 	cd_state.sets = set->next;
 	set->next = NULL;
-	free_cdsets(set);
+	freecdsets(set);
 
 	return 0;
     }
@@ -282,34 +250,36 @@ static int
 bin_compdescribe(char *nam, char **args, char *ops, int func)
 {
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (!args[0][0] || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     switch (args[0][1]) {
     case 'i':
+	cd_parsed = 1;
+	return cd_init(nam, "", args + 1, 0);
     case 'I':
 	cd_parsed = 1;
-	return cd_init(nam, args[1], args + 2, (args[0][1] == 'I'));
+	return cd_init(nam, args[1], args + 2, 1);
     case 'g':
 	if (cd_parsed) {
 	    int n = arrlen(args);
 
 	    if (n != 6) {
-		zerrnam(nam, (n < 6 ? "not enough arguments" :
+		zwarnnam(nam, (n < 6 ? "not enough arguments" :
 			      "too many arguments"), NULL, 0);
 		return 1;
 	    }
 	    return cd_get(args + 1);
 	} else {
-	    zerrnam(nam, "no parsed state", NULL, 0);
+	    zwarnnam(nam, "no parsed state", NULL, 0);
 	    return 1;
 	}
     }
-    zerrnam(nam, "invalid option: %s", args[0], 0);
+    zwarnnam(nam, "invalid option: %s", args[0], 0);
     return 1;
 }
 
@@ -319,29 +289,34 @@ typedef struct cadef *Cadef;
 typedef struct caopt *Caopt;
 typedef struct caarg *Caarg;
 
+/* Cache for a set of _arguments-definitions. */
+
 struct cadef {
-    Cadef next;
-    Caopt opts;
-    int nopts, ndopts, nodopts;
-    Caarg args;
-    Caarg rest;
-    char **defs;
-    int ndefs;
-    int lastt;
-    Caopt *single;
-    char *match;
-    int argsactive;
+    Cadef next;			/* next in cache */
+    Caopt opts;			/* the options */
+    int nopts, ndopts, nodopts;	/* number of options/direct/optional direct */
+    Caarg args;			/* the normal arguments */
+    Caarg rest;			/* the rest-argument */
+    char **defs;		/* the original strings */
+    int ndefs;			/* number of ... */
+    int lastt;			/* last time this was used */
+    Caopt *single;		/* array of single-letter options */
+    char *match;		/* -M spec to use */
+    int argsactive;		/* if arguments are still allowed */
+				/* used while parsing a command line */
 };
 
+/* Description for an option. */
+
 struct caopt {
     Caopt next;
-    char *name;
-    char *descr;
-    char **xor;
-    int type;
-    Caarg args;
-    int active;
-    int num;
+    char *name;			/* option name */
+    char *descr;		/* the description */
+    char **xor;			/* if this, then not ... */
+    int type;			/* type, CAO_* */
+    Caarg args;			/* option arguments */
+    int active;			/* still allowed on command line */
+    int num;			/* it's the num'th option */
 };
 
 #define CAO_NEXT    1
@@ -349,13 +324,18 @@ struct caopt {
 #define CAO_ODIRECT 3
 #define CAO_EQUAL   4
 
+/* Description for an argument */
+
 struct caarg {
     Caarg next;
-    char *descr;
-    char *action;
-    int type;
-    char *end;
-    int num;
+    char *descr;		/* description */
+    char **xor;			/* if this, then not ... */
+    char *action;		/* what to do for it */
+    int type;			/* CAA_* below */
+    char *end;			/* end-pattern for ::<pat>:... */
+    char *opt;			/* option name if for an option */
+    int num;			/* it's the num'th argument */
+    int active;			/* still allowed on command line */
 };
 
 #define CAA_NORMAL 1
@@ -364,9 +344,13 @@ struct caarg {
 #define CAA_RARGS  4
 #define CAA_RREST  5
 
+/* The cache of parsed descriptons. */
+
 #define MAX_CACACHE 8
 static Cadef cadef_cache[MAX_CACACHE];
 
+/* Compare two arrays of strings for equality. */
+
 static int
 arrcmp(char **a, char **b)
 {
@@ -383,45 +367,54 @@ arrcmp(char **a, char **b)
     }
 }
 
+/* Memory stuff. Obviously. */
+
 static void
-free_caargs(Caarg a)
+freecaargs(Caarg a)
 {
     Caarg n;
 
     for (; a; a = n) {
 	n = a->next;
 	zsfree(a->descr);
+	if (a->xor)
+	    freearray(a->xor);
 	zsfree(a->action);
 	zsfree(a->end);
+	zsfree(a->opt);
 	zfree(a, sizeof(*a));
     }
 }
 
 static void
-free_cadef(Cadef d)
+freecadef(Cadef d)
 {
     if (d) {
 	Caopt p, n;
 
 	zsfree(d->match);
-	freearray(d->defs);
+	if (d->defs)
+	    freearray(d->defs);
 
 	for (p = d->opts; p; p = n) {
 	    n = p->next;
 	    zsfree(p->name);
 	    zsfree(p->descr);
-	    freearray(p->xor);
-	    free_caargs(p->args);
+	    if (p->xor)
+		freearray(p->xor);
+	    freecaargs(p->args);
 	    zfree(p, sizeof(*p));
 	}
-	free_caargs(d->args);
-	free_caargs(d->rest);
+	freecaargs(d->args);
+	freecaargs(d->rest);
 	if (d->single)
 	    zfree(d->single, 256 * sizeof(Caopt));
 	zfree(d, sizeof(*d));
     }
 }
 
+/* Remove backslashes before colons. */
+
 static char *
 rembslashcolon(char *s)
 {
@@ -439,16 +432,41 @@ rembslashcolon(char *s)
     return r;
 }
 
+/* Add backslashes before colons. */
+
+static char *
+bslashcolon(char *s)
+{
+    char *p, *r;
+
+    r = p = zhalloc((2 * strlen(s)) + 1);
+
+    while (*s) {
+	if (*s == ':')
+	    *p++ = '\\';
+	*p++ = *s++;
+    }
+    *p = '\0';
+
+    return r;
+}
+
+/* Parse an argument definition. */
+
 static Caarg
-parse_caarg(int mult, int type, int num, char **def)
+parse_caarg(int mult, int type, int num, char *oname, char **def)
 {
     Caarg ret = (Caarg) zalloc(sizeof(*ret));
     char *p = *def, *d, sav;
 
     ret->next = NULL;
     ret->descr = ret->action = ret->end = NULL;
+    ret->xor = NULL;
     ret->num = num;
     ret->type = type;
+    ret->opt = ztrdup(oname);
+
+    /* Get the description. */
 
     for (d = p; *p && *p != ':'; p++)
 	if (*p == '\\' && p[1])
@@ -456,6 +474,9 @@ parse_caarg(int mult, int type, int num, char **def)
     sav = *p;
     *p = '\0';
     ret->descr = ztrdup(rembslashcolon(d));
+
+    /* Get the action if there is one. */
+
     if (sav) {
 	if (mult) {
 	    for (d = ++p; *p && *p != ':'; p++)
@@ -474,6 +495,8 @@ parse_caarg(int mult, int type, int num, char **def)
     return ret;
 }
 
+/* Parse an array of definitions. */
+
 static Cadef
 parse_cadef(char *nam, char **args)
 {
@@ -485,6 +508,8 @@ parse_cadef(char *nam, char **args)
 
     nopts = ndopts = nodopts = 0;
 
+    /* First string is the auto-description definition. */
+
     for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
 
     if (*p) {
@@ -495,6 +520,8 @@ parse_cadef(char *nam, char **args)
     } else
 	adpre = adsuf = NULL;
 
+    /* Now get the -s and -M options. */
+
     args++;
     while ((p = *args)) {
 	if (!strcmp(p, "-s"))
@@ -515,26 +542,30 @@ parse_cadef(char *nam, char **args)
     if (!*args)
 	return NULL;
 
-    PERMALLOC {
-	ret = (Cadef) zalloc(sizeof(*ret));
-	ret->next = NULL;
-	ret->opts = NULL;
-	ret->args = ret->rest = NULL;
-	ret->defs = arrdup(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);
-    } LASTALLOC;
+    /* 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);
+
+    /* Get the definitions. */
 
     for (optp = &(ret->opts); *args; args++) {
 	p = dupstring(*args);
 	xnum = 0;
 	if (*p == '(') {
+	    /* There is a xor list, get it. */
+
 	    LinkList list = newlinklist();
 	    LinkNode node;
 	    char **xp, sav;
@@ -555,9 +586,10 @@ parse_cadef(char *nam, char **args)
 		xnum++;
 		*p = sav;
 	    }
+	    /* Oops, end-of-string. */
 	    if (*p != ')') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
@@ -568,9 +600,10 @@ parse_cadef(char *nam, char **args)
 	    p++;
 	} else
 	    xor = NULL;
-	
+
 	if (*p == '-' || *p == '+' ||
 	    (*p == '*' && (p[1] == '-' || p[1] == '+'))) {
+	    /* It's an option. */
 	    Caopt opt;
 	    Caarg oargs = NULL;
 	    int multi, otype = CAO_NEXT, again = 0;
@@ -578,25 +611,39 @@ parse_cadef(char *nam, char **args)
 
 	    rec:
 
+	    /* Allowed more than once? */
 	    if ((multi = (*p == '*')))
 		p++;
 
-	    if ((p[0] == '-' && p[1] == '+') ||
-		(p[0] == '+' && p[1] == '-')) {
+	    if (((p[0] == '-' && p[1] == '+') ||
+		 (p[0] == '+' && p[1] == '-')) &&
+		p[2] && p[2] != ':' && p[2] != '[' &&
+		p[2] != '=' && p[2] != '-' && p[2] != '+') {
+		/* It's a -+ or +- definition. We just execute the whole
+		 * stuff twice for such things. */
 		name = ++p;
 		*p = (again ? '-' : '+');
 		again = 1 - again;
 	    } else {
 		name = p;
+		/* If it's a long option skip over the first `-'. */
 		if (p[0] == '-' && p[1] == '-')
 		    p++;
 	    }
+	    if (!p[1]) {
+		freecadef(ret);
+		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++)
 		if (*p == '\\' && p[1])
 		    p++;
 
+	    /* The character after the option name specifies the type. */
 	    c = *p;
 	    *p = '\0';
 	    if (c == '-') {
@@ -609,14 +656,15 @@ parse_cadef(char *nam, char **args)
 		otype = CAO_EQUAL;
 		c = *++p;
 	    }
+	    /* Get the optional description, if any. */
 	    if (c == '[') {
 		for (descr = ++p; *p && *p != ']'; p++)
 		    if (*p == '\\' && p[1])
 			p++;
 
 		if (!*p) {
-		    free_cadef(ret);
-		    zerrnam(nam, "invalid option definition: %s", *args, 0);
+		    freecadef(ret);
+		    zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		    return NULL;
 		}
 		*p++ = '\0';
@@ -625,10 +673,11 @@ parse_cadef(char *nam, char **args)
 		descr = NULL;
 
 	    if (c && c != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid option definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid option definition: %s", *args, 0);
 		return NULL;
 	    }
+	    /* Add the option name to the xor list if not `*-...'. */
 	    if (!multi) {
 		if (!xor) {
 		    xor = (char **) zalloc(2 * sizeof(char *));
@@ -637,27 +686,39 @@ parse_cadef(char *nam, char **args)
 		xor[xnum] = ztrdup(name);
 	    }
 	    if (c == ':') {
+		/* There's at least one argument. */
+
 		Caarg *oargp = &oargs;
-		int atype, rest;
+		int atype, rest, oanum = 1;
 		char *end;
 
+		/* Loop over the arguments. */
+
 		while (c == ':') {
 		    rest = 0;
 		    end = NULL;
 
+		    /* Get the argument type. */
 		    if (*++p == ':') {
 			atype = CAA_OPT;
 			p++;
 		    } else if (*p == '*') {
 			if (*++p != ':') {
-			    for (end = ++p; *p && *p != ':'; p++)
+			    char sav;
+
+			    for (end = p++; *p && *p != ':'; p++)
 				if (*p == '\\' && p[1])
 				    p++;
+			    sav = *p;
+			    *p = '\0';
+			    end = dupstring(end);
+			    tokenize(end);
+			    *p = sav;
 			}
 			if (*p != ':') {
-			    free_cadef(ret);
-			    free_caargs(oargs);
-			    zerrnam(nam, "invalid option definition: %s",
+			    freecadef(ret);
+			    freecaargs(oargs);
+			    zwarnnam(nam, "invalid option definition: %s",
 				    *args, 0);
 			    return NULL;
 			}
@@ -672,54 +733,74 @@ parse_cadef(char *nam, char **args)
 			rest = 1;
 		    } else
 			atype = CAA_NORMAL;
-		    *oargp = parse_caarg(!rest, atype, 0, &p);
+
+		    /* And the definition. */
+
+		    *oargp = parse_caarg(!rest, atype, oanum++, name, &p);
+		    if (end)
+			(*oargp)->end = ztrdup(end);
 		    oargp = &((*oargp)->next);
 		    if (rest)
 			break;
 		    c = *p;
 		}
 	    }
-	    PERMALLOC {
-		*optp = opt = (Caopt) zalloc(sizeof(*opt));
-		optp = &((*optp)->next);
-
-		opt->next = NULL;
-		opt->name = ztrdup(name);
-		if (descr)
-		    opt->descr = ztrdup(descr);
-		else if (adpre && oargs && !oargs->next)
+	    /* Store the option definition. */
+
+	    *optp = opt = (Caopt) zalloc(sizeof(*opt));
+	    optp = &((*optp)->next);
+
+	    opt->next = NULL;
+	    opt->name = ztrdup(rembslashcolon(name));
+	    if (descr)
+		opt->descr = ztrdup(descr);
+	    else if (adpre && oargs && !oargs->next) {
+		char *d;
+
+		for (d = oargs->descr; *d; d++)
+		    if (!iblank(*d))
+			break;
+
+		if (*d)
 		    opt->descr = tricat(adpre, oargs->descr, adsuf);
 		else
 		    opt->descr = NULL;
-		opt->xor = xor;
-		opt->type = otype;
-		opt->args = oargs;
-		opt->num = nopts++;
-	    } LASTALLOC;
+	    } else
+		opt->descr = NULL;
+	    opt->xor = xor;
+	    opt->type = otype;
+	    opt->args = oargs;
+	    opt->num = nopts++;
 
 	    if (otype == CAO_DIRECT)
 		ndopts++;
 	    else if (otype == CAO_ODIRECT || otype == CAO_EQUAL)
 		nodopts++;
 
+	    /* If this is for single-letter option we also store a
+	     * pointer for the definition in the array for fast lookup. */
+
 	    if (single && name[1] && !name[2])
 		ret->single[STOUC(name[1])] = opt;
 
 	    if (again) {
+		/* Do it all again for `*-...'. */
 		p = dupstring(*args);
 		goto rec;
 	    }
 	} else if (*p == '*') {
+	    /* It's a rest-argument definition. */
+
 	    int type = CAA_REST;
 
 	    if (*++p != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid rest argument definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    if (ret->rest) {
-		free_cadef(ret);
-		zerrnam(nam, "doubled rest argument definition: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "doubled rest argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
@@ -729,40 +810,49 @@ parse_cadef(char *nam, char **args)
 		} else
 		    type = CAA_RARGS;
 	    }
-	    ret->rest = parse_caarg(0, type, -1, &p);
+	    ret->rest = parse_caarg(0, type, -1, NULL, &p);
+	    ret->rest->xor = xor;
 	} else {
+	    /* It's a normal argument definition. */
+
 	    int type = CAA_NORMAL;
 	    Caarg arg, tmp, pre;
 
 	    if (idigit(*p)) {
+		/* Argment number is given. */
 		int num = 0;
 
 		while (*p && idigit(*p))
-		    num = (num * 10) + ((int) *p++);
+		    num = (num * 10) + (((int) *p++) - '0');
 
 		anum = num + 1;
 	    } else
+		/* Default number. */
 		anum++;
 
 	    if (*p != ':') {
-		free_cadef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecadef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
+		/* Optional argument. */
 		type = CAA_OPT;
 		p++;
 	    }
-	    arg = parse_caarg(0, type, anum - 1, &p);
+	    arg = parse_caarg(0, type, anum - 1, NULL, &p);
+	    arg->xor = xor;
+
+	    /* Sort the new definition into the existing list. */
 
 	    for (tmp = ret->args, pre = NULL;
 		 tmp && tmp->num < anum - 1;
 		 pre = tmp, tmp = tmp->next);
 
 	    if (tmp && tmp->num == anum - 1) {
-		free_cadef(ret);
-		free_caargs(arg);
-		zerrnam(nam, "doubled argument definition: %s", *args, 0);
+		freecadef(ret);
+		freecaargs(arg);
+		zwarnnam(nam, "doubled argument definition: %s", *args, 0);
 		return NULL;
 	    }
 	    arg->next = tmp;
@@ -779,13 +869,16 @@ parse_cadef(char *nam, char **args)
     return ret;
 }
 
+/* Given an array of definitions, return the cadef for it. From the cache
+ * are newly built. */
+
 static Cadef
 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++)
+    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; *p && i; p++, i--)
 	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
 	    (*p)->lastt = time(0);
 
@@ -795,26 +888,36 @@ get_cadef(char *nam, char **args)
     if (i)
 	min = p;
     if ((new = parse_cadef(nam, args))) {
-	free_cadef(*min);
+	freecadef(*min);
 	*min = new;
     }
     return new;
 }
 
+/* Get the option used in a word from the line, if any. */
+
 static Caopt
 ca_get_opt(Cadef d, char *line, int full, char **end)
 {
     Caopt p;
 
-    if (full) {
-	for (p = d->opts; p; p = p->next)
-	    if (p->active && !strcmp(p->name, line))
-		return p;
-    } else {
+    /* The full string may be an option. */
+
+    for (p = d->opts; p; p = p->next)
+	if (p->active && !strcmp(p->name, line)) {
+	    if (end)
+		*end = line + strlen(line);
+
+	    return p;
+	}
+
+    if (!full) {
+	/* The string from the line probably only begins with an option. */
 	for (p = d->opts; p; p = p->next)
-	    if (p->active && p->args && p->type != CAO_NEXT &&
-		strpfx(p->name, line)) {
+	    if (p->active && ((!p->args || p->type == CAO_NEXT) ?
+			      !strcmp(p->name, line) : strpfx(p->name, line))) {
 		if (end) {
+		    /* Return a pointer to the end of the option. */
 		    int l = strlen(p->name);
 
 		    if (p->type == CAO_EQUAL && line[l] == '=')
@@ -828,12 +931,13 @@ ca_get_opt(Cadef d, char *line, int full, char **end)
     return NULL;
 }
 
+/* Same as above, only for single-letter-style. */
+
 static Caopt
 ca_get_sopt(Cadef d, char *line, int full, char **end)
 {
     Caopt p;
-
-    line++;
+    char pre = *line++;
 
     if (full) {
 	for (p = NULL; *line; line++)
@@ -844,7 +948,7 @@ ca_get_sopt(Cadef d, char *line, int full, char **end)
     } else {
 	for (p = NULL; *line; line++)
 	    if ((p = d->single[STOUC(*line)]) && p->active &&
-		p->args && p->type != CAO_NEXT) {
+		p->args && p->type != CAO_NEXT && p->name[0] == pre) {
 		if (end) {
 		    line++;
 		    if (p->type == CAO_EQUAL && *line == '=')
@@ -852,13 +956,18 @@ ca_get_sopt(Cadef d, char *line, int full, char **end)
 		    *end = line;
 		}
 		break;
-	    } else if (!p || !p->active || (line[1] && p->args))
+	    } else if (!p || !p->active || (line[1] && p->args) ||
+		       p->name[0] != pre)
 		return NULL;
+	if (p && end)
+	    *end = line;
 	return p;
     }
     return NULL;
 }
 
+/* Return the n'th argument definition. */
+
 static Caarg
 ca_get_arg(Cadef d, int n)
 {
@@ -868,14 +977,16 @@ ca_get_arg(Cadef d, int n)
 	while (a && a->num < n)
 	    a = a->next;
 
-	if (a && a->num == n)
+	if (a && a->num == n && a->active)
 	    return a;
 
-	return d->rest;
+	return (d->rest && d->rest->active ? d->rest : NULL);
     }
     return NULL;
 }
 
+/* Use a xor list, marking options as inactive. */
+
 static void
 ca_inactive(Cadef d, char **xor)
 {
@@ -885,17 +996,32 @@ ca_inactive(Cadef d, char **xor)
 	for (; *xor; xor++) {
 	    if (xor[0][0] == ':' && !xor[0][1])
 		d->argsactive = 0;
-	    else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
+	    else if (xor[0][0] == '*' && !xor[0][1]) {
+		if (d->rest)
+		    d->rest->active = 0;
+	    } else if (xor[0][0] >= '0' && xor[0][0] <= '9') {
+		int n = atoi(xor[0]);
+		Caarg a = d->args;
+
+		while (a && a->num < n)
+		    a = a->next;
+
+		if (a && a->num == n)
+		    a->active = 0;
+	    } else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
 		opt->active = 0;
 	}
     }
 }
 
+/* State when parsing a command line. */
+
 struct castate {
     Cadef d;
+    int nopts;
     Caarg def, ddef;
     Caopt curopt;
-    int opt, arg, argbeg, optbeg, nargbeg, restbeg;
+    int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos;
     int inopt, inrest, inarg, nth, doff, singles;
     LinkList args;
     LinkList *oargs;
@@ -904,40 +1030,55 @@ 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)
 {
     Caarg adef, ddef;
-    Caopt ptr;
+    Caopt ptr, wasopt;
     struct castate state;
-    char *line, *pe;
+    char *line, *pe, **argxor = NULL;
     int cur, doff;
     Patprog endpat = NULL;
 
+    /* Free old state. */
+
     if (ca_alloced) {
-	int i = ca_laststate.d->nopts;
+	int i = ca_laststate.nopts;
 	LinkList *p = ca_laststate.oargs;
 
 	freelinklist(ca_laststate.args, freestr);
 	while (i--)
 	    if (*p++)
 		freelinklist(p[-1], freestr);
+
+	zfree(ca_laststate.oargs, ca_laststate.d->nopts * sizeof(LinkList));
     }
+    /* Mark everything as active. */
+
     for (ptr = d->opts; ptr; ptr = ptr->next)
 	ptr->active = 1;
     d->argsactive = 1;
+    if (d->rest)
+	d->rest->active = 1;
+    for (adef = d->args; adef; adef = adef->next)
+	adef->active = 1;
+
+    /* Default values for the state. */
 
     state.d = d;
+    state.nopts = d->nopts;
     state.def = state.ddef = NULL;
     state.curopt = NULL;
     state.argbeg = state.optbeg = state.nargbeg = state.restbeg =
 	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
     state.inrest = state.doff = state.singles = state.doff = 0;
-    PERMALLOC {
-	state.args = newlinklist();
-	state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
-	memset(state.oargs, 0, d->nopts * sizeof(LinkList));
-    } LASTALLOC;
+    state.curpos = compcurrent;
+    state.args = znewlinklist();
+    state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
+    memset(state.oargs, 0, d->nopts * sizeof(LinkList));
+
     ca_alloced = 1;
 
     memcpy(&ca_laststate, &state, sizeof(state));
@@ -947,46 +1088,67 @@ ca_parse_line(Cadef d)
 
 	return;
     }
+    /* 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. */
+
 	if (state.def) {
 	    state.arg = 0;
-	    if (state.curopt) {
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(line));
-		} LASTALLOC;
-	    }
-	    state.opt = (state.def->type == CAA_OPT && line[0] && line[1]);
+	    if (state.curopt)
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(line));
+
+	    state.opt = (state.def->type == CAA_OPT);
 
 	    if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
 		state.def->type == CAA_RREST) {
 		if (state.def->end && pattry(endpat, line)) {
 		    state.def = NULL;
 		    state.curopt = NULL;
+		    state.opt = state.arg = 1;
 		    continue;
 		}
 	    } else if ((state.def = state.def->next))
 		state.argbeg = cur;
-	    else
+	    else {
 		state.curopt = NULL;
+		state.opt = 1;
+	    }
 	} else {
-	    state.opt = (line[0] && line[1]);
-	    state.arg = 1;
+	    state.opt = state.arg = 1;
 	    state.curopt = NULL;
 	}
+	if (state.opt)
+	    state.opt = (line[0] ? (line[1] ? 2 : 1) : 0);
+
 	pe = NULL;
 
-	if (state.opt && (state.curopt = ca_get_opt(d, line, 0, &pe))) {
+	wasopt = NULL;
+
+	/* 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] == '=')) {
+
 	    ddef = state.def = state.curopt->args;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
-	    PERMALLOC {
-		state.oargs[state.curopt->num] = newlinklist();
-	    } LASTALLOC;
+	    state.singles = (d->single && (!pe || !*pe) &&
+			     state.curopt->name[1] && !state.curopt->name[2]);
+
+	    state.oargs[state.curopt->num] = znewlinklist();
+
 	    ca_inactive(d, state.curopt->xor);
 
+	    /* Collect the argument strings. Maybe. */
+
 	    if (state.def &&
 		(state.curopt->type == CAO_DIRECT ||
 		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
@@ -996,27 +1158,32 @@ ca_parse_line(Cadef d)
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
-		} LASTALLOC;
+
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
 	    }
-	    if (!state.def)
+	    if (state.def)
+		state.opt = 0;
+	    else {
+		if (!d->single || (state.curopt->name[1] && state.curopt->name[2]))
+		    wasopt = state.curopt;
 		state.curopt = NULL;
-	} else if (state.opt && d->single &&
+	    }
+	} else if (state.opt == 2 && d->single &&
 		   (state.curopt = ca_get_sopt(d, line, 0, &pe))) {
+	    /* Or maybe it's a single-letter option? */
+
 	    char *p;
 	    Caopt tmpopt;
 
 	    ddef = state.def = state.curopt->args;
 	    doff = pe - line;
 	    state.optbeg = state.argbeg = state.inopt = cur;
-	    state.singles = !*pe;
+	    state.singles = (!pe || !*pe);
 
-	    for (p = line + 1; p <= pe; p++) {
+	    for (p = line + 1; p < pe; p++) {
 		if ((tmpopt = d->single[STOUC(*p)])) {
-		    PERMALLOC {
-			state.oargs[tmpopt->num] = newlinklist();
-		    } LASTALLOC;
+		    state.oargs[tmpopt->num] = znewlinklist();
+
 		    ca_inactive(d, tmpopt->xor);
 		}
 	    }
@@ -1029,31 +1196,40 @@ ca_parse_line(Cadef d)
 		    state.def->type != CAA_RARGS &&
 		    state.def->type != CAA_RREST)
 		    state.def = state.def->next;
-		PERMALLOC {
-		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
-		} LASTALLOC;
+
+		zaddlinknode(state.oargs[state.curopt->num], ztrdup(pe));
 	    }
-	    if (!state.def)
+	    if (state.def)
+		state.opt = 0;
+	    else
 		state.curopt = NULL;
 	} else if (state.arg) {
-	    PERMALLOC {
-		addlinknode(state.args, ztrdup(line));
-	    } LASTALLOC;
+	    /* Otherwise it's a normal argument. */
+	    if (state.inopt) {
+		state.inopt = 0;
+		state.nargbeg = cur - 1;
+	    }
 	    if ((adef = state.def = ca_get_arg(d, state.nth)) &&
 		(state.def->type == CAA_RREST ||
 		 state.def->type == CAA_RARGS)) {
 		state.inrest = 0;
-		for (; line; line = compwords[cur++]) {
-		    PERMALLOC {
-			addlinknode(state.args, ztrdup(line));
-		    } LASTALLOC;
-		}
+		state.opt = (cur == state.nargbeg + 1);
+		state.optbeg = state.nargbeg;
+		state.argbeg = cur - 1;
+
+		for (; line; line = compwords[cur++])
+		    zaddlinknode(state.args, ztrdup(line));
+
+		memcpy(&ca_laststate, &state, sizeof(state));
+		ca_laststate.ddef = NULL;
+		ca_laststate.doff = 0;
 		break;
 	    }
-	    if (state.inopt) {
-		state.inopt = 0;
-		state.nargbeg = cur - 1;
-	    }
+	    zaddlinknode(state.args, ztrdup(line));
+
+	    if (state.def)
+		argxor = state.def->xor;
+
 	    if (state.def && state.def->type != CAA_NORMAL &&
 		state.def->type != CAA_OPT && state.inarg) {
 		state.restbeg = cur;
@@ -1064,6 +1240,8 @@ ca_parse_line(Cadef d)
 	    state.nth++;
 	    state.def = NULL;
 	}
+	/* Do the end-pattern test if needed. */
+
 	if (state.def && state.curopt &&
 	    (state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
 	    if (state.def->end)
@@ -1071,52 +1249,77 @@ ca_parse_line(Cadef d)
 	    else {
 		LinkList l = state.oargs[state.curopt->num];
 
-		for (; line; line = compwords[cur++]) {
-		    PERMALLOC {
-			addlinknode(l, line);
-		    } LASTALLOC;
-		}
+		if (cur < compcurrent)
+		    memcpy(&ca_laststate, &state, sizeof(state));
+
+		for (; line; line = compwords[cur++])
+		    zaddlinknode(l, ztrdup(line));
+
+		ca_laststate.ddef = NULL;
+		ca_laststate.doff = 0;
 		break;
 	    }
-	}
+	} else if (state.def && state.def->end)
+	    endpat = patcompile(state.def->end, 0, NULL);
+
+	/* Copy the state into the global one. */
+
 	if (cur + 1 == compcurrent) {
 	    memcpy(&ca_laststate, &state, sizeof(state));
 	    ca_laststate.ddef = NULL;
 	    ca_laststate.doff = 0;
 	} else if (cur == compcurrent && !ca_laststate.def) {
-	    if ((ca_laststate.def = ddef))
-		ca_laststate.doff = doff;
-	    else {
+	    if ((ca_laststate.def = ddef)) {
+		ca_laststate.singles = state.singles;
+		if (state.curopt && state.curopt->type == CAO_NEXT) {
+		    ca_laststate.ddef = ddef;
+		    ca_laststate.def = NULL;
+		    ca_laststate.opt = 1;
+		    state.curopt->active = 1;
+		} else {
+		    ca_laststate.doff = doff;
+		    ca_laststate.opt = 0;
+		}
+	    } else {
 		ca_laststate.def = adef;
 		ca_laststate.ddef = NULL;
-		ca_laststate.argbeg = state.nargbeg;
-		ca_laststate.optbeg = state.restbeg;
+		ca_laststate.optbeg = state.nargbeg;
+		ca_laststate.argbeg = state.restbeg;
 		ca_laststate.singles = state.singles;
+		if (wasopt)
+		    wasopt->active = 1;
 	    }
 	}
     }
 }
 
+/* Build a colon-list from a list. */
+
 static char *
 ca_colonlist(LinkList l)
 {
     if (l) {
 	LinkNode n;
-	int len = 1;
+	int len = 0;
 	char *p, *ret, *q;
 
-	for (n = firstnode(l); n; incnode(n))
+	for (n = firstnode(l); n; incnode(n)) {
+	    len++;
 	    for (p = (char *) getdata(n); *p; p++)
 		len += (*p == ':' ? 2 : 1);
-
+	}
 	ret = q = (char *) zalloc(len);
 
-	for (n = firstnode(l); n; incnode(n))
+	for (n = firstnode(l); n;) {
 	    for (p = (char *) getdata(n); *p; p++) {
 		if (*p == ':')
 		    *q++ = '\\';
 		*q++ = *p;
 	    }
+	    incnode(n);
+	    if (n)
+		*q++ = ':';
+	}
 	*q = '\0';
 
 	return ret;
@@ -1130,36 +1333,37 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
     int min, max, n;
 
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     if (args[0][1] != 'i' && !ca_parsed) {
-	zerrnam(nam, "no parsed state", NULL, 0);
+	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 'O': min = 4; max =  4; break;
-    case 'L': min = 3; max =  3; break;
+    case 'L': min = 3; max =  4; break;
     case 's': min = 1; max =  1; break;
     case 'M': min = 1; max =  1; break;
     case 'a': min = 0; max =  0; break;
     case 'W': min = 2; max =  2; break;
     default:
-	zerrnam(nam, "invalid option: %s", args[0], 0);
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
 	return 1;
     }
     n = arrlen(args) - 1;
     if (n < min) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return 1;
     } else if (max >= 0 && n > max) {
-	zerrnam(nam, "too many arguments", NULL, 0);
+	zwarnnam(nam, "too many arguments", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
@@ -1189,19 +1393,45 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		setsparam(args[1], ztrdup(arg->descr));
 		setsparam(args[2], ztrdup(arg->action));
 
-		ignore_prefix(ca_laststate.doff);
+		if (ca_laststate.doff > 0)
+		    ignore_prefix(ca_laststate.doff);
 		if (arg->type == CAA_RARGS)
-		    restrict_range(ca_laststate.argbeg - 1,
+		    restrict_range(ca_laststate.optbeg,
 				   arrlen(compwords) - 1);
 		else if (arg->type == CAA_RREST)
-		    restrict_range(ca_laststate.optbeg - 1,
+		    restrict_range(ca_laststate.argbeg,
 				   arrlen(compwords) - 1);
 		return 0;
 	    }
 	    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;
+	    }
+	    return 1;
+	}
     case 'O':
-	if (ca_laststate.opt) {
+	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)))) {
 	    LinkList next = newlinklist();
 	    LinkList direct = newlinklist();
 	    LinkList odirect = newlinklist();
@@ -1218,14 +1448,15 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		    default:          l = equal;   break;
 		    }
 		    if (p->descr) {
-			int len = strlen(p->name) + strlen(p->descr) + 2;
+			char *n = bslashcolon(p->name);
+			int len = strlen(n) + strlen(p->descr) + 2;
 
 			str = (char *) zhalloc(len);
-			strcpy(str, p->name);
+			strcpy(str, n);
 			strcat(str, ":");
 			strcat(str, p->descr);
 		    } else
-			str = p->name;
+			str = bslashcolon(p->name);
 		    addlinknode(l, str);
 		}
 	    }
@@ -1235,8 +1466,8 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 	    set_list_array(args[4], equal);
 
 	    return 0;
-	} else
-	    return 1;
+	}
+	return 1;
     case 'L':
 	{
 	    Caopt opt = ca_get_opt(ca_laststate.d, args[1], 1, NULL);
@@ -1245,12 +1476,16 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 		setsparam(args[2], ztrdup(opt->args->descr));
 		setsparam(args[3], ztrdup(opt->args->action));
 
+		if (args[4])
+		    setsparam(args[4], tricat(opt->name, "-1", ""));
+
 		return 0;
 	    }
 	    return 1;
 	}
     case 's':
-	if (ca_laststate.d->single && ca_laststate.singles) {
+	if (ca_laststate.d->single && ca_laststate.singles &&
+	    ca_laststate.opt) {
 	    setsparam(args[1],
 		      ztrdup(ca_laststate.ddef ?
 			     (ca_laststate.ddef->type == CAO_DIRECT ?
@@ -1258,8 +1493,8 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 			      (ca_laststate.ddef->type == CAO_EQUAL ?
 			       "equal" : "next")) : ""));
 	    return 0;
-	} else
-	    return 1;
+	}
+	return 1;
     case 'M':
 	setsparam(args[1], ztrdup(ca_laststate.d->match));
 	return 0;
@@ -1310,67 +1545,79 @@ bin_comparguments(char *nam, char **args, char *ops, int func)
 typedef struct cvdef *Cvdef;
 typedef struct cvval *Cvval;
 
+/* Definitions for _values. */
+
 struct cvdef {
-    char *descr;
-    int hassep;
-    char sep;
-    Cvdef next;
-    Cvval vals;
-    char **defs;
-    int ndefs;
-    int lastt;
+    char *descr;		/* global description */
+    int hassep;			/* multiple values allowed */
+    char sep;			/* separator character */
+    Cvdef next;			/* next in cache */
+    Cvval vals;			/* value definitions */
+    char **defs;		/* original strings */
+    int ndefs;			/* number of ... */
+    int lastt;			/* last time used */
 };
 
+/* One value definition. */
+
 struct cvval {
     Cvval next;
-    char *name;
-    char *descr;
-    char **xor;
-    int type;
-    Caarg arg;
-    int active;
+    char *name;			/* value name */
+    char *descr;		/* description */
+    char **xor;			/* xor-list */
+    int type;			/* CVV_* below */
+    Caarg arg;			/* argument definition */
+    int active;			/* still allowed */
 };
 
 #define CVV_NOARG 0
 #define CVV_ARG   1
 #define CVV_OPT   2
 
+/* Cache. */
+
 #define MAX_CVCACHE 8
 static Cvdef cvdef_cache[MAX_CVCACHE];
 
+/* Memory stuff. */
+
 static void
-free_cvdef(Cvdef d)
+freecvdef(Cvdef d)
 {
     if (d) {
 	Cvval p, n;
 
 	zsfree(d->descr);
-	freearray(d->defs);
+	if (d->defs)
+	    freearray(d->defs);
 
 	for (p = d->vals; p; p = n) {
 	    n = p->next;
 	    zsfree(p->name);
 	    zsfree(p->descr);
-	    freearray(p->xor);
-	    free_caargs(p->arg);
+	    if (p->xor)
+		freearray(p->xor);
+	    freecaargs(p->arg);
 	    zfree(p, sizeof(*p));
 	}
 	zfree(d, sizeof(*d));
     }
 }
 
+/* Parse option definitions. */
+
 static Cvdef
 parse_cvdef(char *nam, char **args)
 {
     Cvdef ret;
     Cvval val, *valp;
     Caarg arg;
-    char **oargs = args, sep, *name, *descr, *p, *q, **xor, c;
-    int xnum, multi, vtype, hassep;
+    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]) {
-	    zerrnam(nam, "invalid separator: %s", args[1], 0);
+	    zwarnnam(nam, "invalid separator: %s", args[1], 0);
 	    return NULL;
 	}
 	hassep = 1;
@@ -1378,26 +1625,26 @@ parse_cvdef(char *nam, char **args)
 	args += 2;
     }
     if (!args[0] || !args[1]) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return NULL;
     }
     descr = *args++;
 
-    PERMALLOC {
-	ret = (Cvdef) zalloc(sizeof(*ret));
-	ret->descr = ztrdup(descr);
-	ret->hassep = hassep;
-	ret->sep = sep;
-	ret->next = NULL;
-	ret->vals = NULL;
-	ret->defs = arrdup(oargs);
-	ret->ndefs = arrlen(oargs);
-	ret->lastt = time(0);
-    } LASTALLOC;
+    ret = (Cvdef) zalloc(sizeof(*ret));
+    ret->descr = ztrdup(descr);
+    ret->hassep = hassep;
+    ret->sep = sep;
+    ret->next = NULL;
+    ret->vals = NULL;
+    ret->defs = zarrdup(oargs);
+    ret->ndefs = arrlen(oargs);
+    ret->lastt = time(0);
 
     for (valp = &(ret->vals); *args; args++) {
 	p = dupstring(*args);
 	xnum = 0;
+
+	/* xor list? */
 	if (*p == '(') {
 	    LinkList list = newlinklist();
 	    LinkNode node;
@@ -1420,8 +1667,8 @@ parse_cvdef(char *nam, char **args)
 		*p = sav;
 	    }
 	    if (*p != ')') {
-		free_cvdef(ret);
-		zerrnam(nam, "invalid argument: %s", *args, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "invalid argument: %s", *args, 0);
 		return NULL;
 	    }
 	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
@@ -1433,18 +1680,23 @@ parse_cvdef(char *nam, char **args)
 	} else
 	    xor = NULL;
 
+	/* More than once allowed? */
 	if ((multi = (*p == '*')))
 	    p++;
 
+	/* Skip option name. */
+
 	for (name = p; *p && *p != ':' && *p != '['; p++)
 	    if (*p == '\\' && p[1])
 		p++;
 
 	if (hassep && !sep && name + 1 != p) {
-	    free_cvdef(ret);
-	    zerrnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
+	    freecvdef(ret);
+	    zwarnnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
 	    return NULL;
 	}
+	/* Optional description? */
+
 	if ((c = *p) == '[') {
 	    *p = '\0';
 	    for (descr = ++p; *p && *p != ']'; p++)
@@ -1452,8 +1704,8 @@ parse_cvdef(char *nam, char **args)
 		    p++;
 
 	    if (!*p) {
-		free_cvdef(ret);
-		zerrnam(nam, "invalid value definition: %s", *args, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "invalid value definition: %s", *args, 0);
 		return NULL;
 	    }
 	    *p++ = '\0';
@@ -1463,8 +1715,8 @@ parse_cvdef(char *nam, char **args)
 	    descr = NULL;
 	}
 	if (c && c != ':') {
-	    free_cvdef(ret);
-	    zerrnam(nam, "invalid value definition: %s", *args, 0);
+	    freecvdef(ret);
+	    zwarnnam(nam, "invalid value definition: %s", *args, 0);
 	    return NULL;
 	}
 	if (!multi) {
@@ -1474,10 +1726,12 @@ parse_cvdef(char *nam, char **args)
 	    }
 	    xor[xnum] = ztrdup(name);
 	}
+	/* Get argument? */
+
 	if (c == ':') {
 	    if (hassep && !sep) {
-		free_cvdef(ret);
-		zerrnam(nam, "no value with argument with empty separator allowed", NULL, 0);
+		freecvdef(ret);
+		zwarnnam(nam, "no value with argument with empty separator allowed", NULL, 0);
 		return NULL;
 	    }
 	    if (*++p == ':') {
@@ -1485,26 +1739,26 @@ parse_cvdef(char *nam, char **args)
 		vtype = CVV_OPT;
 	    } else
 		vtype = CVV_ARG;
-	    arg = parse_caarg(0, 0, 0, &p);
+	    arg = parse_caarg(0, 0, 0, name, &p);
 	} else {
 	    vtype = CVV_NOARG;
 	    arg = NULL;
 	}
-	PERMALLOC {
-	    *valp = val = (Cvval) zalloc(sizeof(*val));
-	    valp = &((*valp)->next);
-
-	    val->next = NULL;
-	    val->name = ztrdup(name);
-	    val->descr = ztrdup(descr);
-	    val->xor = xor;
-	    val->type = vtype;
-	    val->arg = arg;
-	} LASTALLOC;
+	*valp = val = (Cvval) zalloc(sizeof(*val));
+	valp = &((*valp)->next);
+
+	val->next = NULL;
+	val->name = ztrdup(name);
+	val->descr = ztrdup(descr);
+	val->xor = xor;
+	val->type = vtype;
+	val->arg = arg;
     }
     return ret;
 }
 
+/* Get the definition from the cache or newly built. */
+
 static Cvdef
 get_cvdef(char *nam, char **args)
 {
@@ -1521,12 +1775,14 @@ get_cvdef(char *nam, char **args)
     if (i)
 	min = p;
     if ((new = parse_cvdef(nam, args))) {
-	free_cvdef(*min);
+	freecvdef(*min);
 	*min = new;
     }
     return new;
 }
 
+/* Get the definition for a value. */
+
 static Cvval
 cv_get_val(Cvdef d, char *name)
 {
@@ -1539,6 +1795,8 @@ cv_get_val(Cvdef d, char *name)
     return NULL;
 }
 
+/* Handle a xor list. */
+
 static void
 cv_inactive(Cvdef d, char **xor)
 {
@@ -1551,6 +1809,8 @@ cv_inactive(Cvdef d, char **xor)
     }
 }
 
+/* Parse state. */
+
 struct cvstate {
     Cvdef d;
     Caarg def;
@@ -1561,6 +1821,8 @@ struct cvstate {
 static struct cvstate cv_laststate;
 static int cv_parsed = 0, cv_alloced = 0;
 
+/* Parse the current word. */
+
 static void
 cv_parse_word(Cvdef d)
 {
@@ -1577,9 +1839,8 @@ cv_parse_word(Cvdef d)
     state.d = d;
     state.def = NULL;
     state.val = NULL;
-    PERMALLOC {
-	state.vals = (LinkList) newlinklist();
-    } LASTALLOC;
+    state.vals = (LinkList) znewlinklist();
+
     cv_alloced = 1;
 
     if (d->hassep) {
@@ -1596,10 +1857,8 @@ cv_parse_word(Cvdef d)
 		    eq = "";
 
 		if ((ptr = cv_get_val(d, str))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(str));
-			addlinknode(state.vals, ztrdup(eq));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(str));
+		    zaddlinknode(state.vals, ztrdup(eq));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1625,10 +1884,8 @@ cv_parse_word(Cvdef d)
 			eq = "";
 
 		    if ((ptr = cv_get_val(d, str))) {
-			PERMALLOC {
-			    addlinknode(state.vals, ztrdup(str));
-			    addlinknode(state.vals, ztrdup(eq));
-			} LASTALLOC;
+			zaddlinknode(state.vals, ztrdup(str));
+			zaddlinknode(state.vals, ztrdup(eq));
 
 			cv_inactive(d, ptr->xor);
 		    }
@@ -1647,10 +1904,8 @@ cv_parse_word(Cvdef d)
 	    for (str = compprefix; *str; str++) {
 		tmp[0] = *str;
 		if ((ptr = cv_get_val(d, tmp))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(tmp));
-			addlinknode(state.vals, ztrdup(""));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(tmp));
+		    zaddlinknode(state.vals, ztrdup(""));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1658,10 +1913,8 @@ cv_parse_word(Cvdef d)
 	    for (str = compsuffix; *str; str++) {
 		tmp[0] = *str;
 		if ((ptr = cv_get_val(d, tmp))) {
-		    PERMALLOC {
-			addlinknode(state.vals, ztrdup(tmp));
-			addlinknode(state.vals, ztrdup(""));
-		    } LASTALLOC;
+		    zaddlinknode(state.vals, ztrdup(tmp));
+		    zaddlinknode(state.vals, ztrdup(""));
 
 		    cv_inactive(d, ptr->xor);
 		}
@@ -1696,35 +1949,36 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     int min, max, n;
 
     if (incompfunc != 1) {
-	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
 	return 1;
     }
     if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
-	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
 	return 1;
     }
     if (args[0][1] != 'i' && !cv_parsed) {
-	zerrnam(nam, "no parsed state", NULL, 0);
+	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 'V': min = 3; max =  3; break;
     case 's': min = 1; max =  1; break;
     case 'd': min = 1; max =  1; break;
-    case 'L': min = 3; max =  3; break;
+    case 'L': min = 3; max =  4; break;
     case 'v': min = 1; max =  1; break;
     default:
-	zerrnam(nam, "invalid option: %s", args[0], 0);
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
 	return 1;
     }
     n = arrlen(args) - 1;
     if (n < min) {
-	zerrnam(nam, "not enough arguments", NULL, 0);
+	zwarnnam(nam, "not enough arguments", NULL, 0);
 	return 1;
     } else if (max >= 0 && n > max) {
-	zerrnam(nam, "too many arguments", NULL, 0);
+	zwarnnam(nam, "too many arguments", NULL, 0);
 	return 1;
     }
     switch (args[0][1]) {
@@ -1758,6 +2012,17 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 	    }
 	    return 1;
 	}
+    case 'C':
+	{
+	    Caarg arg = cv_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->opt));
+
+		return 0;
+	    }
+	    return 1;
+	}
     case 'V':
 	{
 	    LinkList noarg = newlinklist();
@@ -1813,6 +2078,9 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
 		setsparam(args[2], val->arg->descr);
 		setsparam(args[3], val->arg->action);
 
+		if (args[4])
+		    setsparam(args[4], ztrdup(val->name));
+
 		return 0;
 	    }
 	    return 1;
@@ -1838,37 +2106,508 @@ bin_compvalues(char *nam, char **args, char *ops, int func)
     return 1;
 }
 
+static int
+bin_compquote(char *nam, char **args, char *ops, int func)
+{
+    char *name;
+    struct value vbuf;
+    Value v;
+
+    /* Anything to do? */
+
+    if (!compqstack || !*compqstack)
+	return 0;
+
+    /* For all parameters given... */
+
+    while ((name = *args++)) {
+	name = dupstring(name);
+	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));
+		}
+		break;
+	    case PM_ARRAY:
+		{
+		    char **val = v->pm->gets.afn(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 = NULL;
+
+		    setarrvalue(v, new);
+		}
+		break;
+	    default:
+		zwarnnam(nam, "invalid parameter type: %s", args[-1], 0);
+	    }
+	} else
+	    zwarnnam(nam, "unknown parameter: %s", args[-1], 0);
+    }
+    return 0;
+}
+
+/* Tags stuff. */
+
+typedef struct ctags *Ctags;
+typedef struct ctset *Ctset;
+
+/* A bunch of tag sets. */
+
+struct ctags {
+    char **all;			/* all tags offered */
+    char *context;		/* the current context */
+    int init;			/* not yet used */
+    Ctset sets;			/* the tag sets */
+};
+
+/* A tag set. */
+
+struct ctset {
+    Ctset next;
+    char **tags;		/* the tags */
+    char *tag;			/* last tag checked for -A */
+    char **ptr;			/* ptr into tags for -A */
+};
+
+/* Array of tag-set infos. Index is the locallevel. */
+
+#define MAX_TAGS 256
+static Ctags comptags[MAX_TAGS];
+
+/* locallevel at last comptags -i */
+
+static int lasttaglevel;
+
+static void
+freectset(Ctset s)
+{
+    Ctset n;
+
+    while (s) {
+	n = s->next;
+
+	if (s->tags)
+	    freearray(s->tags);
+	zsfree(s->tag);
+	zfree(s, sizeof(*s));
+
+	s = n;
+    }
+}
+
+static void
+freectags(Ctags t)
+{
+    if (t) {
+	if (t->all)
+	    freearray(t->all);
+	zsfree(t->context);
+	freectset(t->sets);
+	zfree(t, sizeof(*t));
+    }
+}
+
+/* Set the tags for the current local level. */
+
+static void
+settags(int level, char **tags)
+{
+    Ctags t;
+
+    if (comptags[level])
+	freectags(comptags[level]);
+
+    comptags[level] = t = (Ctags) zalloc(sizeof(*t));
+
+    t->all = zarrdup(tags + 1);
+    t->context = ztrdup(*tags);
+    t->sets = NULL;
+    t->init = 1;
+}
+
+/* Check if an array contains a string. */
+
+static int
+arrcontains(char **a, char *s, int colon)
+{
+    char *p, *q;
+
+    while (*a) {
+	if (colon) {
+	    for (p = s, q = *a++; *p && *q && *p != ':' && *q != ':'; p++, q++)
+		if (*p != *q)
+		    break;
+	    if ((!*p || *p == ':') && (!*q || *q == ':'))
+		return 1;
+	} else if (!strcmp(*a++, s))
+	    return 1;
+    }
+    return 0;
+}
+
+static int
+bin_comptags(char *nam, char **args, char *ops, int func)
+{
+    int min, max, n, level;
+
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] ||
+	(args[0][2] && (args[0][2] != '-' || args[0][3]))) {
+	zwarnnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    level = locallevel - (args[0][2] ? 1 : 0);
+    if (level >= MAX_TAGS) {
+	zwarnnam(nam, "nesting level too deep", NULL, 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && args[0][1] != 'I' && !comptags[level]) {
+	zwarnnam(nam, "no tags registered", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i': min = 2; max = -1; break;
+    case 'C': min = 1; max =  1; break;
+    case 'T': min = 0; max =  0; break;
+    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;
+    default:
+	zwarnnam(nam, "invalid option: %s", args[0], 0);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zwarnnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zwarnnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	settags(level, args + 1);
+	lasttaglevel = level;
+	break;
+    case 'C':
+	setsparam(args[1], ztrdup(comptags[level]->context));
+	break;
+    case 'T':
+	return !comptags[level]->sets;
+    case 'N':
+	{
+	    Ctset s;
+
+	    if (comptags[level]->init)
+		comptags[level]->init = 0;
+	    else if ((s = comptags[level]->sets)) {
+		comptags[level]->sets = s->next;
+		s->next = NULL;
+		freectset(s);
+	    }
+	    return !comptags[level]->sets;
+	}
+    case 'R':
+	{
+	    Ctset s;
+
+	    return !((s = comptags[level]->sets) &&
+		     arrcontains(s->tags, args[1], 1));
+	}
+    case 'A':
+	{
+	    Ctset s;
+
+	    if (comptags[level] && (s = comptags[level]->sets)) {
+		char **q, *v = NULL;
+		int l = strlen(args[1]);
+
+		if (!s->tag || strcmp(s->tag, args[1])) {
+		    zsfree(s->tag);
+		    s->tag = ztrdup(args[1]);
+		    s->ptr = s->tags;
+		}
+		for (q = s->ptr; *q; q++) {
+		    if (strpfx(args[1], *q)) {
+			if (!(*q)[l]) {
+			    v = *q;
+			    break;
+			} else if ((*q)[l] == ':') {
+			    v = (*q) + l + 1;
+			    break;
+			}
+		    }
+		}
+		if (!v) {
+		    zsfree(s->tag);
+		    s->tag = NULL;
+		    return 1;
+		}
+		s->ptr = q + 1;
+		setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'S':
+	if (comptags[level]->sets) {
+	    char **ret;
+
+	    ret = zarrdup(comptags[level]->sets->tags);
+	    setaparam(args[1], ret);
+	} else
+	    return 1;
+
+	break;
+    }
+    return 0;
+}
+
+static int
+bin_comptry(char *nam, char **args, char *ops, int func)
+{
+    if (incompfunc != 1) {
+	zwarnnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (!lasttaglevel || !comptags[lasttaglevel]) {
+	zwarnnam(nam, "no tags registered", NULL, 0);
+	return 1;
+    }
+    if (*args) {
+	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 && !iblank(*s); s++) {
+			if (!c && *s == ':')
+			    c = p;
+			if (*s == '\\' && s[1])
+			    s++;
+			*p++ = *s;
+		    }
+		    if (*s)
+			s++;
+		    *p = '\0';
+		    if (*q) {
+			char *qq = dupstring(q);
+			if (c)
+			    *c = '\0';
+
+			tokenize(qq);
+			if (haswilds(qq)) {
+			    Patprog prog;
+
+			    if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
+				char **a, *n;
+				int l = (c ? strlen(c + 1) + 2 : 1), al;
+
+				for (a = all; *a; a++) {
+				    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;
+
+		    set = (Ctset) zalloc(sizeof(*set));
+
+		    a = set->tags = (char **) zalloc((num + 1) * sizeof(char *));
+		    for (node = firstnode(list); node; incnode(node))
+			*a++ = ztrdup((char *) getdata(node));
+
+		    *a = NULL;
+		    set->next = NULL;
+		    set->ptr = NULL;
+		    set->tag = NULL;
+
+		    if ((l = comptags[lasttaglevel]->sets)) {
+			while (l->next)
+			    l = l->next;
+
+			l->next = set;
+		    } else
+			comptags[lasttaglevel]->sets = set;
+		}
+	    }
+	} else {
+	    char **p, **q, **all;
+	    int sep = 0;
+
+	    if ((sep = !strcmp(*args, "-s")))
+		args++;
+
+	    for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
+		if (arrcontains(all, *p, 1)) {
+		    Ctset s;
+
+		    for (s = comptags[lasttaglevel]->sets; s; s = s->next)
+			if (arrcontains(s->tags, *p, 0))
+			    break;
+
+		    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;
+}
+
+static char *
+fmtstr(char *str, char c, char *repl)
+{
+    int len, num, rlen;
+    char *s, *ret, *rp;
+
+    len = strlen(str);
+    rlen = strlen(repl);
+
+    for (num = 0, s = str; *s; s++)
+	if (*s == '%' && s[1] == c)
+	    num++, s++;
+
+    ret = (char *) zhalloc((num * (rlen - 2)) + len + 1);
+
+    for (s = str, rp = ret; *s; s++) {
+	if (*s == '%' && s[1] == c) {
+	    strcpy(rp, repl);
+	    rp += rlen;
+	    s++;
+	} else
+	    *rp++ = *s;
+    }
+    *rp = '\0';
+
+    return ret;
+}
+
+static int
+bin_compfmt(char *nam, char **args, char *ops, int func)
+{
+    char *param = args[0], *str = args[1];
+
+    for (args += 2; *args; args++) {
+	if (args[0][1] != ':') {
+	    zwarnnam(nam, "invalid argument `%s'", args[0], 0);
+	    return 1;
+	}
+	str = fmtstr(str, **args, *args + 2);
+    }
+    setsparam(param, ztrdup(str));
+    return 0;
+}
 
 static struct builtin bintab[] = {
-    BUILTIN("compdisplay", 0, bin_compdisplay, 2, -1, 0, NULL, NULL),
     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("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),
 };
 
 
 /**/
 int
-setup_computil(Module m)
+setup_(Module m)
 {
     memset(cadef_cache, 0, sizeof(cadef_cache));
     memset(cvdef_cache, 0, sizeof(cvdef_cache));
 
+    memset(comptags, 0, sizeof(comptags));
+
+    lasttaglevel = 0;
+
     return 0;
 }
 
 /**/
 int
-boot_computil(Module m)
+boot_(Module m)
 {
     return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
 }
 
-#ifdef MODULE
-
 /**/
 int
-cleanup_computil(Module m)
+cleanup_(Module m)
 {
     deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
@@ -1876,16 +2615,17 @@ cleanup_computil(Module m)
 
 /**/
 int
-finish_computil(Module m)
+finish_(Module m)
 {
     int i;
 
     for (i = 0; i < MAX_CACACHE; i++)
-	free_cadef(cadef_cache[i]);
+	freecadef(cadef_cache[i]);
     for (i = 0; i < MAX_CVCACHE; i++)
-	free_cvdef(cvdef_cache[i]);
+	freecvdef(cvdef_cache[i]);
+
+    for (i = 0; i < MAX_TAGS; i++)
+	freectags(comptags[i]);
 
     return 0;
 }
-
-#endif