1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-08 16:46:22 +02:00
zsh/Src/Zle/complete.c
Oliver Kiddle a73c705b0c 51212: remove STOUC() macro
This served as a workaround for ancient compilers where casts to
unsigned char were broken.
2022-12-16 23:28:10 +01:00

1822 lines
39 KiB
C

/*
* complete.c - the complete module, interface part
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1999 Sven Wischnowsky
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Sven Wischnowsky or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Sven Wischnowsky and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Sven Wischnowsky and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "complete.mdh"
#include "complete.pro"
/* global variables for shell parameters in new style completion */
/**/
mod_export
zlong compcurrent,
complistmax;
/**/
zlong complistlines,
compignored;
/**/
mod_export
char **compwords,
**compredirs,
*compprefix,
*compsuffix,
*complastprefix,
*complastsuffix,
*compisuffix,
*compqiprefix,
*compqisuffix,
*compquote,
*compqstack, /* compstate[all_quotes] */
*comppatmatch,
*complastprompt;
/**/
char *compiprefix,
*compcontext,
*compparameter,
*compredirect,
*compquoting,
*comprestore,
*complist,
*compinsert,
*compexact,
*compexactstr,
*comppatinsert,
*comptoend, /* compstate[to_end]; populates 'movetoend' */
*compoldlist,
*compoldins,
*compvared;
/*
* An array of Param structures for compsys special parameters;
* see 'comprparams' below. An entry for $compstate is added
* by makecompparams().
*
* See CP_REALPARAMS.
*/
/**/
Param *comprpms;
/*
* An array of Param structures for elements of $compstate; see
* 'compkparams' below.
*
* See CP_KEYPARAMS.
*/
/**/
Param *compkpms;
/**/
mod_export void
freecmlist(Cmlist l)
{
Cmlist n;
while (l) {
n = l->next;
freecmatcher(l->matcher);
zsfree(l->str);
zfree(l, sizeof(struct cmlist));
l = n;
}
}
/**/
mod_export void
freecmatcher(Cmatcher m)
{
Cmatcher n;
if (!m || --(m->refc))
return;
while (m) {
n = m->next;
freecpattern(m->line);
freecpattern(m->word);
freecpattern(m->left);
freecpattern(m->right);
zfree(m, sizeof(struct cmatcher));
m = n;
}
}
/**/
void
freecpattern(Cpattern p)
{
Cpattern n;
while (p) {
n = p->next;
if (p->tp <= CPAT_EQUIV)
free(p->u.str);
zfree(p, sizeof(struct cpattern));
p = n;
}
}
/* Copy a completion matcher list into permanent storage. */
/**/
mod_export Cmatcher
cpcmatcher(Cmatcher m)
{
Cmatcher r = NULL, *p = &r, n;
while (m) {
*p = n = (Cmatcher) zalloc(sizeof(struct cmatcher));
n->refc = 1;
n->next = NULL;
n->flags = m->flags;
n->line = cpcpattern(m->line);
n->llen = m->llen;
n->word = cpcpattern(m->word);
n->wlen = m->wlen;
n->left = cpcpattern(m->left);
n->lalen = m->lalen;
n->right = cpcpattern(m->right);
n->ralen = m->ralen;
p = &(n->next);
m = m->next;
}
return r;
}
/*
* Copy a single entry in a matcher pattern.
* If useheap is 1, it comes from the heap.
*/
/**/
mod_export Cpattern
cp_cpattern_element(Cpattern o)
{
Cpattern n = zalloc(sizeof(struct cpattern));
n->next = NULL;
n->tp = o->tp;
switch (o->tp)
{
case CPAT_CCLASS:
case CPAT_NCLASS:
case CPAT_EQUIV:
n->u.str = ztrdup(o->u.str);
break;
case CPAT_CHAR:
n->u.chr = o->u.chr;
break;
default:
/* just to keep compiler quiet */
break;
}
return n;
}
/* Copy a completion matcher pattern. */
/**/
static Cpattern
cpcpattern(Cpattern o)
{
Cpattern r = NULL, *p = &r;
while (o) {
*p = cp_cpattern_element(o);
p = &((*p)->next);
o = o->next;
}
return r;
}
/*
* Parse a string for matcher control, containing multiple matchers.
*
* 's' is the string to be parsed.
*
* 'name' is the name of the builtin from which this is called, for errors.
*
* Return 'pcm_err' on error; a NULL return value means ...
*/
/**/
mod_export Cmatcher
parse_cmatcher(char *name, char *s)
{
Cmatcher ret = NULL, r = NULL, n;
Cpattern line, word, left, right;
int fl, fl2, ll, wl, lal, ral, err, both;
if (!*s)
return NULL;
while (*s) {
lal = ral = both = fl2 = 0;
left = right = NULL;
while (*s && inblank(*s)) s++;
if (!*s) break;
switch (*s) {
case 'b': fl2 = CMF_INTER; /* FALLTHROUGH */
case 'l': fl = CMF_LEFT; break;
case 'e': fl2 = CMF_INTER; /* FALLTHROUGH */
case 'r': fl = CMF_RIGHT; break;
case 'm': fl = 0; break;
case 'B': fl2 = CMF_INTER; /* FALLTHROUGH */
case 'L': fl = CMF_LEFT | CMF_LINE; break;
case 'E': fl2 = CMF_INTER; /* FALLTHROUGH */
case 'R': fl = CMF_RIGHT | CMF_LINE; break;
case 'M': fl = CMF_LINE; break;
case 'x': break;
default:
if (name)
zwarnnam(name, "unknown match specification character `%c'",
*s);
return pcm_err;
}
if (s[1] != ':') {
if (name)
zwarnnam(name, "missing `:'");
return pcm_err;
}
if (*s == 'x') {
if (s[2] && !inblank(s[2])) {
if (name)
zwarnnam(name,
"unexpected pattern following x: specification");
return pcm_err;
}
return ret;
}
s += 2;
if (!*s) {
if (name)
zwarnnam(name, "missing patterns");
return pcm_err;
}
if ((fl & CMF_LEFT) && !fl2) {
left = parse_pattern(name, &s, &lal, '|', &err);
if (err)
return pcm_err;
if ((both = (*s && s[1] == '|')))
s++;
if (!*s || !*++s) {
if (name) {
if (both)
zwarnnam(name, "missing right anchor");
else
zwarnnam(name, "missing line pattern");
}
return pcm_err;
}
} else
left = NULL;
line = parse_pattern(name, &s, &ll,
(((fl & CMF_RIGHT) && !fl2) ? '|' : '='),
&err);
if (err)
return pcm_err;
if (both) {
right = line;
ral = ll;
line = NULL;
ll = 0;
}
if ((fl & CMF_RIGHT) && !fl2 && (!*s || !*++s)) {
if (name)
zwarnnam(name, "missing right anchor");
return pcm_err;
} else if (!(fl & CMF_RIGHT) || fl2) {
if (!*s) {
if (name)
zwarnnam(name, "missing word pattern");
return pcm_err;
}
s++;
}
if ((fl & CMF_RIGHT) && !fl2) {
if (*s == '|') {
left = line;
lal = ll;
line = NULL;
ll = 0;
s++;
}
right = parse_pattern(name, &s, &ral, '=', &err);
if (err)
return pcm_err;
if (!*s) {
if (name)
zwarnnam(name, "missing word pattern");
return pcm_err;
}
s++;
}
if (*s == '*') {
if (!(fl & (CMF_LEFT | CMF_RIGHT))) {
if (name)
zwarnnam(name, "need anchor for `*'");
return pcm_err;
}
word = NULL;
if (*++s == '*') {
s++;
wl = -2;
} else
wl = -1;
} else {
word = parse_pattern(name, &s, &wl, 0, &err);
if (!word && !line) {
if (name)
zwarnnam(name, "need non-empty word or line pattern");
return pcm_err;
}
}
if (err)
return pcm_err;
n = (Cmatcher) hcalloc(sizeof(*ret));
n->next = NULL;
n->flags = fl | fl2;
n->line = line;
n->llen = ll;
n->word = word;
n->wlen = wl;
n->left = left;
n->lalen = lal;
n->right = right;
n->ralen = ral;
if (ret)
r->next = n;
else
ret = n;
r = n;
}
return ret;
}
/*
* Parse a pattern for matcher control.
* name is the name of the builtin from which this is called, for errors.
* *sp is the input string and will be updated to the end of the parsed
* pattern.
* *lp will be set to the number of characters (possibly multibyte)
* that the pattern will match. This must be deterministic, given
* the syntax allowed here.
* e, if non-zero, is the ASCII end character to match; if zero,
* stop on a blank.
* *err is set to 1 to indicate an error, else to 0.
*/
/**/
static Cpattern
parse_pattern(char *name, char **sp, int *lp, char e, int *err)
{
Cpattern ret = NULL, r = NULL, n;
char *s = *sp;
convchar_t inchar;
int l = 0, inlen;
*err = 0;
MB_METACHARINIT();
while (*s && (e ? (*s != e) : !inblank(*s))) {
n = (Cpattern) hcalloc(sizeof(*n));
n->next = NULL;
if (*s == '[' || *s == '{') {
s = parse_class(n, s);
if (!*s) {
*err = 1;
zwarnnam(name, "unterminated character class");
return NULL;
}
s++;
} else if (*s == '?') {
n->tp = CPAT_ANY;
s++;
} else if (*s == '*' || *s == '(' || *s == ')' || *s == '=') {
*err = 1;
zwarnnam(name, "invalid pattern character `%c'", *s);
return NULL;
} else {
if (*s == '\\' && s[1])
s++;
inlen = MB_METACHARLENCONV(s, &inchar);
#ifdef MULTIBYTE_SUPPORT
if (inchar == WEOF)
inchar = (convchar_t)(*s == Meta ? s[1] ^ 32 : *s);
#endif
s += inlen;
n->tp = CPAT_CHAR;
n->u.chr = inchar;
}
if (ret)
r->next = n;
else
ret = n;
r = n;
l++;
}
*sp = (char *) s;
*lp = l;
return ret;
}
/* Parse a character class for matcher control. */
/**/
static char *
parse_class(Cpattern p, char *iptr)
{
int endchar, firsttime = 1;
char *optr, *nptr;
if (*iptr++ == '[') {
endchar = ']';
/* TODO: surely [^]] is valid? */
if ((*iptr == '!' || *iptr == '^') && iptr[1] != ']') {
p->tp = CPAT_NCLASS;
iptr++;
} else
p->tp = CPAT_CCLASS;
} else {
endchar = '}';
p->tp = CPAT_EQUIV;
}
/* find end of class. End character can appear literally first. */
for (optr = iptr; optr == iptr || *optr != endchar; optr++)
if (!*optr)
return optr;
/*
* We can always fit the parsed class within the same length
* because of the tokenization (including a null byte).
*
* As the input string is metafied, but shouldn't contain shell
* tokens, we can just add our own tokens willy nilly.
*/
optr = p->u.str = zhalloc((optr-iptr) + 1);
while (firsttime || *iptr != endchar) {
int ch;
if (*iptr == '[' && iptr[1] == ':' &&
(nptr = strchr((char *)iptr + 2, ':')) && nptr[1] == ']') {
/* Range type */
iptr += 2;
ch = range_type((char *)iptr, nptr-iptr);
iptr = nptr + 2;
if (ch != PP_UNKWN)
*optr++ = (unsigned char) Meta + ch;
} else {
/* characters stay metafied */
char *ptr1 = iptr;
if (*iptr == Meta)
iptr++;
iptr++;
if (*iptr == '-' && iptr[1] && iptr[1] != endchar) {
/* a run of characters */
iptr++;
/* range token */
*optr++ = Meta + PP_RANGE;
/* start of range character */
if (*ptr1 == Meta) {
*optr++ = Meta;
*optr++ = ptr1[1] ^ 32;
} else
*optr++ = *ptr1;
if (*iptr == Meta) {
*optr++ = *iptr++;
*optr++ = *iptr++;
} else
*optr++ = *iptr++;
} else {
if (*ptr1 == Meta) {
*optr++ = Meta;
*optr++ = ptr1[1] ^ 32;
} else
*optr++ = *ptr1;
}
}
firsttime = 0;
}
*optr = '\0';
return iptr;
}
static struct { char *name; int abbrev; int oflag; } orderopts[] = {
{ "nosort", 2, CAF_NOSORT },
{ "match", 3, CAF_MATSORT },
{ "numeric", 3, CAF_NUMSORT },
{ "reverse", 3, CAF_REVSORT }
};
/* Parse the option to compadd -o, if flags is non-NULL set it
* returns -1 if the argument isn't a valid ordering, 0 otherwise */
/**/
static int
parse_ordering(const char *arg, int *flags)
{
int o, fl = 0;
const char *next, *opt = arg;
do {
int found = 0;
next = strchr(opt, ',');
if (!next)
next = opt + strlen(opt);
for (o = sizeof(orderopts)/sizeof(*orderopts) - 1; o >= 0 &&
!found; --o)
{
if ((found = next - opt >= orderopts[o].abbrev &&
!strncmp(orderopts[o].name, opt, next - opt)))
fl |= orderopts[o].oflag;
}
if (!found) {
if (flags) /* default to "match" */
*flags = CAF_MATSORT;
return -1;
}
} while (*next && ((opt = next + 1)));
if (flags)
*flags |= fl;
return 0;
}
/**/
static int
bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
{
struct cadata dat;
char *mstr = NULL; /* argument of -M options, accumulated */
char *oarg = NULL; /* argument of -o option */
int added; /* return value */
Cmatcher match = NULL;
size_t dparlen = 0, dparsize = 0; /* no. of -D options and array size */
if (incompfunc != 1) {
zwarnnam(name, "can only be called from completion function");
return 1;
}
dat.ipre = dat.isuf = dat.ppre = dat.psuf = dat.prpre = dat.mesg =
dat.pre = dat.suf = dat.group = dat.rems = dat.remf = dat.disp =
dat.ign = dat.exp = dat.apar = dat.opar = NULL;
dat.dpar = NULL;
dat.match = NULL;
dat.flags = 0;
dat.aflags = CAF_MATCH;
dat.dummies = -1;
for (; *argv && **argv == '-'; argv++) {
char *p; /* loop variable, points into argv */
if (!(*argv)[1]) {
argv++;
break;
}
for (p = *argv + 1; *p; p++) {
char *m = NULL; /* argument of -M option (this one only) */
int order = 0; /* if -o found (argument to which is optional) */
char **sp = NULL; /* the argument to an option should be copied
to *sp. */
const char *e; /* error message */
switch (*p) {
case 'q':
dat.flags |= CMF_REMOVE;
break;
case 'Q':
dat.aflags |= CAF_QUOTE;
break;
case 'C':
dat.aflags |= CAF_ALL;
break;
case 'f':
dat.flags |= CMF_FILE;
break;
case 'e':
dat.flags |= CMF_ISPAR;
break;
case 'a':
dat.aflags |= CAF_ARRAYS;
break;
case 'k':
dat.aflags |= CAF_ARRAYS|CAF_KEYS;
break;
case 'F':
sp = &(dat.ign);
e = "string expected after -%c";
break;
case 'n':
dat.flags |= CMF_NOLIST;
break;
case 'U':
dat.aflags &= ~CAF_MATCH;
break;
case 'P':
sp = &(dat.pre);
e = "string expected after -%c";
break;
case 'S':
sp = &(dat.suf);
e = "string expected after -%c";
break;
case 'J':
sp = &(dat.group);
e = "group name expected after -%c";
break;
case 'V':
if (!dat.group)
dat.aflags |= CAF_NOSORT;
sp = &(dat.group);
e = "group name expected after -%c";
break;
case '1':
if (!(dat.aflags & CAF_UNIQCON))
dat.aflags |= CAF_UNIQALL;
break;
case '2':
if (!(dat.aflags & CAF_UNIQALL))
dat.aflags |= CAF_UNIQCON;
break;
case 'i':
sp = &(dat.ipre);
e = "string expected after -%c";
break;
case 'I':
sp = &(dat.isuf);
e = "string expected after -%c";
break;
case 'p':
sp = &(dat.ppre);
e = "string expected after -%c";
break;
case 's':
sp = &(dat.psuf);
e = "string expected after -%c";
break;
case 'W':
sp = &(dat.prpre);
e = "string expected after -%c";
break;
case 'M':
sp = &m;
e = "matching specification expected after -%c";
break;
case 'X':
sp = &(dat.exp);
e = "string expected after -%c";
break;
case 'x':
sp = &(dat.mesg);
e = "string expected after -%c";
break;
case 'r':
dat.flags |= CMF_REMOVE;
sp = &(dat.rems);
e = "string expected after -%c";
break;
case 'R':
dat.flags |= CMF_REMOVE;
sp = &(dat.remf);
e = "function name expected after -%c";
break;
case 'A':
sp = &(dat.apar);
e = "parameter name expected after -%c";
break;
case 'O':
sp = &(dat.opar);
e = "parameter name expected after -%c";
break;
case 'D':
if (dparsize <= dparlen + 1) {
dparsize = (dparsize + 1) * 2;
dat.dpar = (char **)zrealloc(dat.dpar, sizeof(char *) * dparsize);
}
sp = dat.dpar + dparlen++;
*sp = dat.dpar[dparlen] = NULL;
e = "parameter name expected after -%c";
break;
case 'd':
sp = &(dat.disp);
e = "parameter name expected after -%c";
break;
case 'l':
dat.flags |= CMF_DISPLINE;
break;
case 'o':
/* we honour just the first -o option but need to skip
* over a valid argument to subsequent -o options */
order = oarg ? -1 : 1;
sp = &oarg;
/* no error string because argument is optional */
break;
case 'E':
if (p[1]) {
dat.dummies = atoi(p + 1);
p += strlen(p+1);
} else if (argv[1]) {
argv++;
dat.dummies = atoi(*argv);
} else {
zwarnnam(name, "number expected after -%c", *p);
zsfree(mstr);
zfree(dat.dpar, dparsize);
return 1;
}
if (dat.dummies < 0) {
zwarnnam(name, "invalid number: %d", dat.dummies);
zsfree(mstr);
zfree(dat.dpar, dparsize);
return 1;
}
break;
case '-':
argv++;
goto ca_args;
default:
zwarnnam(name, "bad option: -%c", *p);
zsfree(mstr);
zfree(dat.dpar, dparsize);
return 1;
}
if (sp) {
if (p[1]) {
/* Pasted argument: -Xfoo. */
if (!*sp) /* take first option only */
*sp = p + 1;
if (!order || !parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
p += strlen(p+1);
} else if (argv[1]) {
/* Argument in a separate word: -X foo. */
argv++;
if (!*sp)
*sp = *argv;
if (order && parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
--argv;
} else if (!order) {
/* Missing argument: argv[N] == "-X", argv[N+1] == NULL. */
zwarnnam(name, e, *p);
zsfree(mstr);
zfree(dat.dpar, dparsize);
return 1;
}
if (m) {
if (mstr) {
char *tmp = tricat(mstr, " ", m);
zsfree(mstr);
mstr = tmp;
} else
mstr = ztrdup(m);
}
}
}
}
ca_args:
if (mstr && (match = parse_cmatcher(name, mstr)) == pcm_err) {
zsfree(mstr);
zfree(dat.dpar, dparsize);
return 1;
}
zsfree(mstr);
if (!*argv && !dat.group && !dat.mesg &&
!(dat.aflags & (CAF_NOSORT|CAF_UNIQALL|CAF_UNIQCON|CAF_ALL))) {
zfree(dat.dpar, dparsize);
return 1;
}
dat.match = match = cpcmatcher(match);
added = addmatches(&dat, argv);
freecmatcher(match);
zfree(dat.dpar, dparsize);
return added;
}
#define CVT_RANGENUM 0
#define CVT_RANGEPAT 1
#define CVT_PRENUM 2
#define CVT_PREPAT 3
#define CVT_SUFNUM 4
#define CVT_SUFPAT 5
/**/
mod_export void
ignore_prefix(int l)
{
if (l) {
char *tmp, sav;
int pl = strlen(compprefix);
if (l > pl)
l = pl;
sav = compprefix[l];
compprefix[l] = '\0';
tmp = tricat(compiprefix, compprefix, "");
zsfree(compiprefix);
compiprefix = tmp;
compprefix[l] = sav;
tmp = ztrdup(compprefix + l);
zsfree(compprefix);
compprefix = tmp;
}
}
/**/
mod_export void
ignore_suffix(int l)
{
if (l) {
char *tmp, sav;
int sl = strlen(compsuffix);
if ((l = sl - l) < 0)
l = 0;
tmp = tricat(compsuffix + l, compisuffix, "");
zsfree(compisuffix);
compisuffix = tmp;
sav = compsuffix[l];
compsuffix[l] = '\0';
tmp = ztrdup(compsuffix);
compsuffix[l] = sav;
zsfree(compsuffix);
compsuffix = tmp;
}
}
/**/
mod_export void
restrict_range(int b, int e)
{
int wl = arrlen(compwords) - 1;
if (wl && b >= 0 && e >= 0 && (b > 0 || e < wl)) {
int i;
char **p, **q, **pp;
if (e > wl)
e = wl;
i = e - b + 1;
p = (char **) zshcalloc((i + 1) * sizeof(char *));
for (q = p, pp = compwords + b; i; i--, q++, pp++)
*q = ztrdup(*pp);
freearray(compwords);
compwords = p;
compcurrent -= b;
}
}
/**/
static int
do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
{
switch (test) {
case CVT_RANGENUM:
{
int l = arrlen(compwords);
if (na < 0)
na += l;
else
na--;
if (nb < 0)
nb += l;
else
nb--;
if (compcurrent - 1 < na || compcurrent - 1 > nb)
return 0;
if (mod)
restrict_range(na, nb);
return 1;
}
case CVT_RANGEPAT:
{
char **p;
int i, l = arrlen(compwords), t = 0, b = 0, e = l - 1;
Patprog pp;
i = compcurrent - 1;
if (i < 0 || i >= l)
return 0;
singsub(&sa);
pp = patcompile(sa, PAT_HEAPDUP, NULL);
for (i--, p = compwords + i; i >= 0; p--, i--) {
if (pattry(pp, *p)) {
b = i + 1;
t = 1;
break;
}
}
if (t && sb) {
int tt = 0;
singsub(&sb);
pp = patcompile(sb, PAT_STATIC, NULL);
for (i++, p = compwords + i; i < l; p++, i++) {
if (pattry(pp, *p)) {
e = i - 1;
tt = 1;
break;
}
}
if (tt && i < compcurrent)
t = 0;
}
if (e < b)
t = 0;
if (t && mod)
restrict_range(b, e);
return t;
}
case CVT_PRENUM:
case CVT_SUFNUM:
if (na < 0)
return 0;
if (na > 0 && mod) {
#ifdef MULTIBYTE_SUPPORT
if (isset(MULTIBYTE)) {
if (test == CVT_PRENUM) {
const char *ptr = compprefix;
int len = 1;
int sum = 0;
while (*ptr && na && len) {
wint_t wc;
len = mb_metacharlenconv(ptr, &wc);
ptr += len;
sum += len;
na--;
}
if (na)
return 0;
na = sum;
} else {
char *end = compsuffix + strlen(compsuffix);
char *ptr = end;
while (na-- && ptr > compsuffix)
ptr = backwardmetafiedchar(compsuffix, ptr, NULL);
if (na >= 0)
return 0;
na = end - ptr;
}
} else
#endif
if ((int)strlen(test == CVT_PRENUM ? compprefix : compsuffix) < na)
return 0;
if (test == CVT_PRENUM)
ignore_prefix(na);
else
ignore_suffix(na);
return 1;
}
return 1;
case CVT_PREPAT:
case CVT_SUFPAT:
{
Patprog pp;
if (!na)
return 0;
if (!(pp = patcompile(sa, PAT_HEAPDUP, 0)))
return 0;
if (test == CVT_PREPAT) {
int l, add;
char *p, sav;
if (!(l = strlen(compprefix)))
return ((na == 1 || na == -1) && pattry(pp, compprefix));
if (na < 0) {
p = compprefix + l;
na = -na;
add = -1;
} else {
p = compprefix + 1 + (*compprefix == Meta);
if (p > compprefix + l)
p = compprefix + l;
add = 1;
}
for (;;) {
sav = *p;
*p = '\0';
test = pattry(pp, compprefix);
*p = sav;
if (test && !--na)
break;
if (add > 0) {
if (p == compprefix + l)
return 0;
p = p + 1 + (*p == Meta);
if (p > compprefix + l)
p = compprefix + l;
} else {
if (p == compprefix)
return 0;
p--;
if (p > compprefix && p[-1] == Meta)
p--;
}
}
if (mod)
ignore_prefix(p - compprefix);
} else {
int l, ol, add;
char *p;
if (!(ol = l = strlen(compsuffix)))
return ((na == 1 || na == -1) && pattry(pp, compsuffix));
if (na < 0) {
p = compsuffix;
na = -na;
add = 1;
} else {
p = compsuffix + l - 1;
if (p > compsuffix && p[-1] == Meta)
p--;
add = -1;
}
for (;;) {
if (pattry(pp, p) && !--na)
break;
if (add > 0) {
if (p == compsuffix + l)
return 0;
if (*p == Meta)
p += 2;
else
p++;
} else {
if (p == compsuffix)
return 0;
p--;
if (p > compsuffix && p[-1] == Meta)
p--;
}
}
if (mod)
ignore_suffix(ol - (p - compsuffix));
}
return 1;
}
}
return 0;
}
/**/
static int
bin_compset(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
{
int test = 0, na = 0, nb = 0;
char *sa = NULL, *sb = NULL;
if (incompfunc != 1) {
zwarnnam(name, "can only be called from completion function");
return 1;
}
if (argv[0][0] != '-') {
zwarnnam(name, "missing option");
return 1;
}
switch (argv[0][1]) {
case 'n': test = CVT_RANGENUM; break;
case 'N': test = CVT_RANGEPAT; break;
case 'p': test = CVT_PRENUM; break;
case 'P': test = CVT_PREPAT; break;
case 's': test = CVT_SUFNUM; break;
case 'S': test = CVT_SUFPAT; break;
case 'q': return set_comp_sep();
default:
zwarnnam(name, "bad option -%c", argv[0][1]);
return 1;
}
if (argv[0][2]) {
sa = argv[0] + 2;
sb = argv[1];
na = 2;
} else {
if (!(sa = argv[1])) {
zwarnnam(name, "missing string for option -%c", argv[0][1]);
return 1;
}
sb = argv[2];
na = 3;
}
if (((test == CVT_PRENUM || test == CVT_SUFNUM) ? !!sb :
(sb && argv[na]))) {
zwarnnam(name, "too many arguments");
return 1;
}
switch (test) {
case CVT_RANGENUM:
na = atoi(sa);
nb = (sb ? atoi(sb) : -1);
break;
case CVT_RANGEPAT:
tokenize(sa);
remnulargs(sa);
if (sb) {
tokenize(sb);
remnulargs(sb);
}
break;
case CVT_PRENUM:
case CVT_SUFNUM:
na = atoi(sa);
break;
case CVT_PREPAT:
case CVT_SUFPAT:
if (sb) {
na = atoi(sa);
sa = sb;
} else
na = -1;
tokenize(sa);
remnulargs(sa);
break;
}
return !do_comp_vars(test, na, sa, nb, sb, 1);
}
/* Definitions for the special parameters. Note that these have to match the
* order of the CP_* bits in comp.h */
#define VAL(X) ((void *) (&(X)))
#define GSU(X) ((GsuScalar)(void *) (&(X)))
struct compparam {
char *name;
int type;
void *var;
GsuScalar gsu;
};
static const struct gsu_scalar compvarscalar_gsu =
{ strvargetfn, strvarsetfn, compunsetfn };
static const struct gsu_scalar complist_gsu =
{ get_complist, set_complist, compunsetfn };
static const struct gsu_scalar unambig_gsu =
{ get_unambig, nullstrsetfn, compunsetfn };
static const struct gsu_scalar unambig_pos_gsu =
{ get_unambig_pos, nullstrsetfn, compunsetfn };
static const struct gsu_scalar insert_pos_gsu =
{ get_insert_pos, nullstrsetfn, compunsetfn };
static const struct gsu_scalar compqstack_gsu =
{ get_compqstack, nullstrsetfn, compunsetfn };
static const struct gsu_integer compvarinteger_gsu =
{ intvargetfn, intvarsetfn, compunsetfn };
static const struct gsu_integer nmatches_gsu =
{ get_nmatches, NULL, compunsetfn };
static const struct gsu_integer unambig_curs_gsu =
{ get_unambig_curs, NULL, compunsetfn };
static const struct gsu_integer listlines_gsu =
{ get_listlines, NULL, compunsetfn };
static const struct gsu_array compvararray_gsu =
{ arrvargetfn, arrvarsetfn, compunsetfn };
static struct compparam comprparams[] = {
{ "words", PM_ARRAY, VAL(compwords), NULL },
{ "redirections", PM_ARRAY, VAL(compredirs), NULL },
{ "CURRENT", PM_INTEGER, VAL(compcurrent), NULL },
{ "PREFIX", PM_SCALAR, VAL(compprefix), NULL },
{ "SUFFIX", PM_SCALAR, VAL(compsuffix), NULL },
{ "IPREFIX", PM_SCALAR, VAL(compiprefix), NULL },
{ "ISUFFIX", PM_SCALAR, VAL(compisuffix), NULL },
{ "QIPREFIX", PM_SCALAR | PM_READONLY, VAL(compqiprefix), NULL },
{ "QISUFFIX", PM_SCALAR | PM_READONLY, VAL(compqisuffix), NULL },
{ NULL, 0, NULL, NULL }
};
static struct compparam compkparams[] = {
{ "nmatches", PM_INTEGER | PM_READONLY, NULL, GSU(nmatches_gsu) },
{ "context", PM_SCALAR, VAL(compcontext), NULL },
{ "parameter", PM_SCALAR, VAL(compparameter), NULL },
{ "redirect", PM_SCALAR, VAL(compredirect), NULL },
{ "quote", PM_SCALAR | PM_READONLY, VAL(compquote), NULL },
{ "quoting", PM_SCALAR | PM_READONLY, VAL(compquoting), NULL },
{ "restore", PM_SCALAR, VAL(comprestore), NULL },
{ "list", PM_SCALAR, NULL, GSU(complist_gsu) },
{ "insert", PM_SCALAR, VAL(compinsert), NULL },
{ "exact", PM_SCALAR, VAL(compexact), NULL },
{ "exact_string", PM_SCALAR, VAL(compexactstr), NULL },
{ "pattern_match", PM_SCALAR, VAL(comppatmatch), NULL },
{ "pattern_insert", PM_SCALAR, VAL(comppatinsert), NULL },
{ "unambiguous", PM_SCALAR | PM_READONLY, NULL, GSU(unambig_gsu) },
{ "unambiguous_cursor", PM_INTEGER | PM_READONLY, NULL,
GSU(unambig_curs_gsu) },
{ "unambiguous_positions", PM_SCALAR | PM_READONLY, NULL,
GSU(unambig_pos_gsu) },
{ "insert_positions", PM_SCALAR | PM_READONLY, NULL,
GSU(insert_pos_gsu) },
{ "list_max", PM_INTEGER, VAL(complistmax), NULL },
{ "last_prompt", PM_SCALAR, VAL(complastprompt), NULL },
{ "to_end", PM_SCALAR, VAL(comptoend), NULL },
{ "old_list", PM_SCALAR, VAL(compoldlist), NULL },
{ "old_insert", PM_SCALAR, VAL(compoldins), NULL },
{ "vared", PM_SCALAR, VAL(compvared), NULL },
{ "list_lines", PM_INTEGER | PM_READONLY, NULL, GSU(listlines_gsu) },
{ "all_quotes", PM_SCALAR | PM_READONLY, NULL, GSU(compqstack_gsu) },
{ "ignored", PM_INTEGER | PM_READONLY, VAL(compignored), NULL },
{ NULL, 0, NULL, NULL }
};
#define COMPSTATENAME "compstate"
static void
addcompparams(struct compparam *cp, Param *pp)
{
for (; cp->name; cp++, pp++) {
Param pm = createparam(cp->name,
cp->type |PM_SPECIAL|PM_REMOVABLE|PM_LOCAL);
if (!pm)
pm = (Param) paramtab->getnode(paramtab, cp->name);
DPUTS(!pm, "param not set in addcompparams");
*pp = pm;
pm->level = locallevel + 1;
if ((pm->u.data = cp->var)) {
switch(PM_TYPE(cp->type)) {
case PM_SCALAR:
pm->gsu.s = &compvarscalar_gsu;
break;
case PM_INTEGER:
pm->gsu.i = &compvarinteger_gsu;
pm->base = 10;
break;
case PM_ARRAY:
pm->gsu.a = &compvararray_gsu;
break;
}
} else {
pm->gsu.s = cp->gsu;
}
}
}
static const struct gsu_hash compstate_gsu =
{ get_compstate, set_compstate, compunsetfn };
/**/
void
makecompparams(void)
{
Param cpm;
HashTable tht;
addcompparams(comprparams, comprpms);
if (!(cpm = createparam(
COMPSTATENAME,
PM_SPECIAL|PM_REMOVABLE|PM_SINGLE|PM_LOCAL|PM_HASHED)))
cpm = (Param) paramtab->getnode(paramtab, COMPSTATENAME);
DPUTS(!cpm, "param not set in makecompparams");
comprpms[CPN_COMPSTATE] = cpm;
tht = paramtab;
cpm->level = locallevel + 1;
cpm->gsu.h = &compstate_gsu;
cpm->u.hash = paramtab = newparamtable(31, COMPSTATENAME);
addcompparams(compkparams, compkpms);
paramtab = tht;
}
/**/
static HashTable
get_compstate(Param pm)
{
return pm->u.hash;
}
/**/
static void
set_compstate(Param pm, HashTable ht)
{
struct compparam *cp;
Param *pp;
HashNode hn;
int i;
struct value v;
char *str;
if (!ht)
return;
for (i = 0; i < ht->hsize; i++)
for (hn = ht->nodes[i]; hn; hn = hn->next)
for (cp = compkparams,
pp = compkpms; cp->name; cp++, pp++)
if (!strcmp(hn->nam, cp->name)) {
v.isarr = v.flags = v.start = 0;
v.end = -1;
v.arr = NULL;
v.pm = (Param) hn;
if (cp->type == PM_INTEGER)
*((zlong *) cp->var) = getintvalue(&v);
else if ((str = getstrvalue(&v))) {
zsfree(*((char **) cp->var));
*((char **) cp->var) = ztrdup(str);
}
(*pp)->node.flags &= ~PM_UNSET;
break;
}
if (ht != pm->u.hash)
deleteparamtable(ht);
}
/**/
static zlong
get_nmatches(UNUSED(Param pm))
{
return (permmatches(0) ? 0 : nmatches);
}
/**/
static zlong
get_listlines(UNUSED(Param pm))
{
return list_lines();
}
/**/
static void
set_complist(UNUSED(Param pm), char *v)
{
comp_list(v);
}
/**/
static char *
get_complist(UNUSED(Param pm))
{
return complist;
}
/**/
static char *
get_unambig(UNUSED(Param pm))
{
return unambig_data(NULL, NULL, NULL);
}
/**/
static zlong
get_unambig_curs(UNUSED(Param pm))
{
int c;
unambig_data(&c, NULL, NULL);
return c;
}
/**/
static char *
get_unambig_pos(UNUSED(Param pm))
{
char *p;
unambig_data(NULL, &p, NULL);
return p;
}
/**/
static char *
get_insert_pos(UNUSED(Param pm))
{
char *p;
unambig_data(NULL, NULL, &p);
return p;
}
/**/
static char *
get_compqstack(UNUSED(Param pm))
{
char *p, *ptr, *cqp;
if (!compqstack) /* TODO: don't think this can happen... */
return "";
ptr = p = zhalloc(2*strlen(compqstack)+1);
for (cqp = compqstack; *cqp; cqp++) {
char *str = comp_quoting_string(*cqp);
*ptr++ = *str;
}
*ptr = '\0';
return p;
}
/**/
static void
compunsetfn(Param pm, int exp)
{
if (exp) {
if (pm->u.data) {
if (PM_TYPE(pm->node.flags) == PM_SCALAR) {
zsfree(*((char **) pm->u.data));
*((char **) pm->u.data) = ztrdup("");
} else if (PM_TYPE(pm->node.flags) == PM_ARRAY) {
freearray(*((char ***) pm->u.data));
*((char ***) pm->u.data) = zshcalloc(sizeof(char *));
} else if (PM_TYPE(pm->node.flags) == PM_HASHED) {
deleteparamtable(pm->u.hash);
pm->u.hash = NULL;
}
}
} else if (PM_TYPE(pm->node.flags) == PM_HASHED) {
Param *p;
int i;
deletehashtable(pm->u.hash);
pm->u.hash = NULL;
for (p = compkpms, i = CP_KEYPARAMS; i--; p++)
*p = NULL;
}
if (!exp) {
Param *p;
int i;
for (p = comprpms, i = CP_REALPARAMS; i; p++, i--)
if (*p == pm) {
*p = NULL;
break;
}
}
}
/**/
void
comp_setunset(int rset, int runset, int kset, int kunset)
{
Param *p;
if (comprpms && (rset >= 0 || runset >= 0)) {
for (p = comprpms; rset || runset; rset >>= 1, runset >>= 1, p++) {
if (*p) {
if (rset & 1)
(*p)->node.flags &= ~PM_UNSET;
if (runset & 1)
(*p)->node.flags |= PM_UNSET;
}
}
}
if (compkpms && (kset >= 0 || kunset >= 0)) {
for (p = compkpms; kset || kunset; kset >>= 1, kunset >>= 1, p++) {
if (*p) {
if (kset & 1)
(*p)->node.flags &= ~PM_UNSET;
if (kunset & 1)
(*p)->node.flags |= PM_UNSET;
}
}
}
}
/**/
static int
comp_wrapper(Eprog prog, FuncWrap w, char *name)
{
if (incompfunc != 1)
return 1;
else {
char *orest, *opre, *osuf, *oipre, *oisuf, **owords, **oredirs;
char *oqipre, *oqisuf, *oq, *oqi, *oqs, *oaq;
zlong ocur;
unsigned int runset = 0, kunset = 0, m, sm;
Param *pp;
m = CP_WORDS | CP_REDIRS | CP_CURRENT | CP_PREFIX | CP_SUFFIX |
CP_IPREFIX | CP_ISUFFIX | CP_QIPREFIX | CP_QISUFFIX;
for (pp = comprpms, sm = 1; m; pp++, m >>= 1, sm <<= 1) {
if ((m & 1) && ((*pp)->node.flags & PM_UNSET))
runset |= sm;
}
if (compkpms[CPN_RESTORE]->node.flags & PM_UNSET)
kunset = CP_RESTORE;
orest = comprestore;
comprestore = ztrdup("auto");
ocur = compcurrent;
opre = ztrdup(compprefix);
osuf = ztrdup(compsuffix);
oipre = ztrdup(compiprefix);
oisuf = ztrdup(compisuffix);
oqipre = ztrdup(compqiprefix);
oqisuf = ztrdup(compqisuffix);
oq = ztrdup(compquote);
oqi = ztrdup(compquoting);
oqs = ztrdup(compqstack);
oaq = ztrdup(autoq);
owords = zarrdup(compwords);
oredirs = zarrdup(compredirs);
runshfunc(prog, w, name);
if (comprestore && !strcmp(comprestore, "auto")) {
compcurrent = ocur;
zsfree(compprefix);
compprefix = opre;
zsfree(compsuffix);
compsuffix = osuf;
zsfree(compiprefix);
compiprefix = oipre;
zsfree(compisuffix);
compisuffix = oisuf;
zsfree(compqiprefix);
compqiprefix = oqipre;
zsfree(compqisuffix);
compqisuffix = oqisuf;
zsfree(compquote);
compquote = oq;
zsfree(compquoting);
compquoting = oqi;
zsfree(compqstack);
compqstack = oqs;
zsfree(autoq);
autoq = oaq;
freearray(compwords);
freearray(compredirs);
compwords = owords;
compredirs = oredirs;
comp_setunset(CP_COMPSTATE |
(~runset & (CP_WORDS | CP_REDIRS |
CP_CURRENT | CP_PREFIX |
CP_SUFFIX | CP_IPREFIX | CP_ISUFFIX |
CP_QIPREFIX | CP_QISUFFIX)),
(runset & CP_ALLREALS),
(~kunset & CP_RESTORE), (kunset & CP_ALLKEYS));
} else {
comp_setunset(CP_COMPSTATE, 0, (~kunset & CP_RESTORE),
(kunset & CP_RESTORE));
zsfree(opre);
zsfree(osuf);
zsfree(oipre);
zsfree(oisuf);
zsfree(oqipre);
zsfree(oqisuf);
zsfree(oq);
zsfree(oqi);
zsfree(oqs);
zsfree(oaq);
freearray(owords);
freearray(oredirs);
}
zsfree(comprestore);
comprestore = orest;
return 0;
}
}
/**/
static int
comp_check(void)
{
if (incompfunc != 1) {
zerr("condition can only be used in completion function");
return 0;
}
return 1;
}
/**/
static int
cond_psfix(char **a, int id)
{
if (comp_check()) {
if (a[1])
return do_comp_vars(id, cond_val(a, 0), cond_str(a, 1, 1),
0, NULL, 0);
else
return do_comp_vars(id, -1, cond_str(a, 0, 1), 0, NULL, 0);
}
return 0;
}
/**/
static int
cond_range(char **a, int id)
{
return do_comp_vars(CVT_RANGEPAT, 0, cond_str(a, 0, 1), 0,
(id ? cond_str(a, 1, 1) : NULL), 0);
}
static struct builtin bintab[] = {
BUILTIN("compadd", BINF_HANDLES_OPTS, bin_compadd, 0, -1, 0, NULL, NULL),
BUILTIN("compset", 0, bin_compset, 1, 3, 0, NULL, NULL),
};
static struct conddef cotab[] = {
CONDDEF("after", 0, cond_range, 1, 1, 0),
CONDDEF("between", 0, cond_range, 2, 2, 1),
CONDDEF("prefix", 0, cond_psfix, 1, 2, CVT_PREPAT),
CONDDEF("suffix", 0, cond_psfix, 1, 2, CVT_SUFPAT),
};
static struct funcwrap wrapper[] = {
WRAPDEF(comp_wrapper),
};
/* The order of the entries in this table has to match the *HOOK
* macros in comp.h */
/**/
struct hookdef comphooks[] = {
HOOKDEF("insert_match", NULL, HOOKF_ALL),
HOOKDEF("menu_start", NULL, HOOKF_ALL),
HOOKDEF("compctl_make", NULL, 0),
HOOKDEF("compctl_cleanup", NULL, 0),
HOOKDEF("comp_list_matches", ilistmatches, 0),
};
static struct features module_features = {
bintab, sizeof(bintab)/sizeof(*bintab),
cotab, sizeof(cotab)/sizeof(*cotab),
NULL, 0,
NULL, 0,
0
};
/**/
int
setup_(UNUSED(Module m))
{
hasperm = 0;
comprpms = compkpms = NULL;
compwords = compredirs = NULL;
compprefix = compsuffix = compiprefix = compisuffix =
compqiprefix = compqisuffix =
compcontext = compparameter = compredirect = compquote =
compquoting = comprestore = complist = compinsert =
compexact = compexactstr = comppatmatch = comppatinsert =
complastprompt = comptoend = compoldlist = compoldins =
compvared = compqstack = NULL;
complastprefix = ztrdup("");
complastsuffix = ztrdup("");
complistmax = 0;
hascompmod = 1;
return 0;
}
/**/
int
features_(Module m, char ***features)
{
*features = featuresarray(m, &module_features);
return 0;
}
/**/
int
enables_(Module m, int **enables)
{
return handlefeatures(m, &module_features, enables);
}
/**/
int
boot_(Module m)
{
addhookfunc("complete", (Hookfn) do_completion);
addhookfunc("before_complete", (Hookfn) before_complete);
addhookfunc("after_complete", (Hookfn) after_complete);
addhookfunc("accept_completion", (Hookfn) accept_last);
addhookfunc("list_matches", (Hookfn) list_matches);
addhookfunc("invalidate_list", (Hookfn) invalidate_list);
(void)addhookdefs(m, comphooks, sizeof(comphooks)/sizeof(*comphooks));
return addwrapper(m, wrapper);
}
/**/
int
cleanup_(Module m)
{
deletehookfunc("complete", (Hookfn) do_completion);
deletehookfunc("before_complete", (Hookfn) before_complete);
deletehookfunc("after_complete", (Hookfn) after_complete);
deletehookfunc("accept_completion", (Hookfn) accept_last);
deletehookfunc("list_matches", (Hookfn) list_matches);
deletehookfunc("invalidate_list", (Hookfn) invalidate_list);
(void)deletehookdefs(m, comphooks,
sizeof(comphooks)/sizeof(*comphooks));
deletewrapper(m, wrapper);
return setfeatureenables(m, &module_features, NULL);
}
/**/
int
finish_(UNUSED(Module m))
{
if (compwords)
freearray(compwords);
if (compredirs)
freearray(compredirs);
zsfree(compprefix);
zsfree(compsuffix);
zsfree(complastprefix);
zsfree(complastsuffix);
zsfree(compiprefix);
zsfree(compisuffix);
zsfree(compqiprefix);
zsfree(compqisuffix);
zsfree(compcontext);
zsfree(compparameter);
zsfree(compredirect);
zsfree(compquote);
zsfree(compqstack);
zsfree(compquoting);
zsfree(comprestore);
zsfree(complist);
zsfree(compinsert);
zsfree(compexact);
zsfree(compexactstr);
zsfree(comppatmatch);
zsfree(comppatinsert);
zsfree(complastprompt);
zsfree(comptoend);
zsfree(compoldlist);
zsfree(compoldins);
zsfree(compvared);
hascompmod = 0;
return 0;
}