about summary refs log tree commit diff
path: root/Src/Zle/compctl.c
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
commite74702b467171dbdafb56dfe354794a212e020d9 (patch)
treec295b3e9b2e93e2de10331877442615b0f37e779 /Src/Zle/compctl.c
parentc175751b501a3a4cb40ad4787340a597ea769be4 (diff)
downloadzsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz
zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.xz
zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip
Initial revision
Diffstat (limited to 'Src/Zle/compctl.c')
-rw-r--r--Src/Zle/compctl.c1085
1 files changed, 1085 insertions, 0 deletions
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
new file mode 100644
index 000000000..658cf4161
--- /dev/null
+++ b/Src/Zle/compctl.c
@@ -0,0 +1,1085 @@
+/*
+ * compctl.c - the compctl builtin
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * 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 Paul Falstad 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 Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad 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 Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "compctl.mdh"
+#include "compctl.pro"
+
+#define COMP_LIST	(1<<0)	/* -L */
+#define COMP_COMMAND	(1<<1)	/* -C */
+#define COMP_DEFAULT	(1<<2)	/* -D */
+#define COMP_FIRST	(1<<3)	/* -T */
+#define COMP_REMOVE	(1<<4)
+
+#define COMP_SPECIAL (COMP_COMMAND|COMP_DEFAULT|COMP_FIRST)
+
+/* Flag for listing, command, default, or first completion */
+static int cclist;
+
+/* Mask for determining what to print */
+static unsigned long showmask = 0;
+
+/* Parse the basic flags for `compctl' */
+
+/**/
+static int
+get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
+{
+    /* Parse the basic flags for completion:
+     * first is a flag that we are not in extended completion,
+     * while hx indicates or (+) completion (need to know for
+     * default and command completion as the initial compctl is special). 
+     * cct is a temporary just to hold flags; it never needs freeing.
+     */
+    struct compctl cct;
+    char **argv = *av;
+    int ready = 0, hx = 0;
+
+    /* Handle `compctl + foo ...' specially:  turn it into
+     * a default compctl by removing it from the hash table.
+     */
+    if (first && argv[0][0] == '+' && !argv[0][1] &&
+	!(argv[1] && argv[1][0] == '-' && argv[1][1])) {
+	argv++;
+	if(argv[0] && argv[0][0] == '-')
+	    argv++;
+	*av = argv;
+	freecompctl(cc);
+ 	cclist = COMP_REMOVE;
+	return 0;
+    }
+
+    memset((void *)&cct, 0, sizeof(cct));
+
+    /* Loop through the flags until we have no more:        *
+     * those with arguments are not properly allocated yet, *
+     * we just hang on to the argument that was passed.     */
+    for (; !ready && argv[0] && argv[0][0] == '-' && (argv[0][1] || !first);) {
+	if (!argv[0][1])
+	    *argv = "-+";
+	while (!ready && *++(*argv)) {
+	    if(**argv == Meta)
+		*++*argv ^= 32;
+	    switch (**argv) {
+	    case 'f':
+		cct.mask |= CC_FILES;
+		break;
+	    case 'c':
+		cct.mask |= CC_COMMPATH;
+		break;
+	    case 'm':
+		cct.mask |= CC_EXTCMDS;
+		break;
+	    case 'w':
+		cct.mask |= CC_RESWDS;
+		break;
+	    case 'o':
+		cct.mask |= CC_OPTIONS;
+		break;
+	    case 'v':
+		cct.mask |= CC_VARS;
+		break;
+	    case 'b':
+		cct.mask |= CC_BINDINGS;
+		break;
+	    case 'A':
+		cct.mask |= CC_ARRAYS;
+		break;
+	    case 'I':
+		cct.mask |= CC_INTVARS;
+		break;
+	    case 'F':
+		cct.mask |= CC_SHFUNCS;
+		break;
+	    case 'p':
+		cct.mask |= CC_PARAMS;
+		break;
+	    case 'E':
+		cct.mask |= CC_ENVVARS;
+		break;
+	    case 'j':
+		cct.mask |= CC_JOBS;
+		break;
+	    case 'r':
+		cct.mask |= CC_RUNNING;
+		break;
+	    case 'z':
+		cct.mask |= CC_STOPPED;
+		break;
+	    case 'B':
+		cct.mask |= CC_BUILTINS;
+		break;
+	    case 'a':
+		cct.mask |= CC_ALREG | CC_ALGLOB;
+		break;
+	    case 'R':
+		cct.mask |= CC_ALREG;
+		break;
+	    case 'G':
+		cct.mask |= CC_ALGLOB;
+		break;
+	    case 'u':
+		cct.mask |= CC_USERS;
+		break;
+	    case 'd':
+		cct.mask |= CC_DISCMDS;
+		break;
+	    case 'e':
+		cct.mask |= CC_EXCMDS;
+		break;
+	    case 'N':
+		cct.mask |= CC_SCALARS;
+		break;
+	    case 'O':
+		cct.mask |= CC_READONLYS;
+		break;
+	    case 'Z':
+		cct.mask |= CC_SPECIALS;
+		break;
+	    case 'q':
+		cct.mask |= CC_REMOVE;
+		break;
+	    case 'U':
+		cct.mask |= CC_DELETE;
+		break;
+	    case 'n':
+		cct.mask |= CC_NAMED;
+		break;
+	    case 'Q':
+		cct.mask |= CC_QUOTEFLAG;
+		break;
+	    case '/':
+		cct.mask |= CC_DIRS;
+		break;
+	    case 'k':
+		if ((*argv)[1]) {
+		    cct.keyvar = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "variable name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.keyvar = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'K':
+		if ((*argv)[1]) {
+		    cct.func = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "function name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.func = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'Y':
+		cct.mask |= CC_EXPANDEXPL;
+		goto expl;
+	    case 'X':
+		cct.mask &= ~CC_EXPANDEXPL;
+	    expl:
+		if ((*argv)[1]) {
+		    cct.explain = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.explain = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'y':
+		if ((*argv)[1]) {
+		    cct.ylist = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "function/variable expected after -%c",
+			     NULL, **argv);
+		} else {
+		    cct.ylist = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'P':
+		if ((*argv)[1]) {
+		    cct.prefix = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.prefix = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'S':
+		if ((*argv)[1]) {
+		    cct.suffix = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "string expected after -%c", NULL, **argv);
+		    return 1;
+		} else {
+		    cct.suffix = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'g':
+		if ((*argv)[1]) {
+		    cct.glob = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "glob pattern expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.glob = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 's':
+		if ((*argv)[1]) {
+		    cct.str = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "command string expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.str = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'l':
+		if ((*argv)[1]) {
+		    cct.subcmd = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "command name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.subcmd = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'W':
+		if ((*argv)[1]) {
+		    cct.withd = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "path expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.withd = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'H':
+		if ((*argv)[1])
+		    cct.hnum = atoi((*argv) + 1);
+		else if (argv[1])
+		    cct.hnum = atoi(*++argv);
+		else {
+		    zwarnnam(name, "number expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		if (!argv[1]) {
+		    zwarnnam(name, "missing pattern after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		cct.hpat = *++argv;
+		if (cct.hnum < 1)
+		    cct.hnum = 0;
+		if (*cct.hpat == '*' && !cct.hpat[1])
+		    cct.hpat = "";
+		*argv = "" - 1;
+		break;
+	    case 'C':
+		if (first && !hx) {
+		    cclist |= COMP_COMMAND;
+		} else {
+		    zwarnnam(name, "misplaced command completion (-C) flag",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+	    case 'D':
+		if (first && !hx) {
+		    isdef = 1;
+		    cclist |= COMP_DEFAULT;
+		} else {
+		    zwarnnam(name, "misplaced default completion (-D) flag",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+ 	    case 'T':
+              if (first && !hx) {
+ 		    cclist |= COMP_FIRST;
+ 		} else {
+ 		    zwarnnam(name, "misplaced first completion (-T) flag",
+ 			    NULL, 0);
+ 		    return 1;
+ 		}
+ 		break;
+	    case 'L':
+		if (!first || hx) {
+		    zwarnnam(name, "illegal use of -L flag", NULL, 0);
+		    return 1;
+		}
+		cclist |= COMP_LIST;
+		break;
+	    case 'x':
+		if (!argv[1]) {
+		    zwarnnam(name, "condition expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		}
+		if (first) {
+		    argv++;
+		    if (get_xcompctl(name, &argv, &cct, isdef)) {
+			if (cct.ext)
+			    freecompctl(cct.ext);
+			return 1;
+		    }
+		    ready = 2;
+		} else {
+		    zwarnnam(name, "recursive extended completion not allowed",
+			    NULL, 0);
+		    return 1;
+		}
+		break;
+	    default:
+		if (!first && (**argv == '-' || **argv == '+'))
+		    (*argv)--, argv--, ready = 1;
+		else {
+		    zwarnnam(name, "bad option: -%c", NULL, **argv);
+		    return 1;
+		}
+	    }
+	}
+
+	if (*++argv && (!ready || ready == 2) &&
+	    **argv == '+' && !argv[0][1]) {
+	    /* There's an alternative (+) completion:  assign
+	     * what we have so far before moving on to that.
+	     */
+	    if (cc_assign(name, &cc, &cct, first && !hx))
+		return 1;
+
+	    hx = 1;
+	    ready = 0;
+
+	    if (!*++argv || **argv != '-' ||
+		(**argv == '-' && (!argv[0][1] ||
+				   (argv[0][1] == '-' && !argv[0][2])))) {
+		/* No argument to +, which means do default completion */
+		if (isdef)
+		    zwarnnam(name,
+			    "recursive xor'd default completions not allowed",
+			    NULL, 0);
+		else
+		    cc->xor = &cc_default;
+	    } else {
+		/* more flags follow:  prepare to loop again */
+		cc->xor = (Compctl) zcalloc(sizeof(*cc));
+		cc = cc->xor;
+		memset((void *)&cct, 0, sizeof(cct));
+	    }
+	}
+    }
+    if (!ready && *argv && **argv == '-')
+	argv++;
+
+    if (! (cct.mask & (CC_EXCMDS | CC_DISCMDS)))
+	cct.mask |= CC_EXCMDS;
+
+    /* assign the last set of flags we parsed */
+    if (cc_assign(name, &cc, &cct, first && !hx))
+	return 1;
+
+    *av = argv;
+
+    return 0;
+}
+
+/* Handle the -x ... -- part of compctl. */
+
+/**/
+static int
+get_xcompctl(char *name, char ***av, Compctl cc, int isdef)
+{
+    char **argv = *av, *t, *tt, sav;
+    int n, l = 0, ready = 0;
+    Compcond m, c, o;
+    Compctl *next = &(cc->ext);
+
+    while (!ready) {
+	/* o keeps track of or's, m remembers the starting condition,
+	 * c is the current condition being parsed
+	 */
+	o = m = c = (Compcond) zcalloc(sizeof(*c));
+	/* Loop over each condition:  something like 's[...][...], p[...]' */
+	for (t = *argv; *t;) {
+	    while (*t == ' ')
+		t++;
+	    /* First get the condition code */
+	    switch (*t) {
+	    case 's':
+		c->type = CCT_CURSUF;
+		break;
+	    case 'S':
+		c->type = CCT_CURPRE;
+		break;
+	    case 'p':
+		c->type = CCT_POS;
+		break;
+	    case 'c':
+		c->type = CCT_CURSTR;
+		break;
+	    case 'C':
+		c->type = CCT_CURPAT;
+		break;
+	    case 'w':
+		c->type = CCT_WORDSTR;
+		break;
+	    case 'W':
+		c->type = CCT_WORDPAT;
+		break;
+	    case 'n':
+		c->type = CCT_CURSUB;
+		break;
+	    case 'N':
+		c->type = CCT_CURSUBC;
+		break;
+	    case 'm':
+		c->type = CCT_NUMWORDS;
+		break;
+	    case 'r':
+		c->type = CCT_RANGESTR;
+		break;
+	    case 'R':
+		c->type = CCT_RANGEPAT;
+		break;
+	    default:
+		t[1] = '\0';
+		zwarnnam(name, "unknown condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    /* Now get the arguments in square brackets */
+	    if (t[1] != '[') {
+		t[1] = '\0';
+		zwarnnam(name, "expected condition after condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    t++;
+	    /* First count how many or'd arguments there are,
+	     * marking the active ]'s and ,'s with unprintable characters.
+	     */
+	    for (n = 0, tt = t; *tt == '['; n++) {
+		for (l = 1, tt++; *tt && l; tt++)
+		    if (*tt == '\\' && tt[1])
+			tt++;
+		    else if (*tt == '[')
+			l++;
+		    else if (*tt == ']')
+			l--;
+		    else if (l == 1 && *tt == ',')
+			*tt = '\201';
+		if (tt[-1] == ']')
+		    tt[-1] = '\200';
+	    }
+
+	    if (l) {
+		t[1] = '\0';
+		zwarnnam(name, "error after condition code: %s", t, 0);
+		zfree(m, sizeof(struct compcond));
+
+		return 1;
+	    }
+	    c->n = n;
+
+	    /* Allocate space for all the arguments of the conditions */
+	    if (c->type == CCT_POS ||
+		c->type == CCT_NUMWORDS) {
+		c->u.r.a = (int *)zcalloc(n * sizeof(int));
+		c->u.r.b = (int *)zcalloc(n * sizeof(int));
+	    } else if (c->type == CCT_CURSUF ||
+		       c->type == CCT_CURPRE)
+		c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+
+	    else if (c->type == CCT_RANGESTR ||
+		     c->type == CCT_RANGEPAT) {
+		c->u.l.a = (char **)zcalloc(n * sizeof(char *));
+		c->u.l.b = (char **)zcalloc(n * sizeof(char *));
+	    } else {
+		c->u.s.p = (int *)zcalloc(n * sizeof(int));
+		c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+	    }
+	    /* Now loop over the actual arguments */
+	    for (l = 0; *t == '['; l++, t++) {
+		for (t++; *t && *t == ' '; t++);
+		tt = t;
+		if (c->type == CCT_POS ||
+		    c->type == CCT_NUMWORDS) {
+		    /* p[...] or m[...]:  one or two numbers expected */
+		    for (; *t && *t != '\201' && *t != '\200'; t++);
+		    if (!(sav = *t)) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.r.a[l] = atoi(tt);
+		    /* Second argument is optional:  see if it's there */
+		    if (sav == '\200')
+			/* no:  copy first argument */
+			c->u.r.b[l] = c->u.r.a[l];
+		    else {
+			tt = ++t;
+			for (; *t && *t != '\200'; t++);
+			if (!*t) {
+			    zwarnnam(name, "error in condition", NULL, 0);
+			    freecompcond(m);
+			    return 1;
+			}
+			*t = '\0';
+			c->u.r.b[l] = atoi(tt);
+		    }
+		} else if (c->type == CCT_CURSUF ||
+			   c->type == CCT_CURPRE) {
+		    /* -s[..] or -S[..]:  single string expected */
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.s[l] = ztrdup(tt);
+		} else if (c->type == CCT_RANGESTR ||
+			   c->type == CCT_RANGEPAT) {
+		    /* -r[..,..] or -R[..,..]:  two strings expected */
+		    for (; *t && *t != '\201'; t++);
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.l.a[l] = ztrdup(tt);
+		    tt = ++t;
+		    /* any more commas are text, not active */
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.l.b[l] = ztrdup(tt);
+		} else {
+		    /* remaining patterns are number followed by string */
+		    for (; *t && *t != '\200' && *t != '\201'; t++);
+		    if (!*t || *t == '\200') {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.p[l] = atoi(tt);
+		    tt = ++t;
+		    for (; *t && *t != '\200'; t++)
+			if (*t == '\201')
+			    *t = ',';
+		    if (!*t) {
+			zwarnnam(name, "error in condition", NULL, 0);
+			freecompcond(m);
+			return 1;
+		    }
+		    *t = '\0';
+		    c->u.s.s[l] = ztrdup(tt);
+		}
+	    }
+	    while (*t == ' ')
+		t++;
+	    if (*t == ',') {
+		/* Another condition to `or' */
+		o->or = c = (Compcond) zcalloc(sizeof(*c));
+		o = c;
+		t++;
+	    } else if (*t) {
+		/* Another condition to `and' */
+		c->and = (Compcond) zcalloc(sizeof(*c));
+		c = c->and;
+	    }
+	}
+	/* Assign condition to current compctl */
+	*next = (Compctl) zcalloc(sizeof(*cc));
+	(*next)->cond = m;
+	argv++;
+	/* End of the condition; get the flags that go with it. */
+	if (get_compctl(name, &argv, *next, 0, isdef))
+	    return 1;
+ 	if ((!argv || !*argv) && (cclist & COMP_SPECIAL))
+ 	    /* default, first, or command completion finished */
+	    ready = 1;
+	else {
+	    /* see if we are looking for more conditions or are
+	     * ready to return (ready = 1)
+	     */
+	    if (!argv || !*argv || **argv != '-' ||
+		((!argv[0][1] || argv[0][1] == '+') && !argv[1])) {
+		zwarnnam(name, "missing command names", NULL, 0);
+		return 1;
+	    }
+	    if (!strcmp(*argv, "--"))
+		ready = 1;
+	    else if (!strcmp(*argv, "-+") && argv[1] &&
+		     !strcmp(argv[1], "--")) {
+		ready = 1;
+		argv++;
+	    }
+	    argv++;
+	    /* prepare to put the next lot of conditions on the end */
+	    next = &((*next)->next);
+	}
+    }
+    /* save position at end of parsing */
+    *av = argv - 1;
+    return 0;
+}
+
+/**/
+static int
+cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
+{
+    /* Copy over the details from the values in cct to those in *ccptr */
+    Compctl cc;
+
+    if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
+			cct->func || cct->explain || cct->ylist ||
+			cct->prefix)) {
+	zwarnnam(name, "illegal combination of options", NULL, 0);
+	return 1;
+    }
+
+    /* Handle assignment of new default or command completion */
+    if (reass && !(cclist & COMP_LIST)) {
+	/* if not listing */
+	if (cclist == (COMP_COMMAND|COMP_DEFAULT)
+	    || cclist == (COMP_COMMAND|COMP_FIRST)
+	    || cclist == (COMP_DEFAULT|COMP_FIRST)
+	    || cclist == COMP_SPECIAL) {
+ 	    zwarnnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0);
+	    /* ... because the following code wouldn't work. */
+	    return 1;
+	}
+	if (cclist & COMP_COMMAND) {
+	    /* command */
+	    *ccptr = &cc_compos;
+	    cc_reassign(*ccptr);
+	} else if (cclist & COMP_DEFAULT) {
+	    /* default */
+	    *ccptr = &cc_default;
+	    cc_reassign(*ccptr);
+ 	} else if (cclist & COMP_FIRST) {
+ 	    /* first */
+ 	    *ccptr = &cc_first;
+ 	    cc_reassign(*ccptr);
+	}
+    }
+
+    /* Free the old compctl */
+    cc = *ccptr;
+    zsfree(cc->keyvar);
+    zsfree(cc->glob);
+    zsfree(cc->str);
+    zsfree(cc->func);
+    zsfree(cc->explain);
+    zsfree(cc->ylist);
+    zsfree(cc->prefix);
+    zsfree(cc->suffix);
+    zsfree(cc->subcmd);
+    zsfree(cc->withd);
+    zsfree(cc->hpat);
+    
+    /* and copy over the new stuff, (permanently) allocating
+     * space for strings.
+     */
+    cc->mask = cct->mask;
+    cc->keyvar = ztrdup(cct->keyvar);
+    cc->glob = ztrdup(cct->glob);
+    cc->str = ztrdup(cct->str);
+    cc->func = ztrdup(cct->func);
+    cc->explain = ztrdup(cct->explain);
+    cc->ylist = ztrdup(cct->ylist);
+    cc->prefix = ztrdup(cct->prefix);
+    cc->suffix = ztrdup(cct->suffix);
+    cc->subcmd = ztrdup(cct->subcmd);
+    cc->withd = ztrdup(cct->withd);
+    cc->hpat = ztrdup(cct->hpat);
+    cc->hnum = cct->hnum;
+
+    /* careful with extended completion:  it's already allocated */
+    cc->ext = cct->ext;
+
+    return 0;
+}
+
+/**/
+static void
+cc_reassign(Compctl cc)
+{
+    /* Free up a new default or command completion:
+     * this is a hack to free up the parts which should be deleted,
+     * without removing the basic variable which is statically allocated
+     */
+    Compctl c2;
+
+    c2 = (Compctl) zcalloc(sizeof *cc);
+    c2->xor = cc->xor;
+    c2->ext = cc->ext;
+    c2->refc = 1;
+
+    freecompctl(c2);
+
+    cc->ext = cc->xor = NULL;
+}
+
+/**/
+static void
+compctl_process_cc(char **s, Compctl cc)
+{
+    Compctlp ccp;
+
+    if (cclist & COMP_REMOVE) {
+	/* Delete entries for the commands listed */
+	for (; *s; s++) {
+	    if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s)))
+		compctltab->freenode((HashNode) ccp);
+	}
+    } else {
+	/* Add the compctl just read to the hash table */
+
+	cc->refc = 0;
+	for (; *s; s++) {
+	    cc->refc++;
+	    ccp = (Compctlp) zalloc(sizeof *ccp);
+	    ccp->cc = cc;
+	    compctltab->addnode(compctltab, ztrdup(*s), ccp);
+	}
+    }
+}
+
+/* Print a `compctl' */
+
+/**/
+static void
+printcompctl(char *s, Compctl cc, int printflags)
+{
+    Compctl cc2;
+    char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
+    char *mss = " pcCwWsSnNmrR";
+    unsigned long t = 0x7fffffff;
+    unsigned long flags = cc->mask;
+    unsigned long oldshowmask;
+
+    if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS))
+	flags &= ~CC_EXCMDS;
+
+    /* If showmask is non-zero, then print only those *
+     * commands with that flag set.                   */
+    if (showmask && !(flags & showmask))
+	return;
+
+    /* Temporarily clear showmask in case we make *
+     * recursive calls to printcompctl.           */
+    oldshowmask = showmask;
+    showmask = 0;
+
+    /* print either command name or start of compctl command itself */
+    if (s) {
+	if (cclist & COMP_LIST) {
+	    printf("compctl");
+	    if (cc == &cc_compos)
+		printf(" -C");
+	    if (cc == &cc_default)
+		printf(" -D");
+	    if (cc == &cc_first)
+		printf(" -T");
+	} else
+	    quotedzputs(s, stdout);
+    }
+
+    /* loop through flags w/o args that are set, printing them if so */
+    if (flags & t) {
+	printf(" -");
+	if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB))
+	    putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB);
+	while (*css) {
+	    if (flags & t & 1)
+		putchar(*css);
+	    css++;
+	    flags >>= 1;
+	    t >>= 1;
+	}
+    }
+
+    /* now flags with arguments */
+    flags = cc->mask;
+    printif(cc->keyvar, 'k');
+    printif(cc->func, 'K');
+    printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X');
+    printif(cc->ylist, 'y');
+    printif(cc->prefix, 'P');
+    printif(cc->suffix, 'S');
+    printif(cc->glob, 'g');
+    printif(cc->str, 's');
+    printif(cc->subcmd, 'l');
+    printif(cc->withd, 'W');
+    if (cc->hpat) {
+	printf(" -H %d ", cc->hnum);
+	quotedzputs(cc->hpat, stdout);
+    }
+
+    /* now the -x ... -- extended completion part */
+    if (cc->ext) {
+	Compcond c, o;
+	int i;
+
+	cc2 = cc->ext;
+	printf(" -x");
+
+	while (cc2) {
+	    /* loop over conditions */
+	    c = cc2->cond;
+
+	    printf(" '");
+	    for (c = cc2->cond; c;) {
+		/* loop over or's */
+		o = c->or;
+		while (c) {
+		    /* loop over and's */
+		    putchar(mss[c->type]);
+
+		    for (i = 0; i < c->n; i++) {
+			/* for all [...]'s of a given condition */
+			putchar('[');
+			switch (c->type) {
+			case CCT_POS:
+			case CCT_NUMWORDS:
+			    printf("%d,%d", c->u.r.a[i], c->u.r.b[i]);
+			    break;
+			case CCT_CURSUF:
+			case CCT_CURPRE:
+			    printqt(c->u.s.s[i]);
+			    break;
+			case CCT_RANGESTR:
+			case CCT_RANGEPAT:
+			    printqt(c->u.l.a[i]);
+			    putchar(',');
+			    printqt(c->u.l.b[i]);
+			    break;
+			default:
+			    printf("%d,", c->u.s.p[i]);
+			    printqt(c->u.s.s[i]);
+			}
+			putchar(']');
+		    }
+		    if ((c = c->and))
+			putchar(' ');
+		}
+		if ((c = o))
+		    printf(" , ");
+	    }
+	    putchar('\'');
+	    c = cc2->cond;
+	    cc2->cond = NULL;
+	    /* now print the flags for the current condition */
+	    printcompctl(NULL, cc2, 0);
+	    cc2->cond = c;
+	    if ((cc2 = (Compctl) (cc2->next)))
+		printf(" -");
+	}
+	if (cclist & COMP_LIST)
+	    printf(" --");
+    }
+    if (cc && cc->xor) {
+	/* print xor'd (+) completions */
+	printf(" +");
+	if (cc->xor != &cc_default)
+	    printcompctl(NULL, cc->xor, 0);
+    }
+    if (s) {
+	if ((cclist & COMP_LIST) && (cc != &cc_compos)
+	    && (cc != &cc_default) && (cc != &cc_first)) {
+	    if(s[0] == '-' || s[0] == '+')
+		printf(" -");
+	    putchar(' ');
+	    quotedzputs(s, stdout);
+	}
+	putchar('\n');
+    }
+
+    showmask = oldshowmask;
+}
+
+/**/
+static void
+printcompctlp(HashNode hn, int printflags)
+{
+    Compctlp ccp = (Compctlp) hn;
+
+    /* Function needed for use by scanhashtable() */
+    printcompctl(ccp->nam, ccp->cc, printflags);
+}
+
+/* Main entry point for the `compctl' builtin */
+
+/**/
+static int
+bin_compctl(char *name, char **argv, char *ops, int func)
+{
+    Compctl cc = NULL;
+    int ret = 0;
+
+    /* clear static flags */
+    cclist = 0;
+    showmask = 0;
+
+    /* Parse all the arguments */
+    if (*argv) {
+	cc = (Compctl) zcalloc(sizeof(*cc));
+	if (get_compctl(name, &argv, cc, 1, 0)) {
+	    freecompctl(cc);
+	    return 1;
+	}
+
+	/* remember flags for printing */
+	showmask = cc->mask;
+	if ((showmask & CC_EXCMDS) && !(showmask & CC_DISCMDS))
+	    showmask &= ~CC_EXCMDS;
+
+	/* if no command arguments or just listing, we don't want cc */
+	if (!*argv || (cclist & COMP_LIST))
+	    freecompctl(cc);
+    }
+
+    /* If no commands and no -C, -T, or -D, print all the compctl's *
+     * If some flags (other than -C, -T, or -D) were given, then    *
+     * only print compctl containing those flags.                   */
+    if (!*argv && !(cclist & COMP_SPECIAL)) {
+	scanhashtable(compctltab, 1, 0, 0, compctltab->printnode, 0);
+	printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0);
+	printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0);
+ 	printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0);
+	return ret;
+    }
+
+    /* If we're listing and we've made it to here, then there are arguments *
+     * or a COMP_SPECIAL flag (-D, -C, -T), so print only those.            */
+    if (cclist & COMP_LIST) {
+	HashNode hn;
+	char **ptr;
+
+	showmask = 0;
+	for (ptr = argv; *ptr; ptr++) {
+	    if ((hn = compctltab->getnode(compctltab, *ptr))) {
+		compctltab->printnode(hn, 0);
+	    } else {
+		zwarnnam(name, "no compctl defined for %s", *ptr, 0);
+		ret = 1;
+	    }
+	}
+	if (cclist & COMP_COMMAND)
+	    printcompctl("", &cc_compos, 0);
+	if (cclist & COMP_DEFAULT)
+	    printcompctl("", &cc_default, 0);
+	if (cclist & COMP_FIRST)
+	    printcompctl("", &cc_first, 0);
+	return ret;
+    }
+
+    /* Assign the compctl to the commands given */
+    if (*argv) {
+	if(cclist & COMP_SPECIAL)
+	    /* Ideally we'd handle this properly, setting both the *
+	     * special and normal completions.  For the moment,    *
+	     * this is better than silently failing.               */
+	    zwarnnam(name, "extraneous commands ignored", NULL, 0);
+	else
+	    compctl_process_cc(argv, cc);
+    }
+
+    return ret;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_compctl(Module m)
+{
+    if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
+	return 1;
+    compctltab->printnode = printcompctlp;
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_compctl(Module m)
+{
+    compctltab->printnode = NULL;
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+#endif