about summary refs log tree commit diff
path: root/Src/Zle/compctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Zle/compctl.c')
-rw-r--r--Src/Zle/compctl.c664
1 files changed, 621 insertions, 43 deletions
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 658cf4161..7104bfc6e 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -29,6 +29,9 @@
 
 #include "compctl.mdh"
 #include "compctl.pro"
+#define GLOBAL_PROTOTYPES
+#include "zle_tricky.pro"
+#undef GLOBAL_PROTOTYPES
 
 #define COMP_LIST	(1<<0)	/* -L */
 #define COMP_COMMAND	(1<<1)	/* -C */
@@ -44,6 +47,328 @@ static int cclist;
 /* Mask for determining what to print */
 static unsigned long showmask = 0;
 
+/* This is a special return value for parse_cmatcher(), *
+ * signalling an error. */
+
+#define pcm_err ((Cmatcher) 1)
+
+/* Copy a list of completion matchers. */
+
+static Cmlist
+cpcmlist(Cmlist l)
+{
+    Cmlist r = NULL, *p = &r, n;
+
+    while (l) {
+	*p = n = (Cmlist) zalloc(sizeof(struct cmlist));
+	n->next = NULL;
+	n->matcher = cpcmatcher(l->matcher);
+	n->str = ztrdup(l->str);
+
+	p = &(n->next);
+	l = l->next;
+    }
+    return r;
+}
+
+/* Copy a completion matcher list. */
+
+/**/
+static Cmatcher
+cpcmatcher(Cmatcher m)
+{
+    Cmatcher r = NULL, *p = &r, n;
+
+    while (m) {
+	*p = n = (Cmatcher) zalloc(sizeof(struct cmatcher));
+
+	n->next = NULL;
+	n->flags = m->flags;
+	n->line = cpcpattern(m->line);
+	n->llen = m->llen;
+	n->word = cpcpattern(m->word);
+	n->wlen = m->wlen;
+	n->left = cpcpattern(m->left);
+	n->lalen = m->lalen;
+	n->right = cpcpattern(m->right);
+	n->ralen = m->ralen;
+
+	p = &(n->next);
+	m = m->next;
+    }
+    return r;
+}
+
+/* Copy a completion matcher pattern. */
+
+/**/
+static Cpattern
+cpcpattern(Cpattern o)
+{
+    Cpattern r = NULL, *p = &r, n;
+
+    while (o) {
+	*p = n = (Cpattern) zalloc(sizeof(struct cpattern));
+
+	n->next = NULL;
+	memcpy(n->tab, o->tab, 256);
+	n->equiv = o->equiv;
+
+	p = &(n->next);
+	o = o->next;
+    }
+    return r;
+}
+
+/* Try to get the global matcher from the given compctl. */
+
+/**/
+static int
+get_gmatcher(char *name, char **argv)
+{
+    if (!strcmp(*argv, "-M")) {
+	char **p = ++argv;
+	Cmlist l = NULL, *q = &l, n;
+	Cmatcher m;
+
+	while (*p) {
+	    if (**p++ == '-')
+		return 0;
+	}
+	while (*argv) {
+	    if ((m = parse_cmatcher(name, *argv)) == pcm_err)
+		return 2;
+	    *q = n = (Cmlist) halloc(sizeof(struct cmlist));
+	    n->next = NULL;
+	    n->matcher = m;
+	    n->str = *argv++;
+
+	    q = &(n->next);
+	}
+	freecmlist(cmatcher);
+	PERMALLOC {
+	    cmatcher = cpcmlist(l);
+	} LASTALLOC;
+
+	return 1;
+    }
+    return 0;
+}
+
+/* This prints the global matcher definitions. */
+
+/**/
+static void
+print_gmatcher(int ac)
+{
+    Cmlist p;
+
+    if ((p = cmatcher)) {
+	printf((ac ? "compctl -M" : "MATCH"));
+
+	while (p) {
+	    printf(" \'%s\'", p->str);
+
+	    p = p->next;
+	}
+	putchar('\n');
+    }
+}
+
+/* Parse a string for matcher control, containing multiple matchers. */
+
+/**/
+static Cmatcher
+parse_cmatcher(char *name, char *s)
+{
+    Cmatcher ret = NULL, r, n;
+    Cpattern line, word, left, right;
+    int fl, ll, wl, lal, ral, err;
+
+    if (!*s)
+	return NULL;
+
+    while (*s) {
+	while (*s && inblank(*s)) s++;
+
+	if (!*s) break;
+
+	switch (*s) {
+	case 'l': fl = CMF_LEFT; break;
+	case 'r': fl = CMF_RIGHT; break;
+	case 'm': fl = 0; break;
+	case 'L': fl = CMF_LEFT | CMF_LINE; break;
+	case 'R': fl = CMF_RIGHT | CMF_LINE; break;
+	case 'M': fl = CMF_LINE; break;
+	default:
+	    zwarnnam(name, "unknown match specification character `%c'", NULL, *s);
+	    return pcm_err;
+	}
+	if (s[1] != ':') {
+	    zwarnnam(name, "missing `:'", NULL, 0);
+	    return pcm_err;
+	}
+	s += 2;
+	if (!*s) {
+	    zwarnnam(name, "missing patterns", NULL, 0);
+	    return pcm_err;
+	}
+	if (fl & CMF_LEFT) {
+	    left = parse_pattern(name, &s, &lal, '|', &err);
+	    if (err)
+		return pcm_err;
+	    if (!*s || !*++s) {
+		zwarnnam(name, "missing line pattern", NULL, 0);
+		return pcm_err;
+	    }
+	} else
+	    left = NULL;
+
+	line = parse_pattern(name, &s, &ll, ((fl & CMF_RIGHT) ? '|' : '='),
+			     &err);
+	if (err)
+	    return pcm_err;
+	if (!*s || !*++s) {
+	    zwarnnam(name, ((fl & CMF_RIGHT) ? "missing right anchor" : "missing word pattern"), NULL, 0);
+	    return pcm_err;
+	}
+	if (fl & CMF_RIGHT) {
+	    right = parse_pattern(name, &s, &ral, '=', &err);
+	    if (err)
+		return pcm_err;
+	    if (!*s || !*++s) {
+		zwarnnam(name, "missing word pattern", NULL, 0);
+		return pcm_err;
+	    }
+	} else
+	    right = NULL;
+
+	if (*s == '*') {
+	    if (!(fl & (CMF_LEFT | CMF_RIGHT))) {
+		zwarnnam(name, "need anchor for `*'", NULL, 0);
+		return pcm_err;
+	    }
+	    word = NULL;
+	    wl = -1;
+	    s++;
+	} else {
+	    word = parse_pattern(name, &s, &wl, 0, &err);
+
+	    if (!word && !line) {
+		zwarnnam(name, "need non-empty word or line pattern", NULL, 0);
+		return pcm_err;
+	    }
+	}
+	if (err)
+	    return pcm_err;
+
+	n = (Cmatcher) zcalloc(sizeof(*ret));
+	n->next = NULL;
+	n->flags = fl;
+	n->line = line;
+	n->llen = ll;
+	n->word = word;
+	n->wlen = wl;
+	n->left = left;
+	n->lalen = lal;
+	n->right = right;
+	n->ralen = ral;
+
+	if (ret) r->next = n;
+	else ret = n;
+
+	r = n;
+    }
+    return ret;
+}
+
+/* Parse a pattern for matcher control. */
+
+/**/
+static Cpattern
+parse_pattern(char *name, char **sp, int *lp, char e, int *err)
+{
+    Cpattern ret = NULL, r, n;
+    unsigned char *s = (unsigned char *) *sp;
+    int l = 0;
+
+    *err = 0;
+
+    while (*s && (e ? (*s != e) : !inblank(*s))) {
+	n = (Cpattern) hcalloc(sizeof(*n));
+	n->next = NULL;
+	n->equiv = 0;
+
+	if (*s == '[') {
+	    s = parse_class(n, s + 1, ']');
+	    if (!*s) {
+		*err = 1;
+		zwarnnam(name, "unterminated character class", NULL, 0);
+		return NULL;
+	    }
+	} else if (*s == '{') {
+	    n->equiv = 1;
+	    s = parse_class(n, s + 1, '}');
+	    if (!*s) {
+		*err = 1;
+		zwarnnam(name, "unterminated character class", NULL, 0);
+		return NULL;
+	    }
+	} else if (*s == '?') {
+	    memset(n->tab, 1, 256);
+	} else if (*s == '*' || *s == '(' || *s == ')' || *s == '=') {
+	    *err = 1;
+	    zwarnnam(name, "invalid pattern character `%c'", NULL, *s);
+	    return NULL;
+	} else {
+	    if (*s == '\\' && s[1])
+		s++;
+
+	    memset(n->tab, 0, 256);
+	    n->tab[*s] = 1;
+	}
+	if (ret)
+	    r->next = n;
+	else
+	    ret = n;
+
+	r = n;
+
+	l++;
+	s++;
+    }
+    *sp = (char *) s;
+    *lp = l;
+    return ret;
+}
+
+/* Parse a character class for matcher control. */
+
+/**/
+static unsigned char *
+parse_class(Cpattern p, unsigned char *s, unsigned char e)
+{
+    int n = 0, i = 1, j, eq = (e == '}'), k = 1;
+
+    if (!eq && (*s == '!' || *s == '^') && s[1] != e) { n = 1; s++; }
+
+    memset(p->tab, n, 256);
+
+    n = !n;
+    while (*s && (k || *s != e)) {
+	if (s[1] == '-' && s[2] != e) {
+	    /* a run of characters */
+	    for (j = (int) *s; j <= (int) s[2]; j++)
+		p->tab[j] = (eq ? i++ : n);
+
+	    s += 3;
+	} else
+	    p->tab[*s++] = (eq ? i++ : n);
+	k = 0;
+    }
+    return s;
+}
+
 /* Parse the basic flags for `compctl' */
 
 /**/
@@ -176,6 +501,44 @@ get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
 	    case '/':
 		cct.mask |= CC_DIRS;
 		break;
+	    case 't':
+		{
+		    char *p;
+
+		    if ((*argv)[1]) {
+			p = (*argv) + 1;
+			*argv = "" - 1;
+		    } else if (!argv[1]) {
+			zwarnnam(name, "retry specification expected after -%c", NULL,
+				 **argv);
+			return 1;
+		    } else {
+			p = *++argv;
+			*argv = "" - 1;
+		    }
+		    while (*p) {
+			switch (*p) {
+			case '+':
+			    cct.mask2 |= CC_XORCONT;
+			    break;
+			case 'c':
+			    cct.mask2 |= CC_CCCONT;
+			    break;
+			case '-':
+			    cct.mask2 |= CC_PATCONT;
+			    break;
+			case 'x':
+			    cct.mask2 |= CC_DEFCONT;
+			    break;
+			default:
+			    zwarnnam(name, "invalid retry specification character `%c'",
+				     NULL, *p);
+			    return 1;
+			}
+			p++;
+		    }
+		}
+		break;
 	    case 'k':
 		if ((*argv)[1]) {
 		    cct.keyvar = (*argv) + 1;
@@ -307,6 +670,58 @@ get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
 		    *argv = "" - 1;
 		}
 		break;
+	    case 'J':
+		if ((*argv)[1]) {
+		    cct.gname = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "group name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.gname = *++argv;
+		    *argv = "" - 1;
+		}
+		break;
+	    case 'V':
+		if ((*argv)[1]) {
+		    cct.gname = (*argv) + 1;
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "group name expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    cct.gname = *++argv;
+		    *argv = "" - 1;
+		}
+		cct.mask2 |= CC_NOSORT;
+		break;
+	    case 'M':
+		if ((*argv)[1]) {
+		    if ((cct.matcher =
+			 parse_cmatcher(name, (cct.mstr = (*argv) + 1))) ==
+			pcm_err) {
+			cct.matcher = NULL;
+			cct.mstr = NULL;
+			return 1;
+		    }
+		    *argv = "" - 1;
+		} else if (!argv[1]) {
+		    zwarnnam(name, "matching specification expected after -%c", NULL,
+			    **argv);
+		    return 1;
+		} else {
+		    if ((cct.matcher =
+			 parse_cmatcher(name, (cct.mstr = *++argv))) ==
+			pcm_err) {
+			cct.matcher = NULL;
+			cct.mstr = NULL;
+			return 1;
+		    }
+		    *argv = "" - 1;
+		}
+		break;
 	    case 'H':
 		if ((*argv)[1])
 		    cct.hnum = atoi((*argv) + 1);
@@ -600,27 +1015,34 @@ get_xcompctl(char *name, char ***av, Compctl cc, int isdef)
 		    c->u.s.s[l] = ztrdup(tt);
 		} else if (c->type == CCT_RANGESTR ||
 			   c->type == CCT_RANGEPAT) {
+		    int hc;
+
 		    /* -r[..,..] or -R[..,..]:  two strings expected */
-		    for (; *t && *t != '\201'; t++);
+		    for (; *t && *t != '\201' && *t != '\200'; t++);
 		    if (!*t) {
 			zwarnnam(name, "error in condition", NULL, 0);
 			freecompcond(m);
 			return 1;
 		    }
+		    hc = (*t == '\201');
 		    *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;
+		    if (hc) {
+			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);
 		    }
-		    *t = '\0';
-		    c->u.l.b[l] = ztrdup(tt);
+		    else
+			c->u.l.b[l] = NULL;
 		} else {
 		    /* remaining patterns are number followed by string */
 		    for (; *t && *t != '\200' && *t != '\201'; t++);
@@ -700,13 +1122,6 @@ 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 */
@@ -746,11 +1161,15 @@ cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
     zsfree(cc->subcmd);
     zsfree(cc->withd);
     zsfree(cc->hpat);
-    
+    zsfree(cc->gname);
+    zsfree(cc->mstr);
+    freecmatcher(cc->matcher);
+
     /* and copy over the new stuff, (permanently) allocating
      * space for strings.
      */
     cc->mask = cct->mask;
+    cc->mask2 = cct->mask2;
     cc->keyvar = ztrdup(cct->keyvar);
     cc->glob = ztrdup(cct->glob);
     cc->str = ztrdup(cct->str);
@@ -761,8 +1180,13 @@ cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
     cc->suffix = ztrdup(cct->suffix);
     cc->subcmd = ztrdup(cct->subcmd);
     cc->withd = ztrdup(cct->withd);
+    cc->gname = ztrdup(cct->gname);
     cc->hpat = ztrdup(cct->hpat);
     cc->hnum = cct->hnum;
+    PERMALLOC {
+	cc->matcher = cpcmatcher(cct->matcher);
+    } LASTALLOC;
+    cc->mstr = ztrdup(cct->mstr);
 
     /* careful with extended completion:  it's already allocated */
     cc->ext = cct->ext;
@@ -790,16 +1214,62 @@ cc_reassign(Compctl cc)
     cc->ext = cc->xor = NULL;
 }
 
+/* Check if the given string is a pattern. If so, return one and tokenize *
+ * it. If not, we just remove the backslashes. */
+
+static int
+compctl_name_pat(char **p)
+{
+    char *s = *p;
+
+    tokenize(s = dupstring(s));
+    remnulargs(s);
+
+    if (haswilds(s)) {
+	*p = s;
+	return 1;
+    } else
+	*p = rembslash(*p);
+
+    return 0;
+}
+
+/* Delete the pattern compctl with the given name. */
+
+static void
+delpatcomp(char *n)
+{
+    Patcomp p, q;
+
+    for (q = 0, p = patcomps; p; q = p, p = p->next) {
+	if (!strcmp(n, p->pat)) {
+	    if (q)
+		q->next = p->next;
+	    else
+		patcomps = p->next;
+	    zsfree(p->pat);
+	    freecompctl(p->cc);
+	    free(p);
+
+	    break;
+	}
+    }
+}
+
 /**/
 static void
 compctl_process_cc(char **s, Compctl cc)
 {
     Compctlp ccp;
+    char *n;
 
     if (cclist & COMP_REMOVE) {
 	/* Delete entries for the commands listed */
 	for (; *s; s++) {
-	    if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s)))
+	    n = *s;
+	    if (compctl_name_pat(&n))
+		delpatcomp(n);
+	    else if ((ccp = (Compctlp) compctltab->removenode(compctltab, n)))
 		compctltab->freenode((HashNode) ccp);
 	}
     } else {
@@ -807,10 +1277,23 @@ compctl_process_cc(char **s, Compctl cc)
 
 	cc->refc = 0;
 	for (; *s; s++) {
+	    n = *s;
+
 	    cc->refc++;
-	    ccp = (Compctlp) zalloc(sizeof *ccp);
-	    ccp->cc = cc;
-	    compctltab->addnode(compctltab, ztrdup(*s), ccp);
+	    if (compctl_name_pat(&n)) {
+		Patcomp pc;
+
+		delpatcomp(n);
+		pc = zalloc(sizeof *pc);
+		pc->pat = ztrdup(n);
+		pc->cc = cc;
+		pc->next = patcomps;
+		patcomps = pc;
+	    } else {
+		ccp = (Compctlp) zalloc(sizeof *ccp);
+		ccp->cc = cc;
+		compctltab->addnode(compctltab, ztrdup(n), ccp);
+	    }
 	}
     }
 }
@@ -819,15 +1302,21 @@ compctl_process_cc(char **s, Compctl cc)
 
 /**/
 static void
-printcompctl(char *s, Compctl cc, int printflags)
+printcompctl(char *s, Compctl cc, int printflags, int ispat)
 {
     Compctl cc2;
     char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
     char *mss = " pcCwWsSnNmrR";
     unsigned long t = 0x7fffffff;
-    unsigned long flags = cc->mask;
+    unsigned long flags = cc->mask, flags2 = cc->mask2;
     unsigned long oldshowmask;
 
+    /* Printflags is used outside the standard compctl commands*/
+    if (printflags & PRINT_LIST)
+	cclist |= COMP_LIST;
+    else if (printflags & PRINT_TYPE)
+	cclist &= ~COMP_LIST;
+
     if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS))
 	flags &= ~CC_EXCMDS;
 
@@ -851,8 +1340,13 @@ printcompctl(char *s, Compctl cc, int printflags)
 		printf(" -D");
 	    if (cc == &cc_first)
 		printf(" -T");
+	} else if (ispat) {
+	    char *p = dupstring(s);
+
+	    untokenize(p);
+	    quotedzputs(p, stdout);
 	} else
-	    quotedzputs(s, stdout);
+	    quotedzputs(quotename(s, NULL, NULL, NULL), stdout);
     }
 
     /* loop through flags w/o args that are set, printing them if so */
@@ -868,9 +1362,23 @@ printcompctl(char *s, Compctl cc, int printflags)
 	    t >>= 1;
 	}
     }
-
+    if (flags2 & (CC_XORCONT | CC_CCCONT | CC_PATCONT | CC_DEFCONT)) {
+	printf(" -t");
+	if (flags2 & CC_XORCONT)
+	    putchar('+');
+	if (flags2 & CC_CCCONT)
+	    putchar('c');
+	if (flags2 & CC_PATCONT)
+	    putchar('-');
+	if (flags2 & CC_DEFCONT)
+	    putchar('x');
+    }
     /* now flags with arguments */
-    flags = cc->mask;
+    printif(cc->mstr, 'M');
+    if (flags2 & CC_NOSORT)
+	printif(cc->gname, 'V');
+    else
+	printif(cc->gname, 'J');
     printif(cc->keyvar, 'k');
     printif(cc->func, 'K');
     printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X');
@@ -940,7 +1448,7 @@ printcompctl(char *s, Compctl cc, int printflags)
 	    c = cc2->cond;
 	    cc2->cond = NULL;
 	    /* now print the flags for the current condition */
-	    printcompctl(NULL, cc2, 0);
+	    printcompctl(NULL, cc2, 0, 0);
 	    cc2->cond = c;
 	    if ((cc2 = (Compctl) (cc2->next)))
 		printf(" -");
@@ -952,7 +1460,7 @@ printcompctl(char *s, Compctl cc, int printflags)
 	/* print xor'd (+) completions */
 	printf(" +");
 	if (cc->xor != &cc_default)
-	    printcompctl(NULL, cc->xor, 0);
+	    printcompctl(NULL, cc->xor, 0, 0);
     }
     if (s) {
 	if ((cclist & COMP_LIST) && (cc != &cc_compos)
@@ -960,7 +1468,17 @@ printcompctl(char *s, Compctl cc, int printflags)
 	    if(s[0] == '-' || s[0] == '+')
 		printf(" -");
 	    putchar(' ');
-	    quotedzputs(s, stdout);
+	    if (ispat) {
+		char *p = dupstring(s);
+
+		untokenize(p);
+		quotedzputs(p, stdout);
+	    } else {
+		char *p = dupstring(s);
+
+		untokenize(p);
+		quotedzputs(quotename(p, NULL, NULL, NULL), stdout);
+	    }
 	}
 	putchar('\n');
     }
@@ -975,7 +1493,7 @@ printcompctlp(HashNode hn, int printflags)
     Compctlp ccp = (Compctlp) hn;
 
     /* Function needed for use by scanhashtable() */
-    printcompctl(ccp->nam, ccp->cc, printflags);
+    printcompctl(ccp->nam, ccp->cc, printflags, 0);
 }
 
 /* Main entry point for the `compctl' builtin */
@@ -991,8 +1509,14 @@ bin_compctl(char *name, char **argv, char *ops, int func)
     cclist = 0;
     showmask = 0;
 
+    instring = 0;
+
     /* Parse all the arguments */
     if (*argv) {
+	/* Let's see if this is a global matcher definition. */
+	if ((ret = get_gmatcher(name, argv)))
+	    return ret - 1;
+
 	cc = (Compctl) zcalloc(sizeof(*cc));
 	if (get_compctl(name, &argv, cc, 1, 0)) {
 	    freecompctl(cc);
@@ -1013,10 +1537,16 @@ bin_compctl(char *name, char **argv, char *ops, int func)
      * If some flags (other than -C, -T, or -D) were given, then    *
      * only print compctl containing those flags.                   */
     if (!*argv && !(cclist & COMP_SPECIAL)) {
+	Patcomp pc;
+
+	for (pc = patcomps; pc; pc = pc->next)
+	    printcompctl(pc->pat, pc->cc, 0, 1);
+
 	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);
+	printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0, 0);
+	printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0, 0);
+ 	printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0, 0);
+	print_gmatcher((cclist & COMP_LIST));
 	return ret;
     }
 
@@ -1024,23 +1554,35 @@ bin_compctl(char *name, char **argv, char *ops, int func)
      * or a COMP_SPECIAL flag (-D, -C, -T), so print only those.            */
     if (cclist & COMP_LIST) {
 	HashNode hn;
-	char **ptr;
+	char **ptr, *n;
 
 	showmask = 0;
 	for (ptr = argv; *ptr; ptr++) {
-	    if ((hn = compctltab->getnode(compctltab, *ptr))) {
+	    n = *ptr;
+	    if (compctl_name_pat(&n)) {
+		Patcomp pc;
+
+		for (pc = patcomps; pc; pc = pc->next)
+		    if (!strcmp(n, pc->pat)) {
+			printcompctl(pc->pat, pc->cc, 0, 1);
+			n = NULL;
+			break;
+		    }
+	    } else if ((hn = compctltab->getnode(compctltab, n))) {
 		compctltab->printnode(hn, 0);
-	    } else {
-		zwarnnam(name, "no compctl defined for %s", *ptr, 0);
+		n = NULL;
+	    }
+	    if (n) {
+		zwarnnam(name, "no compctl defined for %s", n, 0);
 		ret = 1;
 	    }
 	}
 	if (cclist & COMP_COMMAND)
-	    printcompctl("", &cc_compos, 0);
+	    printcompctl("", &cc_compos, 0, 0);
 	if (cclist & COMP_DEFAULT)
-	    printcompctl("", &cc_default, 0);
+	    printcompctl("", &cc_default, 0, 0);
 	if (cclist & COMP_FIRST)
-	    printcompctl("", &cc_first, 0);
+	    printcompctl("", &cc_first, 0, 0);
 	return ret;
     }
 
@@ -1058,6 +1600,38 @@ bin_compctl(char *name, char **argv, char *ops, int func)
     return ret;
 }
 
+/* Externally callable version of get_compctl.  Used for completion widgets */
+
+/**/
+static Compctl
+compctl_widget(char *name, char **argv)
+{
+  Compctl cc = (Compctl) zcalloc(sizeof(*cc));
+  cclist = 0;
+  showmask = 0;
+
+  if (get_compctl(name, &argv, cc, 1, 0)) {
+      freecompctl(cc);
+      return NULL;
+  }
+
+  if (cclist & COMP_REMOVE) {
+      zwarnnam(name, "use -D to delete widget", NULL, 0);
+      return NULL;
+  } else if (cclist) {
+      zwarnnam(name, "special options illegal in widget", NULL, 0);
+      freecompctl(cc);
+      return NULL;
+  } else if (*argv) {
+      zwarnnam(name, "command names illegal in widget", NULL, 0);
+      freecompctl(cc);
+      return NULL;
+  }
+  cc->refc++;
+
+  return cc;
+}
+
 static struct builtin bintab[] = {
     BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL),
 };
@@ -1069,6 +1643,8 @@ boot_compctl(Module m)
     if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
 	return 1;
     compctltab->printnode = printcompctlp;
+    printcompctlptr = printcompctl;
+    compctl_widgetptr = compctl_widget;
     return 0;
 }
 
@@ -1080,6 +1656,8 @@ cleanup_compctl(Module m)
 {
     compctltab->printnode = NULL;
     deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    printcompctlptr = NULL;
+    compctl_widgetptr = NULL;
     return 0;
 }
 #endif