You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zsh/Src/Zle/computil.c

5185 lines
128 KiB
C

/*
* computil.c - completion utilities
*
* 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 "computil.mdh"
#include "computil.pro"
/* Help for `_describe'. */
typedef struct cdset *Cdset;
typedef struct cdstr *Cdstr;
typedef struct cdrun *Cdrun;
struct cdstate {
int showd; /* != 0 if descriptions should be shown */
char *sep; /* the separator string */
int slen; /* its metafied length */
int swidth; /* its screen width */
int maxmlen; /* maximum length to allow for the matches */
Cdset sets; /* the sets of matches */
int pre; /* longest prefix length (before description) */
int premaxw; /* ... and its screen width */
int suf; /* longest suffix (description) */
int maxg; /* size of largest group */
int maxglen; /* columns for matches of largest group */
int groups; /* number of groups */
int descs; /* number of non-group matches with desc */
int gprew; /* prefix screen width for group display */
Cdrun runs; /* runs to report to shell code */
};
struct cdstr {
Cdstr next; /* the next one in this set */
char *str; /* the string to display */
char *desc; /* the description or NULL */
char *match; /* the match to add */
char *sortstr; /* unmetafied string used to sort matches */
int len; /* length of str or match */
int width; /* ... and its screen width */
Cdstr other; /* next string with the same description */
int kind; /* 0: not in a group, 1: the first, 2: other */
Cdset set; /* the set this string is in */
Cdstr run; /* next in this run */
};
struct cdrun {
Cdrun next; /* ... */
int type; /* see CRT_* below */
Cdstr strs; /* strings in this run */
int count; /* number of strings in this run */
};
#define CRT_SIMPLE 0
#define CRT_DESC 1
#define CRT_SPEC 2
#define CRT_DUMMY 3
#define CRT_EXPL 4
struct cdset {
Cdset next; /* guess what */
char **opts; /* the compadd-options */
Cdstr strs; /* the strings/matches */
int count; /* number of matches in this set */
int desc; /* number of matches with description */
};
static struct cdstate cd_state;
static int cd_parsed = 0;
static void
freecdsets(Cdset p)
{
Cdset n;
Cdstr s, sn;
Cdrun r, rn;
for (; p; p = n) {
n = p->next;
if (p->opts)
freearray(p->opts);
for (s = p->strs; s; s = sn) {
sn = s->next;
zfree(s->sortstr, strlen(s->str) + 1);
zsfree(s->str);
zsfree(s->desc);
if (s->match != s->str)
zsfree(s->match);
zfree(s, sizeof(*s));
}
for (r = cd_state.runs; r; r = rn) {
rn = r->next;
zfree(r, sizeof(*r));
}
zfree(p, sizeof(*p));
}
}
/* Find matches with same descriptions and group them. */
static void
cd_group(int maxg)
{
Cdset set1, set2;
Cdstr str1, str2, *strp;
int num, width;
cd_state.groups = cd_state.descs = cd_state.maxglen = 0;
cd_state.maxg = 0;
for (set1 = cd_state.sets; set1; set1 = set1->next)
for (str1 = set1->strs; str1; str1 = str1->next) {
str1->kind = 0;
str1->other = NULL;
}
for (set1 = cd_state.sets; set1; set1 = set1->next) {
for (str1 = set1->strs; str1; str1 = str1->next) {
if (!str1->desc || str1->kind != 0)
continue;
num = 1;
width = str1->width + cd_state.swidth;
if (width > cd_state.maxglen)
cd_state.maxglen = width;
strp = &(str1->other);
for (set2 = set1; set2; set2 = set2->next) {
for (str2 = (set2 == set1 ? str1->next : set2->strs);
str2; str2 = str2->next)
if (str2->desc && !strcmp(str1->desc, str2->desc)) {
width += CM_SPACE + str2->width;
if (width > cd_state.maxmlen || num == maxg)
break;
if (width > cd_state.maxglen)
cd_state.maxglen = width;
str1->kind = 1;
str2->kind = 2;
num++;
*strp = str2;
strp = &(str2->other);
}
if (str2)
break;
}
*strp = NULL;
if (num > 1)
cd_state.groups++;
else
cd_state.descs++;
if (num > cd_state.maxg)
cd_state.maxg = num;
}
}
}
/* Calculate longest prefix and suffix and count the strings with
* descriptions. */
static void
cd_calc(void)
{
Cdset set;
Cdstr str;
int l;
cd_state.pre = cd_state.suf = 0;
for (set = cd_state.sets; set; set = set->next) {
set->count = set->desc = 0;
for (str = set->strs; str; str = str->next) {
set->count++;
if ((l = strlen(str->str)) > cd_state.pre)
cd_state.pre = l;
if ((l = ZMB_nicewidth(str->str)) > cd_state.premaxw)
cd_state.premaxw = l;
if (str->desc) {
set->desc++;
if ((l = strlen(str->desc)) > cd_state.suf) /* ### strlen() assumes no \n */
cd_state.suf = l;
}
}
}
}
/* Return 1 if cd_state specifies unsorted groups, 0 otherwise. */
static int
cd_groups_want_sorting(void)
{
Cdset set;
char *const *i;
for (set = cd_state.sets; set; set = set->next)
for (i = set->opts; *i; i++) {
if (!strncmp(*i, "-V", 2))
return 0;
else if (!strncmp(*i, "-J", 2))
return 1;
}
/* Sorted by default */
return 1;
}
static int
cd_sort(const void *a, const void *b)
{
return zstrcmp((*((Cdstr *) a))->sortstr, (*((Cdstr *) b))->sortstr, 0);
}
static int
cd_prep(void)
{
Cdrun run, *runp;
Cdset set;
Cdstr str, *strp;
runp = &(cd_state.runs);
if (cd_state.groups) {
int preplines = cd_state.groups + cd_state.descs;
VARARR(Cdstr, grps, preplines);
VARARR(int, wids, cd_state.maxg);
Cdstr gs, gp, gn, *gpp;
int i, j, d;
Cdrun expl;
Cdstr *strp2;
memset(wids, 0, cd_state.maxg * sizeof(int));
strp = grps;
for (set = cd_state.sets; set; set = set->next)
for (str = set->strs; str; str = str->next) {
if (str->kind != 1) {
if (!str->kind && str->desc) {
if (str->width > wids[0])
wids[0] = str->width;
str->other = NULL;
*strp++ = str;
}
continue;
}
gs = str;
gs->kind = 2;
gp = str->other;
gs->other = NULL;
for (; gp; gp = gn) {
gn = gp->other;
gp->other = NULL;
for (gpp = &gs; *gpp && (*gpp)->width > gp->width;
gpp = &((*gpp)->other));
gp->other = *gpp;
*gpp = gp;
}
for (gp = gs, i = 0; gp; gp = gp->other, i++)
if (gp->width > wids[i])
wids[i] = gp->width;
*strp++ = gs;
}
cd_state.gprew = 0;
for (i = 0; i < cd_state.maxg; i++) {
cd_state.gprew += wids[i] + CM_SPACE;
}
if (cd_state.gprew > cd_state.maxmlen && cd_state.maxglen > 1)
return 1;
for (i = 0; i < preplines; i++) {
Cdstr s = grps[i];
int dummy;
s->sortstr = ztrdup(s->str);
unmetafy(s->sortstr, &dummy);
}
if (cd_groups_want_sorting())
qsort(grps, preplines, sizeof(Cdstr), cd_sort);
for (i = preplines, strp = grps; i > 1; i--, strp++) {
strp2 = strp + 1;
if (!strcmp((*strp)->desc, (*strp2)->desc))
continue;
for (j = i - 2, strp2++; j > 0; j--, strp2++)
if (!strcmp((*strp)->desc, (*strp2)->desc)) {
Cdstr tmp = *strp2;
memmove(strp + 2, strp + 1,
(strp2 - strp - 1) * sizeof(Cdstr));
*++strp = tmp;
i--;
}
}
expl = (Cdrun) zalloc(sizeof(*run));
expl->type = CRT_EXPL;
expl->strs = grps[0];
expl->count = preplines;
for (i = preplines, strp = grps, strp2 = NULL; i; i--, strp++) {
str = *strp;
*strp = str->other;
if (strp2)
*strp2 = str;
strp2 = &(str->run);
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_SPEC;
run->strs = str;
run->count = 1;
}
*strp2 = NULL;
for (i = cd_state.maxg - 1; i; i--) {
for (d = 0, j = preplines, strp = grps; j; j--, strp++) {
if ((str = *strp)) {
if (d) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_DUMMY;
run->strs = expl->strs;
run->count = d;
d = 0;
}
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_SPEC;
run->strs = str;
run->strs->run = NULL;
run->count = 1;
*strp = str->other;
} else
d++;
}
if (d) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_DUMMY;
run->strs = expl->strs;
run->count = d;
}
}
*runp = expl;
runp = &(expl->next);
for (set = cd_state.sets; set; set = set->next) {
for (i = 0, gs = NULL, gpp = &gs, str = set->strs;
str; str = str->next) {
if (str->kind || str->desc)
continue;
i++;
*gpp = str;
gpp = &(str->run);
}
*gpp = NULL;
if (i) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_SIMPLE;
run->strs = gs;
run->count = i;
}
}
} else if (cd_state.showd) {
for (set = cd_state.sets; set; set = set->next) {
if (set->desc) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_DESC;
strp = &(run->strs);
for (str = set->strs; str; str = str->next)
if (str->desc) {
*strp = str;
strp = &(str->run);
}
*strp = NULL;
run->count = set->desc;
}
if (set->desc != set->count) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_SIMPLE;
strp = &(run->strs);
for (str = set->strs; str; str = str->next)
if (!str->desc) {
*strp = str;
strp = &(str->run);
}
*strp = NULL;
run->count = set->count - set->desc;
}
}
} else {
for (set = cd_state.sets; set; set = set->next)
if (set->count) {
*runp = run = (Cdrun) zalloc(sizeof(*run));
runp = &(run->next);
run->type = CRT_SIMPLE;
run->strs = set->strs;
for (str = set->strs; str; str = str->next)
str->run = str->next;
run->count = set->count;
}
}
*runp = NULL;
return 0;
}
/* Duplicate and concatenate two arrays. Return the result. */
static char **
cd_arrcat(char **a, char **b)
{
if (!b)
return zarrdup(a);
else {
char **r = (char **) zalloc((arrlen(a) + arrlen(b) + 1) *
sizeof(char *));
char **p = r;
for (; *a; a++)
*p++ = ztrdup(*a);
for (; *b; b++)
*p++ = ztrdup(*b);
*p = NULL;
return r;
}
}
/* Initialisation. Store and calculate the string and matches and so on.
*
* nam: argv[0] of the builtin
* hide: ???
* mlen: see max-matches-width style
* sep: see list-seperator style
* opts: options to (eventually) pass to compadd.
* Returned via 2nd return parameter of 'compdescribe -g'.
* args: ??? (the positional arguments to 'compdescribe')
* disp: 1 if descriptions should be shown, 0 otherwise
*/
static int
cd_init(char *nam, char *hide, char *mlen, char *sep,
char **opts, char **args, int disp)
{
Cdset *setp, set;
Cdstr *strp, str;
char **ap, *tmp;
int grp = 0, itmp;
if (cd_parsed) {
zsfree(cd_state.sep);
freecdsets(cd_state.sets);
cd_parsed = 0;
}
setp = &(cd_state.sets);
cd_state.sep = ztrdup(sep);
cd_state.slen = strlen(sep);
cd_state.swidth = ZMB_nicewidth(sep);
cd_state.sets = NULL;
cd_state.showd = disp;
cd_state.maxg = cd_state.groups = cd_state.descs = 0;
cd_state.maxmlen = atoi(mlen);
cd_state.premaxw = 0;
itmp = zterm_columns - cd_state.swidth - 4;
if (cd_state.maxmlen > itmp)
cd_state.maxmlen = itmp;
if (cd_state.maxmlen < 4)
cd_state.maxmlen = 4;
if (*args && !strcmp(*args, "-g")) {
args++;
grp = 1;
}
while (*args) {
*setp = set = (Cdset) zshcalloc(sizeof(*set));
setp = &(set->next);
*setp = NULL;
set->opts = NULL;
set->strs = NULL;
if (!(ap = get_user_var(*args))) {
zwarnnam(nam, "invalid argument: %s", *args);
zsfree(cd_state.sep);
freecdsets(cd_state.sets);
return 1;
}
for (str = NULL, strp = &(set->strs); *ap; ap++) {
*strp = str = (Cdstr) zalloc(sizeof(*str));
strp = &(str->next);
str->kind = 0;
str->other = NULL;
str->set = set;
/* Advance tmp to the first unescaped colon. */
for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
if (*tmp == '\\' && tmp[1])
tmp++;
if (*tmp)
str->desc = ztrdup(rembslash(tmp + 1));
else
str->desc = NULL;
*tmp = '\0';
str->str = str->match = ztrdup(rembslash(*ap));
str->len = strlen(str->str);
str->width = ZMB_nicewidth(str->str);
str->sortstr = NULL;
}
if (str)
str->next = NULL;
if (*++args && **args != '-') {
if (!(ap = get_user_var(*args))) {
zwarnnam(nam, "invalid argument: %s", *args);
zsfree(cd_state.sep);
freecdsets(cd_state.sets);
return 1;
}
for (str = set->strs; str && *ap; str = str->next, ap++)
str->match = ztrdup(*ap);
args++;
}
if (hide && *hide) {
for (str = set->strs; str; str = str->next) {
if (str->str == str->match)
str->str = ztrdup(str->str);
if (hide[1] && str->str[0] == '-' && str->str[1] == '-')
memmove(str->str, str->str + 2, strlen(str->str) - 1);
else if (str->str[0] == '-' || str->str[0] == '+')
memmove(str->str, str->str + 1, strlen(str->str));
}
}
for (ap = args; *args &&
(args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
args++);
tmp = *args;
*args = NULL;
set->opts = cd_arrcat(ap, opts);
if ((*args = tmp))
args++;
}
if (disp && grp) {
int mg = zterm_columns;
do {
cd_group(mg);
mg = cd_state.maxg - 1;
cd_calc();
} while (cd_prep());
} else {
cd_calc();
cd_prep();
}
cd_parsed = 1;
return 0;
}
/* Copy an array with one element in reserve (at the beginning). */
static char **
cd_arrdup(char **a)
{
char **r = (char **) zalloc((arrlen(a) + 2) * sizeof(char *));
char **p = r + 1;
while (*a)
*p++ = ztrdup(*a++);
*p = NULL;
return r;
}
/* Get the next set. */
static int
cd_get(char **params)
{
Cdrun run;
if ((run = cd_state.runs)) {
Cdstr str;
char **mats, **mp, **dpys, **dp, **opts, *csl = "";
cd_state.runs = run->next;
switch (run->type) {
case CRT_SIMPLE:
mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
for (str = run->strs; str; str = str->run) {
*mp++ = ztrdup(str->match);
*dp++ = ztrdup(str->str ? str->str : str->match);
}
*mp = *dp = NULL;
opts = zarrdup(run->strs->set->opts);
if (cd_state.groups) {
/* We are building a columnised list with dummy matches
* but there are also matches without descriptions.
* Those end up in a different group, so make sure that
* group doesn't have an explanation. */
for (mp = dp = opts; *mp; mp++) {
if (dp[0][0] == '-' && dp[0][1] == 'X') {
if (!dp[0][2] && dp[1])
mp++;
} else
*dp++ = *mp;
}
*dp = NULL;
}
break;
case CRT_DESC:
{
/*
* The buffer size:
* max prefix length (cd_state.pre) +
* max padding (cd_state.premaxw generously :) +
* separator length (cd_state.slen) +
* inter matches gap (CM_SPACE) +
* max description length (cd_state.suf) +
* trailing \0
*/
VARARR(char, buf,
cd_state.pre + cd_state.suf +
cd_state.premaxw + cd_state.slen + 3);
mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
for (str = run->strs; str; str = str->run) {
char *p = buf, *pp, *d;
int l, remw, w;
*mp++ = ztrdup(str->match);
strcpy(p, str->str);
p += str->len;
memset(p, ' ', (l = (cd_state.premaxw - str->width + CM_SPACE)));
p += l;
remw = zterm_columns - cd_state.premaxw -
cd_state.swidth - 3;
while (remw < 0 && zterm_columns) {
/* line wrapped, use remainder of the extra line */
remw += zterm_columns;
}
if (cd_state.slen < remw) {
strcpy(p, cd_state.sep);
p += cd_state.slen;
remw -= cd_state.slen;
/*
* copy a character at once until no more screen
* width is available. Leave 1 character at the
* end of screen as safety margin
*/
d = str->desc;
w = ZMB_nicewidth(d);
if (w <= remw)
strcpy(p, d);
else {
pp = p;
while (remw > 0 && *d) {
l = MB_METACHARLEN(d);
memcpy(pp, d, l);
pp[l] = '\0';
w = ZMB_nicewidth(pp);
if (w > remw) {
*pp = '\0';
break;
}
pp += l;
d += l;
remw -= w;
}
}
}
*dp++ = ztrdup(buf);
}
*mp = *dp = NULL;
opts = cd_arrdup(run->strs->set->opts);
opts[0] = ztrdup("-l");
break;
}
case CRT_SPEC:
mats = (char **) zalloc(2 * sizeof(char *));
dpys = (char **) zalloc(2 * sizeof(char *));
mats[0] = ztrdup(run->strs->match);
dpys[0] = ztrdup(run->strs->str);
mats[1] = dpys[1] = NULL;
opts = cd_arrdup(run->strs->set->opts);
/* Set -2V, possibly reusing the group name from an existing -J/-V
* flag. */
for (dp = opts + 1; *dp; dp++)
if ((dp[0][0] == '-' && dp[0][1] == 'J') ||
(dp[0][0] == '-' && dp[0][1] == 'V'))
break;
if (*dp) {
char *s = tricat("-2V", "", dp[0] + 2);
zsfree(*dp);
*dp = s;
memmove(opts, opts + 1,
(arrlen(opts + 1) + 1) * sizeof(char *));
} else
opts[0] = ztrdup("-2V-default-");
csl = "packed";
break;
case CRT_DUMMY:
{
char buf[20];
sprintf(buf, "-E%d", run->count);
mats = (char **) zalloc(sizeof(char *));
dpys = (char **) zalloc(sizeof(char *));
mats[0] = dpys[0] = NULL;
opts = cd_arrdup(run->strs->set->opts);
opts[0] = ztrdup(buf);
csl = "packed";
}
break;
default: /* This silences the "might be used uninitialized" warnings */
case CRT_EXPL:
{
/* add columns as safety margin */
VARARR(char, dbuf, cd_state.suf + cd_state.slen +
zterm_columns);
char buf[20], *p, *pp, *d;
int i = run->count, remw, w, l;
sprintf(buf, "-E%d", i);
mats = (char **) zalloc(sizeof(char *));
dpys = (char **) zalloc((i + 1) * sizeof(char *));
for (dp = dpys, str = run->strs; str; str = str->run) {
if (str->run && !strcmp(str->desc, str->run->desc)) {
*dp++ = ztrdup("");
continue;
}
strcpy(dbuf, cd_state.sep);
remw = zterm_columns - cd_state.gprew -
cd_state.swidth - CM_SPACE;
p = pp = dbuf + cd_state.slen;
d = str->desc;
w = ZMB_nicewidth(d);
if (w <= remw) {
strcpy(p, d);
remw -= w;
pp += strlen(d);
} else
while (remw > 0 && *d) {
l = MB_METACHARLEN(d);
memcpy(pp, d, l);
pp[l] = '\0';
w = ZMB_nicewidth(pp);
if (w > remw) {
*pp = '\0';
break;
}
pp += l;
d += l;
remw -= w;
}
while (remw-- > 0)
*pp++ = ' ';
*pp = '\0';
*dp++ = ztrdup(dbuf);
}
mats[0] = *dp = NULL;
opts = cd_arrdup(run->strs->set->opts);
opts[0] = ztrdup(buf);
csl = "packed";
}
break;
}
setsparam(params[0], ztrdup(csl));
setaparam(params[1], opts);
setaparam(params[2], mats);
setaparam(params[3], dpys);
zfree(run, sizeof(*run));
return 0;
}
return 1;
}
/**/
static int
bin_compdescribe(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int n = arrlen(args);
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (!args[0][0] || !args[0][1] || args[0][2]) {
zwarnnam(nam, "invalid argument: %s", args[0]);
return 1;
}
switch (args[0][1]) {
case 'i':
if (n < 3) {
zwarnnam(nam, "not enough arguments");
return 1;
}
return cd_init(nam, args[1], args[2], "", NULL, args + 3, 0);
case 'I':
if (n < 6) {
zwarnnam(nam, "not enough arguments");
return 1;
} else {
char **opts;
if (!(opts = getaparam(args[4]))) {
zwarnnam(nam, "unknown parameter: %s", args[4]);
return 1;
}
return cd_init(nam, args[1], args[2], args[3], opts, args + 5, 1);
}
case 'g':
if (cd_parsed) {
if (n != 5) {
zwarnnam(nam, (n < 5 ? "not enough arguments" :
"too many arguments"));
return 1;
}
return cd_get(args + 1);
} else {
zwarnnam(nam, "no parsed state");
return 1;
}
}
zwarnnam(nam, "invalid option: %s", args[0]);
return 1;
}
/* Help for `_arguments'. */
typedef struct cadef *Cadef;
typedef struct caopt *Caopt;
typedef struct caarg *Caarg;
/* Cache for a set of _arguments-definitions. */
struct cadef {
Cadef next; /* next in cache */
Cadef snext; /* next set */
Caopt opts; /* the options */
int nopts, ndopts, nodopts; /* number of options/direct/optional direct */
Caarg args; /* the normal arguments */
Caarg rest; /* the rest-argument */
char **defs; /* the original strings */
int ndefs; /* number of ... */
time_t lastt; /* last time this was used */
Caopt *single; /* array of single-letter options */
char *match; /* -M spec to use */
int argsactive; /* if normal arguments are still allowed */
/* used while parsing a command line */
char *set; /* set name prefix (<name>-), shared */
int flags; /* see CDF_* below */
char *nonarg; /* pattern for non-args (-A argument) */
};
#define CDF_SEP 1 /* -S was specified: -- terminates options */
/* Description for an option. */
struct caopt {
Caopt next;
char *name; /* option name */
char *descr; /* the description */
char **xor; /* if this, then not ... */
int type; /* type, CAO_* */
Caarg args; /* option arguments */
int active; /* still allowed on command line */
int num; /* it's the num'th option */
char *gsname; /* group or set name, shared */
int not; /* don't complete this option (`!...') */
};
#define CAO_NEXT 1 /* argument follows in next argument (`-opt:...') */
#define CAO_DIRECT 2 /* argument follows option directly (`-opt-:...') */
#define CAO_ODIRECT 3 /* argument may follow option directly (`-opt+:...') */
#define CAO_EQUAL 4 /* argument follows mandatory equals (`-opt=-:...') */
#define CAO_OEQUAL 5 /* argument follows optional equals (`-opt=:...') */
/* Description for an argument */
struct caarg {
Caarg next;
char *descr; /* description */
char **xor; /* if this, then not ... */
char *action; /* what to do for it */
int type; /* CAA_* below */
char *end; /* end-pattern for ::<pat>:... */
char *opt; /* option name if for an option */
int num; /* it's the num'th argument */
int min; /* earliest possible arg pos, given optional args */
int direct; /* true if argument number was given explicitly */
int active; /* still allowed on command line */
char *gsname; /* group or set name, shared */
};
#define CAA_NORMAL 1
#define CAA_OPT 2
#define CAA_REST 3
#define CAA_RARGS 4
#define CAA_RREST 5
/* The cache of parsed descriptions. */
#define MAX_CACACHE 8
static Cadef cadef_cache[MAX_CACACHE];
/* Compare two arrays of strings for equality. */
static int
arrcmp(char **a, char **b)
{
if (!a && !b)
return 1;
else if (!a || !b)
return 0;
else {
while (*a && *b)
if (strcmp(*a++, *b++))
return 0;
return (!*a && !*b);
}
}
/* Memory stuff. Obviously. */
static void
freecaargs(Caarg a)
{
Caarg n;
for (; a; a = n) {
n = a->next;
zsfree(a->descr);
if (a->xor)
freearray(a->xor);
zsfree(a->action);
zsfree(a->end);
zsfree(a->opt);
zfree(a, sizeof(*a));
}
}
static void
freecadef(Cadef d)
{
Cadef s;
Caopt p, n;
while (d) {
s = d->snext;
zsfree(d->match);
zsfree(d->set);
if (d->defs)
freearray(d->defs);
for (p = d->opts; p; p = n) {
n = p->next;
zsfree(p->name);
zsfree(p->descr);
if (p->xor)
freearray(p->xor);
freecaargs(p->args);
zfree(p, sizeof(*p));
}
freecaargs(d->args);
freecaargs(d->rest);
zsfree(d->nonarg);
if (d->single)
zfree(d->single, 188 * sizeof(Caopt));
zfree(d, sizeof(*d));
d = s;
}
}
/* Remove backslashes before colons. */
static char *
rembslashcolon(char *s)
{
char *p, *r;
r = p = s = dupstring(s);
while (*s) {
if (s[0] != '\\' || s[1] != ':')
*p++ = *s;
s++;
}
*p = '\0';
return r;
}
/* Add backslashes before colons. */
static char *
bslashcolon(char *s)
{
char *p, *r;
r = p = zhalloc((2 * strlen(s)) + 1);
while (*s) {
if (*s == ':')
*p++ = '\\';
*p++ = *s++;
}
*p = '\0';
return r;
}
/* Get an index into the single array used in struct cadef
* opt is the option letter and pre is either - or +
* we only keep an array for the 94 ASCII characters from ! to ~ so
* with - and + prefixes, the array is double that at 188 elements
* returns -1 if opt is out-of-range
*/
static int
single_index(char pre, char opt)
{
if (opt <= 0x20 || opt > 0x7e)
return -1;
return opt + (pre == '-' ? -0x21 : 94 - 0x21);
}
/* Parse an argument definition. */
static Caarg
parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
char *set)
{
Caarg ret = (Caarg) zalloc(sizeof(*ret));
char *p = *def, *d, sav;
ret->next = NULL;
ret->descr = ret->action = ret->end = NULL;
ret->xor = NULL;
ret->num = num;
ret->min = num - opt;
ret->type = type;
ret->opt = ztrdup(oname);
ret->direct = 0;
ret->gsname = set;
/* Get the description. */
for (d = p; *p && *p != ':'; p++)
if (*p == '\\' && p[1])
p++;
sav = *p;
*p = '\0';
ret->descr = ztrdup(rembslashcolon(d));
/* Get the action if there is one. */
if (sav) {
if (mult) {
for (d = ++p; *p && *p != ':'; p++)
if (*p == '\\' && p[1])
p++;
sav = *p;
*p = '\0';
ret->action = ztrdup(rembslashcolon(d));
if (sav)
*p = ':';
} else
ret->action = ztrdup(rembslashcolon(p + 1));
} else
ret->action = ztrdup("");
*def = p;
return ret;
}
static Cadef
alloc_cadef(char **args, int single, char *match, char *nonarg, int flags)
{
Cadef ret;
ret = (Cadef) zalloc(sizeof(*ret));
ret->next = ret->snext = NULL;
ret->opts = NULL;
ret->args = ret->rest = NULL;
ret->nonarg = ztrdup(nonarg);
if (args) {
ret->defs = zarrdup(args);
ret->ndefs = arrlen(args);
} else {
ret->defs = NULL;
ret->ndefs = 0;
}
ret->nopts = 0;
ret->ndopts = 0;
ret->nodopts = 0;
ret->lastt = time(0);
ret->set = NULL;
if (single) {
ret->single = (Caopt *) zalloc(188 * sizeof(Caopt));
memset(ret->single, 0, 188 * sizeof(Caopt));
} else
ret->single = NULL;
ret->match = ztrdup(match);
ret->flags = flags;
return ret;
}
static void
set_cadef_opts(Cadef def)
{
Caarg argp;
int xnum;
for (argp = def->args, xnum = 0; argp; argp = argp->next) {
if (!argp->direct)
argp->min = argp->num - xnum;
if (argp->type == CAA_OPT)
xnum++;
}
}
/* Parse an array of definitions. */
static Cadef
parse_cadef(char *nam, char **args)
{
Cadef all, ret;
Caopt *optp;
char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
char *adpre, *adsuf, *axor = NULL, *doset = NULL, **pendset = NULL, **curset = NULL;
char *nonarg = NULL;
int single = 0, anum = 1, xnum, flags = 0;
int foreignset = 0, not = 0;
/* First string is the auto-description definition. */
for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
if (*p) {
*p = '\0';
adpre = dupstring(args[0]);
*p = '%';
adsuf = dupstring(p + 2);
} else
adpre = adsuf = NULL;
/* Now get the -s, -A, -S and -M options. */
args++;
while ((p = *args) && *p == '-' && p[1]) {
for (q = ++p; *q; q++)
if (*q == 'M' || *q == 'A') {
q = "";
break;
} else if (*q != 's' && *q != 'S')
break;
if (*q)
break;
for (; *p; p++) {
if (*p == 's')
single = 1;
else if (*p == 'S')
flags |= CDF_SEP;
else if (*p == 'A') {
if (p[1]) {
nonarg = p + 1;
p += strlen(p+1);
} else if (args[1])
nonarg = *++args;
else
break;
} else if (*p == 'M') {
if (p[1]) {
match = p + 1;
p += strlen(p+1);
} else if (args[1])
match = *++args;
else
break;
}
}
if (*p)
break;
args++;
}
if (*args && !strcmp(*args, ":"))
args++;
if (!*args)
return NULL;
if (nonarg)
tokenize(nonarg = dupstring(nonarg));
/* Looks good. Optimistically allocate the cadef structure. */
all = ret = alloc_cadef(orig_args, single, match, nonarg, flags);
optp = &(ret->opts);
sargs = args;
/* Get the definitions. */
for (; *args || pendset; args++) {
if (!*args) {
/* start new set */
args = sargs; /* go back and repeat parse of common options */
doset = NULL;
set_cadef_opts(ret);
ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags);
optp = &(ret->opts);
anum = 1;
foreignset = 0;
curset = pendset;
pendset = 0;
}
if (args[0][0] == '-' && !args[0][1] && args[1]) {
if ((foreignset = curset && args != curset)) {
if (!pendset && args > curset)
pendset = args; /* mark pointer to next pending set */
++args;
} else {
/* Carrying on: this is the current set */
char *p = *++args;
int l = strlen(p) - 1;
if (*p == '(' && p[l] == ')') {
axor = p = dupstring(p + 1);
p[l - 1] = '\0';
} else
axor = NULL;
if (!*p) {
freecadef(all);
zwarnnam(nam, "empty set name");
return NULL;
}
ret->set = doset = tricat(p, "-", "");
curset = args; /* needed for the first set */
}
continue;
} else if (args[0][0] == '+' && !args[0][1] && args[1]) {
char *p;
int l;
foreignset = 0; /* group not in any set, don't want to skip it */
p = *++args;
l = strlen(p) - 1;
if (*p == '(' && p[l] == ')') {
axor = p = dupstring(p + 1);
p[l - 1] = '\0';
} else
axor = NULL;
if (!*p) {
freecadef(all);
zwarnnam(nam, "empty group name");
return NULL;
}
doset = tricat(p, "-", "");
continue;
} else if (foreignset) /* skipping over a different set */
continue;
p = dupstring(*args);
xnum = 0;
if ((not = (*p == '!')))
p++;
if (*p == '(') {
/* There is a xor list, get it. */
LinkList list = newlinklist();
LinkNode node;
char **xp, sav;
while (*p && *p != ')') {
for (p++; inblank(*p); p++);
if (*p == ')')
break;
for (q = p++; *p && *p != ')' && !inblank(*p); p++);
if (!*p)
break;
sav = *p;
*p = '\0';
addlinknode(list, dupstring(q));
xnum++;
*p = sav;
}
/* Oops, end-of-string. */
if (*p != ')') {
freecadef(all);
zwarnnam(nam, "invalid argument: %s", *args);
return NULL;
}
if (doset && axor)
xnum++;
xor = (char **) zalloc((xnum + 2) * sizeof(char *));
for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
*xp = ztrdup((char *) getdata(node));
if (doset && axor)
*xp++ = ztrdup(axor);
xp[0] = xp[1] = NULL;
p++;
} else if (doset && axor) {
xnum = 1;
xor = (char **) zalloc(3 * sizeof(char *));
xor[0] = ztrdup(axor);
xor[1] = xor[2] = NULL;
} else
xor = NULL;
if (*p == '-' || *p == '+' ||
(*p == '*' && (p[1] == '-' || p[1] == '+'))) {
/* It's an option. */
Caopt opt;
Caarg oargs = NULL;
int multi, otype = CAO_NEXT, again = 0;
char *name, *descr, c, *againp = NULL;
rec:
/* Allowed more than once? */
if ((multi = (*p == '*')))
p++;
if (((p[0] == '-' && p[1] == '+') ||
(p[0] == '+' && p[1] == '-')) &&
p[2] && p[2] != ':' && p[2] != '[' &&
p[2] != '=' && p[2] != '-' && p[2] != '+') {
/* It's a -+ or +- definition. We just execute the whole
* stuff twice for such things. */
againp = dupstring(p);
name = ++p;
*p = (again ? '-' : '+');
again++;
} else {
name = p;
/* If it's a long option skip over the first `-'. */
if (p[0] == '-' && p[1] == '-')
p++;
}
if (!p[1]) {
freecadef(all);
zwarnnam(nam, "invalid argument: %s", *args);
return NULL;
}
/* Skip over the name. */
for (p++; *p && *p != ':' && *p != '[' &&
((*p != '-' && *p != '+') ||
(p[1] != ':' && p[1] != '[')) &&
(*p != '=' ||
(p[1] != ':' && p[1] != '[' && p[1] != '-')); p++)
if (*p == '\\' && p[1])
p++;
/* The character after the option name specifies the type. */
c = *p;
*p = '\0';
if (c == '-') {
otype = CAO_DIRECT;
c = *++p;
} else if (c == '+') {
otype = CAO_ODIRECT;
c = *++p;
} else if (c == '=') {
otype = CAO_OEQUAL;
if ((c = *++p) == '-') {
otype = CAO_EQUAL;
c = *++p;
}
}
/* Get the optional description, if any. */
if (c == '[') {
for (descr = ++p; *p && *p != ']'; p++)
if (*p == '\\' && p[1])
p++;
if (!*p) {
freecadef(all);
zwarnnam(nam, "invalid option definition: %s", *args);
return NULL;
}
*p++ = '\0';
c = *p;
} else
descr = NULL;
if (c && c != ':') {
freecadef(all);
zwarnnam(nam, "invalid option definition: %s", *args);
return NULL;
}
/* Add the option name to the xor list if not `*-...'. */
if (!multi) {
if (!xor) {
xor = (char **) zalloc(2 * sizeof(char *));
xor[0] = xor[1] = NULL;
}
zsfree(xor[xnum]);
xor[xnum] = ztrdup(rembslashcolon(name));
}
if (c == ':') {
/* There's at least one argument. */
Caarg *oargp = &oargs;
int atype, rest, oanum = 1, onum = 0;
char *end;
/* Loop over the arguments. */
while (c == ':') {
rest = 0;
end = NULL;
/* Get the argument type. */
if (*++p == ':') {
atype = CAA_OPT;
p++;
} else if (*p == '*') {
if (*++p != ':') {
char sav;
for (end = p++; *p && *p != ':'; p++)
if (*p == '\\' && p[1])
p++;
sav = *p;
*p = '\0';
end = dupstring(end);
tokenize(end);
*p = sav;
}
if (*p != ':') {
freecadef(all);
freecaargs(oargs);
zwarnnam(nam, "invalid option definition: %s",
*args);
return NULL;
}
if (*++p == ':') {
if (*++p == ':') {
atype = CAA_RREST;
p++;
} else
atype = CAA_RARGS;
} else
atype = CAA_REST;
rest = 1;
} else
atype = CAA_NORMAL;
/* And the definition. */
*oargp = parse_caarg(!rest, atype, oanum++, onum,
name, &p, doset);
if (atype == CAA_OPT)
onum++;
if (end)
(*oargp)->end = ztrdup(end);
oargp = &((*oargp)->next);
if (rest)
break;
c = *p;
}
}
/* Store the option definition. */
*optp = opt = (Caopt) zalloc(sizeof(*opt));
optp = &((*optp)->next);
opt->next = NULL;
opt->gsname = doset;
opt->name = ztrdup(rembslashcolon(name));
if (descr)
opt->descr = ztrdup(descr);
else if (adpre && oargs && !oargs->next) {
char *d;
for (d = oargs->descr; *d; d++)
if (!iblank(*d))
break;
if (*d)
opt->descr = tricat(adpre, oargs->descr, adsuf);
else
opt->descr = NULL;
} else
opt->descr = NULL;
opt->xor = (again == 1 && xor ? zarrdup(xor) : xor);
opt->type = otype;
opt->args = oargs;
opt->num = ret->nopts++;
opt->not = not;
if (otype == CAO_DIRECT || otype == CAO_EQUAL)
ret->ndopts++;
else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL)
ret->nodopts++;
/* If this is for single-letter option we also store a
* pointer for the definition in the array for fast lookup.
* But don't treat '--' as a single option called '-' */
if (single && name[1] && !name[2] && name[1] != '-') {
int sidx = single_index(*name, name[1]);
if (sidx >= 0) ret->single[sidx] = opt;
}
if (again == 1) {
/* Do it all again for `*-...'. */
p = againp;
goto rec;
}
} else if (*p == '*') {
/* It's a rest-argument definition. */
int type = CAA_REST;
if (not)
continue;
if (*++p != ':') {
freecadef(all);
zwarnnam(nam, "invalid rest argument definition: %s", *args);
return NULL;
}
if (ret->rest) {
freecadef(all);
zwarnnam(nam, "doubled rest argument definition: %s", *args);
return NULL;
}
if (*++p == ':') {
if (*++p == ':') {
type = CAA_RREST;
p++;
} else
type = CAA_RARGS;
}
ret->rest = parse_caarg(0, type, -1, 0, NULL, &p, doset);
ret->rest->xor = xor;
} else {
/* It's a normal argument definition. */
int type = CAA_NORMAL, direct;
Caarg arg, tmp, pre;
if (not)
continue;
if ((direct = idigit(*p))) {
/* Argument number is given. */
int num = 0;
while (*p && idigit(*p))
num = (num * 10) + (((int) *p++) - '0');
anum = num + 1;
} else
/* Default number. */
anum++;
if (*p != ':') {
freecadef(all);
zwarnnam(nam, "invalid argument: %s", *args);
if (xor)
free(xor);
return NULL;
}
if (*++p == ':') {
/* Optional argument. */
type = CAA_OPT;
p++;
}
arg = parse_caarg(0, type, anum - 1, 0, NULL, &p, doset);
arg->xor = xor;
arg->direct = direct;
/* Sort the new definition into the existing list. */
for (tmp = ret->args, pre = NULL;
tmp && tmp->num < anum - 1;
pre = tmp, tmp = tmp->next);
if (tmp && tmp->num == anum - 1) {
freecadef(all);
freecaargs(arg);
zwarnnam(nam, "doubled argument definition: %s", *args);
return NULL;
}
arg->next = tmp;
if (pre)
pre->next = arg;
else
ret->args = arg;
}
}
set_cadef_opts(ret);
return all;
}
/* Given an array of definitions, return the cadef for it. From the cache
* are newly built. */
static Cadef
get_cadef(char *nam, char **args)
{
Cadef *p, *min, new;
int i, na = arrlen(args);
for (i = MAX_CACACHE, p = cadef_cache, min = NULL; i && *p; p++, i--)
if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
(*p)->lastt = time(0);
return *p;
} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
min = p;
if (i > 0)
min = p;
if ((new = parse_cadef(nam, args))) {
freecadef(*min);
*min = new;
}
return new;
}
/*
* Get the option used in a word from the line, if any.
*
* "d" is a complete set of argument/option definitions to scan.
* "line" is the word we are scanning.
* "full" indicates that the option must match a full word; otherwise
* we look for "=" arguments or prefixes.
* *"end" is set to point to the end of the option, in some cases
* leaving an option argument after it.
*/
static Caopt
ca_get_opt(Cadef d, char *line, int full, char **end)
{
Caopt p;
/* The full string may be an option. */
for (p = d->opts; p; p = p->next)
if (p->active && !strcmp(p->name, line)) {
if (end)
*end = line + strlen(line);
return p;
}
if (!full) {
/* The string from the line probably only begins with an option. */
for (p = d->opts; p; p = p->next)
if (p->active && ((!p->args || p->type == CAO_NEXT) ?
!strcmp(p->name, line) : strpfx(p->name, line))) {
int l = strlen(p->name);
if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
line[l] && line[l] != '=')
continue;
if (end) {
/* Return a pointer to the end of the option. */
if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
line[l] == '=')
l++;
*end = line + l;
}
return p;
}
}
return NULL;
}
/* Same as above, only for single-letter-style. */
static Caopt
ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp)
{
Caopt p, pp = NULL;
char pre = *line++;
LinkList l = NULL;
int sidx;
*lp = NULL;
for (p = NULL; *line; line++) {
if ((sidx = single_index(pre, *line)) != -1 &&
(p = d->single[sidx]) && p->active && p->args) {
if (p->type == CAO_NEXT) {
if (!l)
*lp = l = newlinklist();
addlinknode(l, p);
} else {
if (end) {
line++;
if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
*line == '=')
line++;
*end = line;
}
pp = p;
break;
}
} else if (!p || (p && !p->active))
return NULL;
pp = (p->name[0] == pre ? p : NULL);
p = NULL;
}
if (pp && end)
*end = line;
return pp;
}
/* Search for an option in all sets except the current one.
* Return true if found */
static int
ca_foreign_opt(Cadef curset, Cadef all, char *option)
{
Cadef d;
Caopt p;
for (d = all; d; d = d->snext) {
if (d == curset)
continue;
for (p = d->opts; p; p = p->next) {
if (!strcmp(p->name, option))
return 1;
}
}
return 0;
}
/* Return the n'th argument definition. */
static Caarg
ca_get_arg(Cadef d, int n)
{
if (d->argsactive) {
Caarg a = d->args;
while (a && (!a->active || n < a->min || n > a->num)) {
if (!a->active)
n++;
a = a->next;
}
if (a && a->min <= n && a->num >= n && a->active)
return a;
return (d->rest && d->rest->active ? d->rest : NULL);
}
return NULL;
}
/* Mark options as inactive.
* d: option definitions for a set
* pass either:
* xor: a list of exclusions
* opts: if set, all options excluded leaving only nornal/rest arguments */
static void
ca_inactive(Cadef d, char **xor, int cur, int opts)
{
if ((xor || opts) && cur <= compcurrent) {
Caopt opt;
char *x;
/* current word could be a prefix of a longer one so only do
* exclusions for single-letter options (for option clumping) */
int single = !opts && (cur == compcurrent);
for (; (x = (opts ? "-" : *xor)); xor++) {
int excludeall = 0;
char *grp = NULL;
size_t grplen;
char *next, *sep = x;
while (*sep != '+' && *sep != '-' && *sep != ':' && *sep != '*' && !idigit(*sep)) {
if (!(next = strchr(sep, '-')) || !*++next) {
/* exclusion is just the name of a set or group */
excludeall = 1; /* excluding options and args */
sep += strlen(sep);
/* A trailing '-' is included in the various gsname fields but is not
* there for this branch. This is why we add excludeall to grplen
* when checking for the null in a few places below */
break;
}
sep = next;
}
if (sep > x) { /* exclusion included a set or group name */
grp = x;
grplen = sep - grp;
x = sep;
}
if (excludeall || (x[0] == ':' && !x[1])) {
if (grp) {
Caarg a;
for (a = d->args; a; a = a->next)
if (a->gsname && !strncmp(a->gsname, grp, grplen) &&
!a->gsname[grplen + excludeall])
a->active = 0;
if (d->rest && d->rest->gsname &&
!strncmp(d->rest->gsname, grp, grplen) &&
!d->rest->gsname[grplen + excludeall])
d->rest->active = 0;
} else
d->argsactive = 0;
}
if (excludeall || (x[0] == '-' && !x[1])) {
Caopt p;
for (p = d->opts; p; p = p->next)
if ((!grp || (p->gsname && !strncmp(p->gsname, grp, grplen) &&
!p->gsname[grplen + excludeall])) &&
!(single && *p->name && p->name[1] && p->name[2]))
p->active = 0;
}
if (excludeall || (x[0] == '*' && !x[1])) {
if (d->rest && (!grp || (d->rest->gsname &&
!strncmp(d->rest->gsname, grp, grplen) &&
!d->rest->gsname[grplen + excludeall])))
d->rest->active = 0;
}
if (!excludeall) {
if (idigit(x[0])) {
int n = atoi(x);
Caarg a = d->args;
while (a && a->num < n)
a = a->next;
if (a && a->num == n && (!grp || (a->gsname &&
!strncmp(a->gsname, grp, grplen))))
a->active = 0;
} else if ((opt = ca_get_opt(d, x, 1, NULL)) &&
(!grp || (opt->gsname && !strncmp(opt->gsname, grp, grplen))) &&
!(single && *opt->name && opt->name[1] && opt->name[2]))
opt->active = 0;
if (opts)
break;
}
}
}
}
/* State when parsing a command line. */
typedef struct castate *Castate;
/* Encapsulates details from parsing the current line against a particular set,
* Covers positions of options and normal arguments. Used as a linked list
* with one state for each set. */
struct castate {
Castate snext; /* state for next set */
Cadef d; /* parsed _arguments specs for the set */
int nopts; /* number of specified options (size of oargs) */
Caarg def; /* definition for the current set */
Caarg ddef;
Caopt curopt; /* option description corresponding to option found on the command-line */
Caopt dopt;
int opt; /* the length of the option up to a maximum of 2 */
int arg; /* completing arguments to an option or rest args */
int argbeg; /* position of first rest argument (+1) */
int optbeg; /* first word after the last option to the left of the cursor:
* in effect the start of any arguments to the current option */
int nargbeg; /* same as optbeg but used during parse */
int restbeg; /* same as argbeg but used during parse */
int curpos; /* current word position */
int argend; /* total number of words */
int inopt; /* set to current word pos if word is a recognised option */
int inarg; /* in a normal argument */
int nth; /* number of current normal arg */
int singles; /* argument consists of clumped options */
int oopt;
int actopts; /* count of active options */
LinkList args; /* list of non-option args used for populating $line */
LinkList *oargs; /* list of lists used for populating $opt_args */
};
static struct castate ca_laststate;
static int ca_parsed = 0, ca_alloced = 0;
static int ca_doff; /* no. of chars of ignored prefix (for clumped options or arg to an option) */
static void
freecastate(Castate s)
{
int i;
LinkList *p;
freelinklist(s->args, freestr);
for (i = s->nopts, p = s->oargs; i--; p++)
if (*p)
freelinklist(*p, freestr);
zfree(s->oargs, s->d->nopts * sizeof(LinkList));
}
/* Return a copy of an option's argument, ignoring possible quoting
* in the option name. */
static char *
ca_opt_arg(Caopt opt, char *line)
{
char *o = opt->name;
while (1) {
if (*o == '\\')
o++;
if (*line == '\\' || *line == '\'' || *line == '"')
line++;
if (!*o || *o != *line)
break;
o++;
line++;
}
if (*line && (opt->type == CAO_EQUAL || opt->type == CAO_OEQUAL)) {
if (*line == '\\')
line++;
if (*line == '=')
line++;
}
return ztrdup(line);
}
/* Parse the command line for a particular argument set (d).
* Returns 1 if the set should be skipped because it doesn't match
* existing options on the line. */
static int
ca_parse_line(Cadef d, Cadef all, int multi, int first)
{
Caarg adef, ddef;
Caopt ptr, wasopt = NULL, dopt;
struct castate state;
char *line, *oline, *pe, **argxor = NULL;
int cur, doff, argend, arglast;
Patprog endpat = NULL, napat = NULL;
LinkList sopts = NULL;
#if 0
int ne;
#endif
/* Free old state. */
if (first && ca_alloced) {
Castate s = &ca_laststate, ss;
while (s) {
ss = s->snext;
freecastate(s);
s = ss;
}
}
/* Mark everything as active. */
for (ptr = d->opts; ptr; ptr = ptr->next)
ptr->active = 1;
d->argsactive = 1;
if (d->rest)
d->rest->active = 1;
for (adef = d->args; adef; adef = adef->next)
adef->active = 1;
/* Default values for the state. */
state.snext = NULL;
state.d = d;
state.nopts = d->nopts;
state.def = state.ddef = NULL;
state.curopt = state.dopt = NULL;
state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts =
state.nth = state.inarg = state.opt = state.arg = 1;
state.argend = argend = arrlen(compwords) - 1;
state.inopt = state.singles = state.oopt = 0;
state.curpos = compcurrent;
state.args = znewlinklist();
state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
memset(state.oargs, 0, d->nopts * sizeof(LinkList));
ca_alloced = 1;
memcpy(&ca_laststate, &state, sizeof(state));
if (!compwords[1]) {
ca_laststate.opt = ca_laststate.arg = 0;
goto end;
}
if (d->nonarg) /* argument to -A */
napat = patcompile(d->nonarg, 0, NULL);
/* Loop over the words from the line. */
for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
line; line = compwords[cur++]) {
ddef = adef = NULL;
dopt = NULL;
state.singles = arglast = 0;
oline = line;
#if 0
/*
* remove quotes.
* This is commented out: it doesn't allow you to discriminate
* between command line values that can be expanded and those
* that can't, and in some cases this generates inconsistency;
* for example, ~/foo\[bar unqotes to ~/foo[bar which doesn't
* work either way---it's wrong if the ~ is quoted, and
* wrong if the [ isn't quoted.. So it's now up to the caller to
* unquote.
*/
line = dupstring(line);
ne = noerrs;
noerrs = 2;
parse_subst_string(line);
noerrs = ne;
#endif
remnulargs(line);
untokenize(line);
if (argxor) {
ca_inactive(d, argxor, cur - 1, 0);
argxor = NULL;
}
if ((d->flags & CDF_SEP) && cur != compcurrent && state.actopts &&
!strcmp(line, "--")) {
ca_inactive(d, NULL, cur, 1);
state.actopts = 0;
continue;
}
/* We've got a definition for an option/rest argument. For an option,
* this means that we're completing arguments to that option. */
if (state.def) {
state.arg = 0;
if (state.curopt)
zaddlinknode(state.oargs[state.curopt->num], ztrdup(oline));
if ((state.opt = (state.def->type == CAA_OPT)) && state.def->opt)
state.oopt++;
if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
state.def->type == CAA_RREST) {
if (state.def->end && pattry(endpat, line)) {
state.def = NULL;
state.curopt = NULL;
state.opt = state.arg = 1;
state.argend = ca_laststate.argend = cur - 1;
goto cont;
}
} else if ((state.def = state.def->next)) {
state.argbeg = cur;
state.argend = argend;
} else if (sopts && nonempty(sopts)) {
state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
state.def = state.curopt->args;
state.opt = 0;
state.argbeg = state.optbeg = state.inopt = cur;
state.argend = argend;
doff = 0;
state.singles = 1;
if (!state.oargs[state.curopt->num])
state.oargs[state.curopt->num] = znewlinklist();
goto cont;
} else {
state.curopt = NULL;
state.opt = 1;
}
} else {
state.opt = state.arg = 1;
state.curopt = NULL;
}
if (state.opt)
state.opt = (line[0] ? (line[1] ? 2 : 1) : 0);
pe = NULL;
wasopt = NULL;
/* See if it's an option. */
if (state.opt == 2 && (*line == '-' || *line == '+') &&
(state.curopt = ca_get_opt(d, line, 0, &pe)) &&
(state.curopt->type == CAO_OEQUAL ?
(compwords[cur] || pe[-1] == '=') :
(state.curopt->type == CAO_EQUAL ?
(pe[-1] == '=' || !pe[0]) : 1))) {
if ((ddef = state.def = ((state.curopt->type != CAO_EQUAL ||
pe[-1] == '=') ?
state.curopt->args : NULL)))
dopt = state.curopt;
doff = pe - line;
state.optbeg = state.argbeg = state.inopt = cur;
state.argend = argend;
state.singles = (d->single && (!pe || !*pe) &&
state.curopt->name[1] && !state.curopt->name[2] &&
/* Don't treat '--' as a single option called '-' */
state.curopt->name[1] != '-');
if (!state.oargs[state.curopt->num])
state.oargs[state.curopt->num] = znewlinklist();
ca_inactive(d, state.curopt->xor, cur, 0);
/* Collect the argument strings. Maybe. */
if (state.def &&
(state.curopt->type == CAO_DIRECT ||
state.curopt->type == CAO_EQUAL ||
(state.curopt->type == CAO_ODIRECT && pe[0]) ||
(state.curopt->type == CAO_OEQUAL &&
(pe[0] || pe[-1] == '=')))) {
if (state.def->type != CAA_REST &&
state.def->type != CAA_RARGS &&
state.def->type != CAA_RREST)
state.def = state.def->next;
zaddlinknode(state.oargs[state.curopt->num],
ca_opt_arg(state.curopt, oline));
}
if (state.def)
state.opt = 0;
else {
if (!d->single || (state.curopt->name[1] && state.curopt->name[2]))
wasopt = state.curopt;
state.curopt = NULL;
}
} else if (state.opt == 2 && d->single &&
(*line == '-' || *line == '+') &&
((state.curopt = ca_get_sopt(d, line, &pe, &sopts)) ||
(cur != compcurrent && sopts && nonempty(sopts)))) {
/* Or maybe it's a single-letter option? */
char *p;
Caopt tmpopt;
int sidx;
if (cur != compcurrent && sopts && nonempty(sopts))
state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
if (!state.oargs[state.curopt->num])
state.oargs[state.curopt->num] = znewlinklist();
state.def = state.curopt->args;
ddef = (state.curopt->type == CAO_NEXT && cur == compcurrent ?
NULL : state.def);
dopt = state.curopt;
doff = pe - line;
state.optbeg = state.argbeg = state.inopt = cur;
state.argend = argend;
state.singles = (!pe || !*pe);
for (p = line + 1; p < pe; p++) {
if ((sidx = single_index(*line, *p)) != -1 &&
(tmpopt = d->single[sidx])) {
if (!state.oargs[tmpopt->num])
state.oargs[tmpopt->num] = znewlinklist();
ca_inactive(d, tmpopt->xor, cur, 0);
}
}
if (state.def &&
(state.curopt->type == CAO_DIRECT ||
state.curopt->type == CAO_EQUAL ||
(state.curopt->type == CAO_ODIRECT && pe[0]) ||
(state.curopt->type == CAO_OEQUAL &&
(pe[0] || pe[-1] == '=')))) {
if (state.def->type != CAA_REST &&
state.def->type != CAA_RARGS &&
state.def->type != CAA_RREST)
state.def = state.def->next;
zaddlinknode(state.oargs[state.curopt->num],
ca_opt_arg(state.curopt, line));
}
if (state.def)
state.opt = 0;
else
state.curopt = NULL;
} else if (multi && (*line == '-' || *line == '+') && cur != compcurrent
&& (ca_foreign_opt(d, all, line)))
return 1;
else if (state.arg && cur <= compcurrent) {
/* Otherwise it's a normal argument. */
/* test pattern passed to -A. if it matches treat this as an unknown
* option and continue to the next word */
if (napat && cur < compcurrent && state.actopts) {
if (pattry(napat, line))
continue;
ca_inactive(d, NULL, cur + 1, 1);
state.actopts = 0;
}
arglast = 1;
/* if this is the first normal arg after an option, may have been
* earlier normal arguments if they're intermixed with options */
if (state.inopt) {
state.inopt = 0;
state.nargbeg = cur - 1;
state.argend = argend;
}
if (!d->args && !d->rest && *line && *line != '-' && *line != '+') {
if (!multi && cur > compcurrent)
break;
return 1;
}
if ((adef = state.def = ca_get_arg(d, state.nth)) &&
(state.def->type == CAA_RREST ||
state.def->type == CAA_RARGS)) {
/* Bart 2009/11/17:
* We've reached the "rest" definition. If at this point
* we already found another definition that describes the
* current word, use that instead. If not, prep for the
* "narrowing" of scope to only the remaining words.
*
* We can't test ca_laststate.def in the loop conditions
* at the top because this same loop also handles the
* ':*PATTERN:MESSAGE:ACTION' form for multiple arguments
* after an option, which may need to continue scanning.
* There might be an earlier point at which this test can
* be made but tracking it down is not worth the effort.
*/
if (ca_laststate.def)
break;
state.opt = (cur == state.nargbeg + 1 &&
(!multi || !*line ||
*line == '-' || *line == '+'));
state.optbeg = state.nargbeg;
state.argbeg = cur - 1;
state.argend = argend;
for (; line; line = compwords[cur++])
zaddlinknode(state.args, ztrdup(line));
memcpy(&ca_laststate, &state, sizeof(state));
ca_laststate.ddef = NULL;
ca_laststate.dopt = NULL;
break;
}
zaddlinknode(state.args, ztrdup(line));
if (adef)
state.oopt = adef->num - state.nth;
if (state.def && cur != compcurrent)
argxor = state.def->xor;
if (state.def && state.def->type != CAA_NORMAL &&
state.def->type != CAA_OPT && state.inarg) {
state.restbeg = cur;
state.inarg = 0;
} else if (!state.def || state.def->type == CAA_NORMAL ||
state.def->type == CAA_OPT)
state.inarg = 1;
state.nth++;
state.def = NULL;
}
/* Do the end-pattern test if needed. */
if (state.def && state.curopt &&
(state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
if (state.def->end)
endpat = patcompile(state.def->end, 0, NULL);
else {
LinkList l = state.oargs[state.curopt->num];
if (cur < compcurrent)
memcpy(&ca_laststate, &state, sizeof(state));
for (; line; line = compwords[cur++])
zaddlinknode(l, ztrdup(line));
ca_laststate.ddef = NULL;
ca_laststate.dopt = NULL;
break;
}
} else if (state.def && state.def->end)
endpat = patcompile(state.def->end, 0, NULL);
/* Copy the state into the global one. */
cont:
if (cur + 1 == compcurrent) {
memcpy(&ca_laststate, &state, sizeof(state));
ca_laststate.ddef = NULL;
ca_laststate.dopt = NULL;
} else if (cur == compcurrent && !ca_laststate.def) {
if ((ca_laststate.def = ddef)) {
ca_laststate.singles = state.singles;
if (state.curopt && state.curopt->type == CAO_NEXT) {
ca_laststate.ddef = ddef;
ca_laststate.dopt = dopt;
ca_laststate.def = NULL;
ca_laststate.opt = 1;
state.curopt->active = 1;
} else {
ca_doff = doff;
ca_laststate.opt = 0;
}
} else {
ca_laststate.def = adef;
ca_laststate.opt = (!arglast || !multi || !*line ||
*line == '-' || *line == '+');
ca_laststate.ddef = NULL;
ca_laststate.dopt = NULL;
ca_laststate.optbeg = state.nargbeg;
ca_laststate.argbeg = state.restbeg;
ca_laststate.argend = state.argend;
ca_laststate.singles = state.singles;
ca_laststate.oopt = state.oopt;
if (wasopt)
wasopt->active = 1;
}
}
}
end:
ca_laststate.actopts = 0;
for (ptr = d->opts; ptr; ptr = ptr->next)
if (ptr->active)
ca_laststate.actopts++;
return 0;
}
/* Build a NUL-separated from a list.
*
* This is only used to populate values of $opt_args.
*/
static char *
ca_nullist(LinkList l)
{
if (l) {
char **array = zlinklist2array(l, 0 /* don't dup elements */);
char *ret = zjoin(array, '\0', 0 /* permanent allocation */);
free(array); /* the elements are owned by the list */
return ret;
} else
return ztrdup("");
}
/* Build a colon-list from a list.
*
* This is only used to populate values of $opt_args.
*/
static char *
ca_colonlist(LinkList l)
{
if (l) {
LinkNode n;
int len = 0;
char *p, *ret, *q;
/* Compute the length to be allocated. */
for (n = firstnode(l); n; incnode(n)) {
len++;
for (p = (char *) getdata(n); *p; p++)
len += (*p == ':' || *p == '\\') ? 2 : 1;
}
ret = q = (char *) zalloc(len);
/* Join L into RET, joining with colons and escaping colons and
* backslashes. */
for (n = firstnode(l); n;) {
for (p = (char *) getdata(n); *p; p++) {
if (*p == ':' || *p == '\\')
*q++ = '\\';
*q++ = *p;
}
incnode(n);
if (n)
*q++ = ':';
}
*q = '\0';
return ret;
} else
return ztrdup("");
}
/*
* This function adds the current set of descriptions, actions,
* and subcontext descriptions to the given linked list for passing
* up in comparguments -D and comparguments -L. opt is the
* option string (may be NULL if this isn't an option argument) and arg the
* argument structure (either an option argument or a normal argument
* as determined by arg->type).
*/
static void
ca_set_data(LinkList descr, LinkList act, LinkList subc,
char *opt, Caarg arg, Caopt optdef, int single)
{
LinkNode dnode, anode;
char nbuf[40], *buf;
int restr = 0, onum, miss = 0, rest, oopt = 1, lopt = 0, addopt;
rec:
addopt = (opt ? 0 : ca_laststate.oopt);
for (; arg && (opt || (arg->num < 0 ||
(arg->min <= ca_laststate.nth + addopt &&
arg->num >= ca_laststate.nth)));) {
lopt = (arg->type == CAA_OPT);
if (!opt && !lopt && oopt > 0)
oopt = 0;
for (dnode = firstnode(descr), anode = firstnode(act);
dnode; incnode(dnode), incnode(anode))
if (!strcmp((char *) getdata(dnode), arg->descr) &&
!strcmp((char *) getdata(anode), arg->action))
break;
/* with an ignored prefix, we're not completing any normal arguments */
if (single && !arg->opt)
return;
if (!dnode) {
addlinknode(descr, arg->descr);
addlinknode(act, arg->action);
if (!restr) {
if ((restr = (arg->type == CAA_RARGS)))
restrict_range(ca_laststate.optbeg, ca_laststate.argend);
else if ((restr = (arg->type == CAA_RREST)))
restrict_range(ca_laststate.argbeg, ca_laststate.argend);
}
if (arg->opt) {
buf = (char *) zhalloc((arg->gsname ? strlen(arg->gsname) : 0) +
strlen(arg->opt) + 40);
if (arg->num > 0 && arg->type < CAA_REST)
sprintf(buf, "%soption%s-%d",
(arg->gsname ? arg->gsname : ""), arg->opt, arg->num);
else
sprintf(buf, "%soption%s-rest",
(arg->gsname ? arg->gsname : ""), arg->opt);
} else if (arg->num > 0) {
sprintf(nbuf, "argument-%d", arg->num);
buf = (arg->gsname ? dyncat(arg->gsname, nbuf) : dupstring(nbuf));
} else
buf = (arg->gsname ? dyncat(arg->gsname, "argument-rest") :
dupstring("argument-rest"));
addlinknode(subc, buf);
}
/*
* If this is an argument to an option, and the option definition says
* the argument to the option is required and in the following
* (i.e. this) word, then it must match what we've just told it to
* match---don't try to match normal arguments.
*
* This test may be too stringent for what we need, or it
* may be too loose; I've simply tweaked it until it gets
* the case above right.
*/
if (arg->type == CAA_NORMAL &&
opt && optdef &&
(optdef->type == CAO_NEXT || optdef->type == CAO_ODIRECT ||
optdef->type == CAO_OEQUAL))
return;
if (single)
break;
if (!opt) {
if (arg->num >= 0 && !arg->next && miss)
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
ca_laststate.d->rest : NULL);
else {
onum = arg->num;
rest = (onum != arg->min && onum == ca_laststate.nth);
if ((arg = arg->next)) {
if (arg->num != onum + 1)
miss = 1;
} else if (rest || (oopt > 0 && !opt)) {
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
ca_laststate.d->rest : NULL);
oopt = -1;
}
}
} else {
if (!lopt)
break;
arg = arg->next;
}
}
if (!single && opt && (lopt || ca_laststate.oopt)) {
opt = NULL;
arg = ca_get_arg(ca_laststate.d, ca_laststate.nth);
goto rec;
}
if (!opt && oopt > 0) {
oopt = -1;
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
ca_laststate.d->rest : NULL);
goto rec;
}
}
static int
bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int min, max, n;
Castate lstate = &ca_laststate;
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
zwarnnam(nam, "invalid argument: %s", args[0]);
return 1;
}
if (args[0][1] != 'i' && args[0][1] != 'I' && !ca_parsed) {
zwarnnam(nam, "no parsed state");
return 1;
}
switch (args[0][1]) {
case 'i': min = 2; max = -1; break;
case 'D': min = 3; max = 3; break;
case 'O': min = 4; max = 4; break;
case 'L': min = 3; max = 4; break;
case 's': min = 1; max = 1; break;
case 'M': min = 1; max = 1; break;
case 'a': min = 0; max = 0; break;
case 'W': min = 3; max = 3; break;
case 'n': min = 1; max = 1; break;
default:
zwarnnam(nam, "invalid option: %s", args[0]);
return 1;
}
n = arrlen(args) - 1;
if (n < min) {
zwarnnam(nam, "not enough arguments");
return 1;
} else if (max >= 0 && n > max) {
zwarnnam(nam, "too many arguments");
return 1;
}
switch (args[0][1]) {
case 'i':
/* This initialises the internal data structures. Arguments are the
* auto-description string, the optional -s, -S, -A and -M options
* given to _arguments and the specs. */
if (compcurrent > 1 && compwords[0]) {
Cadef def, all;
int cap = ca_parsed, multi, first = 1, use, ret = 0;
Castate states = NULL, sp;
ca_parsed = 0;
if (!(def = all = get_cadef(nam, args + 1)))
return 1;
multi = !!def->snext; /* if we have sets */
ca_parsed = cap;
ca_doff = 0;
while (def) { /* for each set */
use = !ca_parse_line(def, all, multi, first);
def = def->snext;
if (use && def) {
/* entry needed so save it into list */
sp = (Castate) zalloc(sizeof(*sp));
memcpy(sp, &ca_laststate, sizeof(*sp));
sp->snext = states;
states = sp;
} else if (!use && !def) {
/* final entry not needed */
if (states) {
freecastate(&ca_laststate);
memcpy(&ca_laststate, states, sizeof(*sp));
sp = states->snext;
zfree(states, sizeof(*states));
states = sp;
} else
ret = 1;
}
first = 0;
}
ca_parsed = 1;
ca_laststate.snext = states;
return ret;
}
return 1;
case 'D':
/* This returns the descriptions, actions and sub-contexts for the
* things _arguments has to execute at this place on the line (the
* sub-contexts are used as tags).
* The return value is particularly important here, it says if
* there are arguments to complete at all. */
{
LinkList descr, act, subc;
Caarg arg;
int ret = 1;
descr = newlinklist();
act = newlinklist();
subc = newlinklist();
ignore_prefix(ca_doff);
while (lstate) {
arg = lstate->def;
if (arg) {
ret = 0;
ca_set_data(descr, act, subc, arg->opt, arg,
lstate->curopt, (ca_doff > 0));
}
lstate = lstate->snext;
}
if (!ret) {
set_list_array(args[1], descr);
set_list_array(args[2], act);
set_list_array(args[3], subc);
}
return ret;
}
case 'O':
/* This returns the descriptions for the options in the arrays whose
* names are given as arguments. The descriptions are strings in a
* form usable by _describe. The return value says if there are any
* options to be completed. */
{
LinkList next = newlinklist();
LinkList direct = newlinklist();
LinkList odirect = newlinklist();
LinkList equal = newlinklist(), l;
LinkNode node;
Caopt p;
char *str;
int ret = 1;
for (; lstate; lstate = lstate->snext) {
if (lstate->actopts &&
(lstate->opt || (ca_doff && lstate->def) ||
(lstate->def && lstate->def->opt &&
(lstate->def->type == CAA_OPT ||
(lstate->def->type >= CAA_RARGS &&
lstate->def->num < 0)))) &&
(!lstate->def || lstate->def->type < CAA_RARGS ||
(lstate->def->type == CAA_RARGS ?
(lstate->curpos == lstate->argbeg + 1) :
(compcurrent == 1)))) {
ret = 0;
for (p = lstate->d->opts; p; p = p->next) {
if (p->active && !p->not) {
switch (p->type) {
case CAO_NEXT: l = next; break;
case CAO_DIRECT: l = direct; break;
case CAO_ODIRECT: l = odirect; break;
default: l = equal; break;
}
if (p->descr) {
char *n = bslashcolon(p->name);
int len = strlen(n) + strlen(p->descr) + 2;
str = (char *) zhalloc(len);
strcpy(str, n);
strcat(str, ":");
strcat(str, p->descr);
} else
str = bslashcolon(p->name);
for (node = firstnode(l); node; incnode(node))
if (!strcmp(str, (char *) getdata(node)))
break;
if (!node)
addlinknode(l, str);
}
}
}
}
if (!ret) {
set_list_array(args[1], next);
set_list_array(args[2], direct);
set_list_array(args[3], odirect);
set_list_array(args[4], equal);
return 0;
}
return (ca_laststate.singles ? 2 : 1);
}
case 'L':
/* This tests if the beginning of the current word matches an option.
* It is for cases like `./configure --pre=/<TAB>' which should
* complete to `--prefix=/...'. The options name isn't fully typed
* and _arguments finds out that there is no option `--pre' and that
* it should complete some argument to an option. It then uses -L
* to find the option the argument is for. */
{
LinkList descr, act, subc;
Caopt opt;
int ret = 1;
descr = newlinklist();
act = newlinklist();
subc = newlinklist();
while (lstate) {
opt = ca_get_opt(lstate->d, args[1], 1, NULL);
if (opt && opt->args) {
ret = 0;
ca_set_data(descr, act, subc, opt->name, opt->args, opt, 1);
}
lstate = lstate->snext;
}
if (!ret) {
set_list_array(args[2], descr);
set_list_array(args[3], act);
set_list_array(args[4], subc);
}
return ret;
}
case 's':
/* This returns zero if we are completing single letter options.
* It also uses its argument as the name of a parameter and sets
* that to a string describing the argument behaviour of the last
* option in the current word so that we can get the auto-suffix
* right. */
for (; lstate; lstate = lstate->snext)
if (lstate->d->single && lstate->singles &&
lstate->actopts
#if 0
/* let's try without, for the -W option of _arguments */
&& lstate->opt
#endif
) {
setsparam(args[1],
ztrdup((lstate->ddef && lstate->dopt) ?
(lstate->dopt->type == CAO_DIRECT ?
"direct" :
((lstate->dopt->type == CAO_OEQUAL ||
lstate->dopt->type == CAO_EQUAL) ?
"equal" : "next")) : ""));
return 0;
}
return 1;
case 'M':
/* This returns the match specs defined for the set of specs we are
* using. Returned, as usual in a parameter whose name is given as
* the argument. */
setsparam(args[1], ztrdup(ca_laststate.d->match));
return 0;
case 'a':
/* This just sets the return value. To zero if there would be or
* were any normal arguments to be completed. Used to decide if
* _arguments should say `no arguments' or `no more arguments'. */
for (; lstate; lstate = lstate->snext)
if (lstate->d->args || lstate->d->rest)
return 0;
return 1;
case 'W':
/* This gets two parameter names and one integer as arguments.
*
* The first parameter is set to the current word sans any option
* prefixes handled by comparguments.
*
* The second parameter is set to an array containing the options on
* the line and their arguments. I.e. the stuff _arguments returns
* to its caller in the `line' and `opt_args' parameters.
*
* The integer is one if the second parameter (which is just $opt_args,
* you know) should encode multiple values by joining them with NULs
* and zero if it should encode multiple values by joining them with
* colons after backslash-escaping colons and backslashes.
*/
{
Castate s;
char **ret, **p;
LinkNode n;
LinkList *a;
Caopt o;
int num;
int opt_args_use_NUL_separators = (args[3][0] != '0');
for (num = 0, s = lstate; s; s = s->snext)
num += countlinknodes(s->args);
ret = p = zalloc((num + 1) * sizeof(char *));
for (s = lstate; s; s = s->snext)
for (n = firstnode(s->args); n; incnode(n))
*p++ = ztrdup((char *) getdata(n));
*p = NULL;
setaparam(args[1], ret);
for (num = 0, s = lstate; s; s = s->snext)
for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
if (*a)
num += 2;
ret = p = zalloc((num + 1) * sizeof(char *));
for (s = lstate; s; s = s->snext)
for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
if (*a) {
*p++ = (o->gsname ? tricat(o->gsname, o->name, "") :
ztrdup(o->name));
if (opt_args_use_NUL_separators)
*p++ = ca_nullist(*a);
else
*p++ = ca_colonlist(*a);
}
*p = NULL;
sethparam(args[2], ret);
}
return 0;
case 'n':
/*
* This returns the array index of the word where normal
* arguments began. It uses optbeg rather than nargbeg
* (the value used when parsing) because nargbeg is assigned
* to optbeg in the returned value and nargbeg isn't
* used.
*
* -->PLEASE DON'T ASK<--
*
* Thank you.
*/
setiparam(args[1], (zlong)ca_laststate.optbeg + !isset(KSHARRAYS));
return 0;
}
return 1;
}
/* Help for `_values'. */
typedef struct cvdef *Cvdef;
typedef struct cvval *Cvval;
/* Definitions for _values. */
struct cvdef {
char *descr; /* global description */
int hassep; /* multiple values allowed */
char sep; /* separator character */
char argsep; /* argument separator */
Cvdef next; /* next in cache */
Cvval vals; /* value definitions */
char **defs; /* original strings */
int ndefs; /* number of ... */
time_t lastt; /* last time used */
int words; /* if to look at other words */
};
/* One value definition. */
struct cvval {
Cvval next;
char *name; /* value name */
char *descr; /* description */
char **xor; /* xor-list */
int type; /* CVV_* below */
Caarg arg; /* argument definition */
int active; /* still allowed */
};
#define CVV_NOARG 0
#define CVV_ARG 1
#define CVV_OPT 2
/* Cache. */
#define MAX_CVCACHE 8
static Cvdef cvdef_cache[MAX_CVCACHE];
/* Memory stuff. */
static void
freecvdef(Cvdef d)
{
if (d) {
Cvval p, n;
zsfree(d->descr);
if (d->defs)
freearray(d->defs);
for (p = d->vals; p; p = n) {
n = p->next;
zsfree(p->name);
zsfree(p->descr);
if (p->xor)
freearray(p->xor);
freecaargs(p->arg);
zfree(p, sizeof(*p));
}
zfree(d, sizeof(*d));
}
}
/* Parse option definitions. */
static Cvdef
parse_cvdef(char *nam, char **args)
{
Cvdef ret;
Cvval val, *valp;
Caarg arg;
char **oargs = args, sep = '\0', asep = '=', *name, *descr, *p, *q, **xor, c;
int xnum, multi, vtype, hassep = 0, words = 0;
while (args && args[0] && args[1] &&
args[0][0] == '-' &&
(args[0][1] == 's' || args[0][1] == 'S' || args[0][1] == 'w') &&
!args[0][2]) {
if (args[0][1] == 's') {
hassep = 1;
sep = args[1][0];
args += 2;
} else if (args[0][1] == 'S') {
asep = args[1][0];
args += 2;
} else {
words = 1;
args++;
}
}
if (!args[0] || !args[1]) {
zwarnnam(nam, "not enough arguments");
return NULL;
}
descr = *args++;
ret = (Cvdef) zalloc(sizeof(*ret));
ret->descr = ztrdup(descr);
ret->hassep = hassep;
ret->sep = sep;
ret->argsep = asep;
ret->next = NULL;
ret->vals = NULL;
ret->defs = zarrdup(oargs);
ret->ndefs = arrlen(oargs);
ret->lastt = time(0);
ret->words = words;
for (valp = &(ret->vals); *args; args++) {
int bs = 0;
p = dupstring(*args);
xnum = 0;
/* xor list? */
if (*p == '(') {
LinkList list = newlinklist();
LinkNode node;
char **xp, sav;
while (*p && *p != ')') {
for (p++; inblank(*p); p++);
if (*p == ')')
break;
for (q = p++; *p && *p != ')' && !inblank(*p); p++);
if (!*p)
break;
sav = *p;
*p = '\0';
addlinknode(list, dupstring(q));
xnum++;
*p = sav;
}
if (*p != ')') {
freecvdef(ret);
zwarnnam(nam, "invalid argument: %s", *args);
return NULL;
}
xor = (char **) zalloc((xnum + 2) * sizeof(char *));
for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
*xp = ztrdup((char *) getdata(node));
xp[0] = xp[1] = NULL;
p++;
} else
xor = NULL;
/* More than once allowed? */
if ((multi = (*p == '*')))
p++;
/* Skip option name. */
for (name = p; *p && *p != ':' && *p != '['; p++)
if (*p == '\\' && p[1])
p++, bs = 1;
if (hassep && !sep && name + bs + 1 < p) {
freecvdef(ret);
if (xor) freearray(xor);
zwarnnam(nam, "no multi-letter values with empty separator allowed");
return NULL;
}
/* Optional description? */
if ((c = *p) == '[') {
*p = '\0';
for (descr = ++p; *p && *p != ']'; p++)
if (*p == '\\' && p[1])
p++;
if (!*p) {
freecvdef(ret);
if (xor) freearray(xor);
zwarnnam(nam, "invalid value definition: %s", *args);
return NULL;
}
*p++ = '\0';
c = *p;
} else {
*p = '\0';
descr = NULL;
}
if (c && c != ':') {
freecvdef(ret);
if (xor) freearray(xor);
zwarnnam(nam, "invalid value definition: %s", *args);
return NULL;
}
/* Get argument? */
if (c == ':') {
if (hassep && !sep) {
freecvdef(ret);
if (xor) freearray(xor);
zwarnnam(nam, "no value with argument with empty separator allowed");
return NULL;
}
if (*++p == ':') {
p++;
vtype = CVV_OPT;
} else
vtype = CVV_ARG;
arg = parse_caarg(0, 0, 0, 0, name, &p, NULL);
} else {
vtype = CVV_NOARG;
arg = NULL;
}
if (!multi) {
if (!xor) {
xor = (char **) zalloc(2 * sizeof(char *));
xor[1] = NULL;
}
xor[xnum] = ztrdup(name);
}
*valp = val = (Cvval) zalloc(sizeof(*val));
valp = &((*valp)->next);
val->next = NULL;
val->name = ztrdup(name);
val->descr = ztrdup(descr);
val->xor = xor;
val->type = vtype;
val->arg = arg;
}
return ret;
}
/* Get the definition from the cache or newly built. */
static Cvdef
get_cvdef(char *nam, char **args)
{
Cvdef *p, *min, new;
int i, na = arrlen(args);
for (i = MAX_CVCACHE, p = cvdef_cache, min = NULL; *p && i--; p++)
if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
(*p)->lastt = time(0);
return *p;
} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
min = p;
if (i > 0)
min = p;
if ((new = parse_cvdef(nam, args))) {
freecvdef(*min);
*min = new;
}
return new;
}
/* Get the definition for a value. */
static Cvval
cv_get_val(Cvdef d, char *name)
{
Cvval p;
for (p = d->vals; p; p = p->next)
if (!strcmp(name, p->name))
return p;
return NULL;
}
static Cvval
cv_quote_get_val(Cvdef d, char *name)
{
int ne;
/* remove quotes */
name = dupstring(name);
ne = noerrs;
noerrs = 2;
parse_subst_string(name);
noerrs = ne;
remnulargs(name);
untokenize(name);
return cv_get_val(d, name);
}
/* Handle a xor list. */
static void
cv_inactive(Cvdef d, char **xor)
{
if (xor) {
Cvval val;
for (; *xor; xor++)
if ((val = cv_get_val(d, *xor)))
val->active = 0;
}
}
/* Parse state. */
struct cvstate {
Cvdef d;
Caarg def;
Cvval val;
LinkList vals;
};
static struct cvstate cv_laststate;
static int cv_parsed = 0, cv_alloced = 0;
/* Get the next value in the string. Return it's definition and update the
* sp pointer to point to the end of the value (plus argument, if any).
* If there is no next value, the string pointer is set to null. In any
* case ap will point to the beginning of the argument or will be a null
* pointer if there is no argument.
*/
static Cvval
cv_next(Cvdef d, char **sp, char **ap)
{
Cvval r = NULL;
char *s = *sp;
if (!*s) {
*sp = *ap = NULL;
return NULL;
}
if ((d->hassep && !d->sep) || !d->argsep) {
char sav, ec, *v = s, *os;
ec = ((d->hassep && d->sep) ? d->sep : d->argsep);
do {
sav = *++s;
*s = '\0';
if ((r = cv_quote_get_val(d, v))) {
*s = sav;
break;
}
*s = sav;
} while (*s && *s != ec);
os = s;
if (d->hassep && d->sep) {
if ((s = strchr(s, d->sep)))
*sp = s + 1;
else
*sp = NULL;
} else
*sp = s;
if (d->argsep && *os == d->argsep) {
*ap = os + 1;
*sp = NULL;
} else if (r && r->type != CVV_NOARG)
*ap = os;
else
*ap = NULL;
return r;
} else if (d->hassep) {
char *ns = strchr(s, d->sep), *as = 0, *sap, sav = 0;
int skip = 0;
if (d->argsep && (as = strchr(s, d->argsep)) && (!ns || as <= ns)) {
*ap = as + 1;
ns = strchr(as + 1, d->sep);
skip = 1;
sap = as;
} else {
*ap = NULL;
sap = ns;
}
if (sap) {
sav = *sap;
*sap = '\0';
}
if ((!(r = cv_quote_get_val(d, s)) || r->type == CVV_NOARG) && skip)
ns = as;
if (sap)
*sap = sav;
*sp = ((!ns || (ns == as && r && r->type != CVV_NOARG)) ? NULL : ns + 1);
return r;
} else {
char *as = strchr(s, d->argsep), *sap, sav = 0;
*sp = NULL;
if (as) {
*ap = as + 1;
sap = as;
sav = *as;
*sap = '\0';
} else
*ap = sap = NULL;
r = cv_quote_get_val(d, s);
if (sap)
*sap = sav;
return r;
}
}
/* Parse the current word. */
static void
cv_parse_word(Cvdef d)
{
Cvval val;
struct cvstate state;
char *str, *arg = NULL, *pign = compprefix;
int nosfx = 0;
if (cv_alloced)
freelinklist(cv_laststate.vals, freestr);
for (val = d->vals; val; val = val->next)
val->active = 1;
state.d = d;
state.def = NULL;
state.val = NULL;
state.vals = (LinkList) znewlinklist();
cv_alloced = 1;
if (d->words && compwords[0]) {
int i;
for (i = 1; compwords[i]; i++)
if (i != compcurrent - 1)
for (str = compwords[i]; str && *str; ) {
if ((val = cv_next(d, &str, &arg))) {
zaddlinknode(state.vals, ztrdup(val->name));
if (arg) {
char sav = '\0';
if (str) {
sav = str[-1];
str[-1] = '\0';
}
zaddlinknode(state.vals, ztrdup(arg));
if (str)
str[-1] = sav;
} else
zaddlinknode(state.vals, ztrdup(""));
if (i + 1 < compcurrent)
cv_inactive(d, val->xor);
}
}
val = NULL;
arg = NULL;
}
for (str = compprefix; str && *str; ) {
if ((val = cv_next(d, &str, &arg))) {
zaddlinknode(state.vals, ztrdup(val->name));
if (arg) {
if (str) {
char sav = str[-1];
str[-1] = '\0';
zaddlinknode(state.vals, ztrdup(arg));
str[-1] = sav;
} else {
zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
nosfx = 1;
}
} else
zaddlinknode(state.vals, ztrdup(""));
cv_inactive(d, val->xor);
if (str)
pign = str;
else
val->active = 1;
}
}
state.val = val;
if (val && arg && !str)
state.def = val->arg;
if (!nosfx && d->hassep) {
int ign = 0;
char *more = NULL;
ignore_prefix(pign - compprefix);
if (!d->sep && (!val || val->type == CVV_NOARG)) {
ign = strlen(compsuffix);
more = compsuffix;
} else {
if (d->sep) {
char *ns = strchr(compsuffix, d->sep), *as;
if (d->argsep && (as = strchr(compsuffix, d->argsep)) &&
(!ns || as <= ns)) {
ign = strlen(as);
} else
ign = (ns ? strlen(ns) : 0);
more = (ns ? ns + 1 : NULL);
} else if (d->argsep) {
char *as;
if ((as = strchr(compsuffix, d->argsep)))
ign = strlen(as);
}
}
more = dupstring(more);
if (ign)
ignore_suffix(ign);
while (more && *more) {
if ((val = cv_next(d, &more, &arg))) {
zaddlinknode(state.vals, ztrdup(val->name));
if (arg) {
if (more) {
char sav = more[-1];
more[-1] = '\0';
zaddlinknode(state.vals, ztrdup(arg));
more[-1] = sav;
} else {
zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
nosfx = 1;
}
} else
zaddlinknode(state.vals, ztrdup(""));
cv_inactive(d, val->xor);
}
}
} else if (arg)
ignore_prefix(arg - compprefix);
else
ignore_prefix(pign - compprefix);
memcpy(&cv_laststate, &state, sizeof(state));
}
static int
bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int min, max, n;
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
zwarnnam(nam, "invalid argument: %s", args[0]);
return 1;
}
if (args[0][1] != 'i' && !cv_parsed) {
zwarnnam(nam, "no parsed state");
return 1;
}
switch (args[0][1]) {
case 'i': min = 2; max = -1; break;
case 'D': min = 2; max = 2; break;
case 'C': min = 1; max = 1; break;
case 'V': min = 3; max = 3; break;
case 's': min = 1; max = 1; break;
case 'S': min = 1; max = 1; break;
case 'd': min = 1; max = 1; break;
case 'L': min = 3; max = 4; break;
case 'v': min = 1; max = 1; break;
default:
zwarnnam(nam, "invalid option: %s", args[0]);
return 1;
}
n = arrlen(args) - 1;
if (n < min) {
zwarnnam(nam, "not enough arguments");
return 1;
} else if (max >= 0 && n > max) {
zwarnnam(nam, "too many arguments");
return 1;
}
switch (args[0][1]) {
case 'i':
/* This initialises the internal data structures. The arguments are
* just the arguments that were given to _values itself. */
{
Cvdef def = get_cvdef(nam, args + 1);
int cvp = cv_parsed;
cv_parsed = 0;
if (!def)
return 1;
cv_parsed = cvp;
cv_parse_word(def);
cv_parsed = 1;
return 0;
}
case 'D':
/* This returns the description and action to use if we are at
* a place where some action has to be used at all. In that case
* zero is returned and non-zero otherwise. */
{
Caarg arg = cv_laststate.def;
if (arg) {
setsparam(args[1], ztrdup(arg->descr));
setsparam(args[2], ztrdup(arg->action));
return 0;
}
return 1;
}
case 'C':
/* This returns the sub-context (i.e.: the tag) to use when executing
* an action. */
{
Caarg arg = cv_laststate.def;
if (arg) {
setsparam(args[1], ztrdup(arg->opt));
return 0;
}
return 1;
}
case 'V':
/* This is what -O is for comparguments: it returns (in three arrays)
* the values for values without arguments, with arguments and with
* optional arguments (so that we can get the auto-suffixes right).
* As for comparguments, the strings returned are usable for _describe. */
{
LinkList noarg = newlinklist();
LinkList arg = newlinklist();
LinkList opt = newlinklist(), l;
Cvval p;
char *str;
for (p = cv_laststate.d->vals; p; p = p->next) {
if (p->active) {
switch (p->type) {
case CVV_NOARG: l = noarg; break;
case CVV_ARG: l = arg; break;
default: l = opt; break;
}
if (p->descr) {
int len = strlen(p->name) + strlen(p->descr) + 2;
str = (char *) zhalloc(len);
strcpy(str, p->name);
strcat(str, ":");
strcat(str, p->descr);
} else
str = p->name;
addlinknode(l, str);
}
}
set_list_array(args[1], noarg);
set_list_array(args[2], arg);
set_list_array(args[3], opt);
return 0;
}
case 's':
/* This returns the value separator, if any, and sets the return
* value to say if there is such a separator. */
if (cv_laststate.d->hassep) {
char tmp[2];
tmp[0] = cv_laststate.d->sep;
tmp[1] = '\0';
setsparam(args[1], ztrdup(tmp));
return 0;
}
return 1;
case 'S':
/* Like -s, but for the separator between values and their arguments. */
{
char tmp[2];
tmp[0] = cv_laststate.d->argsep;
tmp[1] = '\0';
setsparam(args[1], ztrdup(tmp));
}
return 0;
case 'd':
/* This returns the description string (first argument to _values)
* which is passed down to _describe. */
setsparam(args[1], ztrdup(cv_laststate.d->descr));
return 0;
case 'L':
/* Almost the same as for comparguments. This gets a value name
* and returns the description and action of its first argument, if
* any. The rest (prefix matching) is in _values. Return non-zero
* if there is no such option. */
{
Cvval val = cv_get_val(cv_laststate.d, args[1]);
if (val && val->arg) {
setsparam(args[2], ztrdup(val->arg->descr));
setsparam(args[3], ztrdup(val->arg->action));
if (args[4])
setsparam(args[4], ztrdup(val->name));
return 0;
}
return 1;
}
case 'v':
/* Again, as for comparguments. This returns the values and their
* arguments as an array which will be stored in val_args in _values. */
if (cv_laststate.vals) {
char **ret;
ret = zlinklist2array(cv_laststate.vals, 1);
sethparam(args[1], ret);
return 0;
}
return 1;
}
return 1;
}
static char *
comp_quote(char *str, int prefix)
{
int x;
char *ret;
if ((x = (prefix && *str == '=')))
*str = 'x';
ret = quotestring(str, *compqstack);
if (x)
*str = *ret = '=';
return ret;
}
static int
bin_compquote(char *nam, char **args, Options ops, UNUSED(int func))
{
char *name;
struct value vbuf;
Value v;
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
/* Anything to do? */
if (!compqstack || !*compqstack)
return 0;
/* For all parameters given... */
while ((name = *args++)) {
name = dupstring(name);
queue_signals();
if ((v = getvalue(&vbuf, &name, 0))) {
switch (PM_TYPE(v->pm->node.flags)) {
case PM_SCALAR:
setstrvalue(v, ztrdup(comp_quote(getstrvalue(v),
OPT_ISSET(ops,'p'))));
break;
case PM_ARRAY:
{
char **val = v->pm->gsu.a->getfn(v->pm);
char **new = (char **) zalloc((arrlen(val) + 1) *
sizeof(char *));
char **p = new;
for (; *val; val++, p++)
*p = ztrdup(comp_quote(*val, OPT_ISSET(ops,'p')));
*p = NULL;
setarrvalue(v, new);
}
break;
default:
zwarnnam(nam, "invalid parameter type: %s", args[-1]);
}
} else
zwarnnam(nam, "unknown parameter: %s", args[-1]);
unqueue_signals();
}
return 0;
}
/* Tags stuff. */
typedef struct ctags *Ctags;
typedef struct ctset *Ctset;
/* A bunch of tag sets. */
struct ctags {
char **all; /* all tags offered */
char *context; /* the current context */
int init; /* not yet used */
Ctset sets; /* the tag sets */
};
/* A tag set. */
struct ctset {
Ctset next;
char **tags; /* the tags */
char *tag; /* last tag checked for -A */
char **ptr; /* ptr into tags for -A */
};
/* Array of tag-set infos. Index is the locallevel. */
#define MAX_TAGS 256
static Ctags comptags[MAX_TAGS];
/* locallevel at last comptags -i */
static int lasttaglevel;
static void
freectset(Ctset s)
{
Ctset n;
while (s) {
n = s->next;
if (s->tags)
freearray(s->tags);
zsfree(s->tag);
zfree(s, sizeof(*s));
s = n;
}
}
static void
freectags(Ctags t)
{
if (t) {
if (t->all)
freearray(t->all);
zsfree(t->context);
freectset(t->sets);
zfree(t, sizeof(*t));
}
}
/* Set the tags for the current local level. */
static void
settags(int level, char **tags)
{
Ctags t;
if (comptags[level])
freectags(comptags[level]);
comptags[level] = t = (Ctags) zalloc(sizeof(*t));
t->all = zarrdup(tags + 1);
t->context = ztrdup(*tags);
t->sets = NULL;
t->init = 1;
}
/* Check if an array contains a string. */
/**/
static int
arrcontains(char **a, char *s, int colon)
{
char *p, *q;
while (*a) {
if (colon) {
for (p = s, q = *a++; *p && *q && *p != ':' && *q != ':'; p++, q++)
if (*p != *q)
break;
if ((!*p || *p == ':') && (!*q || *q == ':'))
return 1;
} else if (!strcmp(*a++, s))
return 1;
}
return 0;
}
static int
bin_comptags(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int min, max, n, level;
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (args[0][0] != '-' || !args[0][1] ||
(args[0][2] && (args[0][2] != '-' || args[0][3]))) {
zwarnnam(nam, "invalid argument: %s", args[0]);
return 1;
}
level = locallevel - (args[0][2] ? 1 : 0);
if (level >= MAX_TAGS) {
zwarnnam(nam, "nesting level too deep");
return 1;
}
if (args[0][1] != 'i' && args[0][1] != 'I' && !comptags[level]) {
zwarnnam(nam, "no tags registered");
return 1;
}
switch (args[0][1]) {
case 'i': min = 2; max = -1; break;
case 'C': min = 1; max = 1; break;
case 'T': min = 0; max = 0; break;
case 'N': min = 0; max = 0; break;
case 'R': min = 1; max = 1; break;
case 'S': min = 1; max = 1; break;
case 'A': min = 2; max = 3; break;
default:
zwarnnam(nam, "invalid option: %s", args[0]);
return 1;
}
n = arrlen(args) - 1;
if (n < min) {
zwarnnam(nam, "not enough arguments");
return 1;
} else if (max >= 0 && n > max) {
zwarnnam(nam, "too many arguments");
return 1;
}
switch (args[0][1]) {
case 'i':
settags(level, args + 1);
lasttaglevel = level;
break;
case 'C':
setsparam(args[1], ztrdup(comptags[level]->context));
break;
case 'T':
return !comptags[level]->sets;
case 'N':
{
Ctset s;
if (comptags[level]->init)
comptags[level]->init = 0;
else if ((s = comptags[level]->sets)) {
comptags[level]->sets = s->next;
s->next = NULL;
freectset(s);
}
return !comptags[level]->sets;
}
case 'R':
{
Ctset s;
return !((s = comptags[level]->sets) &&
arrcontains(s->tags, args[1], 1));
}
case 'A':
{
Ctset s;
if (comptags[level] && (s = comptags[level]->sets)) {
char **q, *v = NULL;
int l = strlen(args[1]);
if (!s->tag || strcmp(s->tag, args[1])) {
zsfree(s->tag);
s->tag = ztrdup(args[1]);
s->ptr = s->tags;
}
for (q = s->ptr; *q; q++) {
if (strpfx(args[1], *q)) {
if (!(*q)[l]) {
v = *q;
break;
} else if ((*q)[l] == ':') {
v = (*q) + l + 1;
break;
}
}
}
if (!v) {
zsfree(s->tag);
s->tag = NULL;
return 1;
}
s->ptr = q + 1;
setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
if (args[3]) {
char *r = dupstring(*q), *p;
for (p = r + (v - *q); *p && *p != ':'; p++);
*p = '\0';
setsparam(args[3], ztrdup(r));
}
return 0;
}
return 1;
}
case 'S':
if (comptags[level]->sets) {
char **ret;
ret = zarrdup(comptags[level]->sets->tags);
setaparam(args[1], ret);
} else
return 1;
break;
}
return 0;
}
static int
bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (!lasttaglevel || !comptags[lasttaglevel]) {
zwarnnam(nam, "no tags registered");
return 1;
}
if (*args) {
if (!strcmp(*args, "-m")) {
char *s, *p, *q, *c, **all = comptags[lasttaglevel]->all;
LinkList list = newlinklist();
int num = 0;
Ctset set;
while ((s = *++args)) {
while (*s) {
while (*s && iblank(*s))
s++;
for (p = q = s, c = NULL; *s && !inblank(*s); s++) {
if (!c && *s == ':')
c = p;
if (*s == '\\' && s[1])
s++;
*p++ = *s;
}
if (*s)
s++;
*p = '\0';
if (*q) {
char *qq, *qqq;
queue_signals();
if (c)
*c = '\0';
qqq = qq = dupstring(q);
while (*qqq) {
if (*qqq == '\\' && qqq[1])
qqq++;
else if (*qqq == '{')
*qqq = Inbrace;
else if (*qqq == '}')
*qqq = Outbrace;
else if (*qqq == ',')
*qqq = Comma;
qqq++;
}
tokenize(qq);
if (haswilds(qq) || hasbraces(qq)) {
Patprog prog;
LinkNode bnode, node;
LinkList blist = newlinklist();
addlinknode(blist, qq);
for (bnode = firstnode(blist); bnode; incnode(bnode))
while (hasbraces(getdata(bnode)))
xpandbraces(blist, &bnode);
for (bnode = firstnode(blist); bnode; incnode(bnode)) {
qq = (char *) getdata(bnode);
if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
char **a, *n;
int l = (c ? strlen(c + 1) + 2 : 1), al;
for (a = all; *a; a++) {
for (node = firstnode(list); node;
incnode(node)) {
char *as, *ls;
for (as = *a, ls = (char *) getdata(node);
*as && *ls && *ls != ':'; as++, ls++)
if (*as != *ls)
break;
if (!*as && (!*ls || *ls == ':'))
break;
}
if (node)
continue;
if (pattry(prog, *a)) {
n = (char *) zhalloc((al = strlen(*a)) + l);
strcpy(n, *a);
if (c) {
n[al] = ':';
strcpy(n + al + 1, c + 1);
}
addlinknode(list, n);
num++;
}
}
}
}
} else if (arrcontains(all, q, 0)) {
for (set = comptags[lasttaglevel]->sets; set;
set = set->next)
if (arrcontains(set->tags, q, 0))
break;
if (!set) {
addlinknode(list, q);
num++;
}
}
if (c)
*c = ':';
unqueue_signals();
}
}
if (num) {
Ctset l;
set = (Ctset) zalloc(sizeof(*set));
set->tags = zlinklist2array(list, 1);
set->next = NULL;
set->ptr = NULL;
set->tag = NULL;
if ((l = comptags[lasttaglevel]->sets)) {
while (l->next)
l = l->next;
l->next = set;
} else
comptags[lasttaglevel]->sets = set;
}
}
} else {
char **p, **q, **all;
int sep = 0;
if ((sep = !strcmp(*args, "-s")))
args++;
for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
if (arrcontains(all, *p, 1)) {
Ctset s;
for (s = comptags[lasttaglevel]->sets; s; s = s->next)
if (arrcontains(s->tags, *p, 0))
break;
if (!s)
*q++ = *p;
}
*q = NULL;
if (*args) {
char *dummy[2];
do {
Ctset s = (Ctset) zalloc(sizeof(*s)), l;
if (sep) {
dummy[0] = *args++;
dummy[1] = NULL;
s->tags = zarrdup(dummy);
} else
s->tags = zarrdup(args);
s->next = NULL;
s->ptr = NULL;
s->tag = NULL;
if ((l = comptags[lasttaglevel]->sets)) {
while (l->next)
l = l->next;
l->next = s;
} else
comptags[lasttaglevel]->sets = s;
} while (sep && *args);
}
}
}
return 0;
}
#define PATH_MAX2 (PATH_MAX * 2)
/*
* Return a list of files we should accept exactly, without
* trying pattern matching.
*
* This is based on the accept-exact style, which may be
* an array so is passed in via "accept". The trial files
* are input in "names". "skipped" is passed down straight
* from the file completion function: it's got something to
* do with other components in the path but it's hard to work out
* quite what.
*
* There is one extra trick here for Cygwin. Regardless of the style,
* if the file ends in a colon it has to be a drive or a special device
* file and we always accept it exactly because treating it as a pattern
* won't work.
*/
static LinkList
cfp_test_exact(LinkList names, char **accept, char *skipped)
{
char buf[PATH_MAX2 + 1], *suf, *p;
int l, sl, found = 0;
struct stat st;
LinkNode node;
LinkList ret = newlinklist(), alist = NULL;
#ifdef __CYGWIN__
int accept_off = 0;
#endif
/*
* Don't do this unless completion has provided either a
* prefix or suffix from the command line.
*/
if (!(compprefix && *compprefix) && !(compsuffix && *compsuffix))
return NULL;
/*
* See if accept-exact is off, implicitly or explicitly.
*/
if (!accept || !*accept ||
((!strcmp(*accept, "false") || !strcmp(*accept, "no") ||
!strcmp(*accept, "off") || !strcmp(*accept, "0")) && !accept[1])) {
#ifdef __CYGWIN__
accept_off = 1;
#else
/* If not Cygwin, nothing to do here. */
return NULL;
#endif
}
/*
* See if the style is something other than just a boolean.
*/
if (
#ifdef __CYGWIN__
!accept_off &&
#endif
(accept[1] ||
(strcmp(*accept, "true") && strcmp(*accept, "yes") &&
strcmp(*accept, "on") && strcmp(*accept, "1")))) {
Patprog prog;
alist = newlinklist();
for (; (p = *accept); accept++) {
if (*p == '*' && !p[1]) {
alist = NULL;
break;
}
tokenize(p = dupstring(p));
if ((prog = patcompile(p, 0, NULL)))
addlinknode(alist, prog);
}
}
/*
* Assemble the bits other than the set of file names:
* the other components, and the prefix and suffix.
*/
sl = strlen(skipped) + (compprefix ? strlen(compprefix) : 0) +
(compsuffix ? strlen(compsuffix) : 0);
if (sl > PATH_MAX2)
return NULL;
suf = dyncat(skipped, rembslash(dyncat(compprefix ? compprefix : "",
compsuffix ? compsuffix : "")));
for (node = firstnode(names); node; incnode(node)) {
l = strlen(p = (char *) getdata(node));
if (l + sl < PATH_MAX2) {
#ifdef __CYGWIN__
char *testbuf;
#define TESTBUF testbuf
#else
#define TESTBUF buf
#endif
strcpy(buf, p);
strcpy(buf + l, suf);
#ifdef __CYGWIN__
if (accept_off) {
int sl = strlen(buf);
/*
* If accept-exact is not set, accept this only if
* it looks like a special file such as a drive.
* We still test if it exists.
*/
if (!sl || strchr(buf, '/') || buf[sl-1] != ':')
continue;
if (sl == 2) {
/*
* Recent versions of Cygwin only recognise "c:/",
* but not "c:", as special directories. So
* we have to append the slash for the purpose of
* the test.
*/
testbuf = zhalloc(sl + 2);
strcpy(testbuf, buf);
testbuf[sl] = '/';
testbuf[sl+1] = '\0';
} else {
/* Don't do this with stuff like PRN: */
testbuf = buf;
}
} else {
testbuf = buf;
}
#endif
if (!ztat(TESTBUF, &st, 0)) {
/*
* File exists; if accept-exact contained non-boolean
* values it must match those, too.
*/
if (alist) {
LinkNode anode;
for (anode = firstnode(alist); anode; incnode(anode))
if (pattry((Patprog) getdata(anode), buf))
break;
if (!anode)
continue;
}
found = 1;
addlinknode(ret, dupstring(buf));
}
}
}
return (found ? ret : NULL);
}
/*
* This code constructs (from heap) and returns a string that
* corresponds to a series of matches; when compiled as a pattern, at
* each position it matches either the character from the string "add"
* or the corresponding single-character match from the set of matchers.
* To take a simple case, if add is "a" and the single matcher for the
* character position matches "[0-9]", the pattern returned is "[0-9a]".
* We take account of equivalences between the word and line, too.
*
* As there are virtually no comments in this file, I don't really
* know why we're doing this, but it's to do with a matcher which
* is passed as an argument to the utility compfiles -p/-P.
*/
static char *
cfp_matcher_range(Cmatcher *ms, char *add)
{
Cmatcher *mp, m;
int len = 0, mt;
char *ret = NULL, *p = NULL, *adds = add;
/*
* Do this twice: once to work out the length of the
* string in len, the second time to build it in ret.
* This is probably worthwhile because otherwise memory
* management is difficult.
*/
for (;;) {
MB_METACHARINIT();
for (mp = ms; *add; ) {
convchar_t addc;
int addlen;
addlen = MB_METACHARLENCONV(add, &addc);
#ifdef MULTIBYTE_SUPPORT
if (addc == WEOF)
addc = (wchar_t)(*add == Meta ? add[1] ^ 32 : *add);
#endif
if (!(m = *mp)) {
/*
* No matcher, so just match the character
* itself.
*
* TODO: surely this needs quoting if it's a
* metacharacter?
*/
if (ret) {
memcpy(p, add, addlen);
p += addlen;
} else
len += addlen;
} else if (m->flags & CMF_RIGHT) {
/*
* Right-anchored: match anything followed
* by the character itself.
*/
if (ret) {
*p++ = '*';
/* TODO: quote again? */
memcpy(p, add, addlen);
p += addlen;
} else
len += addlen + 1;
} else {
/* The usual set of matcher possibilities. */
convchar_t ind;
if (m->line->tp == CPAT_EQUIV &&
m->word->tp == CPAT_EQUIV) {
/*
* Genuine equivalence. Add the character to match
* and the equivalent character from the word
* pattern.
*
* TODO: we could be more careful here with special
* cases as we are in the basic character class
* code below.
*/
if (ret) {
*p++ = '[';
memcpy(p, add, addlen);
p += addlen;
} else
len += addlen + 1;
if (PATMATCHRANGE(m->line->u.str, addc, &ind, &mt)) {
/*
* Find the equivalent match for ind in the
* word pattern.
*/
if ((ind = pattern_match_equivalence
(m->word, ind+1, mt, addc)) != CHR_INVALID) {
if (ret) {
if (imeta(ind)) {
*p++ = Meta;
*p++ = ind ^ 32;
} else
*p++ = ind;
} else
len += imeta(ind) ? 2 : 1;
}
}
if (ret)
*p++ = ']';
else
len++;
} else {
int newlen, addadd;
switch (m->word->tp) {
case CPAT_NCLASS:
/*
* TODO: the old logic implies that we need to
* match *add, i.e. it should be deleted from
* the set of character's we're not allowed to
* match. That's too much like hard work for
* now. Indeed, in general it's impossible
* without trickery. Consider *add == 'A',
* range == "[^[:upper:]]": we would have to
* resort to something like "(A|[^[:upper:]])";
* and in an expression like that *add may or
* may not need backslashing. So we're deep
* into see-if-we-can-get-away-without
* territory.
*/
if (ret) {
*p++ = '[';
*p++ = '^';
} else
len += 2;
/*
* Convert the compiled range string back
* to an ordinary string.
*/
newlen =
pattern_range_to_string(m->word->u.str, p);
DPUTS(!newlen, "empty character range");
if (ret) {
p += newlen;
*p++ = ']';
} else
len += newlen + 1;
break;
case CPAT_CCLASS:
/*
* If there is an equivalence only on one
* side it's not equivalent to anything.
* Treat it as an ordinary character class.
*/
case CPAT_EQUIV:
case CPAT_CHAR:
if (ret)
*p++ = '[';
else
len++;
/*
* We needed to add *add specially only if
* it is not covered by the range. This
* is necessary for correct syntax---consider
* if *add is ] and ] is also the first
* character in the range.
*/
addadd = !pattern_match1(m->word, addc, &mt);
if (addadd && *add == ']') {
if (ret)
*p++ = *add;
else
len++;
}
if (m->word->tp == CPAT_CHAR) {
/*
* The matcher just matches a single
* character, but we need to be able
* to match *add, too, hence we do
* this as a [...].
*/
if (ret) {
if (imeta(m->word->u.chr)) {
*p++ = Meta;
*p++ = m->word->u.chr ^ 32;
} else
*p++ = m->word->u.chr;
} else
len += imeta(m->word->u.chr) ? 2 : 1;
} else {
/*
* Convert the compiled range string back
* to an ordinary string.
*/
newlen =
pattern_range_to_string(m->word->u.str, p);
DPUTS(!newlen, "empty character range");
if (ret)
p += newlen;
else
len += newlen;
}
if (addadd && *add != ']') {
if (ret) {
memcpy(p, add, addlen);
p += addlen;
} else
len += addlen;
}
if (ret)
*p++ = ']';
else
len++;
break;
case CPAT_ANY:
if (ret)
*p++ = '?';
else
len++;
break;
}
}
}
add += addlen;
mp++;
}
if (ret) {
*p = '\0';
return ret;
}
p = ret = zhalloc(len + 1);
add = adds;
}
}
static char *
cfp_matcher_pats(char *matcher, char *add)
{
Cmatcher m = parse_cmatcher(NULL, matcher);
if (m && m != pcm_err) {
char *tmp;
int al = strlen(add), zl = ztrlen(add), tl, cl;
VARARR(Cmatcher, ms, zl); /* One Cmatcher per character */
Cmatcher *mp;
Cpattern stopp;
int stopl = 0;
/* zl >= (number of wide characters) is guaranteed */
memset(ms, 0, zl * sizeof(Cmatcher));
for (; m && *add; m = m->next) {
stopp = NULL;
if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) {
if (m->llen == 1 && m->wlen == 1) {
/*
* In this loop and similar loops below we step
* through tmp one (possibly wide) character at a
* time. pattern_match() compares only the first
* character using unmeta_one() so keep in step.
*/
for (tmp = add, tl = al, mp = ms; tl; ) {
if (pattern_match(m->line, tmp, NULL, NULL)) {
if (*mp) {
*tmp = '\0';
al = tmp - add;
break;
} else
*mp = m;
}
(void) unmeta_one(tmp, &cl);
tl -= cl;
tmp += cl;
mp++;
}
} else {
stopp = m->line;
stopl = m->llen;
}
} else if (m->flags & CMF_RIGHT) {
if (m->wlen < 0 && !m->llen && m->ralen == 1) {
for (tmp = add, tl = al, mp = ms; tl; ) {
if (pattern_match(m->right, tmp, NULL, NULL)) {
if (*mp || (tmp == add && *tmp == '.')) {
*tmp = '\0';
al = tmp - add;
break;
} else
*mp = m;
}
(void) unmeta_one(tmp, &cl);
tl -= cl;
tmp += cl;
mp++;
}
} else if (m->llen) {
stopp = m->line;
stopl = m->llen;
} else {
stopp = m->right;
stopl = m->ralen;
}
} else {
if (!m->lalen)
return "";
stopp = m->left;
stopl = m->lalen;
}
if (stopp)
for (tmp = add, tl = al; tl >= stopl; ) {
if (pattern_match(stopp, tmp, NULL, NULL)) {
*tmp = '\0';
al = tmp - add;
break;
}
(void) unmeta_one(tmp, &cl);
tl -= cl;
tmp += cl;
}
}
if (*add)
return cfp_matcher_range(ms, add);
}
return add;
}
/*
* ### This function call is skipped by _approximate, so "opt" probably means "optimize".
*/
static void
cfp_opt_pats(char **pats, char *matcher)
{
char *add, **p, *q, *t, *s;
if (!compprefix || !*compprefix)
return;
if (comppatmatch && *comppatmatch) {
tokenize(t = rembslash(dyncat(compprefix, compsuffix)));
remnulargs(t);
if (haswilds(t))
return;
}
add = (char *) zhalloc(strlen(compprefix) * 2 + 1);
for (s = compprefix, t = add; *s; s++) {
if (*s != '\\' || !s[1] || s[1] == '*' || s[1] == '?' ||
s[1] == '<' || s[1] == '>' || s[1] == '(' || s[1] == ')' ||
s[1] == '[' || s[1] == ']' || s[1] == '|' || s[1] == '#' ||
s[1] == '^' || s[1] == '~' || s[1] == '=') {
if ((s == compprefix || s[-1] != '\\') &&
(*s == '*' || *s == '?' || *s == '<' || *s == '>' ||
*s == '(' || *s == ')' || *s == '[' || *s == ']' ||
*s == '|' || *s == '#' || *s == '^' || *s == '~' ||
*s == '='))
*t++ = '\\';
*t++ = *s;
}
}
*t = '\0';
for (p = pats; *add && (q = *p); p++) {
if (*q) {
q = dupstring(q);
t = q + strlen(q) - 1;
if (*t == ')') {
for (s = t--; t > q; t--)
if (*t == ')' || *t == '|' || *t == '~' || *t == '(')
break;
if (t != q && *t == '(')
*t = '\0';
}
for (; *q && *add; q++) {
if (*q == '\\' && q[1]) {
for (s = add, q++; *s && *s != *q; s++);
*s = '\0';
} else if (*q == '<') {
for (s = add; *s && !idigit(*s); s++);
*s = '\0';
} else if (*q == '[') {
int not;
char *x = ++q;
if ((not = (*x == '!' || *x == '^')))
x++;
for (; *x; x++) {
if (x[1] == '-' && x[2]) {
char c1 = *x, c2 = x[2];
for (s = add; *s && (*x < c1 || *x > c2); s++);
*s = '\0';
} else {
for (s = add; *s && *s != *x; s++);
*s = '\0';
}
}
} else if (*q != '?' && *q != '*' && *q != '(' && *q != ')' &&
*q != '|' && *q != '~' && *q != '#') {
for (s = add; *s && *s != *q; s++);
*s = '\0';
}
}
}
}
if (*add) {
if (*matcher && !(add = cfp_matcher_pats(matcher, add)))
return;
for (p = pats; *p; p++)
if (**p == '*')
*p = dyncat(add, *p);
}
}
static LinkList
cfp_bld_pats(UNUSED(int dirs), LinkList names, char *skipped, char **pats)
{
LinkList ret = newlinklist();
LinkNode node;
int ol, sl = strlen(skipped), pl, dot;
char **p, *o, *str;
dot = (unset(GLOBDOTS) && compprefix && *compprefix == '.');
for (node = firstnode(names); node; incnode(node)) {
ol = strlen(o = (char *) getdata(node));
for (p = pats; *p; p++) {
pl = strlen(*p);
str = (char *) zhalloc(ol + sl + pl + 1);
strcpy(str, o);
strcpy(str + ol, skipped);
strcpy(str + ol + sl, *p);
addlinknode(ret, str);
if (dot && **p != '.') {
str = (char *) zhalloc(ol + sl + pl + 2);
strcpy(str, o);
strcpy(str + ol, skipped);
str[ol + sl] = '.';
strcpy(str + ol + sl + 1, *p);
addlinknode(ret, str);
}
}
}
return ret;
}
static LinkList
cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
char *sdirs, char **fake)
{
int add = 0;
if (*sdirs && (isset(GLOBDOTS) || (compprefix && *compprefix == '.'))) {
if (!strcmp(sdirs, "yes") || !strcmp(sdirs, "true") ||
!strcmp(sdirs, "on") || !strcmp(sdirs, "1"))
add = 2;
else if (!strcmp(sdirs, ".."))
add = 1;
}
if (add) {
LinkNode node;
char *s1 = dyncat(skipped, "..");
char *s2 = (add == 2 ? dyncat(skipped, ".") : NULL), *m;
for (node = firstnode(orig); node; incnode(node)) {
if ((m = (char *) getdata(node))) {
addlinknode(final, dyncat(m, s1));
if (s2)
addlinknode(final, dyncat(m, s2));
}
}
}
if (fake && *fake) {
LinkNode node;
char *m, *f, *p, *t, *a, c;
int sl = strlen(skipped) + 1;
struct stat st1, st2;
Patprog pprog;
for (; (f = *fake); fake++) {
f = dupstring(f);
for (p = t = f; *p; p++) {
if (*p == ':')
break;
else if (*p == '\\' && p[1] == ':') {
/*
* strip quoted colons here; rely
* on tokenization to strip other backslashes
*/
p++;
}
*t++ = *p;
}
if (*p) {
*t = *p++ = '\0';
if (!*p)
continue;
queue_signals(); /* Protect PAT_STATIC */
tokenize(f);
pprog = patcompile(f, PAT_STATIC, NULL);
untokenize(f);
for (node = firstnode(orig); node; incnode(node)) {
if ((m = (char *) getdata(node)) &&
((pprog ? pattry(pprog, m) : !strcmp(f, m)) ||
(!stat(f, &st1) && !stat((*m ? m : "."), &st2) &&
st1.st_dev == st2.st_dev &&
st1.st_ino == st2.st_ino))) {
while (*p) {
while (*p && inblank(*p))
p++;
if (!*p)
break;
for (f = t = p; *p; p++) {
if (inblank(*p))
break;
else if (*p == '\\' && p[1])
p++;
*t++ = *p;
}
c = *t;
*t = '\0';
a = (char *) zhalloc(strlen(m) + sl + strlen(f));
strcpy(a, m);
strcat(a, skipped);
strcat(a, f);
addlinknode(final, a);
*t = c;
}
}
}
unqueue_signals();
}
}
}
return final;
}
static LinkList
cf_pats(int dirs, int noopt, LinkList names, char **accept, char *skipped,
char *matcher, char *sdirs, char **fake, char **pats)
{
LinkList ret;
char *dpats[2];
if ((ret = cfp_test_exact(names, accept, skipped)))
return cfp_add_sdirs(ret, names, skipped, sdirs, fake);
if (dirs) {
dpats[0] = "*(-/)";
dpats[1] = NULL;
pats = dpats;
}
if (!noopt)
cfp_opt_pats(pats, matcher);
return cfp_add_sdirs(cfp_bld_pats(dirs, names, skipped, pats),
names, skipped, sdirs, fake);
}
/*
* This function looks at device/inode pairs to determine if
* a file is one we should ignore because of its relationship
* to the current or parent directory.
*
* We don't follow symbolic links here, because typically
* a user will not want an explicit link to the current or parent
* directory ignored.
*/
static void
cf_ignore(char **names, LinkList ign, char *style, char *path)
{
int pl = strlen(path), tpar, tpwd, found;
struct stat nst, est, st;
char *n, *c, *e;
tpar = !!strstr(style, "parent");
if ((tpwd = !!strstr(style, "pwd")) && lstat(pwd, &est))
tpwd = 0;
if (!tpar && !tpwd)
return;
for (; (n = *names); names++) {
if (!ztat(n, &nst, 1) && S_ISDIR(nst.st_mode)) {
if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
addlinknode(ign, quotestring(n, QT_BACKSLASH));
continue;
}
if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
found = 0;
while ((e = strrchr(c, '/')) && e > c + pl) {
*e = '\0';
if (!ztat(c, &st, 0) &&
st.st_dev == nst.st_dev && st.st_ino == nst.st_ino) {
found = 1;
break;
}
}
if (found || ((e = strrchr(c, '/')) && e > c + pl &&
!ztat(c, &st, 1) && st.st_dev == nst.st_dev &&
st.st_ino == nst.st_ino))
addlinknode(ign, quotestring(n, QT_BACKSLASH));
}
}
}
}
static LinkList
cf_remove_other(char **names, char *pre, int *amb)
{
char *p;
if ((p = strchr(pre, '/'))) {
char **n;
*p = '\0';
pre = dyncat(pre, "/");
*p = '/';
for (n = names; *n; n++)
if (strpfx(pre, *n))
break;
if (*n) {
LinkList ret = newlinklist();
for (; *names; names++)
if (strpfx(pre, *names))
addlinknode(ret, dupstring(*names));
*amb = 0;
return ret;
} else {
if (!(p = *names++))
*amb = 0;
else {
char *q;
if ((q = strchr((p = dupstring(p)), '/')))
*q = '\0';
p = dyncat(p, "/");
for (; *names; names++)
if (!strpfx(p, *names)) {
*amb = 1;
return NULL;
}
}
}
} else {
if (!(p = *names++))
*amb = 0;
else
for (; *names; names++)
if (strcmp(p, *names)) {
*amb = 1;
return NULL;
}
}
return NULL;
}
/*
* SYNOPSIS:
* 1. compfiles -p parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
* 2. compfiles -p- parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
* 3. compfiles -P parnam1 parnam2 skipped matcher sdirs parnam3
*
* 1. Set parnam1 to an array of patterns....
* ${(P)parnam1} is an in/out parameter.
* 2. Like #1 but without calling cfp_opt_pats(). (This is only used by _approximate.)
* 3. Like #1 but varargs is implicitly set to char *varargs[2] = { "*(-/)", NULL };.
*
* parnam2 has to do with the accept-exact style (see cfp_test_exact()).
*/
static int
bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
if (**args != '-') {
zwarnnam(nam, "missing option: %s", *args);
return 1;
}
switch (args[0][1]) {
case 'p':
case 'P':
if (args[0][2] && (args[0][2] != '-' || args[0][3])) {
zwarnnam(nam, "invalid option: %s", *args);
return 1;
} else {
char **tmp;
LinkList l;
if (!args[1] || !args[2] || !args[3] || !args[4] || !args[5] ||
!args[6] || (args[0][1] == 'p' && !args[7])) {
zwarnnam(nam, "too few arguments");
return 1;
}
queue_signals();
if (!(tmp = getaparam(args[1]))) {
unqueue_signals();
zwarnnam(nam, "unknown parameter: %s", args[1]);
return 0;
}
for (l = newlinklist(); *tmp; tmp++)
addlinknode(l, quotestring(*tmp, QT_BACKSLASH_PATTERN));
set_list_array(args[1], cf_pats((args[0][1] == 'P'), !!args[0][2],
l, getaparam(args[2]), args[3],
args[4], args[5],
getaparam(args[6]), args + 7));
unqueue_signals();
return 0;
}
case 'i':
if (args[0][2]) {
zwarnnam(nam, "invalid option: %s", *args);
return 1;
} else {
char **tmp;
LinkList l;
if (!args[1] || !args[2] || !args[3] || !args[4]) {
zwarnnam(nam, "too few arguments");
return 1;
}
if (args[5]) {
zwarnnam(nam, "too many arguments");
return 1;
}
queue_signals();
tmp = getaparam(args[2]);
l = newlinklist();
if (tmp)
for (; *tmp; tmp++)
addlinknode(l, *tmp);
if (!(tmp = getaparam(args[1]))) {
unqueue_signals();
zwarnnam(nam, "unknown parameter: %s", args[1]);
return 0;
}
cf_ignore(tmp, l, args[3], args[4]);
unqueue_signals();
set_list_array(args[2], l);
return 0;
}
case 'r':
{
char **tmp;
LinkList l;
int ret = 0;
if (!args[1] || !args[2]) {
zwarnnam(nam, "too few arguments");
return 1;
}
if (args[3]) {
zwarnnam(nam, "too many arguments");
return 1;
}
queue_signals();
if (!(tmp = getaparam(args[1]))) {
unqueue_signals();
zwarnnam(nam, "unknown parameter: %s", args[1]);
return 0;
}
if ((l = cf_remove_other(tmp, args[2], &ret)))
set_list_array(args[1], l);
unqueue_signals();
return ret;
}
}
zwarnnam(nam, "invalid option: %s", *args);
return 1;
}
static int
bin_compgroups(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
Heap oldheap;
char *n;
if (incompfunc != 1) {
zwarnnam(nam, "can only be called from completion function");
return 1;
}
SWITCHHEAPS(oldheap, compheap) {
while ((n = *args++)) {
endcmgroup(NULL);
begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
endcmgroup(NULL);
begcmgroup(n, CGF_UNIQALL);
endcmgroup(NULL);
begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
endcmgroup(NULL);
begcmgroup(n, CGF_UNIQALL);
endcmgroup(NULL);
begcmgroup(n, CGF_NOSORT);
endcmgroup(NULL);
begcmgroup(n, 0);
}
} SWITCHBACKHEAPS(oldheap);
return 0;
}
static struct builtin bintab[] = {
BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
BUILTIN("compfiles", 0, bin_compfiles, 1, -1, 0, NULL, NULL),
BUILTIN("compgroups", 0, bin_compgroups, 1, -1, 0, NULL, NULL),
BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, "p", NULL),
BUILTIN("comptags", 0, bin_comptags, 1, -1, 0, NULL, NULL),
BUILTIN("comptry", 0, bin_comptry, 0, -1, 0, NULL, NULL),
BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL)
};
static struct features module_features = {
bintab, sizeof(bintab)/sizeof(*bintab),
NULL, 0,
NULL, 0,
NULL, 0,
0
};
/**/
int
setup_(UNUSED(Module m))
{
memset(cadef_cache, 0, sizeof(cadef_cache));
memset(cvdef_cache, 0, sizeof(cvdef_cache));
memset(comptags, 0, sizeof(comptags));
lasttaglevel = 0;
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_(UNUSED(Module m))
{
return 0;
}
/**/
int
cleanup_(Module m)
{
return setfeatureenables(m, &module_features, NULL);
}
/**/
int
finish_(UNUSED(Module m))
{
int i;
for (i = 0; i < MAX_CACACHE; i++)
freecadef(cadef_cache[i]);
for (i = 0; i < MAX_CVCACHE; i++)
freecvdef(cvdef_cache[i]);
for (i = 0; i < MAX_TAGS; i++)
freectags(comptags[i]);
return 0;
}