mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-19 21:44:11 +01:00
3948 lines
94 KiB
C
3948 lines
94 KiB
C
/*
|
|
* 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, Options 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 (OPT_ISSET(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 (OPT_ISSET(ops,'n')) {
|
|
char nbuf[14];
|
|
|
|
if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
|
|
printf("%d\n", cs + 1);
|
|
if (!OPT_ISSET(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 (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
|
|
zputs((char *) line, stdout);
|
|
putchar('\n');
|
|
}
|
|
if (!OPT_ISSET(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 (OPT_ISSET(ops,'n')) {
|
|
char nbuf[14];
|
|
|
|
if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
|
|
printf("%d\n", clwpos + 1);
|
|
if (!OPT_ISSET(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 (OPT_ISSET(ops,'A') && !OPT_ISSET(ops,'e')) {
|
|
/* the -A option means that one array is specified, instead of
|
|
many parameters */
|
|
char **p, **b = (char **)zshcalloc((clwnum + 1) * sizeof(char *));
|
|
|
|
for (i = 0, p = b; i < clwnum; p++, i++)
|
|
*p = ztrdup(clwords[i]);
|
|
|
|
setaparam(reply, b);
|
|
return 0;
|
|
}
|
|
if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
|
|
for (i = 0; i < clwnum; i++) {
|
|
zputs(clwords[i], stdout);
|
|
putchar('\n');
|
|
}
|
|
|
|
if (OPT_ISSET(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) zshcalloc(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) zshcalloc(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 *)zshcalloc(n * sizeof(int));
|
|
c->u.r.b = (int *)zshcalloc(n * sizeof(int));
|
|
} else if (c->type == CCT_CURSUF ||
|
|
c->type == CCT_CURPRE ||
|
|
c->type == CCT_QUOTE)
|
|
c->u.s.s = (char **)zshcalloc(n * sizeof(char *));
|
|
|
|
else if (c->type == CCT_RANGESTR ||
|
|
c->type == CCT_RANGEPAT) {
|
|
c->u.l.a = (char **)zshcalloc(n * sizeof(char *));
|
|
c->u.l.b = (char **)zshcalloc(n * sizeof(char *));
|
|
} else {
|
|
c->u.s.p = (int *)zshcalloc(n * sizeof(int));
|
|
c->u.s.s = (char **)zshcalloc(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) zshcalloc(sizeof(*c));
|
|
o = c;
|
|
t++;
|
|
} else if (*t) {
|
|
/* Another condition to `and' */
|
|
c->and = (Compcond) zshcalloc(sizeof(*c));
|
|
c = c->and;
|
|
}
|
|
}
|
|
/* Assign condition to current compctl */
|
|
*next = (Compctl) zshcalloc(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) zshcalloc(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, Options 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) zshcalloc(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, Options ops, int func)
|
|
{
|
|
if (incompfunc != 1) {
|
|
zwarnnam(name, "can only be called from completion function", NULL, 0);
|
|
return 1;
|
|
}
|
|
return makecomplistctl((OPT_ISSET(ops,'T') ? 0 : CFN_FIRST) |
|
|
(OPT_ISSET(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, s, 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(0);
|
|
} else {
|
|
addexpl(0);
|
|
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(0);
|
|
}
|
|
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;
|
|
}
|