/* * 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" /* Global matcher. */ /**/ static Cmlist cmatcher; /* Default completion infos */ /**/ struct compctl cc_compos, cc_default, cc_first, cc_dummy; /* Hash table for completion info for commands */ /**/ HashTable compctltab; /* List of pattern compctls */ /**/ Patcomp patcomps; #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_LISTMATCH (1<<5) /* -L and -M */ #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; /**/ static void createcompctltable(void) { compctltab = newhashtable(23, "compctltab", NULL); compctltab->hash = hasher; compctltab->emptytable = emptyhashtable; compctltab->filltable = NULL; compctltab->cmpnodes = strcmp; compctltab->addnode = addhashnode; compctltab->getnode = gethashnode2; compctltab->getnode2 = gethashnode2; compctltab->removenode = removehashnode; compctltab->disablenode = NULL; compctltab->enablenode = NULL; compctltab->freenode = freecompctlp; compctltab->printnode = printcompctlp; patcomps = NULL; } /**/ static void freecompctlp(HashNode hn) { Compctlp ccp = (Compctlp) hn; zsfree(ccp->nam); freecompctl(ccp->cc); zfree(ccp, sizeof(struct compctlp)); } /**/ void freecompctl(Compctl cc) { if (cc == &cc_default || cc == &cc_first || cc == &cc_compos || --cc->refc > 0) return; 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->hpat); zsfree(cc->gname); zsfree(cc->subcmd); zsfree(cc->substr); if (cc->cond) freecompcond(cc->cond); if (cc->ext) { Compctl n, m; n = cc->ext; do { m = (Compctl) (n->next); freecompctl(n); n = m; } while (n); } if (cc->xor && cc->xor != &cc_default) freecompctl(cc->xor); if (cc->matcher) freecmatcher(cc->matcher); zsfree(cc->mstr); zfree(cc, sizeof(struct compctl)); } /**/ void freecompcond(void *a) { Compcond cc = (Compcond) a; Compcond and, or, c; int n; for (c = cc; c; c = or) { or = c->or; for (; c; c = and) { and = c->and; if (c->type == CCT_POS || c->type == CCT_NUMWORDS) { free(c->u.r.a); free(c->u.r.b); } else if (c->type == CCT_CURSUF || c->type == CCT_CURPRE) { for (n = 0; n < c->n; n++) if (c->u.s.s[n]) zsfree(c->u.s.s[n]); free(c->u.s.s); } else if (c->type == CCT_RANGESTR || c->type == CCT_RANGEPAT) { for (n = 0; n < c->n; n++) if (c->u.l.a[n]) zsfree(c->u.l.a[n]); free(c->u.l.a); for (n = 0; n < c->n; n++) if (c->u.l.b[n]) zsfree(c->u.l.b[n]); free(c->u.l.b); } else { for (n = 0; n < c->n; n++) if (c->u.s.s[n]) zsfree(c->u.s.s[n]); free(c->u.s.p); free(c->u.s.s); } zfree(c, sizeof(struct compcond)); } } } /**/ int compctlread(char *name, char **args, char *ops, char *reply) { char *buf, *bptr; /* only allowed to be called for completion */ if (!incompctlfunc) { zwarnnam(name, "option valid only in functions called for completion", NULL, 0); return 1; } if (ops['l']) { /* -ln gives the index of the word the cursor is currently on, which is available in cs (but remember that Zsh counts from one, not zero!) */ if (ops['n']) { char nbuf[14]; if (ops['e'] || ops['E']) printf("%d\n", cs + 1); if (!ops['e']) { sprintf(nbuf, "%d", cs + 1); setsparam(reply, ztrdup(nbuf)); } return 0; } /* without -n, the current line is assigned to the given parameter as a scalar */ if (ops['e'] || ops['E']) { zputs((char *) line, stdout); putchar('\n'); } if (!ops['e']) setsparam(reply, ztrdup((char *) line)); } else { int i; /* -cn gives the current cursor position within the current word, which is available in clwpos (but remember that Zsh counts from one, not zero!) */ if (ops['n']) { char nbuf[14]; if (ops['e'] || ops['E']) printf("%d\n", clwpos + 1); if (!ops['e']) { sprintf(nbuf, "%d", clwpos + 1); setsparam(reply, ztrdup(nbuf)); } return 0; } /* without -n, the words of the current line are assigned to the given parameters separately */ if (ops['A'] && !ops['e']) { /* the -A option means that one array is specified, instead of many parameters */ char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *)); for (i = 0, p = b; i < clwnum; p++, i++) *p = ztrdup(clwords[i]); setaparam(reply, b); return 0; } if (ops['e'] || ops['E']) { for (i = 0; i < clwnum; i++) { zputs(clwords[i], stdout); putchar('\n'); } if (ops['e']) return 0; } for (i = 0; i < clwnum && *args; reply = *args++, i++) setsparam(reply, ztrdup(clwords[i])); if (i < clwnum) { int j, len; for (j = i, len = 0; j < clwnum; len += strlen(clwords[j++])); bptr = buf = zalloc(len + j - i); while (i < clwnum) { strucpy(&bptr, clwords[i++]); *bptr++ = ' '; } bptr[-1] = '\0'; } else buf = ztrdup(""); setsparam(reply, buf); } return 0; } /* 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; } /* Set the global match specs. */ /**/ static int set_gmatcher(char *name, char **argv) { Cmlist l = NULL, *q = &l, n; Cmatcher m; while (*argv) { if ((m = parse_cmatcher(name, *argv)) == pcm_err) return 1; *q = n = (Cmlist) zhalloc(sizeof(struct cmlist)); n->next = NULL; n->matcher = m; n->str = *argv++; q = &(n->next); } freecmlist(cmatcher); cmatcher = cpcmlist(l); return 1; } /* 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; while (*p) { if (**p++ == '-') return 0; } if (set_gmatcher(name, argv)) return 2; 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 the basic flags for `compctl' */ /**/ static int get_compctl(char *name, char ***av, Compctl cc, int first, int isdef, int cl) { /* 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; if (cl) return 1; else { freecompctl(cc); cclist = COMP_REMOVE; return 0; } } memset((void *)&cct, 0, sizeof(cct)); cct.mask2 = CC_CCCONT; /* 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 't': { char *p; if (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } 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; } switch (*p) { case '+': cct.mask2 = CC_XORCONT; break; case 'n': cct.mask2 = 0; 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; } if (p[1]) { zwarnnam(name, "too many retry specification characters: `%s'", p + 1, 0); return 1; } } 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 (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } else 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 'h': if (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } else if ((*argv)[1]) { cct.substr = (*argv) + 1; *argv = "" - 1; } else if (!argv[1]) { zwarnnam(name, "command name expected after -%c", NULL, **argv); return 1; } else { cct.substr = *++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 '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 '1': cct.mask2 |= CC_UNIQALL; cct.mask2 &= ~CC_UNIQCON; break; case '2': cct.mask2 |= CC_UNIQCON; cct.mask2 &= ~CC_UNIQALL; break; case 'M': if (cclist & COMP_LIST) { cclist |= COMP_LISTMATCH; } else 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); 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 (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } if (first && !hx) { cclist |= COMP_COMMAND; } else { zwarnnam(name, "misplaced command completion (-C) flag", NULL, 0); return 1; } break; case 'D': if (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } if (first && !hx) { isdef = 1; cclist |= COMP_DEFAULT; } else { zwarnnam(name, "misplaced default completion (-D) flag", NULL, 0); return 1; } break; case 'T': if (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } if (first && !hx) { cclist |= COMP_FIRST; } else { zwarnnam(name, "misplaced first completion (-T) flag", NULL, 0); return 1; } break; case 'L': if (cl) { zwarnnam(name, "illegal option -%c", NULL, **argv); return 1; } if (!first || hx) { zwarnnam(name, "illegal use of -L flag", NULL, 0); return 1; } cclist |= COMP_LIST; break; case 'x': if (cl) { zwarnnam(name, "extended completion not allowed", NULL, 0); return 1; } 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]) { if (cl) { zwarnnam(name, "xor'ed completion illegal", NULL, 0); return 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)); cct.mask2 = CC_CCCONT; } } } 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 'q': c->type = CCT_QUOTE; break; 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->type == CCT_QUOTE) 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 || c->type == CCT_QUOTE) { /* -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) { int hc; /* -r[..,..] or -R[..,..]: two strings expected */ 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); 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); } else c->u.l.b[l] = NULL; } 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, 0)) 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; /* 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->substr); 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); 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->substr = ztrdup(cct->substr); cc->withd = ztrdup(cct->withd); cc->gname = ztrdup(cct->gname); cc->hpat = ztrdup(cct->hpat); cc->hnum = cct->hnum; cc->matcher = cpcmatcher(cct->matcher); cc->mstr = ztrdup(cct->mstr); /* 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; } /* 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++) { n = *s; if (compctl_name_pat(&n)) delpatcomp(n); else if ((ccp = (Compctlp) compctltab->removenode(compctltab, n))) compctltab->freenode((HashNode) ccp); } } else { /* Add the compctl just read to the hash table */ cc->refc = 0; for (; *s; s++) { n = *s; cc->refc++; 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); } } } } /* Print a `compctl' */ /**/ static void printcompctl(char *s, Compctl cc, int printflags, int ispat) { Compctl cc2; char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/"; char *mss = " pcCwWsSnNmrRq"; unsigned long t = 0x7fffffff; 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; /* 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 if (ispat) { char *p = dupstring(s); untokenize(p); quotedzputs(p, stdout); } else quotedzputs(bslashquote(s, NULL, 0), stdout); } /* loop through flags w/o args that are set, printing them if so */ if ((flags & t) || (flags2 & (CC_UNIQALL | CC_UNIQCON))) { 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; } if (flags2 & CC_UNIQALL) putchar('1'); else if (flags2 & CC_UNIQCON) putchar('2'); } if (flags2 & (CC_XORCONT | CC_PATCONT | CC_DEFCONT)) { printf(" -t"); if (flags2 & CC_XORCONT) putchar('+'); if (flags2 & CC_PATCONT) putchar('-'); if (flags2 & CC_DEFCONT) putchar('x'); } else if (!(flags2 & CC_CCCONT)) printf(" -tn"); /* now flags with arguments */ 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'); 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->substr, 'h'); 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: case CCT_QUOTE: 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, 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, 0); } if (s) { if ((cclist & COMP_LIST) && (cc != &cc_compos) && (cc != &cc_default) && (cc != &cc_first)) { if(s[0] == '-' || s[0] == '+') printf(" -"); putchar(' '); if (ispat) { char *p = dupstring(s); untokenize(p); quotedzputs(p, stdout); } else { char *p = dupstring(s); untokenize(p); quotedzputs(bslashquote(p, NULL, 0), 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, 0); } /* 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) { /* 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, 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|COMP_LISTMATCH))) { 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, 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; } /* 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, *n; showmask = 0; for (ptr = argv; *ptr; 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); n = NULL; } if (n) { zwarnnam(name, "no compctl defined for %s", n, 0); ret = 1; } } if (cclist & COMP_COMMAND) printcompctl("", &cc_compos, 0, 0); if (cclist & COMP_DEFAULT) printcompctl("", &cc_default, 0, 0); if (cclist & COMP_FIRST) printcompctl("", &cc_first, 0, 0); if (cclist & COMP_LISTMATCH) print_gmatcher(COMP_LIST); 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; } /* Flags for makecomplist*(). Things not to do. */ #define CFN_FIRST 1 #define CFN_DEFAULT 2 static int bin_compcall(char *name, char **argv, char *ops, int func) { if (incompfunc != 1) { zwarnnam(name, "can only be called from completion function", NULL, 0); return 1; } return makecomplistctl((ops['T'] ? 0 : CFN_FIRST) | (ops['D'] ? 0 : CFN_DEFAULT)); } /* * Functions to generate matches. */ /* A pointer to the compctl we are using. */ static Compctl curcc; /* A list of all compctls we have already used. */ static LinkList ccused, lastccused; /* A stack of currently used compctls. */ static LinkList ccstack; /* The beginning and end of a word range to be used by -l. */ static int brange, erange; /* This is used to detect when and what to continue. */ static unsigned long ccont; /* Two patterns used when doing glob-completion. The first one is built * * from the whole word we are completing and the second one from that * * part of the word that was identified as a possible filename. */ static Patprog patcomp, filecomp; /* We store the following prefixes/suffixes: * * lpre/lsuf -- what's on the line * * rpre/rsuf -- same as lpre/lsuf, but expanded * * ppre/psuf -- the path prefix/suffix * * lppre/lpsuf -- the path prefix/suffix, unexpanded * * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in * * prpre -- ppre in expanded form usable for opendir * * qipre, qisuf-- ingnored quoted string * * * * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, * * fpre, fsuf, lppre, and lpsuf. noreal is non-zero if we have rpre/rsuf. */ static char *lpre, *lsuf; static char *rpre, *rsuf; static char *ppre, *psuf, *lppre, *lpsuf, *prpre; static char *fpre, *fsuf; static char *qfpre, *qfsuf, *qrpre, *qrsuf, *qlpre, *qlsuf; static int lpl, lsl, rpl, rsl, fpl, fsl, lppl, lpsl; static int noreal; /* This is either zero or equal to the special character the word we are * * trying to complete starts with (e.g. Tilde or Equals). */ static char ic; /* This variable says what we are currently adding to the list of matches. */ static int addwhat; /* Convenience macro for calling bslashquote() (formerly quotename()). * * This uses the instring variable above. */ #define quotename(s, e) bslashquote(s, e, instring) /* Hook functions */ static int ccmakehookfn(Hookdef dummy, struct ccmakedat *dat) { char *s = dat->str; int incmd = dat->incmd, lst = dat->lst; struct cmlist ms; Cmlist m; char *os = s; int onm = nmatches, odm = diffmatches, osi = movefd(0); LinkNode n; /* We build a copy of the list of matchers to use to make sure that this * works even if a shell function called from the completion code changes * the global matchers. */ if ((m = cmatcher)) { Cmlist mm, *mp = &mm; int n; for (n = 0; m; m = m->next, n++) { *mp = (Cmlist) zhalloc(sizeof(struct cmlist)); (*mp)->matcher = m->matcher; (*mp)->next = NULL; (*mp)->str = dupstring(m->str); mp = &((*mp)->next); addlinknode(matchers, m->matcher); if (m->matcher) m->matcher->refc++; } m = mm; } /* Walk through the global matchers. */ for (;;) { bmatchers = NULL; if (m) { ms.next = NULL; ms.matcher = m->matcher; mstack = &ms; /* Store the matchers used in the bmatchers list which is used * when building new parts for the string to insert into the * line. */ add_bmatchers(m->matcher); } else mstack = NULL; ainfo = (Aminfo) hcalloc(sizeof(struct aminfo)); fainfo = (Aminfo) hcalloc(sizeof(struct aminfo)); freecl = NULL; if (!validlist) lastambig = 0; amatches = NULL; mnum = 0; unambig_mnum = -1; isuf = NULL; insmnum = 1; #if 0 /* group-numbers in compstate[insert] */ insgnum = 1; insgroup = 0; #endif oldlist = oldins = 0; begcmgroup("default", 0); menucmp = menuacc = newmatches = onlyexpl = 0; ccused = newlinklist(); ccstack = newlinklist(); s = dupstring(os); makecomplistglobal(s, incmd, lst, 0); endcmgroup(NULL); if (amatches && !oldlist) { if (lastccused) freelinklist(lastccused, (FreeFunc) freecompctl); lastccused = znewlinklist(); for (n = firstnode(ccused); n; incnode(n)) zaddlinknode(lastccused, getdata(n)); } else if (ccused) for (n = firstnode(ccused); n; incnode(n)) if (((Compctl) getdata(n)) != &cc_dummy) freecompctl((Compctl) getdata(n)); if (oldlist) { nmatches = onm; diffmatches = odm; validlist = 1; amatches = lastmatches; lmatches = lastlmatches; if (pmatches) { freematches(pmatches, 1); pmatches = NULL; hasperm = 0; } redup(osi, 0); dat->lst = 0; return 0; } if (lastmatches) { freematches(lastmatches, 1); lastmatches = NULL; } permmatches(1); amatches = pmatches; lastpermmnum = permmnum; lastpermgnum = permgnum; lastmatches = pmatches; lastlmatches = lmatches; pmatches = NULL; hasperm = 0; hasoldlist = 1; if (nmatches && !errflag) { validlist = 1; redup(osi, 0); dat->lst = 0; return 0; } if (!m || !(m = m->next)) break; errflag = 0; } redup(osi, 0); dat->lst = 1; return 0; } static int cccleanuphookfn(Hookdef dummy, void *dat) { ccused = ccstack = NULL; return 0; } /* This adds a match to the list of matches. The string to add is given * * in s, the type of match is given in the global variable addwhat and * * the parameter t (if not NULL) is a pointer to a hash node node which * * may be used to give other information to this function. * * * * addwhat contains either one of the special values (negative, see below) * * or the inclusive OR of some of the CC_* flags used for compctls. */ /**/ static void addmatch(char *s, char *t) { int isfile = 0, isalt = 0, isexact; char *ms = NULL, *tt; HashNode hn; Param pm; Cline lc = NULL; Brinfo bp, bpl = brbeg, bsl = brend, bpt, bst; for (bp = brbeg; bp; bp = bp->next) bp->curpos = ((addwhat == CC_QUOTEFLAG) ? bp->qpos : bp->pos); for (bp = brend; bp; bp = bp->next) bp->curpos = ((addwhat == CC_QUOTEFLAG) ? bp->qpos : bp->pos); /* * addwhat: -5 is for files, * -6 is for glob expansions, * -8 is for executable files (e.g. command paths), * -9 is for parameters * -7 is for command names (from cmdnamtab) * -4 is for a cdable parameter * -3 is for executable command names. * -2 is for anything unquoted * -1 is for other file specifications * (things with `~' or `=' at the beginning, ...). */ /* Just to make the code cleaner */ hn = (HashNode) t; pm = (Param) t; if (addwhat == -1 || addwhat == -5 || addwhat == -6 || addwhat == CC_FILES || addwhat == -7 || addwhat == -8) { int ppl = (ppre ? strlen(ppre) : 0), psl = (psuf ? strlen(psuf) : 0); while (bpl && bpl->curpos < ppl) bpl = bpl->next; while (bsl && bsl->curpos < psl) bsl = bsl->next; if ((addwhat == CC_FILES || addwhat == -5) && !*psuf) { /* If this is a filename, do the fignore check. */ char **pt = fignore; int filell, sl = strlen(s); for (isalt = 0; !isalt && *pt; pt++) if ((filell = strlen(*pt)) < sl && !strcmp(*pt, s + sl - filell)) isalt = 1; } ms = ((addwhat == CC_FILES || addwhat == -6 || addwhat == -5 || addwhat == -8) ? comp_match(tildequote(qfpre, 1), multiquote(qfsuf, 1), s, filecomp, &lc, (ppre && *ppre ? 1 : 2), &bpl, ppl ,&bsl, psl, &isexact) : comp_match(multiquote(fpre, 1), multiquote(fsuf, 1), s, filecomp, &lc, 0, &bpl, ppl, &bsl, psl, &isexact)); if (!ms) return; if (addwhat == -7 && !findcmd(s, 0)) return; isfile = CMF_FILE; } else if (addwhat == CC_QUOTEFLAG || addwhat == -2 || (addwhat == -3 && !(hn->flags & DISABLED)) || (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) && !pm->level && (tt = pm->gets.cfn(pm)) && *tt == '/') || (addwhat == -9 && !(hn->flags & PM_UNSET) && !pm->level) || (addwhat > 0 && ((!(hn->flags & PM_UNSET) && (((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) || ((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) || ((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) || ((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) || ((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) || ((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) || ((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED))) && !pm->level) || ((( addwhat & CC_SHFUNCS) || ( addwhat & CC_BUILTINS) || ( addwhat & CC_EXTCMDS) || ( addwhat & CC_RESWDS) || ((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) || ((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) && (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) || ((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED)))) || ((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) { char *p1, *s1, *p2, *s2; if (addwhat == CC_QUOTEFLAG) { p1 = qrpre; s1 = qrsuf; p2 = rpre; s2 = rsuf; } else { p1 = qlpre; s1 = qlsuf; p2 = lpre; s2 = lsuf; } p1 = multiquote(p1, 1); s1 = multiquote(s1, 1); p2 = multiquote(p2, 1); s2 = multiquote(s2, 1); bpt = bpl; bst = bsl; if (!(ms = comp_match(p1, s1, s, patcomp, &lc, (addwhat == CC_QUOTEFLAG), &bpl, strlen(p1), &bsl, strlen(s1), &isexact))) { bpl = bpt; bsl = bst; if (!(ms = comp_match(p2, s2, s, NULL, &lc, (addwhat == CC_QUOTEFLAG), &bpl, strlen(p2), &bsl, strlen(s2), &isexact))) return; } } if (!ms) return; add_match_data(isalt, ms, lc, ipre, ripre, isuf, (incompfunc ? dupstring(curcc->prefix) : curcc->prefix), prpre, (isfile ? lppre : NULL), NULL, (isfile ? lpsuf : NULL), NULL, (incompfunc ? dupstring(curcc->suffix) : curcc->suffix), (mflags | isfile), isexact); } /**/ static void maketildelist(void) { /* add all the usernames to the named directory table */ nameddirtab->filltable(nameddirtab); scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0, addhnmatch, 0); } /* This does the check for compctl -x `n' and `N' patterns. */ /**/ int getcpat(char *str, int cpatindex, char *cpat, int class) { char *s, *t, *p; int d = 0; if (!str || !*str) return -1; cpat = rembslash(cpat); if (!cpatindex) cpatindex++, d = 0; else if ((d = (cpatindex < 0))) cpatindex = -cpatindex; for (s = d ? str + strlen(str) - 1 : str; d ? (s >= str) : *s; d ? s-- : s++) { for (t = s, p = cpat; *t && *p; p++) { if (class) { if (*p == *s && !--cpatindex) return (int)(s - str + 1); } else if (*t++ != *p) break; } if (!class && !*p && !--cpatindex) return t - str; } return -1; } /* Dump a hash table (without sorting). For each element the addmatch * * function is called and at the beginning the addwhat variable is set. * * This could be done using scanhashtable(), but this is easy and much * * more efficient. */ /**/ static void dumphashtable(HashTable ht, int what) { HashNode hn; int i; addwhat = what; for (i = 0; i < ht->hsize; i++) for (hn = ht->nodes[i]; hn; hn = hn->next) addmatch(dupstring(hn->nam), (char *) hn); } /* ScanFunc used by maketildelist() et al. */ /**/ static void addhnmatch(HashNode hn, int flags) { addmatch(hn->nam, NULL); } /* Perform expansion on the given string and return the result. * * During this errors are not reported. */ /**/ static char * getreal(char *str) { LinkList l = newlinklist(); int ne = noerrs; noerrs = 1; addlinknode(l, dupstring(str)); prefork(l, 0); noerrs = ne; if (!errflag && nonempty(l) && ((char *) peekfirst(l)) && ((char *) peekfirst(l))[0]) return dupstring(peekfirst(l)); errflag = 0; return dupstring(str); } /* This reads a directory and adds the files to the list of * * matches. The parameters say which files should be added. */ /**/ static void gen_matches_files(int dirs, int execs, int all) { DIR *d; struct stat buf; char *n, p[PATH_MAX], *q = NULL, *e; LinkList l = NULL; int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat; opts[NULLGLOB] = 1; if (*psuf) { /* If there is a path suffix, check if it doesn't have a `*' or * * `)' at the end (this is used to determine if we should use * * globbing). */ q = psuf + strlen(psuf) - 1; ns = !(*q == Star || *q == Outpar); l = newlinklist(); /* And generate only directory names. */ dirs = 1; all = execs = 0; } /* Open directory. */ if ((d = opendir((prpre && *prpre) ? prpre : "."))) { /* If we search only special files, prepare a path buffer for stat. */ if (!all && prpre) { strcpy(p, prpre); q = p + strlen(prpre); } /* Fine, now read the directory. */ while ((n = zreaddir(d, 1)) && !errflag) { /* Ignore files beginning with `.' unless the thing we found on * * the command line also starts with a dot or GLOBDOTS is set. */ if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) { addwhat = execs ? -8 : -5; if (filecomp) /* If we have a pattern for the filename check, use it. */ test = pattry(filecomp, n); else { /* Otherwise use the prefix and suffix strings directly. */ e = n + strlen(n) - fsl; if ((test = !strncmp(n, fpre, fpl))) test = !strcmp(e, fsuf); if (!test && mstack) { test = 1; addwhat = CC_FILES; } } /* Filename didn't match? */ if (!test) continue; if (!all) { /* We still have to check the file type, so prepare * * the path buffer by appending the filename. */ strcpy(q, n); /* And do the stat. */ if (stat(p, &buf) < 0) continue; } if (all || (dirs && S_ISDIR(buf.st_mode)) || (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) { /* If we want all files or the file has the right type... */ if (*psuf) { /* We have to test for a path suffix. */ int o = strlen(p), tt; /* Append it to the path buffer. */ strcpy(p + o, psuf); /* Do we have to use globbing? */ if (ispattern || (ns && comppatmatch && *comppatmatch)) { /* Yes, so append a `*' if needed. */ if (ns && comppatmatch && *comppatmatch == '*') { int tl = strlen(p); p[tl] = Star; p[tl + 1] = '\0'; } /* Do the globbing... */ remnulargs(p); addlinknode(l, p); globlist(l, 0); /* And see if that produced a filename. */ tt = nonempty(l); while (ugetnode(l)); } else /* Otherwise just check, if we have access * * to the file. */ tt = !access(p, F_OK); p[o] = '\0'; if (tt) /* Ok, we can add the filename to the * * list of matches. */ addmatch(dupstring(n), NULL); } else /* We want all files, so just add the name * * to the matches. */ addmatch(dupstring(n), NULL); } } } closedir(d); } opts[NULLGLOB] = ng; addwhat = aw; } /* This returns the node with the given data. */ /* ...should probably be moved to linklist.c. */ static LinkNode findnode(LinkList list, void *dat) { LinkNode tmp = list->first; while (tmp && tmp->dat != dat) tmp = tmp->next; return tmp; } /* A simple counter to avoid endless recursion between old and new style * * completion. */ static int cdepth = 0; #define MAX_CDEPTH 16 /**/ static int makecomplistctl(int flags) { Heap oldheap; int ret; if (cdepth == MAX_CDEPTH) return 0; cdepth++; SWITCHHEAPS(oldheap, compheap) { int ooffs = offs, lip, lp; char *str = comp_str(&lip, &lp, 0), *t; char *os = cmdstr, **ow = clwords, **p, **q, qc; int on = clwnum, op = clwpos, ois = instring, oib = inbackt; char *oisuf = isuf, *oqp = qipre, *oqs = qisuf, *oaq = autoq; char buf[2]; if (compquote && (qc = *compquote)) { if (qc == '`') { instring = 0; inbackt = 0; autoq = ""; } else { buf[0] = qc; buf[1] = '\0'; instring = (qc == '\'' ? 1 : 2); inbackt = 0; autoq = buf; } } else { instring = inbackt = 0; autoq = ""; } qipre = ztrdup(compqiprefix ? compqiprefix : ""); qisuf = ztrdup(compqisuffix ? compqisuffix : ""); isuf = dupstring(compisuffix); ctokenize(isuf); remnulargs(isuf); clwnum = arrlen(compwords); clwpos = compcurrent - 1; cmdstr = ztrdup(compwords[0]); clwords = (char **) zalloc((clwnum + 1) * sizeof(char *)); for (p = compwords, q = clwords; *p; p++, q++) { t = dupstring(*p); tokenize(t); remnulargs(t); *q = ztrdup(t); } *q = NULL; offs = lip + lp; incompfunc = 2; ret = makecomplistglobal(str, !clwpos, COMP_COMPLETE, flags); incompfunc = 1; isuf = oisuf; zsfree(qipre); zsfree(qisuf); qipre = oqp; qisuf = oqs; instring = ois; inbackt = oib; autoq = oaq; offs = ooffs; zsfree(cmdstr); freearray(clwords); cmdstr = os; clwords = ow; clwnum = on; clwpos = op; } SWITCHBACKHEAPS(oldheap); cdepth--; return ret; } /* This function gets the compctls for the given command line and * * adds all completions for them. */ /**/ static int makecomplistglobal(char *os, int incmd, int lst, int flags) { Compctl cc = NULL; char *s; ccont = CC_CCCONT; cc_dummy.suffix = NULL; if (linwhat == IN_ENV) { /* Default completion for parameter values. */ if (!(flags & CFN_DEFAULT)) { cc = &cc_default; keypm = NULL; } } else if (linwhat == IN_MATH) { if (!(flags & CFN_DEFAULT)) { if (insubscr >= 2) { /* Inside subscript of assoc array, complete keys. */ cc_dummy.mask = 0; cc_dummy.suffix = (insubscr == 2 ? "]" : ""); } else { /* Other math environment, complete paramete names. */ keypm = NULL; cc_dummy.mask = CC_PARAMS; } cc = &cc_dummy; cc_dummy.refc = 10000; } } else if (linwhat == IN_COND) { /* We try to be clever here: in conditions we complete option * * names after a `-o', file names after `-nt', `-ot', and `-ef' * * and file names and parameter names elsewhere. */ if (!(flags & CFN_DEFAULT)) { s = clwpos ? clwords[clwpos - 1] : ""; cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS : ((*s == '-' && s[1] && !s[2]) || !strcmp("-nt", s) || !strcmp("-ot", s) || !strcmp("-ef", s)) ? CC_FILES : (CC_FILES | CC_PARAMS); cc = &cc_dummy; cc_dummy.refc = 10000; keypm = NULL; } } else if (linredir) { if (!(flags & CFN_DEFAULT)) { /* In redirections use default completion. */ cc = &cc_default; keypm = NULL; } } else { /* Otherwise get the matches for the command. */ keypm = NULL; return makecomplistcmd(os, incmd, flags); } if (cc) { /* First, use the -T compctl. */ if (!(flags & CFN_FIRST)) { makecomplistcc(&cc_first, os, incmd); if (!(ccont & CC_CCCONT)) return 0; } makecomplistcc(cc, os, incmd); return 1; } return 0; } /* This produces the matches for a command. */ /**/ static int makecomplistcmd(char *os, int incmd, int flags) { Compctl cc; Compctlp ccp; char *s; int ret = 0; /* First, use the -T compctl. */ if (!(flags & CFN_FIRST)) { makecomplistcc(&cc_first, os, incmd); if (!(ccont & CC_CCCONT)) return 0; } /* Then search the pattern compctls, with the command name and the * * full pathname of the command. */ if (cmdstr) { ret |= makecomplistpc(os, incmd); if (!(ccont & CC_CCCONT)) return ret; } /* If the command string starts with `=', try the path name of the * * command. */ if (cmdstr && cmdstr[0] == Equals) { char *c = findcmd(cmdstr + 1, 1); if (c) { zsfree(cmdstr); cmdstr = ztrdup(c); } } /* Find the compctl for this command, trying the full name and then * * the trailing pathname component. If that doesn't yield anything, * * use default completion. */ if (incmd) cc = &cc_compos; else if (!(cmdstr && (((ccp = (Compctlp) compctltab->getnode(compctltab, cmdstr)) && (cc = ccp->cc)) || ((s = dupstring(cmdstr)) && remlpaths(&s) && (ccp = (Compctlp) compctltab->getnode(compctltab, s)) && (cc = ccp->cc))))) { if (flags & CFN_DEFAULT) return ret; cc = &cc_default; } else ret|= 1; makecomplistcc(cc, os, incmd); return ret; } /* This adds the matches for the pattern compctls. */ /**/ static int makecomplistpc(char *os, int incmd) { Patcomp pc; Patprog pat; char *s; int ret = 0; s = ((shfunctab->getnode(shfunctab, cmdstr) || builtintab->getnode(builtintab, cmdstr)) ? NULL : findcmd(cmdstr, 1)); for (pc = patcomps; pc; pc = pc->next) { if ((pat = patcompile(pc->pat, PAT_STATIC, NULL)) && (pattry(pat, cmdstr) || (s && pattry(pat, s)))) { makecomplistcc(pc->cc, os, incmd); ret |= 2; if (!(ccont & CC_CCCONT)) return ret; } } return ret; } /* This produces the matches for one compctl. */ /**/ static void makecomplistcc(Compctl cc, char *s, int incmd) { cc->refc++; if (!ccused) ccused = newlinklist(); addlinknode(ccused, cc); ccont = 0; makecomplistor(cc, s, incmd, 0, 0); } /* This adds the completions for one run of [x]or'ed completions. */ /**/ static void makecomplistor(Compctl cc, char *s, int incmd, int compadd, int sub) { int mn, ct, um = usemenu; /* Loop over xors. */ do { mn = mnum; /* Loop over ors. */ do { /* Reset the range information if we are not in a sub-list. */ if (!sub) { brange = 0; erange = clwnum - 1; } usemenu = 0; makecomplistlist(cc, s, incmd, compadd); um |= usemenu; ct = cc->mask2 & CC_XORCONT; cc = cc->xor; } while (cc && ct); /* Stop if we got some matches. */ if (mn != mnum) break; if (cc) { ccont &= ~(CC_DEFCONT | CC_PATCONT); if (!sub) ccont &= ~CC_CCCONT; } } while (cc); usemenu = um; } /* This dispatches for simple and extended completion. */ /**/ static void makecomplistlist(Compctl cc, char *s, int incmd, int compadd) { int oloffs = offs, owe = we, owb = wb, ocs = cs; if (cc->ext) /* Handle extended completion. */ makecomplistext(cc, s, incmd); else /* Only normal flags. */ makecomplistflags(cc, s, incmd, compadd); /* Reset some information variables for the next try. */ errflag = 0; offs = oloffs; wb = owb; we = owe; cs = ocs; } /* This add matches for extended completion patterns */ /**/ static void makecomplistext(Compctl occ, char *os, int incmd) { Compctl compc; Compcond or, cc; Patprog pprog; int compadd, m = 0, d = 0, t, tt, i, j, a, b, ins; char *sc = NULL, *s, *ss; ins = (instring ? instring : (inbackt ? 3 : 0)); /* This loops over the patterns separated by `-'s. */ for (compc = occ->ext; compc; compc = compc->next) { compadd = t = brange = 0; erange = clwnum - 1; /* This loops over OR'ed patterns. */ for (cc = compc->cond; cc && !t; cc = or) { or = cc->or; /* This loops over AND'ed patterns. */ for (t = 1; cc && t; cc = cc->and) { /* And this loops over [...] pairs. */ for (t = i = 0; i < cc->n && !t; i++) { s = NULL; brange = 0; erange = clwnum - 1; switch (cc->type) { case CCT_QUOTE: t = ((cc->u.s.s[i][0] == 's' && ins == 1) || (cc->u.s.s[i][0] == 'd' && ins == 2) || (cc->u.s.s[i][0] == 'b' && ins == 3)); break; case CCT_POS: tt = clwpos; goto cct_num; case CCT_NUMWORDS: tt = clwnum; cct_num: if ((a = cc->u.r.a[i]) < 0) a += clwnum; if ((b = cc->u.r.b[i]) < 0) b += clwnum; if (cc->type == CCT_POS) brange = a, erange = b; t = (tt >= a && tt <= b); break; case CCT_CURSUF: case CCT_CURPRE: s = ztrdup(clwpos < clwnum ? os : ""); untokenize(s); if (isset(COMPLETEINWORD)) s[offs] = '\0'; sc = rembslash(cc->u.s.s[i]); a = strlen(sc); if (!strncmp(s, sc, a)) { compadd = (cc->type == CCT_CURSUF ? a : 0); t = 1; } break; case CCT_CURSUB: case CCT_CURSUBC: if (clwpos < 0 || clwpos >= clwnum) t = 0; else { s = ztrdup(os); untokenize(s); if (isset(COMPLETEINWORD)) s[offs] = '\0'; a = getcpat(s, cc->u.s.p[i], cc->u.s.s[i], cc->type == CCT_CURSUBC); if (a != -1) compadd = a, t = 1; } break; case CCT_CURPAT: case CCT_CURSTR: tt = clwpos; goto cct_str; case CCT_WORDPAT: case CCT_WORDSTR: tt = 0; cct_str: if ((a = tt + cc->u.s.p[i]) < 0) a += clwnum; s = ztrdup((a < 0 || a >= clwnum) ? "" : clwords[a]); untokenize(s); if (cc->type == CCT_CURPAT || cc->type == CCT_WORDPAT) { tokenize(ss = dupstring(cc->u.s.s[i])); t = ((pprog = patcompile(ss, PAT_STATIC, NULL)) && pattry(pprog, s)); } else t = (!strcmp(s, rembslash(cc->u.s.s[i]))); break; case CCT_RANGESTR: case CCT_RANGEPAT: if (cc->type == CCT_RANGEPAT) tokenize(sc = dupstring(cc->u.l.a[i])); for (j = clwpos - 1; j > 0; j--) { untokenize(s = ztrdup(clwords[j])); if (cc->type == CCT_RANGESTR) sc = rembslash(cc->u.l.a[i]); if (cc->type == CCT_RANGESTR ? !strncmp(s, sc, strlen(sc)) : ((pprog = patcompile(sc, PAT_STATIC, 0)) && pattry(pprog, s))) { zsfree(s); brange = j + 1; t = 1; break; } zsfree(s); } if (t && cc->u.l.b[i]) { if (cc->type == CCT_RANGEPAT) tokenize(sc = dupstring(cc->u.l.b[i])); for (j++; j < clwnum; j++) { untokenize(s = ztrdup(clwords[j])); if (cc->type == CCT_RANGESTR) sc = rembslash(cc->u.l.b[i]); if (cc->type == CCT_RANGESTR ? !strncmp(s, sc, strlen(sc)) : ((pprog = patcompile(sc, PAT_STATIC, 0)) && pattry(pprog, s))) { zsfree(s); erange = j - 1; t = clwpos <= erange; break; } zsfree(s); } } s = NULL; } zsfree(s); } } } if (t) { /* The patterns matched, use the flags. */ m = 1; ccont &= ~(CC_PATCONT | CC_DEFCONT); makecomplistor(compc, os, incmd, compadd, 1); if (!d && (ccont & CC_DEFCONT)) { d = 1; compadd = 0; brange = 0; erange = clwnum - 1; makecomplistflags(occ, os, incmd, 0); } if (!(ccont & CC_PATCONT)) break; } } /* If no pattern matched, use the standard flags. */ if (!m) { compadd = 0; brange = 0; erange = clwnum - 1; makecomplistflags(occ, os, incmd, 0); } } /**/ static int sep_comp_string(char *ss, char *s, int noffs) { LinkList foo = newlinklist(); LinkNode n; int owe = we, owb = wb, ocs = cs, swb, swe, scs, soffs, ne = noerrs; int sl = strlen(ss), tl, got = 0, i = 0, cur = -1, oll = ll, remq; int ois = instring, oib = inbackt, ona = noaliases; char *tmp, *p, *ns, *ol = (char *) line, sav, *oaq = autoq, *qp, *qs; char *ts, qc = '\0'; swb = swe = soffs = 0; ns = NULL; /* Put the string in the lexer buffer and call the lexer to * * get the words we have to expand. */ zleparse = 1; addedx = 1; noerrs = 1; lexsave(); tmp = (char *) zhalloc(tl = sl + 3 + strlen(s)); strcpy(tmp, ss); tmp[sl] = ' '; memcpy(tmp + sl + 1, s, noffs); tmp[(scs = cs = sl + 1 + noffs)] = 'x'; strcpy(tmp + sl + 2 + noffs, s + noffs); if ((remq = (*compqstack == '\\'))) tmp = rembslash(tmp); inpush(dupstrspace(tmp), 0, NULL); line = (unsigned char *) tmp; ll = tl - 1; strinbeg(0); noaliases = 1; do { ctxtlex(); if (tok == LEXERR) { int j; if (!tokstr) break; for (j = 0, p = tokstr; *p; p++) if (*p == Snull || *p == Dnull) j++; if (j & 1) { tok = STRING; if (p > tokstr && p[-1] == ' ') p[-1] = '\0'; } } if (tok == ENDINPUT || tok == LEXERR) break; if (tokstr && *tokstr) addlinknode(foo, (p = ztrdup(tokstr))); else p = NULL; if (!got && !zleparse) { DPUTS(!p, "no current word in substr"); got = 1; cur = i; swb = wb - 1; swe = we - 1; soffs = cs - swb; chuck(p + soffs); ns = dupstring(p); } i++; } while (tok != ENDINPUT && tok != LEXERR); noaliases = ona; strinend(); inpop(); errflag = zleparse = 0; noerrs = ne; lexrestore(); wb = owb; we = owe; cs = ocs; line = (unsigned char *) ol; ll = oll; if (cur < 0 || i < 1) return 1; owb = offs; offs = soffs; if ((p = check_param(ns, 0, 1))) { for (p = ns; *p; p++) if (*p == Dnull) *p = '"'; else if (*p == Snull) *p = '\''; } offs = owb; untokenize(ts = dupstring(ns)); if (*ns == Snull || *ns == Dnull) { instring = (*ns == Snull ? 1 : 2); inbackt = 0; swb++; if (ns[strlen(ns) - 1] == *ns && ns[1]) swe--; autoq = compqstack[1] ? "" : multiquote(*ns == Snull ? "'" : "\"", 1); qc = (*ns == Snull ? '\'' : '"'); ts++; } else { instring = 0; autoq = ""; } for (p = ns, i = swb; *p; p++, i++) { if (INULL(*p)) { if (i < scs) { soffs--; if (remq && *p == Bnull && p[1]) swb -= 2; } if (p[1] || *p != Bnull) { if (*p == Bnull) { if (scs == i + 1) scs++, soffs++; } else { if (scs > i--) scs--; } } else { if (scs == swe) scs--; } chuck(p--); } } ns = ts; if (instring && strchr(compqstack, '\\')) { int rl = strlen(ns), ql = strlen(multiquote(ns, !!compqstack[1])); if (ql > rl) swb -= ql - rl; } sav = s[(i = swb - sl - 1)]; s[i] = '\0'; qp = tricat(qipre, multiquote(s, 0), ""); s[i] = sav; if (swe < swb) swe = swb; swe -= sl + 1; sl = strlen(s); if (swe > sl) { swe = sl; if (strlen(ns) > swe - swb + 1) ns[swe - swb + 1] = '\0'; } qs = tricat(multiquote(s + swe, 0), qisuf, ""); sl = strlen(ns); if (soffs > sl) soffs = sl; { char **ow = clwords, *os = cmdstr, *oqp = qipre, *oqs = qisuf; char *oqst = compqstack; int olws = clwsize, olwn = clwnum, olwp = clwpos; int obr = brange, oer = erange, oof = offs; unsigned long occ = ccont; compqstack = tricat((instring ? (instring == 1 ? "'" : "\"") : "\\"), compqstack, ""); clwsize = clwnum = countlinknodes(foo); clwords = (char **) zalloc((clwnum + 1) * sizeof(char *)); for (n = firstnode(foo), i = 0; n; incnode(n), i++) { p = clwords[i] = (char *) getdata(n); untokenize(p); } clwords[i] = NULL; clwpos = cur; cmdstr = ztrdup(clwords[0]); brange = 0; erange = clwnum - 1; qipre = qp; qisuf = qs; offs = soffs; ccont = CC_CCCONT; makecomplistcmd(ns, !clwpos, CFN_FIRST); ccont = occ; offs = oof; zsfree(cmdstr); cmdstr = os; freearray(clwords); clwords = ow; clwsize = olws; clwnum = olwn; clwpos = olwp; brange = obr; erange = oer; zsfree(qipre); qipre = oqp; zsfree(qisuf); qisuf = oqs; zsfree(compqstack); compqstack = oqst; } autoq = oaq; instring = ois; inbackt = oib; return 0; } /* This adds the completions for the flags in the given compctl. */ /**/ static void makecomplistflags(Compctl cc, char *s, int incmd, int compadd) { int t, sf1, sf2, ooffs, um = usemenu, delit, oaw, gflags; int mn = mnum, ohp = haspattern; char *p, *sd = NULL, *tt, *s1, *s2, *os = dupstring(s); struct cmlist ms; ccont |= (cc->mask2 & (CC_CCCONT | CC_DEFCONT | CC_PATCONT)); if (incompfunc != 1 && ccstack && findnode(ccstack, cc)) return; if (!ccstack) ccstack = newlinklist(); addlinknode(ccstack, cc); if (incompfunc != 1 && allccs) { if (findnode(allccs, cc)) { uremnode(ccstack, firstnode(ccstack)); return; } addlinknode(allccs, cc); } /* Go to the end of the word if complete_in_word is not set. */ if (unset(COMPLETEINWORD) && cs != we) cs = we, offs = strlen(s); s = dupstring(s); delit = ispattern = 0; usemenu = um; patcomp = filecomp = NULL; rpre = rsuf = lpre = lsuf = ppre = psuf = lppre = lpsuf = fpre = fsuf = ipre = ripre = prpre = qfpre = qfsuf = qrpre = qrsuf = qlpre = qlsuf = NULL; curcc = cc; mflags = 0; gflags = (((cc->mask2 & CC_NOSORT ) ? CGF_NOSORT : 0) | ((cc->mask2 & CC_UNIQALL) ? CGF_UNIQALL : 0) | ((cc->mask2 & CC_UNIQCON) ? CGF_UNIQCON : 0)); if (cc->gname) { endcmgroup(NULL); begcmgroup(cc->gname, gflags); } if (cc->ylist) { endcmgroup(NULL); begcmgroup(NULL, gflags); } if (cc->mask & CC_REMOVE) mflags |= CMF_REMOVE; if (cc->explain) { curexpl = (Cexpl) zhalloc(sizeof(struct cexpl)); curexpl->count = curexpl->fcount = 0; } else curexpl = NULL; /* compadd is the number of characters we have to ignore at the * * beginning of the word. */ if (compadd) { ipre = dupstring(s); ipre[compadd] = '\0'; untokenize(ipre); wb += compadd; s += compadd; if ((offs -= compadd) < 0) { /* It's bigger than our word prefix, so we can't help here... */ uremnode(ccstack, firstnode(ccstack)); return; } } else ipre = NULL; if (cc->matcher) { ms.next = mstack; ms.matcher = cc->matcher; mstack = &ms; if (!mnum) add_bmatchers(cc->matcher); addlinknode(matchers, cc->matcher); cc->matcher->refc++; } if (mnum && (mstack || bmatchers)) update_bmatchers(); /* Insert the prefix (compctl -P), if any. */ if (cc->prefix) { int pl = 0; if (*s) { char *dp = rembslash(cc->prefix); /* First find out how much of the prefix is already on the line. */ sd = dupstring(s); untokenize(sd); pl = pfxlen(dp, sd); s += pl; sd += pl; offs -= pl; } } /* Does this compctl have a suffix (compctl -S)? */ if (cc->suffix) { char *sdup = rembslash(cc->suffix); int sl = strlen(sdup), suffixll; /* Ignore trailing spaces. */ for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--); p[1] = '\0'; if (!sd) { sd = dupstring(s); untokenize(sd); } /* If the suffix is already there, ignore it (and don't add * * it again). */ if (*sd && (suffixll = strlen(sd)) >= sl && offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) s[suffixll - sl] = '\0'; } /* Do we have one of the special characters `~' and `=' at the beginning? */ if (incompfunc || ((ic = *s) != Tilde && ic != Equals)) ic = 0; /* Check if we have to complete a parameter name... */ if (!incompfunc && (p = check_param(s, 1, 0))) { s = p; /* And now make sure that we complete parameter names. */ cc = &cc_dummy; cc_dummy.refc = 10000; cc_dummy.mask = CC_PARAMS | CC_ENVVARS; } ooffs = offs; /* If we have to ignore the word, do that. */ if (cc->mask & CC_DELETE) { delit = 1; *s = '\0'; offs = 0; if (isset(AUTOMENU)) usemenu = 1; } /* Compute line prefix/suffix. */ lpl = offs; lpre = zhalloc(lpl + 1); memcpy(lpre, s, lpl); lpre[lpl] = '\0'; qlpre = quotename(lpre, NULL); lsuf = dupstring(s + offs); lsl = strlen(lsuf); qlsuf = quotename(lsuf, NULL); /* First check for ~.../... */ if (ic == Tilde) { for (p = lpre + lpl; p > lpre; p--) if (*p == '/') break; if (*p == '/') ic = 0; } /* Compute real prefix/suffix. */ noreal = !delit; for (p = lpre; *p && *p != String && *p != Tick; p++); tt = ic && !ispar ? lpre + 1 : lpre; rpre = (*p || *lpre == Tilde || *lpre == Equals) ? (noreal = 0, getreal(tt)) : dupstring(tt); qrpre = quotename(rpre, NULL); for (p = lsuf; *p && *p != String && *p != Tick; p++); rsuf = *p ? (noreal = 0, getreal(lsuf)) : dupstring(lsuf); qrsuf = quotename(rsuf, NULL); /* Check if word is a pattern. */ for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1; p >= rpre && (ispattern != 3 || !sf1); p--) if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde))) ispattern |= sf1 ? 1 : 2; else if (*p == '/') { sf1++; if (!s1) s1 = p; } rsl = strlen(rsuf); for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++) if (itok(*p)) t |= sf2 ? 4 : 2; else if (*p == '/') { sf2++; if (!s2) s2 = p; } ispattern = ispattern | t; /* But if we were asked not to do glob completion, we never treat the * * thing as a pattern. */ if (!comppatmatch || !*comppatmatch) ispattern = 0; if (ispattern) { /* The word should be treated as a pattern, so compute the matcher. */ p = (char *) zhalloc(rpl + rsl + 2); strcpy(p, rpre); if (rpl && p[rpl - 1] != Star && (!comppatmatch || *comppatmatch == '*')) { p[rpl] = Star; strcpy(p + rpl + 1, rsuf); } else strcpy(p + rpl, rsuf); patcomp = patcompile(p, 0, NULL); haspattern = 1; } if (!patcomp) { untokenize(rpre); untokenize(rsuf); rpl = strlen(rpre); rsl = strlen(rsuf); } untokenize(lpre); untokenize(lsuf); if (!(cc->mask & CC_DELETE)) hasmatched = 1; /* Handle completion of files specially (of course). */ if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) { /* s1 and s2 point to the last/first slash in the prefix/suffix. */ if (!s1) s1 = rpre; if (!s2) s2 = rsuf + rsl; /* Compute the path prefix/suffix. */ if (*s1 != '/') ppre = ""; else ppre = dupstrpfx(rpre, s1 - rpre + 1); psuf = dupstring(s2); if (cs != wb) { char save = line[cs]; line[cs] = 0; lppre = dupstring((char *) line + wb + (qipre && *qipre ? (strlen(qipre) - (*qipre == '\'' || *qipre == '\"')) : 0)); line[cs] = save; if (brbeg) { Brinfo bp; for (bp = brbeg; bp; bp = bp->next) strcpy(lppre + bp->qpos, lppre + bp->qpos + strlen(bp->str)); } if ((p = strrchr(lppre, '/'))) { p[1] = '\0'; lppl = strlen(lppre); } else if (!sf1) { lppre = NULL; lppl = 0; } else { lppre = ppre; lppl = strlen(lppre); } } else { lppre = NULL; lppl = 0; } if (cs != we) { int end = we; char save = line[end]; if (qisuf && *qisuf) { int ql = strlen(qisuf); end -= ql - (qisuf[ql-1] == '\'' || qisuf[ql-1] == '"'); } line[end] = 0; lpsuf = dupstring((char *) (line + cs)); line[end] = save; if (brend) { Brinfo bp; char *p; int bl; for (bp = brend; bp; bp = bp->next) { p = lpsuf + (we - cs) - bp->qpos - (bl = strlen(bp->str)); strcpy(p, p + bl); } } if (!(lpsuf = strchr(lpsuf, '/')) && sf2) lpsuf = psuf; lpsl = (lpsuf ? strlen(lpsuf) : 0); } else { lpsuf = NULL; lpsl = 0; } /* And get the file prefix. */ fpre = dupstring(((s1 == s || s1 == rpre || ic) && (*s != '/' || cs == wb)) ? s1 : s1 + 1); qfpre = quotename(fpre, NULL); /* And the suffix. */ fsuf = dupstrpfx(rsuf, s2 - rsuf); qfsuf = quotename(fsuf, NULL); if (comppatmatch && *comppatmatch && (ispattern & 2)) { int t2; /* We have to use globbing, so compute the pattern from * * the file prefix and suffix with a `*' between them. */ p = (char *) zhalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2); strcpy(p, fpre); if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star && (!comppatmatch || *comppatmatch == '*')) p[t2++] = Star; strcpy(p + t2, fsuf); filecomp = patcompile(p, 0, NULL); } if (!filecomp) { untokenize(fpre); untokenize(fsuf); fpl = strlen(fpre); fsl = strlen(fsuf); } addwhat = -1; /* Completion after `~', maketildelist adds the usernames * * and named directories. */ if (ic == Tilde) { char *oi = ipre; ipre = (ipre ? dyncat("~", ipre) : "~"); maketildelist(); ipre = oi; } else if (ic == Equals) { /* Completion after `=', get the command names from * * the cmdnamtab and aliases from aliastab. */ char *oi = ipre; ipre = (ipre ? dyncat("=", ipre) : "="); if (isset(HASHLISTALL)) cmdnamtab->filltable(cmdnamtab); dumphashtable(cmdnamtab, -7); dumphashtable(aliastab, -2); ipre = oi; } else { /* Normal file completion... */ if (ispattern & 1) { /* But with pattern matching. */ LinkList l = newlinklist(); LinkNode n; int ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; addwhat = 0; p = (char *) zhalloc(lpl + lsl + 3); strcpy(p, lpre); if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*') strcat(p, "*"); strcat(p, lsuf); if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')') strcat(p, "*"); /* Do the globbing. */ tokenize(p); remnulargs(p); addlinknode(l, p); globlist(l, 0); if (nonempty(l)) { /* And add the resulting words. */ mflags |= CMF_FILE; for (n = firstnode(l); n; incnode(n)) addmatch(getdata(n), NULL); mflags &= !CMF_FILE; } opts[NULLGLOB] = ng; } else { char **dirs = 0, *ta[2]; /* No pattern matching. */ addwhat = CC_FILES; if (cc->withd) { char **pp, **npp, *tp; int tl = strlen(ppre) + 2, pl; if ((pp = get_user_var(cc->withd))) { dirs = npp = (char**) zhalloc(sizeof(char *)*(arrlen(pp)+1)); while (*pp) { pl = strlen(*pp); tp = (char *) zhalloc(strlen(*pp) + tl); strcpy(tp, *pp); tp[pl] = '/'; strcpy(tp + pl + 1, ppre); *npp++ = tp; pp++; } *npp = '\0'; } } if (!dirs) { dirs = ta; if (cc->withd) { char *tp; int pl = strlen(cc->withd); ta[0] = tp = (char *) zhalloc(strlen(ppre) + pl + 2); strcpy(tp, cc->withd); tp[pl] = '/'; strcpy(tp + pl + 1, ppre); } else ta[0] = ppre; ta[1] = NULL; } while (*dirs) { prpre = *dirs; if (sf2) /* We are in the path, so add only directories. */ gen_matches_files(1, 0, 0); else { if (cc->mask & CC_FILES) /* Add all files. */ gen_matches_files(0, 0, 1); else if (cc->mask & CC_COMMPATH) { /* Completion of command paths. */ if (sf1 || cc->withd) /* There is a path prefix, so add * * directories and executables. */ gen_matches_files(1, 1, 0); else { /* No path prefix, so add the things * * reachable via the PATH variable. */ char **pc = path, *pp = prpre; for (; *pc; pc++) if (!**pc || (pc[0][0] == '.' && !pc[0][1])) break; if (*pc) { prpre = "./"; gen_matches_files(1, 1, 0); prpre = pp; } } } else if (cc->mask & CC_DIRS) gen_matches_files(1, 0, 0); /* The compctl has a glob pattern (compctl -g). */ if (cc->glob) { int ns, pl = strlen(prpre), o, paalloc; char *g = dupstring(cc->glob), *pa; char *p2, *p3; int ne = noerrs, md = opts[MARKDIRS]; /* These are used in the globbing code to make * * things a bit faster. */ if (ispattern || mstack) glob_pre = glob_suf = NULL; else { glob_pre = fpre; glob_suf = fsuf; } noerrs = 1; addwhat = -6; o = strlen(prpre); pa = (char *)zalloc(paalloc = o + PATH_MAX); strcpy(pa, prpre); opts[MARKDIRS] = 0; /* The compctl -g string may contain more than * * one pattern, so we need a loop. */ while (*g) { LinkList l = newlinklist(); int ng; /* Find the blank terminating the pattern. */ while (*g && inblank(*g)) g++; /* Oops, we already reached the end of the string. */ if (!*g) break; for (p = g + 1; *p && !inblank(*p); p++) if (*p == '\\' && p[1]) p++; /* Get the pattern string. */ tokenize(g = dupstrpfx(g, p - g)); if (*g == '=') *g = Equals; if (*g == '~') *g = Tilde; remnulargs(g); if ((*g == Equals || *g == Tilde) && !cc->withd) { /* The pattern has a `~' or `=' at the * * beginning, so we expand this and use * * the result. */ filesub(&g, 0); addlinknode(l, dupstring(g)); } else if (*g == '/' && !cc->withd) /* The pattern is a full path (starting * * with '/'), so add it unchanged. */ addlinknode(l, dupstring(g)); else { /* It's a simple pattern, so append it to * * the path we have on the command line. */ int minlen = o + strlen(g); if (minlen >= paalloc) pa = (char *) zrealloc(pa, paalloc = minlen+1); strcpy(pa + o, g); addlinknode(l, dupstring(pa)); } /* Do the globbing. */ ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; globlist(l, 0); opts[NULLGLOB] = ng; /* Get the results. */ if (nonempty(l) && peekfirst(l)) { for (p2 = (char *)peekfirst(l); *p2; p2++) if (itok(*p2)) break; if (!*p2) { if ((*g == Equals || *g == Tilde || *g == '/') || cc->withd) { /* IF the pattern started with `~', * * `=', or `/', add the result only, * * if it really matches what we have * * on the line. * * Do this if an initial directory * * was specified, too. */ while ((p2 = (char *)ugetnode(l))) if (strpfx(prpre, p2)) addmatch(p2 + pl, NULL); } else { /* Otherwise ignore the path we * * prepended to the pattern. */ while ((p2 = p3 = (char *)ugetnode(l))) { for (ns = sf1; *p3 && ns; p3++) if (*p3 == '/') ns--; addmatch(p3, NULL); } } } } pa[o] = '\0'; g = p; } glob_pre = glob_suf = NULL; noerrs = ne; opts[MARKDIRS] = md; zfree(pa, paalloc); } } dirs++; } prpre = NULL; } } lppre = lpsuf = NULL; lppl = lpsl = 0; } if (ic) { /* Now change the `~' and `=' tokens to the real characters so * * that things starting with these characters will be added. */ rpre = dyncat((ic == Tilde) ? "~" : "=", rpre); rpl++; qrpre = dyncat((ic == Tilde) ? "~" : "=", qrpre); } if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) { /* If we have to complete commands, add alias names, * * shell functions and builtins too. */ dumphashtable(aliastab, -3); dumphashtable(reswdtab, -3); dumphashtable(shfunctab, -3); dumphashtable(builtintab, -3); if (isset(HASHLISTALL)) cmdnamtab->filltable(cmdnamtab); dumphashtable(cmdnamtab, -3); /* And parameter names if autocd and cdablevars are set. */ if (isset(AUTOCD) && isset(CDABLEVARS)) dumphashtable(paramtab, -4); } oaw = addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG; if (cc->mask & CC_NAMED) /* Add named directories. */ dumphashtable(nameddirtab, addwhat); if (cc->mask & CC_OPTIONS) /* Add option names. */ dumphashtable(optiontab, addwhat); if (cc->mask & CC_VARS) { /* And parameter names. */ dumphashtable(paramtab, -9); addwhat = oaw; } if (cc->mask & CC_BINDINGS) { /* And zle function names... */ dumphashtable(thingytab, CC_BINDINGS); addwhat = oaw; } if (cc->keyvar) { /* This adds things given to the compctl -k flag * * (from a parameter or a list of words). */ char **usr = get_user_var(cc->keyvar); if (usr) while (*usr) addmatch(*usr++, NULL); } if (cc->mask & CC_USERS) { /* Add user names. */ maketildelist(); addwhat = oaw; } if (cc->func) { /* This handles the compctl -K flag. */ Eprog prog; char **r; int lv = lastval; /* Get the function. */ if ((prog = getshfunc(cc->func)) != &dummy_eprog) { /* We have it, so build a argument list. */ LinkList args = newlinklist(); int osc = sfcontext; addlinknode(args, cc->func); if (delit) { p = dupstrpfx(os, ooffs); untokenize(p); addlinknode(args, p); p = dupstring(os + ooffs); untokenize(p); addlinknode(args, p); } else { addlinknode(args, lpre); addlinknode(args, lsuf); } /* This flag allows us to use read -l and -c. */ if (incompfunc != 1) incompctlfunc = 1; sfcontext = SFC_COMPLETE; /* Call the function. */ doshfunc(cc->func, prog, args, 0, 1); sfcontext = osc; incompctlfunc = 0; /* And get the result from the reply parameter. */ if ((r = get_user_var("reply"))) while (*r) addmatch(*r++, NULL); } lastval = lv; } if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) { /* Get job names. */ int i; char *j; for (i = 0; i < MAXJOB; i++) if ((jobtab[i].stat & STAT_INUSE) && jobtab[i].procs && jobtab[i].procs->text) { int stopped = jobtab[i].stat & STAT_STOPPED; j = dupstring(jobtab[i].procs->text); if ((cc->mask & CC_JOBS) || (stopped && (cc->mask & CC_STOPPED)) || (!stopped && (cc->mask & CC_RUNNING))) addmatch(j, NULL); } } if (cc->str) { /* Get the stuff from a compctl -s. */ LinkList foo = newlinklist(); LinkNode n; int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb; int ona = noaliases; char *tmpbuf; opts[NULLGLOB] = 1; /* Put the string in the lexer buffer and call the lexer to * * get the words we have to expand. */ zleparse = 1; lexsave(); tmpbuf = (char *)zhalloc(strlen(cc->str) + 5); sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */ inpush(tmpbuf, 0, NULL); strinbeg(0); noaliases = 1; do { ctxtlex(); if (tok == ENDINPUT || tok == LEXERR) break; if (!first && tokstr && *tokstr) addlinknode(foo, ztrdup(tokstr)); first = 0; } while (tok != ENDINPUT && tok != LEXERR); noaliases = ona; strinend(); inpop(); errflag = zleparse = 0; lexrestore(); /* Fine, now do full expansion. */ prefork(foo, 0); if (!errflag) { globlist(foo, 0); if (!errflag) /* And add the resulting words as matches. */ for (n = firstnode(foo); n; incnode(n)) addmatch((char *)n->dat, NULL); } opts[NULLGLOB] = ng; we = oowe; wb = oowb; } if (cc->hpat) { /* We have a pattern to take things from the history. */ Patprog pprogc = NULL; char *e, *h, hpatsav; int i = addhistnum(curhist,-1,HIST_FOREIGN), n = cc->hnum; Histent he = gethistent(i, GETHIST_UPWARD); /* Parse the pattern, if it isn't the null string. */ if (*(cc->hpat)) { char *thpat = dupstring(cc->hpat); tokenize(thpat); pprogc = patcompile(thpat, 0, NULL); } /* n holds the number of history line we have to search. */ if (!n) n = -1; /* Now search the history. */ while (n-- && he) { int iwords; for (iwords = he->nwords - 1; iwords >= 0; iwords--) { h = he->text + he->words[iwords*2]; e = he->text + he->words[iwords*2+1]; hpatsav = *e; *e = '\0'; /* We now have a word from the history, ignore it * * if it begins with a quote or `$'. */ if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' && (!pprogc || pattry(pprogc, h))) /* Otherwise add it if it was matched. */ addmatch(dupstring(h), NULL); if (hpatsav) *e = hpatsav; } he = up_histent(he); } } if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS | CC_READONLYS | CC_SPECIALS | CC_PARAMS))) /* Add various flavours of parameters. */ dumphashtable(paramtab, t); if ((t = cc->mask & CC_SHFUNCS)) /* Add shell functions. */ dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & CC_BUILTINS)) /* Add builtins. */ dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & CC_EXTCMDS)) { /* Add external commands */ if (isset(HASHLISTALL)) cmdnamtab->filltable(cmdnamtab); dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); } if ((t = cc->mask & CC_RESWDS)) /* Add reserved words */ dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & (CC_ALREG | CC_ALGLOB))) /* Add the two types of aliases. */ dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if (keypm && cc == &cc_dummy) { /* Add the keys of the parameter in keypm. */ HashTable t = keypm->gets.hfn(keypm); if (t) scanhashtable(t, 0, 0, PM_UNSET, addhnmatch, 0); keypm = NULL; cc_dummy.suffix = NULL; } if (!errflag && cc->ylist) { /* generate the user-defined display list: if anything fails, * * we silently allow the normal completion list to be used. */ char **yaptr = NULL, *uv = NULL; Eprog prog; if (cc->ylist[0] == '$' || cc->ylist[0] == '(') { /* from variable */ uv = cc->ylist + (cc->ylist[0] == '$'); } else if ((prog = getshfunc(cc->ylist)) != &dummy_eprog) { /* from function: pass completions as arg list */ LinkList args = newlinklist(); LinkNode ln; Cmatch m; int osc = sfcontext; addlinknode(args, cc->ylist); for (ln = firstnode(matches); ln; ln = nextnode(ln)) { m = (Cmatch) getdata(ln); if (m->ppre) { char *s = (m->psuf ? m->psuf : ""); char *p = (char *) zhalloc(strlen(m->ppre) + strlen(m->str) + strlen(s) + 1); sprintf(p, "%s%s%s", m->ppre, m->str, s); addlinknode(args, dupstring(p)); } else addlinknode(args, dupstring(m->str)); } /* No harm in allowing read -l and -c here, too */ if (incompfunc != 1) incompctlfunc = 1; sfcontext = SFC_COMPLETE; doshfunc(cc->ylist, prog, args, 0, 1); sfcontext = osc; incompctlfunc = 0; uv = "reply"; } if (uv) yaptr = get_user_var(uv); if ((tt = cc->explain)) { tt = dupstring(tt); if ((cc->mask & CC_EXPANDEXPL) && !parsestr(tt)) { singsub(&tt); untokenize(tt); } curexpl->str = tt; if (cc->gname) { endcmgroup(yaptr); begcmgroup(cc->gname, gflags); addexpl(); } else { addexpl(); endcmgroup(yaptr); begcmgroup("default", 0); } } else { endcmgroup(yaptr); begcmgroup("default", 0); } } else if ((tt = cc->explain)) { tt = dupstring(tt); if ((cc->mask & CC_EXPANDEXPL) && !parsestr(tt)) { singsub(&tt); untokenize(tt); } curexpl->str = tt; addexpl(); } if (cc->subcmd) { /* Handle -l sub-completion. */ char **ow = clwords, *os = cmdstr, *ops = NULL; int oldn = clwnum, oldp = clwpos, br; unsigned long occ = ccont; ccont = CC_CCCONT; /* So we restrict the words-array. */ if (brange >= clwnum) brange = clwnum - 1; if (brange < 1) brange = 1; if (erange >= clwnum) erange = clwnum - 1; if (erange < 1) erange = 1; clwnum = erange - brange + 1; clwpos = clwpos - brange; br = brange; if (cc->subcmd[0]) { /* And probably put the command name given to the flag * * in the array. */ clwpos++; clwnum++; incmd = 0; ops = clwords[br - 1]; clwords[br - 1] = ztrdup(cc->subcmd); cmdstr = ztrdup(cc->subcmd); clwords += br - 1; } else { cmdstr = ztrdup(clwords[br]); incmd = !clwpos; clwords += br; } /* Produce the matches. */ makecomplistcmd(s, incmd, CFN_FIRST); /* And restore the things we changed. */ clwords = ow; zsfree(cmdstr); cmdstr = os; clwnum = oldn; clwpos = oldp; if (ops) { zsfree(clwords[br - 1]); clwords[br - 1] = ops; } ccont = occ; } if (cc->substr) sep_comp_string(cc->substr, s, offs); uremnode(ccstack, firstnode(ccstack)); if (cc->matcher) mstack = mstack->next; if (mn == mnum) haspattern = ohp; } static struct builtin bintab[] = { BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL), BUILTIN("compcall", 0, bin_compcall, 0, 0, 0, "TD", NULL), }; /**/ int setup_(Module m) { compctlreadptr = compctlread; createcompctltable(); cc_compos.mask = CC_COMMPATH; cc_compos.mask2 = 0; cc_default.refc = 10000; cc_default.mask = CC_FILES; cc_default.mask2 = 0; cc_first.refc = 10000; cc_first.mask = 0; cc_first.mask2 = CC_CCCONT; lastccused = NULL; return 0; } /**/ int boot_(Module m) { addhookfunc("compctl_make", (Hookfn) ccmakehookfn); addhookfunc("compctl_cleanup", (Hookfn) cccleanuphookfn); return (addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) != 1); } /**/ int cleanup_(Module m) { deletehookfunc("compctl_make", (Hookfn) ccmakehookfn); deletehookfunc("compctl_cleanup", (Hookfn) cccleanuphookfn); deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); return 0; } /**/ int finish_(Module m) { deletehashtable(compctltab); if (lastccused) freelinklist(lastccused, (FreeFunc) freecompctl); compctlreadptr = fallback_compctlread; return 0; }