about summary refs log tree commit diff
path: root/Src/Zle/computil.c
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-10-12 09:33:05 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-10-12 09:33:05 +0000
commita564f353d9f0e45d35cfbdee8f4340deae5b8691 (patch)
treea34161d456c2dd732112fc8e5fae25c6d9246743 /Src/Zle/computil.c
parentd3170c8ee6eb6aa6050d6531536e5e9fba278b57 (diff)
downloadzsh-a564f353d9f0e45d35cfbdee8f4340deae5b8691.tar.gz
zsh-a564f353d9f0e45d35cfbdee8f4340deae5b8691.tar.xz
zsh-a564f353d9f0e45d35cfbdee8f4340deae5b8691.zip
Initial revision
Diffstat (limited to 'Src/Zle/computil.c')
-rw-r--r--Src/Zle/computil.c1891
1 files changed, 1891 insertions, 0 deletions
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
new file mode 100644
index 000000000..aed3d9808
--- /dev/null
+++ b/Src/Zle/computil.c
@@ -0,0 +1,1891 @@
+/*
+ * computil.c - completion utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1999 Sven Wischnowsky
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Sven Wischnowsky or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Sven Wischnowsky and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Sven Wischnowsky and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "computil.mdh"
+#include "computil.pro"
+
+
+/* Help for `_display'. */
+
+typedef struct cdisp *Cdisp;
+
+struct cdisp {
+    int pre, suf, colon;
+};
+
+static void
+cdisp_calc(Cdisp disp, char **args)
+{
+    char *cp;
+    int i;
+
+    for (; *args; args++) {
+	if ((cp = strchr(*args, ':')) && cp[1]) {
+	    disp->colon++;
+	    if ((i = cp - *args) > disp->pre)
+		disp->pre = i;
+	    if ((i = strlen(cp + 1)) > disp->suf)
+		disp->suf = i;
+	}
+    }
+}
+
+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;
+};
+
+struct cdset {
+    Cdset next;
+    char **opts;
+    char **strs;
+    char **matches;
+};
+
+static struct cdstate cd_state;
+static int cd_parsed = 0;
+
+static void
+free_cdsets(Cdset p)
+{
+    Cdset n;
+
+    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);
+	zfree(p, sizeof(*p));
+    }
+}
+
+static int
+cd_init(char *nam, char *sep, char **args, int disp)
+{
+    Cdset *setp, set;
+    char **ap, *tmp;
+
+    if (cd_parsed) {
+	zsfree(cd_state.sep);
+	free_cdsets(cd_state.sets);
+    }
+    setp = &(cd_state.sets);
+    cd_state.sep = ztrdup(sep);
+    cd_state.sets = NULL;
+    cd_state.disp.pre = cd_state.disp.suf = cd_state.disp.colon = 0;
+    cd_state.showd = disp;
+
+    while (*args) {
+	*setp = set = (Cdset) zcalloc(sizeof(*set));
+	setp = &(set->next);
+
+	if (!(ap = get_user_var(*args))) {
+	    zerrnam(nam, "invalid argument: %s", *args, 0);
+	    return 1;
+	}
+	PERMALLOC {
+	    set->strs = arrdup(ap);
+	} LASTALLOC;
+
+	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);
+		return 1;
+	    }
+	    PERMALLOC {
+		set->matches = arrdup(ap);
+	    } LASTALLOC;
+	    args++;
+	}
+	for (ap = args; *args &&
+		 (args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
+	     args++);
+
+	tmp = *args;
+	*args = NULL;
+	PERMALLOC {
+	    set->opts = arrdup(ap);
+	} LASTALLOC;
+	if ((*args = tmp))
+	    args++;
+    }
+    return 0;
+}
+
+static int
+cd_get(char **params)
+{
+    Cdset set;
+
+    if ((set = cd_state.sets)) {
+	char **sd, **sdp, **md, **mdp, **ss, **ssp, **ms, **msp;
+	char **p, **mp, *cp;
+	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
+		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;
+	}
+	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) {
+		memset(buf, ' ', pre);
+		memcpy(buf, *p, (cp - *p));
+		strcpy(buf + suf, cp + 1);
+		*sdp++ = ztrdup(buf);
+		if (mp) {
+		    *mdp++ = ztrdup(*mp);
+		    if (*mp)
+			mp++;
+		} else {
+		    *cp = '\0';
+		    *mdp++ = ztrdup(*p);
+		    *cp = ':';
+		}
+	    } else {
+		if (cp)
+		    *cp = '\0';
+		*ssp++ = ztrdup(*p);
+		if (mp) {
+		    *msp++ = ztrdup(*mp);
+		    if (*mp)
+			mp++;
+		} else
+		    *msp++ = ztrdup(*p);
+		if (cp)
+		    *cp = ':';
+	    }
+	}
+	*sdp = *ssp = *mdp = *msp = NULL;
+
+	PERMALLOC {
+	    p = arrdup(set->opts);
+	} LASTALLOC;
+
+	setaparam(params[0], p);
+	setaparam(params[1], sd);
+	setaparam(params[2], md);
+	setaparam(params[3], ss);
+	setaparam(params[4], ms);
+
+	cd_state.sets = set->next;
+	set->next = NULL;
+	free_cdsets(set);
+
+	return 0;
+    }
+    return 1;
+}
+
+/**/
+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);
+	return 1;
+    }
+    if (!args[0][0] || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+    case 'I':
+	cd_parsed = 1;
+	return cd_init(nam, args[1], args + 2, (args[0][1] == 'I'));
+    case 'g':
+	if (cd_parsed) {
+	    int n = arrlen(args);
+
+	    if (n != 6) {
+		zerrnam(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);
+	    return 1;
+	}
+    }
+    zerrnam(nam, "invalid option: %s", args[0], 0);
+    return 1;
+}
+
+/* Help for `_arguments'. */
+
+typedef struct cadef *Cadef;
+typedef struct caopt *Caopt;
+typedef struct caarg *Caarg;
+
+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;
+};
+
+struct caopt {
+    Caopt next;
+    char *name;
+    char *descr;
+    char **xor;
+    int type;
+    Caarg args;
+    int active;
+    int num;
+};
+
+#define CAO_NEXT    1
+#define CAO_DIRECT  2
+#define CAO_ODIRECT 3
+#define CAO_EQUAL   4
+
+struct caarg {
+    Caarg next;
+    char *descr;
+    char *action;
+    int type;
+    char *end;
+    int num;
+};
+
+#define CAA_NORMAL 1
+#define CAA_OPT    2
+#define CAA_REST   3
+#define CAA_RARGS  4
+#define CAA_RREST  5
+
+#define MAX_CACACHE 8
+static Cadef cadef_cache[MAX_CACACHE];
+
+static int
+arrcmp(char **a, char **b)
+{
+    if (!a && !b)
+	return 1;
+    else if (!a || !b)
+	return 0;
+    else {
+	while (*a && *b)
+	    if (strcmp(*a++, *b++))
+		return 0;
+
+	return (!*a && !*b);
+    }
+}
+
+static void
+free_caargs(Caarg a)
+{
+    Caarg n;
+
+    for (; a; a = n) {
+	n = a->next;
+	zsfree(a->descr);
+	zsfree(a->action);
+	zsfree(a->end);
+	zfree(a, sizeof(*a));
+    }
+}
+
+static void
+free_cadef(Cadef d)
+{
+    if (d) {
+	Caopt p, n;
+
+	zsfree(d->match);
+	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);
+	    zfree(p, sizeof(*p));
+	}
+	free_caargs(d->args);
+	free_caargs(d->rest);
+	if (d->single)
+	    zfree(d->single, 256 * sizeof(Caopt));
+	zfree(d, sizeof(*d));
+    }
+}
+
+static char *
+rembslashcolon(char *s)
+{
+    char *p, *r;
+
+    r = p = s = dupstring(s);
+
+    while (*s) {
+	if (s[0] != '\\' || s[1] != ':')
+	    *p++ = *s;
+	s++;
+    }
+    *p = '\0';
+
+    return r;
+}
+
+static Caarg
+parse_caarg(int mult, int type, int num, char **def)
+{
+    Caarg ret = (Caarg) zalloc(sizeof(*ret));
+    char *p = *def, *d, sav;
+
+    ret->next = NULL;
+    ret->descr = ret->action = ret->end = NULL;
+    ret->num = num;
+    ret->type = type;
+
+    for (d = p; *p && *p != ':'; p++)
+	if (*p == '\\' && p[1])
+	    p++;
+    sav = *p;
+    *p = '\0';
+    ret->descr = ztrdup(rembslashcolon(d));
+    if (sav) {
+	if (mult) {
+	    for (d = ++p; *p && *p != ':'; p++)
+		if (*p == '\\' && p[1])
+		    p++;
+	    sav = *p;
+	    *p = '\0';
+	    ret->action = ztrdup(rembslashcolon(d));
+	    if (sav)
+		*p = ':';
+	} else
+	    ret->action = ztrdup(rembslashcolon(p + 1));
+    }
+    *def = p;
+
+    return ret;
+}
+
+static Cadef
+parse_cadef(char *nam, char **args)
+{
+    Cadef ret;
+    Caopt *optp;
+    char **oargs = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor;
+    char *adpre, *adsuf;
+    int single = 0, anum = 1, xnum, nopts, ndopts, nodopts;
+
+    nopts = ndopts = nodopts = 0;
+
+    for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
+
+    if (*p) {
+	*p = '\0';
+	adpre = dupstring(args[0]);
+	*p = '%';
+	adsuf = dupstring(p + 2);
+    } else
+	adpre = adsuf = NULL;
+
+    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++;
+		break;
+	    }
+	} else
+	    break;
+	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;
+
+    for (optp = &(ret->opts); *args; args++) {
+	p = dupstring(*args);
+	xnum = 0;
+	if (*p == '(') {
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    char **xp, sav;
+
+	    while (*p && *p != ')') {
+		for (p++; inblank(*p); p++);
+
+		if (*p == ')')
+		    break;
+		for (q = p++; *p && *p != ')' && !inblank(*p); p++);
+
+		if (!*p)
+		    break;
+
+		sav = *p;
+		*p = '\0';
+		addlinknode(list, dupstring(q));
+		xnum++;
+		*p = sav;
+	    }
+	    if (*p != ')') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
+	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
+		*xp = ztrdup((char *) getdata(node));
+	    xp[0] = xp[1] = NULL;
+
+	    p++;
+	} else
+	    xor = NULL;
+	
+	if (*p == '-' || *p == '+' ||
+	    (*p == '*' && (p[1] == '-' || p[1] == '+'))) {
+	    Caopt opt;
+	    Caarg oargs = NULL;
+	    int multi, otype = CAO_NEXT, again = 0;
+	    char *name, *descr, c;
+
+	    rec:
+
+	    if ((multi = (*p == '*')))
+		p++;
+
+	    if ((p[0] == '-' && p[1] == '+') ||
+		(p[0] == '+' && p[1] == '-')) {
+		name = ++p;
+		*p = (again ? '-' : '+');
+		again = 1 - again;
+	    } else {
+		name = p;
+		if (p[0] == '-' && p[1] == '-')
+		    p++;
+	    }
+	    for (p++; *p && *p != ':' && *p != '[' &&
+		     ((*p != '-' && *p != '+' && *p != '=') ||
+		      (p[1] != ':' && p[1] != '[')); p++)
+		if (*p == '\\' && p[1])
+		    p++;
+
+	    c = *p;
+	    *p = '\0';
+	    if (c == '-') {
+		otype = CAO_DIRECT;
+		c = *++p;
+	    } else if (c == '+') {
+		otype = CAO_ODIRECT;
+		c = *++p;
+	    } else if (c == '=') {
+		otype = CAO_EQUAL;
+		c = *++p;
+	    }
+	    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);
+		    return NULL;
+		}
+		*p++ = '\0';
+		c = *p;
+	    } else
+		descr = NULL;
+
+	    if (c && c != ':') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid option definition: %s", *args, 0);
+		return NULL;
+	    }
+	    if (!multi) {
+		if (!xor) {
+		    xor = (char **) zalloc(2 * sizeof(char *));
+		    xor[1] = NULL;
+		}
+		xor[xnum] = ztrdup(name);
+	    }
+	    if (c == ':') {
+		Caarg *oargp = &oargs;
+		int atype, rest;
+		char *end;
+
+		while (c == ':') {
+		    rest = 0;
+		    end = NULL;
+
+		    if (*++p == ':') {
+			atype = CAA_OPT;
+			p++;
+		    } else if (*p == '*') {
+			if (*++p != ':') {
+			    for (end = ++p; *p && *p != ':'; p++)
+				if (*p == '\\' && p[1])
+				    p++;
+			}
+			if (*p != ':') {
+			    free_cadef(ret);
+			    free_caargs(oargs);
+			    zerrnam(nam, "invalid option definition: %s",
+				    *args, 0);
+			    return NULL;
+			}
+			if (*++p == ':') {
+			    if (*++p == ':') {
+				atype = CAA_RREST;
+				p++;
+			    } else
+				atype = CAA_RARGS;
+			} else
+			    atype = CAA_REST;
+			rest = 1;
+		    } else
+			atype = CAA_NORMAL;
+		    *oargp = parse_caarg(!rest, atype, 0, &p);
+		    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)
+		    opt->descr = tricat(adpre, oargs->descr, adsuf);
+		else
+		    opt->descr = NULL;
+		opt->xor = xor;
+		opt->type = otype;
+		opt->args = oargs;
+		opt->num = nopts++;
+	    } LASTALLOC;
+
+	    if (otype == CAO_DIRECT)
+		ndopts++;
+	    else if (otype == CAO_ODIRECT || otype == CAO_EQUAL)
+		nodopts++;
+
+	    if (single && name[1] && !name[2])
+		ret->single[STOUC(name[1])] = opt;
+
+	    if (again) {
+		p = dupstring(*args);
+		goto rec;
+	    }
+	} else if (*p == '*') {
+	    int type = CAA_REST;
+
+	    if (*++p != ':') {
+		free_cadef(ret);
+		zerrnam(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);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		if (*++p == ':') {
+		    type = CAA_RREST;
+		    p++;
+		} else
+		    type = CAA_RARGS;
+	    }
+	    ret->rest = parse_caarg(0, type, -1, &p);
+	} else {
+	    int type = CAA_NORMAL;
+	    Caarg arg, tmp, pre;
+
+	    if (idigit(*p)) {
+		int num = 0;
+
+		while (*p && idigit(*p))
+		    num = (num * 10) + ((int) *p++);
+
+		anum = num + 1;
+	    } else
+		anum++;
+
+	    if (*p != ':') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		type = CAA_OPT;
+		p++;
+	    }
+	    arg = parse_caarg(0, type, anum - 1, &p);
+
+	    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);
+		return NULL;
+	    }
+	    arg->next = tmp;
+	    if (pre)
+		pre->next = arg;
+	    else
+		ret->args = arg;
+	}
+    }
+    ret->nopts = nopts;
+    ret->ndopts = ndopts;
+    ret->nodopts = nodopts;
+    
+    return ret;
+}
+
+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++)
+	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
+	    (*p)->lastt = time(0);
+
+	    return *p;
+	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
+	    min = p;
+    if (i)
+	min = p;
+    if ((new = parse_cadef(nam, args))) {
+	free_cadef(*min);
+	*min = new;
+    }
+    return new;
+}
+
+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 {
+	for (p = d->opts; p; p = p->next)
+	    if (p->active && p->args && p->type != CAO_NEXT &&
+		strpfx(p->name, line)) {
+		if (end) {
+		    int l = strlen(p->name);
+
+		    if (p->type == CAO_EQUAL && line[l] == '=')
+			l++;
+
+		    *end = line + l;
+		}
+		return p;
+	    }
+    }
+    return NULL;
+}
+
+static Caopt
+ca_get_sopt(Cadef d, char *line, int full, char **end)
+{
+    Caopt p;
+
+    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) {
+		if (end) {
+		    line++;
+		    if (p->type == CAO_EQUAL && *line == '=')
+			line++;
+		    *end = line;
+		}
+		break;
+	    } else if (!p || !p->active || (line[1] && p->args))
+		return NULL;
+	return p;
+    }
+    return NULL;
+}
+
+static Caarg
+ca_get_arg(Cadef d, int n)
+{
+    if (d->argsactive) {
+	Caarg a = d->args;
+
+	while (a && a->num < n)
+	    a = a->next;
+
+	if (a && a->num == n)
+	    return a;
+
+	return d->rest;
+    }
+    return NULL;
+}
+
+static void
+ca_inactive(Cadef d, char **xor)
+{
+    if (xor) {
+	Caopt opt;
+
+	for (; *xor; xor++) {
+	    if (xor[0][0] == ':' && !xor[0][1])
+		d->argsactive = 0;
+	    else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
+		opt->active = 0;
+	}
+    }
+}
+
+struct castate {
+    Cadef d;
+    Caarg def, ddef;
+    Caopt curopt;
+    int opt, arg, argbeg, optbeg, nargbeg, restbeg;
+    int inopt, inrest, inarg, nth, doff, singles;
+    LinkList args;
+    LinkList *oargs;
+};
+
+static struct castate ca_laststate;
+static int ca_parsed = 0, ca_alloced = 0;
+
+static void
+ca_parse_line(Cadef d)
+{
+    Caarg adef, ddef;
+    Caopt ptr;
+    struct castate state;
+    char *line, *pe;
+    int cur, doff;
+    Patprog endpat = NULL;
+
+    if (ca_alloced) {
+	int i = ca_laststate.d->nopts;
+	LinkList *p = ca_laststate.oargs;
+
+	freelinklist(ca_laststate.args, freestr);
+	while (i--)
+	    if (*p++)
+		freelinklist(p[-1], freestr);
+    }
+    for (ptr = d->opts; ptr; ptr = ptr->next)
+	ptr->active = 1;
+    d->argsactive = 1;
+
+    state.d = d;
+    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;
+    ca_alloced = 1;
+
+    memcpy(&ca_laststate, &state, sizeof(state));
+
+    if (!compwords[1]) {
+	ca_laststate.opt = ca_laststate.arg = 0;
+
+	return;
+    }
+    for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
+	 line; line = compwords[cur++]) {
+	ddef = adef = NULL;
+	doff = state.singles = 0;
+	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.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;
+		    continue;
+		}
+	    } else if ((state.def = state.def->next))
+		state.argbeg = cur;
+	    else
+		state.curopt = NULL;
+	} else {
+	    state.opt = (line[0] && line[1]);
+	    state.arg = 1;
+	    state.curopt = NULL;
+	}
+	pe = NULL;
+
+	if (state.opt && (state.curopt = ca_get_opt(d, line, 0, &pe))) {
+	    ddef = state.def = state.curopt->args;
+	    doff = pe - line;
+	    state.optbeg = state.argbeg = state.inopt = cur;
+	    PERMALLOC {
+		state.oargs[state.curopt->num] = newlinklist();
+	    } LASTALLOC;
+	    ca_inactive(d, state.curopt->xor);
+
+	    if (state.def &&
+		(state.curopt->type == CAO_DIRECT ||
+		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
+		 (state.curopt->type == CAO_EQUAL &&
+		  (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;
+		PERMALLOC {
+		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		} LASTALLOC;
+	    }
+	    if (!state.def)
+		state.curopt = NULL;
+	} else if (state.opt && d->single &&
+		   (state.curopt = ca_get_sopt(d, line, 0, &pe))) {
+	    char *p;
+	    Caopt tmpopt;
+
+	    ddef = state.def = state.curopt->args;
+	    doff = pe - line;
+	    state.optbeg = state.argbeg = state.inopt = cur;
+	    state.singles = !*pe;
+
+	    for (p = line + 1; p <= pe; p++) {
+		if ((tmpopt = d->single[STOUC(*p)])) {
+		    PERMALLOC {
+			state.oargs[tmpopt->num] = newlinklist();
+		    } LASTALLOC;
+		    ca_inactive(d, tmpopt->xor);
+		}
+	    }
+	    if (state.def &&
+		(state.curopt->type == CAO_DIRECT ||
+		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
+		 (state.curopt->type == CAO_EQUAL &&
+		  (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;
+		PERMALLOC {
+		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		} LASTALLOC;
+	    }
+	    if (!state.def)
+		state.curopt = NULL;
+	} else if (state.arg) {
+	    PERMALLOC {
+		addlinknode(state.args, ztrdup(line));
+	    } LASTALLOC;
+	    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;
+		}
+		break;
+	    }
+	    if (state.inopt) {
+		state.inopt = 0;
+		state.nargbeg = cur - 1;
+	    }
+	    if (state.def && state.def->type != CAA_NORMAL &&
+		state.def->type != CAA_OPT && state.inarg) {
+		state.restbeg = cur;
+		state.inarg = 0;
+	    } else if (!state.def || state.def->type == CAA_NORMAL ||
+		       state.def->type == CAA_OPT)
+		state.inarg = 1;
+	    state.nth++;
+	    state.def = NULL;
+	}
+	if (state.def && state.curopt &&
+	    (state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
+	    if (state.def->end)
+		endpat = patcompile(state.def->end, 0, NULL);
+	    else {
+		LinkList l = state.oargs[state.curopt->num];
+
+		for (; line; line = compwords[cur++]) {
+		    PERMALLOC {
+			addlinknode(l, line);
+		    } LASTALLOC;
+		}
+		break;
+	    }
+	}
+	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 {
+		ca_laststate.def = adef;
+		ca_laststate.ddef = NULL;
+		ca_laststate.argbeg = state.nargbeg;
+		ca_laststate.optbeg = state.restbeg;
+		ca_laststate.singles = state.singles;
+	    }
+	}
+    }
+}
+
+static char *
+ca_colonlist(LinkList l)
+{
+    if (l) {
+	LinkNode n;
+	int len = 1;
+	char *p, *ret, *q;
+
+	for (n = firstnode(l); n; incnode(n))
+	    for (p = (char *) getdata(n); *p; p++)
+		len += (*p == ':' ? 2 : 1);
+
+	ret = q = (char *) zalloc(len);
+
+	for (n = firstnode(l); n; incnode(n))
+	    for (p = (char *) getdata(n); *p; p++) {
+		if (*p == ':')
+		    *q++ = '\\';
+		*q++ = *p;
+	    }
+	*q = '\0';
+
+	return ret;
+    } else
+	return ztrdup("");
+}
+
+static int
+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);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && !ca_parsed) {
+	zerrnam(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 'O': min = 4; max =  4; break;
+    case 'L': min = 3; max =  3; 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);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zerrnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zerrnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	if (compcurrent > 1 && compwords[0]) {
+	    Cadef def = get_cadef(nam, args + 1);
+	    int cap = ca_parsed;
+
+	    ca_parsed = 0;
+
+	    if (!def)
+		return 1;
+
+	    ca_parsed = cap;
+	    ca_parse_line(def);
+	    ca_parsed = 1;
+
+	    return 0;
+	}
+	return 1;
+
+    case 'D':
+	{
+	    Caarg arg = ca_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->descr));
+		setsparam(args[2], ztrdup(arg->action));
+
+		ignore_prefix(ca_laststate.doff);
+		if (arg->type == CAA_RARGS)
+		    restrict_range(ca_laststate.argbeg - 1,
+				   arrlen(compwords) - 1);
+		else if (arg->type == CAA_RREST)
+		    restrict_range(ca_laststate.optbeg - 1,
+				   arrlen(compwords) - 1);
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'O':
+	if (ca_laststate.opt) {
+	    LinkList next = newlinklist();
+	    LinkList direct = newlinklist();
+	    LinkList odirect = newlinklist();
+	    LinkList equal = newlinklist(), l;
+	    Caopt p;
+	    char *str;
+
+	    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) {
+			int len = strlen(p->name) + strlen(p->descr) + 2;
+
+			str = (char *) zhalloc(len);
+			strcpy(str, p->name);
+			strcat(str, ":");
+			strcat(str, p->descr);
+		    } else
+			str = p->name;
+		    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);
+
+	    return 0;
+	} else
+	    return 1;
+    case 'L':
+	{
+	    Caopt opt = ca_get_opt(ca_laststate.d, args[1], 1, NULL);
+
+	    if (opt && opt->args) {
+		setsparam(args[2], ztrdup(opt->args->descr));
+		setsparam(args[3], ztrdup(opt->args->action));
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 's':
+	if (ca_laststate.d->single && ca_laststate.singles) {
+	    setsparam(args[1],
+		      ztrdup(ca_laststate.ddef ?
+			     (ca_laststate.ddef->type == CAO_DIRECT ?
+			      "direct" :
+			      (ca_laststate.ddef->type == CAO_EQUAL ?
+			       "equal" : "next")) : ""));
+	    return 0;
+	} else
+	    return 1;
+    case 'M':
+	setsparam(args[1], ztrdup(ca_laststate.d->match));
+	return 0;
+    case 'a':
+	return !(ca_laststate.d->args || ca_laststate.d->rest);
+    case 'W':
+	{
+	    char **ret, **p;
+	    LinkNode n;
+	    LinkList *a;
+	    Caopt o;
+	    int num;
+
+	    ret = p = zalloc((countlinknodes(ca_laststate.args) + 1) *
+			     sizeof(char *));
+
+	    for (n = firstnode(ca_laststate.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;
+
+	    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);
+		}
+	    }
+	    *p = NULL;
+
+	    sethparam(args[2], ret);
+	}
+	return 0;
+    }
+    return 1;
+}
+
+/* Help for `_values'. */
+
+typedef struct cvdef *Cvdef;
+typedef struct cvval *Cvval;
+
+struct cvdef {
+    char *descr;
+    int hassep;
+    char sep;
+    Cvdef next;
+    Cvval vals;
+    char **defs;
+    int ndefs;
+    int lastt;
+};
+
+struct cvval {
+    Cvval next;
+    char *name;
+    char *descr;
+    char **xor;
+    int type;
+    Caarg arg;
+    int active;
+};
+
+#define CVV_NOARG 0
+#define CVV_ARG   1
+#define CVV_OPT   2
+
+#define MAX_CVCACHE 8
+static Cvdef cvdef_cache[MAX_CVCACHE];
+
+static void
+free_cvdef(Cvdef d)
+{
+    if (d) {
+	Cvval p, n;
+
+	zsfree(d->descr);
+	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);
+	    zfree(p, sizeof(*p));
+	}
+	zfree(d, sizeof(*d));
+    }
+}
+
+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;
+
+    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);
+	    return NULL;
+	}
+	hassep = 1;
+	sep = args[1][0];
+	args += 2;
+    }
+    if (!args[0] || !args[1]) {
+	zerrnam(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;
+
+    for (valp = &(ret->vals); *args; args++) {
+	p = dupstring(*args);
+	xnum = 0;
+	if (*p == '(') {
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    char **xp, sav;
+
+	    while (*p && *p != ')') {
+		for (p++; inblank(*p); p++);
+
+		if (*p == ')')
+		    break;
+		for (q = p++; *p && *p != ')' && !inblank(*p); p++);
+
+		if (!*p)
+		    break;
+
+		sav = *p;
+		*p = '\0';
+		addlinknode(list, dupstring(q));
+		xnum++;
+		*p = sav;
+	    }
+	    if (*p != ')') {
+		free_cvdef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
+	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
+		*xp = ztrdup((char *) getdata(node));
+	    xp[0] = xp[1] = NULL;
+
+	    p++;
+	} else
+	    xor = NULL;
+
+	if ((multi = (*p == '*')))
+	    p++;
+
+	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);
+	    return NULL;
+	}
+	if ((c = *p) == '[') {
+	    *p = '\0';
+	    for (descr = ++p; *p && *p != ']'; p++)
+		if (*p == '\\' && p[1])
+		    p++;
+
+	    if (!*p) {
+		free_cvdef(ret);
+		zerrnam(nam, "invalid value definition: %s", *args, 0);
+		return NULL;
+	    }
+	    *p++ = '\0';
+	    c = *p;
+	} else {
+	    *p = '\0';
+	    descr = NULL;
+	}
+	if (c && c != ':') {
+	    free_cvdef(ret);
+	    zerrnam(nam, "invalid value definition: %s", *args, 0);
+	    return NULL;
+	}
+	if (!multi) {
+	    if (!xor) {
+		xor = (char **) zalloc(2 * sizeof(char *));
+		xor[1] = NULL;
+	    }
+	    xor[xnum] = ztrdup(name);
+	}
+	if (c == ':') {
+	    if (hassep && !sep) {
+		free_cvdef(ret);
+		zerrnam(nam, "no value with argument with empty separator allowed", NULL, 0);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		p++;
+		vtype = CVV_OPT;
+	    } else
+		vtype = CVV_ARG;
+	    arg = parse_caarg(0, 0, 0, &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;
+    }
+    return ret;
+}
+
+static Cvdef
+get_cvdef(char *nam, char **args)
+{
+    Cvdef *p, *min, new;
+    int i, na = arrlen(args);
+
+    for (i = MAX_CVCACHE, p = cvdef_cache, min = NULL; *p && i--; p++)
+	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
+	    (*p)->lastt = time(0);
+
+	    return *p;
+	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
+	    min = p;
+    if (i)
+	min = p;
+    if ((new = parse_cvdef(nam, args))) {
+	free_cvdef(*min);
+	*min = new;
+    }
+    return new;
+}
+
+static Cvval
+cv_get_val(Cvdef d, char *name)
+{
+    Cvval p;
+
+    for (p = d->vals; p; p = p->next)
+	if (!strcmp(name, p->name))
+	    return p;
+
+    return NULL;
+}
+
+static void
+cv_inactive(Cvdef d, char **xor)
+{
+    if (xor) {
+	Cvval val;
+
+	for (; *xor; xor++)
+	    if ((val = cv_get_val(d, *xor)))
+		val->active = 0;
+    }
+}
+
+struct cvstate {
+    Cvdef d;
+    Caarg def;
+    Cvval val;
+    LinkList vals;
+};
+
+static struct cvstate cv_laststate;
+static int cv_parsed = 0, cv_alloced = 0;
+
+static void
+cv_parse_word(Cvdef d)
+{
+    Cvval ptr;
+    struct cvstate state;
+    char *str, *eq;
+
+    if (cv_alloced)
+	freelinklist(cv_laststate.vals, freestr);
+
+    for (ptr = d->vals; ptr; ptr = ptr->next)
+	ptr->active = 1;
+
+    state.d = d;
+    state.def = NULL;
+    state.val = NULL;
+    PERMALLOC {
+	state.vals = (LinkList) newlinklist();
+    } LASTALLOC;
+    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))) {
+		    PERMALLOC {
+			addlinknode(state.vals, ztrdup(str));
+			addlinknode(state.vals, ztrdup(eq));
+		    } LASTALLOC;
+
+		    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))) {
+			PERMALLOC {
+			    addlinknode(state.vals, ztrdup(str));
+			    addlinknode(state.vals, ztrdup(eq));
+			} LASTALLOC;
+
+			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))) {
+		    PERMALLOC {
+			addlinknode(state.vals, ztrdup(tmp));
+			addlinknode(state.vals, ztrdup(""));
+		    } LASTALLOC;
+
+		    cv_inactive(d, ptr->xor);
+		}
+	    }
+	    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;
+
+		    cv_inactive(d, ptr->xor);
+		}
+	    }
+	    ignore_prefix(strlen(compprefix));
+	    ignore_suffix(strlen(compsuffix));
+	}
+    }
+    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] = '=';
+    }
+    memcpy(&cv_laststate, &state, sizeof(state));
+}
+
+static int
+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);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && !cv_parsed) {
+	zerrnam(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 '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 'v': min = 1; max =  1; break;
+    default:
+	zerrnam(nam, "invalid option: %s", args[0], 0);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zerrnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zerrnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	{
+	    Cvdef def = get_cvdef(nam, args + 1);
+	    int cvp = cv_parsed;
+
+	    cv_parsed = 0;
+
+	    if (!def)
+		return 1;
+
+	    cv_parsed = cvp;
+	    cv_parse_word(def);
+	    cv_parsed = 1;
+
+	    return 0;
+	}
+	return 1;
+
+    case 'D':
+	{
+	    Caarg arg = cv_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->descr));
+		setsparam(args[2], ztrdup(arg->action));
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'V':
+	{
+	    LinkList noarg = newlinklist();
+	    LinkList arg = newlinklist();
+	    LinkList opt = newlinklist(), l;
+	    Cvval p;
+	    char *str;
+
+	    for (p = cv_laststate.d->vals; p; p = p->next) {
+		if (p->active) {
+		    switch (p->type) {
+		    case CVV_NOARG: l = noarg; break;
+		    case CVV_ARG:   l = arg;   break;
+		    default:        l = opt;   break;
+		    }
+		    if (p->descr) {
+			int len = strlen(p->name) + strlen(p->descr) + 2;
+
+			str = (char *) zhalloc(len);
+			strcpy(str, p->name);
+			strcat(str, ":");
+			strcat(str, p->descr);
+		    } else
+			str = p->name;
+		    addlinknode(l, str);
+		}
+	    }
+	    set_list_array(args[1], noarg);
+	    set_list_array(args[2], arg);
+	    set_list_array(args[3], opt);
+
+	    return 0;
+	}
+    case 's':
+	if (cv_laststate.d->hassep) {
+	    char tmp[2];
+
+	    tmp[0] = cv_laststate.d->sep;
+	    tmp[1] = '\0';
+	    setsparam(args[1], ztrdup(tmp));
+
+	    return 0;
+	}
+	return 1;
+    case 'd':
+	setsparam(args[1], ztrdup(cv_laststate.d->descr));
+	return 0;
+    case 'L':
+	{
+	    Cvval val = cv_get_val(cv_laststate.d, args[1]);
+
+	    if (val && val->arg) {
+		setsparam(args[2], val->arg->descr);
+		setsparam(args[3], val->arg->action);
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'v':
+	if (cv_laststate.vals) {
+	    char **ret, **p;
+	    LinkNode n;
+
+	    ret = (char **) zalloc((countlinknodes(cv_laststate.vals) + 1) *
+				   sizeof(char *));
+
+	    for (n = firstnode(cv_laststate.vals), p = ret; n; incnode(n), p++)
+		*p = ztrdup((char *) getdata(n));
+	    *p = NULL;
+
+	    sethparam(args[1], ret);
+
+	    return 0;
+	}
+	return 1;
+    }
+    return 1;
+}
+
+
+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),
+};
+
+
+/**/
+int
+setup_computil(Module m)
+{
+    memset(cadef_cache, 0, sizeof(cadef_cache));
+    memset(cvdef_cache, 0, sizeof(cvdef_cache));
+
+    return 0;
+}
+
+/**/
+int
+boot_computil(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_computil(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+/**/
+int
+finish_computil(Module m)
+{
+    int i;
+
+    for (i = 0; i < MAX_CACACHE; i++)
+	free_cadef(cadef_cache[i]);
+    for (i = 0; i < MAX_CVCACHE; i++)
+	free_cvdef(cvdef_cache[i]);
+
+    return 0;
+}
+
+#endif