1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-05 15:16:19 +02:00
zsh/Src/Zle/compcore.c

3578 lines
86 KiB
C

/*
* compcore.c - the complete module, completion core code
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1999 Sven Wischnowsky
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Sven Wischnowsky or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Sven Wischnowsky and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Sven Wischnowsky and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "complete.mdh"
#include "compcore.pro"
/* Flags saying what we have to do with the result. */
/**/
int useexact, useline, uselist, forcelist, startauto;
/**/
mod_export int iforcemenu;
/* Non-zero if we should go back to the last prompt. */
/**/
mod_export int dolastprompt;
/* Non-zero if we should keep an old list. */
/**/
mod_export int oldlist, oldins;
/* Original prefix/suffix lengths. Flag saying if they changed. */
/**/
int origlpre, origlsuf, lenchanged;
/* This is used to decide when the cursor should be moved to the end of *
* the inserted word: 0 - never, 1 - only when a single match is inserted, *
* 2 - when a full match is inserted (single or menu), 3 - always. */
/**/
int movetoend;
/* The match and group number to insert when starting menucompletion. */
/**/
mod_export int insmnum, insspace;
#if 0
/* group-numbers in compstate[insert] */
int insgnum, insgroup; /* mod_export */
#endif
/* Information about menucompletion. */
/**/
mod_export struct menuinfo minfo;
/* Number of matches accepted with accept-and-menu-complete */
/**/
mod_export int menuacc;
/* Brace insertion stuff. */
/**/
int hasunqu, useqbr, brpcs, brscs;
/* Flags saying in what kind of string we are. */
/**/
mod_export int ispar, linwhat;
/* A parameter expansion prefix (like ${). */
/**/
char *parpre;
/* Flags for parameter expansions for new style completion. */
/**/
int parflags;
/* Match flags for all matches in this group. */
/**/
mod_export int mflags;
/* Flags saying how the parameter expression we are in is quoted. */
/**/
int parq, eparq;
/* We store the following prefixes/suffixes: *
* ipre,ripre -- the ignored prefix (quoted and unquoted) *
* isuf -- the ignored suffix */
/**/
mod_export char *ipre, *ripre, *isuf;
/* The list of matches. fmatches contains the matches we first ignore *
* because of fignore. */
/**/
mod_export LinkList matches;
/**/
LinkList fmatches;
/* This holds the list of matches-groups. lastmatches holds the last list of
* permanently allocated matches, pmatches is the same for the list
* currently built, amatches is the heap allocated stuff during completion
* (after all matches have been generated it is an alias for pmatches), and
* lmatches/lastlmatches is a pointer to the last element in the lists. */
/**/
mod_export Cmgroup lastmatches, pmatches, amatches, lmatches, lastlmatches;
/* Non-zero if we have permanently allocated matches (old and new). */
/**/
mod_export int hasoldlist, hasperm;
/* Non-zero if we have a match representing all other matches. */
/**/
int hasallmatch;
/* Non-zero if we have newly added matches. */
/**/
mod_export int newmatches;
/* Number of permanently allocated matches and groups. */
/**/
mod_export int permmnum, permgnum, lastpermmnum, lastpermgnum;
/* The total number of matches and the number of matches to be listed. */
/**/
mod_export int nmatches;
/**/
mod_export int smatches;
/* != 0 if more than one match and at least two different matches */
/**/
mod_export int diffmatches;
/* The number of messages. */
/**/
mod_export int nmessages;
/* != 0 if only explanation strings should be printed */
/**/
mod_export int onlyexpl;
/* Information about the matches for listing. */
/**/
mod_export struct cldata listdat;
/* This flag is non-zero if we are completing a pattern (with globcomplete) */
/**/
mod_export int ispattern, haspattern;
/* Non-zero if at least one match was added without/with -U. */
/**/
mod_export int hasmatched, hasunmatched;
/* The current group of matches. */
/**/
Cmgroup mgroup;
/* Match counter: all matches. */
/**/
mod_export int mnum;
/* The match counter when unambig_data() was called. */
/**/
mod_export int unambig_mnum;
/* Length of longest/shortest match. */
/**/
int maxmlen, minmlen;
/* This holds the explanation strings we have to print in this group and *
* a pointer to the current cexpl structure. */
/**/
LinkList expls;
/**/
mod_export Cexpl curexpl;
/* A stack of completion matchers to be used. */
/**/
mod_export Cmlist mstack;
/* The completion matchers used when building new stuff for the line. */
/**/
mod_export Cmlist bmatchers;
/* A list with references to all matchers we used. */
/**/
mod_export LinkList matchers;
/* A heap of free Cline structures. */
/**/
mod_export Cline freecl;
/* Ambiguous information. */
/**/
mod_export Aminfo ainfo, fainfo;
/* The memory heap to use for new style completion generation. */
/**/
mod_export Heap compheap;
/* A list of some data.
*
* Well, actually, it's the list of all compctls used so far, but since
* conceptually we don't know anything about compctls here... */
/**/
mod_export LinkList allccs;
/* This says what of the state the line is in when completion is started *
* came from a previous completion. If the FC_LINE bit is set, the *
* string was inserted. If FC_INWORD is set, the last completion moved *
* the cursor into the word although it was at the end of it when the *
* last completion was invoked. *
* This is used to detect if the string should be taken as an exact *
* match (see do_ambiguous()) and if the cursor has to be moved to the *
* end of the word before generating the completions. */
/**/
int fromcomp;
/* This holds the end-position of the last string inserted into the line. */
/**/
mod_export int lastend;
#define inststr(X) inststrlen((X),1,-1)
/*
* Main completion entry point, called from zle.
* At this point the line is already metafied.
*/
/**/
int
do_completion(UNUSED(Hookdef dummy), Compldat dat)
{
int ret = 0, lst = dat->lst, incmd = dat->incmd, osl = showinglist;
char *s = dat->s;
char *opm;
LinkNode n;
METACHECK();
pushheap();
ainfo = fainfo = NULL;
matchers = newlinklist();
zsfree(compqstack);
compqstack = zalloc(2);
/*
* It looks like we may need to do stuff with backslashes even
* if instring is QT_NONE.
*/
*compqstack = (instring == QT_NONE) ? QT_BACKSLASH : (char)instring;
compqstack[1] = '\0';
hasunqu = 0;
useline = (wouldinstab ? -1 : (lst != COMP_LIST_COMPLETE));
useexact = isset(RECEXACT);
zsfree(compexactstr);
compexactstr = ztrdup("");
uselist = (useline ?
((isset(AUTOLIST) && !isset(BASHAUTOLIST)) ?
(isset(LISTAMBIGUOUS) ? 3 : 2) : 0) : 1);
zsfree(comppatmatch);
opm = comppatmatch = ztrdup(useglob ? "*" : "");
zsfree(comppatinsert);
comppatinsert = ztrdup("menu");
forcelist = 0;
haspattern = 0;
complistmax = getiparam("LISTMAX");
zsfree(complastprompt);
complastprompt = ztrdup(isset(ALWAYSLASTPROMPT) ? "yes" : "");
dolastprompt = 1;
zsfree(complist);
complist = ztrdup(isset(LISTROWSFIRST) ?
(isset(LISTPACKED) ? "packed rows" : "rows") :
(isset(LISTPACKED) ? "packed" : ""));
startauto = isset(AUTOMENU);
movetoend = ((zlemetacs == we || isset(ALWAYSTOEND)) ? 2 : 1);
showinglist = 0;
hasmatched = hasunmatched = 0;
minmlen = 1000000;
maxmlen = -1;
compignored = 0;
nmessages = 0;
hasallmatch = 0;
/* Make sure we have the completion list and compctl. */
if (makecomplist(s, incmd, lst)) {
/* Error condition: feeeeeeeeeeeeep(). */
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
inststr(origline);
zlemetacs = origcs;
clearlist = 1;
ret = 1;
minfo.cur = NULL;
if (useline < 0) {
/* unmetafy line before calling ZLE */
unmetafy_line();
ret = selfinsert(zlenoargs);
metafy_line();
}
goto compend;
}
zsfree(lastprebr);
zsfree(lastpostbr);
lastprebr = lastpostbr = NULL;
if (comppatmatch && *comppatmatch && comppatmatch != opm)
haspattern = 1;
if (iforcemenu) {
if (nmatches)
do_ambig_menu();
ret = !nmatches;
} else if (useline < 0) {
/* unmetafy line before calling ZLE */
unmetafy_line();
ret = selfinsert(zlenoargs);
metafy_line();
} else if (!useline && uselist) {
/* All this and the guy only wants to see the list, sigh. */
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
inststr(origline);
zlemetacs = origcs;
showinglist = -2;
} else if (useline == 2 && nmatches > 1) {
do_allmatches(1);
minfo.cur = NULL;
if (forcelist)
showinglist = -2;
else
invalidatelist();
} else if (useline) {
/* We have matches. */
if (nmatches > 1 && diffmatches) {
/* There is more than one match. */
ret = do_ambiguous();
if (!showinglist && uselist && listshown && (usemenu == 2 || oldlist))
showinglist = osl;
} else if (nmatches == 1 || (nmatches > 1 && !diffmatches)) {
/* Only one match. */
Cmgroup m = amatches;
#ifdef ZSH_HEAP_DEBUG
if (memory_validate(m->heap_id)) {
HEAP_ERROR(m->heap_id);
}
#endif
while (!m->mcount)
m = m->next;
minfo.cur = NULL;
minfo.asked = 0;
do_single(m->matches[0]);
if (forcelist) {
if (uselist)
showinglist = -2;
else
clearlist = 1;
} else
invalidatelist();
} else if (nmessages && forcelist) {
if (uselist)
showinglist = -2;
else
clearlist = 1;
}
} else {
invalidatelist();
lastambig = isset(BASHAUTOLIST);
if (forcelist)
clearlist = 1;
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
inststr(origline);
zlemetacs = origcs;
}
/* Print the explanation strings if needed. */
if (!showinglist && validlist && usemenu != 2 && uselist &&
(nmatches != 1 || diffmatches) &&
useline >= 0 && useline != 2 && (!oldlist || !listshown)) {
onlyexpl = 3;
showinglist = -2;
}
compend:
for (n = firstnode(matchers); n; incnode(n))
freecmatcher((Cmatcher) getdata(n));
zlemetall = strlen(zlemetaline);
if (zlemetacs > zlemetall)
zlemetacs = zlemetall;
popheap();
return ret;
}
/* Before and after hooks called by zle. */
static int oldmenucmp;
/**/
int
before_complete(UNUSED(Hookdef dummy), int *lst)
{
oldmenucmp = menucmp;
if (showagain && validlist)
showinglist = -2;
showagain = 0;
/* If we are doing a menu-completion... */
if (minfo.cur && menucmp && *lst != COMP_LIST_EXPAND) {
do_menucmp(*lst);
return 1;
}
if (minfo.cur && menucmp && validlist && *lst == COMP_LIST_COMPLETE) {
showinglist = -2;
onlyexpl = listdat.valid = 0;
return 1;
}
/* We may have to reset the cursor to its position after the *
* string inserted by the last completion. */
/*
* Currently this hook runs before metafication.
* This is the only hook of the three defined here of
* which that is true.
*/
if ((fromcomp & FC_INWORD) && (zlecs = lastend) > zlell)
zlecs = zlell;
/* Check if we have to start a menu-completion (via automenu). */
if (startauto && lastambig &&
(!isset(BASHAUTOLIST) || lastambig == 2))
usemenu = 2;
return 0;
}
/**/
int
after_complete(UNUSED(Hookdef dummy), int *dat)
{
if (menucmp && !oldmenucmp) {
struct chdata cdat;
int ret;
cdat.matches = amatches;
#ifdef ZSH_HEAP_DEBUG
if (memory_validate(cdat.matches->heap_id)) {
HEAP_ERROR(cdat.matches->heap_id);
}
#endif
cdat.num = nmatches;
cdat.nmesg = nmessages;
cdat.cur = NULL;
if ((ret = runhookdef(MENUSTARTHOOK, (void *) &cdat))) {
dat[1] = 0;
menucmp = menuacc = 0;
minfo.cur = NULL;
if (ret >= 2) {
fixsuffix();
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
inststr(origline);
zlemetacs = origcs;
if (ret == 2) {
clearlist = 1;
invalidatelist();
}
}
}
}
return 0;
}
/* This calls the given completion widget function. */
static int parwb, parwe, paroffs;
/**/
static void
callcompfunc(char *s, char *fn)
{
Shfunc shfunc;
int lv = lastval;
char buf[20];
METACHECK();
if ((shfunc = getshfunc(fn))) {
char **p, *tmp;
int aadd = 0, usea = 1, icf = incompfunc, osc = sfcontext;
unsigned int rset, kset;
Param *ocrpms = comprpms, *ockpms = compkpms;
comprpms = (Param *) zalloc(CP_REALPARAMS * sizeof(Param));
compkpms = (Param *) zalloc(CP_KEYPARAMS * sizeof(Param));
rset = CP_ALLREALS;
kset = CP_ALLKEYS &
~(CP_PARAMETER | CP_REDIRECT | CP_QUOTE | CP_QUOTING |
CP_EXACTSTR | CP_OLDLIST | CP_OLDINS |
(useglob ? 0 : CP_PATMATCH));
zsfree(compvared);
if (varedarg) {
compvared = ztrdup(varedarg);
kset |= CP_VARED;
} else
compvared = ztrdup("");
if (!*complastprompt)
kset &= ~CP_LASTPROMPT;
zsfree(compcontext);
zsfree(compparameter);
zsfree(compredirect);
compparameter = compredirect = "";
if (ispar)
compcontext = (ispar == 2 ? "brace_parameter" : "parameter");
else if (linwhat == IN_PAR)
compcontext = "assign_parameter";
else if (linwhat == IN_MATH) {
if (insubscr) {
compcontext = "subscript";
if (varname) {
compparameter = varname;
kset |= CP_PARAMETER;
}
} else
compcontext = "math";
usea = 0;
} else if (lincmd) {
if (insubscr) {
compcontext = "subscript";
kset |= CP_PARAMETER;
} else
compcontext = "command";
} else if (linredir) {
compcontext = "redirect";
if (rdstr)
compredirect = rdstr;
kset |= CP_REDIRECT;
} else {
switch (linwhat) {
case IN_ENV:
compcontext = (linarr ? "array_value" : "value");
compparameter = varname;
kset |= CP_PARAMETER;
if (!clwpos) {
clwpos = 1;
clwnum = 2;
zsfree(clwords[1]);
clwords[1] = ztrdup(s);
zsfree(clwords[2]);
clwords[2] = NULL;
}
aadd = 1;
break;
case IN_COND:
compcontext = "condition";
break;
default:
if (cmdstr)
compcontext = "command";
else {
compcontext = "value";
kset |= CP_PARAMETER;
if (clwords[0])
compparameter = clwords[0];
aadd = 1;
}
}
}
compcontext = ztrdup(compcontext);
if (compwords)
freearray(compwords);
if (usea && (!aadd || clwords[0])) {
char **q;
q = compwords = (char **)
zalloc((clwnum + 1) * sizeof(char *));
for (p = clwords + aadd; *p; p++, q++)
untokenize(*q = ztrdup(*p));
*q = NULL;
} else
compwords = (char **) zshcalloc(sizeof(char *));
if (compredirs)
freearray(compredirs);
if (rdstrs)
compredirs = zlinklist2array(rdstrs, 1);
else
compredirs = (char **) zshcalloc(sizeof(char *));
/*
* We need to untokenize compparameter which is the
* raw internals of a parameter subscript.
*
* The double memory duplication is a bit ugly: the additional
* dupstring() is necessary because untokenize() might change
* the string length and so later zsfree() would get the wrong
* length of the string.
*/
compparameter = dupstring(compparameter);
untokenize(compparameter);
compparameter = ztrdup(compparameter);
compredirect = ztrdup(compredirect);
zsfree(compquote);
zsfree(compquoting);
if (instring > QT_BACKSLASH) {
switch (instring) {
case QT_SINGLE:
compquote = ztrdup("\'");
compquoting = ztrdup("single");
break;
case QT_DOUBLE:
compquote = ztrdup("\"");
compquoting = ztrdup("double");
break;
case QT_DOLLARS:
compquote = ztrdup("$'");
compquoting = ztrdup("dollars");
break;
}
kset |= CP_QUOTE | CP_QUOTING;
} else if (inbackt) {
compquote = ztrdup("`");
compquoting = ztrdup("backtick");
kset |= CP_QUOTE | CP_QUOTING;
} else {
compquote = ztrdup("");
compquoting = ztrdup("");
}
zsfree(compprefix);
zsfree(compsuffix);
makebangspecial(0);
if (unset(COMPLETEINWORD)) {
tmp = (linwhat == IN_MATH ? dupstring(s) : multiquote(s, 0));
untokenize(tmp);
compprefix = ztrdup(tmp);
compsuffix = ztrdup("");
} else {
char *ss, sav;
ss = s + offs;
sav = *ss;
*ss = '\0';
tmp = (linwhat == IN_MATH ? dupstring(s) : multiquote(s, 0));
untokenize(tmp);
compprefix = ztrdup(tmp);
*ss = sav;
ss = (linwhat == IN_MATH ? dupstring(ss) : multiquote(ss, 0));
untokenize(ss);
compsuffix = ztrdup(ss);
}
makebangspecial(1);
zsfree(complastprefix);
zsfree(complastsuffix);
complastprefix = ztrdup(compprefix);
complastsuffix = ztrdup(compsuffix);
zsfree(compiprefix);
zsfree(compisuffix);
if (parwb < 0) {
compiprefix = ztrdup("");
compisuffix = ztrdup("");
} else {
int l;
compiprefix = (char *) zalloc((l = wb - parwb) + 1);
memcpy(compiprefix, zlemetaline + parwb, l);
compiprefix[l] = '\0';
compisuffix = (char *) zalloc((l = parwe - we) + 1);
memcpy(compisuffix, zlemetaline + we, l);
compisuffix[l] = '\0';
wb = parwb;
we = parwe;
offs = paroffs;
}
zsfree(compqiprefix);
compqiprefix = ztrdup(qipre ? qipre : "");
zsfree(compqisuffix);
compqisuffix = ztrdup(qisuf ? qisuf : "");
origlpre = (strlen(compqiprefix) + strlen(compiprefix) +
strlen(compprefix));
origlsuf = (strlen(compqisuffix) + strlen(compisuffix) +
strlen(compsuffix));
lenchanged = 0;
compcurrent = (usea ? (clwpos + 1 - aadd) : 0);
zsfree(complist);
switch (uselist) {
case 0: complist = ""; kset &= ~CP_LIST; break;
case 1: complist = "list"; break;
case 2: complist = "autolist"; break;
case 3: complist = "ambiguous"; break;
}
if (isset(LISTPACKED))
complist = dyncat(complist, " packed");
if (isset(LISTROWSFIRST))
complist = dyncat(complist, " rows");
complist = ztrdup(complist);
zsfree(compinsert);
if (useline) {
switch (usemenu) {
case 0:
compinsert = (isset(AUTOMENU) ?
"automenu-unambiguous" :
"unambiguous");
break;
case 1: compinsert = "menu"; break;
case 2: compinsert = "automenu"; break;
}
} else {
compinsert = "";
kset &= ~CP_INSERT;
}
compinsert = (useline < 0 ? tricat("tab ", "", compinsert) :
ztrdup(compinsert));
zsfree(compexact);
if (useexact)
compexact = ztrdup("accept");
else {
compexact = ztrdup("");
kset &= ~CP_EXACT;
}
zsfree(comptoend);
if (movetoend == 1)
comptoend = ztrdup("single");
else
comptoend = ztrdup("match");
zsfree(compoldlist);
zsfree(compoldins);
if (hasoldlist && lastpermmnum) {
if (listshown)
compoldlist = "shown";
else
compoldlist = "yes";
kset |= CP_OLDLIST;
if (minfo.cur) {
sprintf(buf, "%d", (*(minfo.cur))->gnum);
compoldins = buf;
kset |= CP_OLDINS;
} else
compoldins = "";
} else
compoldlist = compoldins = "";
compoldlist = ztrdup(compoldlist);
compoldins = ztrdup(compoldins);
incompfunc = 1;
startparamscope();
makecompparams();
comp_setunset(rset, (~rset & CP_ALLREALS),
kset, (~kset & CP_ALLKEYS));
makezleparams(1);
sfcontext = SFC_CWIDGET;
NEWHEAPS(compheap) {
LinkList largs = NULL;
int oxt = isset(XTRACE);
if (*cfargs) {
char **p = cfargs;
largs = newlinklist();
addlinknode(largs, dupstring(fn));
while (*p)
addlinknode(largs, dupstring(*p++));
}
opts[XTRACE] = 0;
cfret = doshfunc(shfunc, largs, 1);
opts[XTRACE] = oxt;
} OLDHEAPS;
sfcontext = osc;
endparamscope();
lastcmd = 0;
incompfunc = icf;
startauto = 0;
if (!complist)
uselist = 0;
else if (!strncmp(complist, "list", 4))
uselist = 1;
else if (!strncmp(complist, "auto", 4))
uselist = 2;
else if (!strncmp(complist, "ambig", 5))
uselist = 3;
else
uselist = 0;
forcelist = (complist && strstr(complist, "force"));
onlyexpl = (complist ? ((strstr(complist, "expl") ? 1 : 0) |
(strstr(complist, "messages") ? 2 : 0)) : 0);
if (!compinsert)
useline = 0;
else if (strstr(compinsert, "tab"))
useline = -1;
else if (!strcmp(compinsert, "unambig") ||
!strcmp(compinsert, "unambiguous") ||
!strcmp(compinsert, "automenu-unambiguous"))
useline = 1, usemenu = 0;
else if (!strcmp(compinsert, "all"))
useline = 2, usemenu = 0;
else if (idigit(*compinsert)) {
#if 0
/* group-numbers in compstate[insert] */
char *m;
#endif
useline = 1; usemenu = 3;
insmnum = atoi(compinsert);
#if 0
/* group-numbers in compstate[insert] */
if ((m = strchr(compinsert, ':'))) {
insgroup = 1;
insgnum = atoi(m + 1);
}
#endif
insspace = (compinsert[strlen(compinsert) - 1] == ' ');
} else {
char *p;
if (strpfx("menu", compinsert))
useline = 1, usemenu = 1;
else if (strpfx("auto", compinsert))
useline = 1, usemenu = 2;
else {
useline = usemenu = 0;
/* if compstate[insert] was emptied, no unambiguous prefix
* ever gets inserted so allow the next tab to already start
* menu completion */
startauto = lastambig = isset(AUTOMENU);
}
if (useline && (p = strchr(compinsert, ':'))) {
insmnum = atoi(++p);
#if 0
/* group-numbers in compstate[insert] */
if ((p = strchr(p, ':'))) {
insgroup = 1;
insgnum = atoi(p + 1);
}
#endif
}
}
startauto = startauto || ((compinsert &&
!strcmp(compinsert, "automenu-unambiguous")) ||
(bashlistfirst && isset(AUTOMENU) &&
(!compinsert || !*compinsert)));
useexact = (compexact && !strcmp(compexact, "accept"));
if (!comptoend || !*comptoend)
movetoend = 0;
else if (!strcmp(comptoend, "single"))
movetoend = 1;
else if (!strcmp(comptoend, "always"))
movetoend = 3;
else
movetoend = 2;
oldlist = (hasoldlist && compoldlist && !strcmp(compoldlist, "keep"));
oldins = (hasoldlist && minfo.cur &&
compoldins && !strcmp(compoldins, "keep"));
zfree(comprpms, CP_REALPARAMS * sizeof(Param));
zfree(compkpms, CP_KEYPARAMS * sizeof(Param));
comprpms = ocrpms;
compkpms = ockpms;
}
lastval = lv;
}
/* Create the completion list. This is called whenever some bit of *
* completion code needs the list. *
* Along with the list is maintained the prefixes/suffixes etc. When *
* any of this becomes invalid -- e.g. if some text is changed on the *
* command line -- invalidatelist() should be called, to set *
* validlist to zero and free up the memory used. This function *
* returns non-zero on error. */
/**/
static int
makecomplist(char *s, int incmd, int lst)
{
char *p;
int owb = wb, owe = we, ooffs = offs;
/* Inside $... ? */
if (compfunc && (p = check_param(s, 0, 0))) {
s = p;
parwb = owb;
parwe = owe;
paroffs = ooffs;
} else
parwb = -1;
linwhat = inwhat;
if (compfunc) {
char *os = s;
int onm = nmatches, odm = diffmatches, osi = movefd(0);
bmatchers = NULL;
mstack = NULL;
ainfo = (Aminfo) hcalloc(sizeof(struct aminfo));
fainfo = (Aminfo) hcalloc(sizeof(struct aminfo));
freecl = NULL;
if (!validlist)
lastambig = 0;
amatches = NULL;
mnum = 0;
unambig_mnum = -1;
isuf = NULL;
insmnum = zmult;
#if 0
/* group-numbers in compstate[insert] */
insgnum = 1;
insgroup = 0;
#endif
oldlist = oldins = 0;
begcmgroup("default", 0);
menucmp = menuacc = newmatches = onlyexpl = 0;
s = dupstring(os);
callcompfunc(s, compfunc);
endcmgroup(NULL);
/* Needed for compcall. */
runhookdef(COMPCTLCLEANUPHOOK, NULL);
if (oldlist) {
nmatches = onm;
diffmatches = odm;
validlist = 1;
amatches = lastmatches;
#ifdef ZSH_HEAP_DEBUG
if (memory_validate(amatches->heap_id)) {
HEAP_ERROR(amatches->heap_id);
}
#endif
lmatches = lastlmatches;
if (pmatches) {
freematches(pmatches, 1);
pmatches = NULL;
hasperm = 0;
}
redup(osi, 0);
return 0;
}
if (lastmatches) {
freematches(lastmatches, 1);
lastmatches = NULL;
}
permmatches(1);
amatches = pmatches;
lastpermmnum = permmnum;
lastpermgnum = permgnum;
lastmatches = pmatches;
lastlmatches = lmatches;
pmatches = NULL;
hasperm = 0;
hasoldlist = 1;
if ((nmatches || nmessages) && !errflag) {
validlist = 1;
redup(osi, 0);
return 0;
}
redup(osi, 0);
return 1;
} else {
struct ccmakedat dat;
dat.str = s;
dat.incmd = incmd;
dat.lst = lst;
runhookdef(COMPCTLMAKEHOOK, (void *) &dat);
/* Needed for compcall. */
runhookdef(COMPCTLCLEANUPHOOK, NULL);
return dat.lst;
}
}
/*
* Quote 's' according to compqstack, aka $compstate[all_quotes].
*
* If 'ign' is 1, skip the innermost quoting level. Otherwise 'ign'
* must be 0.
*/
/**/
mod_export char *
multiquote(char *s, int ign)
{
if (s) {
char *os = s, *p = compqstack;
if (p && *p && (ign == 0 || p[1])) {
if (ign)
p++;
while (*p) {
s = quotestring(s, *p);
p++;
}
}
return (s == os ? dupstring(s) : s);
}
DPUTS(1, "BUG: null pointer in multiquote()");
return NULL;
}
/*
* tildequote(s, ign): Equivalent to multiquote(s, ign), except that if
* compqstack[0] == QT_BACKSLASH and s[0] == '~', then that tilde is not
* quoted.
*/
/**/
mod_export char *
tildequote(char *s, int ign)
{
if (s) {
int tilde;
if ((tilde = (*s == '~')))
*s = 'x';
s = multiquote(s, ign);
if (tilde)
*s = '~';
return s;
}
DPUTS(1, "BUG: null pointer in tildequote()");
return NULL;
}
/* Check if we have to complete a parameter name. */
/**/
mod_export char *
check_param(char *s, int set, int test)
{
char *p;
int found = 0, qstring = 0;
zsfree(parpre);
parpre = NULL;
if (!test)
ispar = parq = eparq = 0;
/*
* Try to find a `$'.
*
* TODO: passing s as a parameter while we get some mysterious
* offset "offs" into it via a global sucks badly.
*/
for (p = s + offs; ; p--) {
if (*p == String || *p == Qstring) {
/*
* String followed by Snull (unquoted) or
* QString followed by ' (quoted) indicate a nested
* $'...', not a substitution.
*
* TODO: the argument passing is obscure, no idea if
* it's safe to test for the "'" at the end.
*/
if (p < s + offs &&
!(*p == String && p[1] == Snull) &&
!(*p == Qstring && p[1] == '\'')) {
found = 1;
qstring = (*p == Qstring);
break;
}
}
if (p == s)
break;
}
if (found) {
/*
* Handle $$'s
*
* TODO: this is already bad enough, so I haven't tried
* testing for $'...' here. If we parsed this forwards
* it wouldn't be quite so bad.
*/
while (p > s && (p[-1] == String || p[-1] == Qstring))
p--;
while ((p[1] == String || p[1] == Qstring) &&
(p[2] == String || p[2] == Qstring))
p += 2;
}
if (found &&
p[1] != Inpar && p[1] != Inbrack && p[1] != Snull) {
/* This is a parameter expression, not $(...), $[...], $'...'. */
char *b = p + 1, *e = b, *ie;
int br = 1, nest = 0;
if (*b == Inbrace) {
char *tb = b;
/* If this is a ${...}, see if we are before the '}'. */
if (!skipparens(Inbrace, Outbrace, &tb))
return NULL;
/* Ignore the possible (...) flags. */
b++, br++;
if ((qstring ? skipparens('(', ')', &b) :
skipparens(Inpar, Outpar, &b)) > 0) {
/*
* We are still within the parameter flags. There's no
* point trying to do anything clever here with
* parameter names. Instead, just report that we are in
* a brace parameter but let the completion function
* decide what to do about it.
*/
ispar = 2;
return NULL;
}
for (tb = p - 1; tb > s && *tb != Outbrace && *tb != Inbrace; tb--);
if (tb > s && *tb == Inbrace && (tb[-1] == String || *tb == Qstring))
nest = 1;
}
/* Ignore the stuff before the parameter name. */
for (; *b; b++)
if (*b != '^' && *b != Hat &&
*b != '=' && *b != Equals &&
*b != '~' && *b != Tilde)
break;
if (*b == '#' || *b == Pound || *b == '+')
b++;
e = b;
if (br) {
while (*e == (test ? Dnull : '"'))
e++, parq++;
if (!test)
b = e;
}
/* Find the end of the name. */
if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
*e == '?' || *e == '*' || *e == '$' ||
*e == '-' || *e == '!' || *e == '@')
e++;
else if (idigit(*e))
while (idigit(*e))
e++;
else if ((ie = itype_end(e, IIDENT, 0)) != e) {
do {
e = ie;
if (comppatmatch && *comppatmatch &&
(*e == Star || *e == Quest))
ie = e + 1;
else
ie = itype_end(e, IIDENT, 0);
} while (ie != e);
}
/* Now make sure that the cursor is inside the name. */
if (offs <= e - s && offs >= b - s) {
char sav;
if (br) {
p = e;
while (*p == (test ? Dnull : '"'))
p++, parq--, eparq++;
}
/* It is. */
if (test)
return b;
/* If we were called from makecomplistflags(), we have to set the
* global variables. */
if (set) {
if (br >= 2) {
mflags |= CMF_PARBR;
if (nest)
mflags |= CMF_PARNEST;
}
/* Get the prefix (anything up to the character before the name). */
isuf = dupstring(e);
untokenize(isuf);
sav = *b;
*b = *e = '\0';
ripre = dyncat((ripre ? ripre : ""), s);
ipre = dyncat((ipre ? ipre : ""), s);
*b = sav;
untokenize(ipre);
}
/* Save the prefix. */
if (compfunc) {
parflags = (br >= 2 ? CMF_PARBR | (nest ? CMF_PARNEST : 0) : 0);
sav = *b;
*b = '\0';
untokenize(parpre = ztrdup(s));
*b = sav;
}
/* And adjust wb, we, and offs again. */
offs -= b - s;
wb = zlemetacs - offs;
we = wb + e - b;
ispar = (br >= 2 ? 2 : 1);
b[we-wb] = '\0';
return b;
} else if (offs > e - s && *e == ':') {
/*
* Guess whether we are in modifiers.
* If the name is followed by a : and the stuff after
* that is either colons or alphanumerics we probably are.
* This is a very rough guess.
*/
char *offsptr = s + offs;
for (; e < offsptr; e++) {
if (*e != ':' && !ialnum(*e))
break;
}
ispar = (br >= 2 ? 2 : 1);
return NULL;
}
}
return NULL;
}
/* Copy the given string and remove backslashes from the copy and return it. */
/**/
mod_export char *
rembslash(char *s)
{
char *t = s = dupstring(s);
while (*s)
if (*s == '\\') {
chuck(s);
if (*s)
s++;
} else
s++;
return t;
}
/* Remove one of every pair of single quotes, without copying. Return
* the number of removed quotes. */
/**/
mod_export int
remsquote(char *s)
{
int ret = 0, qa = (isset(RCQUOTES) ? 1 : 3);
char *t = s;
while (*s)
if (qa == 1 ?
(s[0] == '\'' && s[1] == '\'') :
(s[0] == '\'' && s[1] == '\\' && s[2] == '\'' && s[3] == '\'')) {
ret += qa;
*t++ = '\'';
s += qa + 1;
} else
*t++ = *s++;
*t = '\0';
return ret;
}
/* This should probably be moved into tokenize(). */
/**/
mod_export char *
ctokenize(char *p)
{
char *r = p;
int bslash = 0;
tokenize(p);
for (p = r; *p; p++) {
if (*p == '\\')
bslash = 1;
else {
if (*p == '$' || *p == '{' || *p == '}') {
if (bslash)
p[-1] = Bnull;
else
*p = (*p == '$' ? String :
(*p == '{' ? Inbrace : Outbrace));
}
bslash = 0;
}
}
return r;
}
/*
* This function reconstructs the full completion argument in
* heap memory by concatenating and, if untok is non-zero, untokenizing
* the ignored prefix and the active prefix and suffix.
* (It appears from the function that the ignored prefix won't
* be tokenized but I haven't checked this.)
* ipl and/or pl may be passed and if so will be set to the ignored
* prefix length and active prefix length respectively.
*/
/**/
mod_export char *
comp_str(int *ipl, int *pl, int untok)
{
char *p = dupstring(compprefix);
char *s = dupstring(compsuffix);
char *ip = dupstring(compiprefix);
char *str;
int lp, ls, lip;
if (!untok) {
ctokenize(p);
remnulargs(p);
ctokenize(s);
remnulargs(s);
}
lp = strlen(p);
ls = strlen(s);
lip = strlen(ip);
str = zhalloc(lip + lp + ls + 1);
strcpy(str, ip);
strcat(str, p);
strcat(str, s);
if (ipl)
*ipl = lip;
if (pl)
*pl = lp;
return str;
}
/**/
mod_export char *
comp_quoting_string(int stype)
{
switch (stype)
{
case QT_SINGLE:
return "'";
case QT_DOUBLE:
return "\"";
case QT_DOLLARS:
return "$'";
default: /* shuts up compiler */
return "\\";
}
}
/*
* This is the code behind compset -q, which splits the
* the current word as if it were a command line.
*
* This is one of those completion functions that merits the
* coveted title "not just ordinarily horrific".
*/
/**/
int
set_comp_sep(void)
{
/*
* s: full (reconstructed) completion argument
* lip: ignored prefix length
* lp: active prefix length
* 1: the number "one" => untokenize
*/
int lip, lp;
char *s = comp_str(&lip, &lp, 1);
LinkList foo = newlinklist();
LinkNode n;
/* Save word position */
int owe = we, owb = wb;
/*
* Values of word beginning and end and cursor after subtractions
* due to separators. I think these are indexes into zlemetaline,
* but with some subtractions; they don't see to be indexes into
* s, which is the current argument before quote stripping.
*/
int swb, swe, scs;
/* Offset into current word after subtractions. */
int soffs;
/* Current state of error suppression. */
int ne = noerrs;
/* Length of tmp string */
int tl;
/* flag that we've got the current completion word, perhaps? */
int got = 0;
/*
* i starts off as the number of the completion word we're looking at,
* which is why it's initialised, but is then recycled as a
* loop variable. j is always a loop variable.
*/
int i = 0, j;
/*
* cur: completion word currently being completed (0 offset).
* sl: length of string s, the string we're manipulating.
* css: modification of offset into current word beyond cursor
* position due to the effects of backslashing, counted during our first
* examination of compqstack for double quotes and dollar quotes.
* However, for some reason, when the current quoting scheme is
* backslashing we modify swb directly later rather than counting it at
* the point we remove the backquotes.
*/
int cur = -1, sl, css = 0;
/*
* Flag that we're doing the thing with backslashes mentioned
* for css.
*/
int remq = 0;
/*
* dq: backslash-removals for double quotes
* odq: value of dq before modification for active (Bnull'ed)
* backslashes, or something.
* sq: quote-removals for single quotes; either RCQUOTES or '\'' which
* are specially handled (but currently only if RCQUOTES is not
* set, which isn't necessarily correct if the quotes were typed by
* the user).
* osq: c.f. odq, taking account of Snull's and embedded "'"'s.
* qttype: type of quotes using standard QT_* definitions.
* lsq: when quoting is single quotes (QT_SINGLE), counts the offset
* adjustment needed in the word being examined in the lexer loop.
* sqq: the value of lsq for the current completion word.
* qa: not, unfortunately, a question and answer session with the
* original author, but the number of characters being removed
* when stripping single quotes: 1 for RCQUOTES, 3 otherwise
* (because we leave a "'" in the final string).
*/
int dq = 0, odq, sq = 0, qttype, sqq = 0, lsq = 0, qa = 0;
/* dolq: like sq and dq but for dollars quoting. */
int dolq = 0;
/* remember some global variable values (except lp is local) */
int ois = instring, oib = inbackt, noffs = lp, ona = noaliases;
/*
* tmp: used for temporary processing of strings
* p: loop pointer for tmp etc.
* ns: holds yet another version of the current completion string,
* goodness knows how it differs from s, tmp, ts, ...
* ts: untokenized ns
* ol: saves old metafied editing line
* sav: save character when NULLed; careful, there's a nested
* definition of sav just to keep you on your toes
* qp, qs: prefix and suffix strings deduced from s.
*/
char *tmp, *p, *ns, *ts, *ol, sav, *qp, *qs;
METACHECK();
s += lip;
wb += lip;
untokenize(s);
swb = swe = soffs = 0;
ns = NULL;
/* Put the string in the lexer buffer and call the lexer to *
* get the words we have to expand. */
zle_save_positions();
ol = zlemetaline;
addedx = 1;
noerrs = 1;
zcontext_save();
lexflags = LEXFLAGS_ZLE;
/*
* tl is the length of the temporary string including
* the space at the start and the x at the cursor position,
* but not the NULL byte.
*/
tl = strlen(s) + 2;
tmp = (char *) zhalloc(tl + 1);
tmp[0] = ' ';
memcpy(tmp + 1, s, noffs);
tmp[(scs = zlemetacs = 1 + noffs)] = 'x';
strcpy(tmp + 2 + noffs, s + noffs);
switch ((qttype = *compqstack)) {
case QT_BACKSLASH:
remq = 1;
tmp = rembslash(tmp);
break;
case QT_SINGLE:
if (isset(RCQUOTES))
qa = 1;
else
qa = 3;
sq = remsquote(tmp);
break;
case QT_DOUBLE:
for (j = 0, p = tmp; *p; p++, j++)
/*
* I added the handling for " here: before it just handled
* backslashes. This meant that a \" inside a " wasn't
* handled properly. I presume that was an oversight.
* I don't know if this is the right place to fix this
* particular problem because I'm utterly confused by
* the structure of the code in this function.
*/
if (*p == '\\' && (p[1] == '\\' || p[1] == '"')) {
dq++;
chuck(p);
if (*p == '"')
zlemetacs--;
else if (j > zlemetacs) {
zlemetacs++;
css++;
}
if (!*p)
break;
}
break;
case QT_DOLLARS:
j = zlemetacs;
tmp = getkeystring(tmp, &sl,
GETKEY_DOLLAR_QUOTE|GETKEY_UPDATE_OFFSET,
&zlemetacs);
/* The number of bytes we removed because of $' quoting */
dolq = tl - sl;
/* Offset into the word is modified, too... */
css += zlemetacs - j;
break;
case QT_NONE:
default: /* to silence compiler warnings */
#ifdef DEBUG
dputs("BUG: head of compqstack is NULL");
#endif
break;
}
odq = dq;
inpush(dupstrspace(tmp), 0, NULL);
zlemetaline = tmp;
/*
* tl is the length of temporary string, calculated above.
* It seems zlemetall need not include the 'x' added at the cursor.
* addedx is taken care of in function gotword() (lex.c).
*/
zlemetall = tl - addedx;
strinbeg(0);
noaliases = 1;
do {
ctxtlex();
if (tok == LEXERR) {
int j;
if (!tokstr)
break;
/*
* If there was an error, it may be because we're in
* an unterminated string. Count the active quote
* characters to see. We need an odd number.
* This works for $', too, since the ' there is an Snull.
*/
for (j = 0, p = tokstr; *p; p++) {
if (*p == Snull || *p == Dnull)
j++;
}
if (j & 1) {
tok = STRING;
if (p > tokstr && p[-1] == ' ')
p[-1] = '\0';
}
}
if (tok == ENDINPUT)
break;
if (tokstr && *tokstr) {
for (p = tokstr; dq && *p; p++) {
if (*p == Bnull) {
dq--;
if (p[1] == '\\')
dq--;
}
}
if (qttype == QT_SINGLE) {
for (p = tokstr, lsq = 0; *p; p++) {
if (sq && *p == Snull)
sq -= qa;
if (*p == '\'') {
sq -= qa;
lsq += qa;
}
}
}
else
lsq = 0;
addlinknode(foo, (p = ztrdup(tokstr)));
}
else
p = NULL;
if (!got && !lexflags) {
DPUTS(!p, "no current word in substr");
got = 1;
cur = countlinknodes(foo) - 1; /* cur is 0 offset */
swb = wb - dq - sq - dolq;
swe = we - dq - sq - dolq;
sqq = lsq;
soffs = zlemetacs - swb - css;
DPUTS2(p[soffs] != 'x', "expecting 'x' at offset %d of \"%s\"",
soffs, p);
chuck(p + soffs);
ns = dupstring(p);
}
i++;
} while (tok != ENDINPUT && tok != LEXERR);
noaliases = ona;
strinend();
inpop();
errflag &= ~ERRFLAG_ERROR;
noerrs = ne;
zcontext_restore();
wb = owb;
we = owe;
zlemetaline = ol;
zle_restore_positions();
if (cur < 0 || i < 1)
return 1;
owb = offs;
offs = soffs;
if ((p = check_param(ns, 0, 1))) {
for (p = ns; *p; p++)
if (*p == Dnull)
*p = '"';
else if (*p == Snull)
*p = '\'';
}
offs = owb;
untokenize(ts = dupstring(ns));
if (*ns == Snull || *ns == Dnull ||
((*ns == String || *ns == Qstring) && ns[1] == Snull)) {
char *tsptr = ts, *nsptr = ns, sav;
switch (*ns) {
case Snull:
instring = QT_SINGLE;
break;
case Dnull:
instring = QT_DOUBLE;
break;
default:
instring = QT_DOLLARS;
nsptr++;
tsptr++;
swb++;
break;
}
inbackt = 0;
swb++;
if (nsptr[strlen(nsptr) - 1] == *nsptr && nsptr[1])
swe--;
zsfree(autoq);
sav = *++tsptr;
*tsptr = '\0';
autoq = ztrdup(compqstack[1] ? "" : multiquote(ts, 1));
*(ts = tsptr) = sav;
} else {
instring = QT_NONE;
zsfree(autoq);
autoq = NULL;
}
/*
* In the following loop we look for parse quotes yet again.
* I don't really have the faintest idea why, but given that
* ns is immediately reassigned from ts afterwards (why? what's
* wrong with it being in ts?) and scs isn't used again, I
* presume it's in aid of getting the indexes for word beginning
* (swb) and start offset (soffs) into s correct.
*
* I think soffs is an index into s, while swb and scs are indexes
* into the full line but with some jiggery pokery for quote removal.
*/
for (p = ns, i = swb; *p; p++, i++) {
if (inull(*p)) {
if (i < scs) {
if (*p == Bnull) {
if (p[1] && remq)
swb -= 2;
if (odq) {
swb--;
if (p[1] == '\\')
swb--;
}
}
}
if (p[1] || *p != Bnull) {
if (*p == Bnull) {
if (scs == i + 1)
scs++, soffs++;
} else {
if (scs > i--)
scs--;
}
} else {
if (scs == swe)
scs--;
}
chuck(p--);
}
}
ns = ts;
if (instring && strchr(compqstack, QT_BACKSLASH)) {
int rl = strlen(ns), ql = strlen(multiquote(ns, !!compqstack[1]));
if (ql > rl)
swb -= ql - rl;
}
/*
* Using the word beginning and end as an index into the reconstructed
* string s, swb and swe, we can get the strings before and after
* the word we're considering.
*
* Because it would be too easy otherwise, there are random
* additional subtractions to be made. The 1 might be something
* to do with the space that appeared mysteriously at the start of the
* line when we passed it through the lexer. The sqq is to do with
* the single quote quoting when we passed it through the lexer.
*
* TODO: I added the "+ dq" because it seemed to improve matters for
* double quoting but the fact it's arrived at in a rather different way
* from sqq may indicate this is wrong. $'...' may need something, too.
*/
sav = s[(i = swb - 1 - sqq + dq)];
s[i] = '\0';
qp = (qttype == QT_SINGLE) ? dupstring(s) : rembslash(s);
s[i] = sav;
if (swe < swb)
swe = swb;
swe--;
sl = strlen(s);
if (swe > sl) {
swe = sl;
if ((int)strlen(ns) > swe - swb + 1)
ns[swe - swb + 1] = '\0';
}
qs = (qttype == QT_SINGLE) ? dupstring(s + swe) : rembslash(s + swe);
sl = strlen(ns);
if (soffs > sl)
soffs = sl;
if (qttype == QT_SINGLE) {
remsquote(qp);
remsquote(qs);
}
{
int set = CP_QUOTE | CP_QUOTING, unset = 0;
tl = strlen(compqstack);
p = zalloc(tl + 2);
*p = (char)(instring == QT_NONE ? QT_BACKSLASH : instring);
memcpy(p+1, compqstack, tl);
p[tl+1] = '\0';
zsfree(compqstack);
compqstack = p;
zsfree(compquote);
zsfree(compquoting);
switch (instring) {
case QT_DOUBLE:
compquote = "\"";
compquoting = "double";
break;
case QT_SINGLE:
compquote = "'";
compquoting = "single";
break;
case QT_DOLLARS:
compquote = "$'";
compquoting = "dollars";
break;
default:
compquote = compquoting = "";
unset = set;
set = 0;
break;
}
compquote = ztrdup(compquote);
compquoting = ztrdup(compquoting);
comp_setunset(0, 0, set, unset);
zsfree(compprefix);
zsfree(compsuffix);
if (unset(COMPLETEINWORD)) {
untokenize(ns);
compprefix = ztrdup(ns);
compsuffix = ztrdup("");
} else {
char *ss, sav;
ss = ns + soffs;
sav = *ss;
*ss = '\0';
untokenize(ns);
compprefix = ztrdup(ns);
*ss = sav;
untokenize(ss);
compsuffix = ztrdup(ss);
}
if ((i = strlen(compprefix)) > 1 && compprefix[i - 1] == '\\' &&
compprefix[i - 2] != '\\' && compprefix[i - 2] != Meta)
compprefix[i - 1] = '\0';
tmp = tricat(compqiprefix, compiprefix, multiquote(qp, 1));
zsfree(compqiprefix);
compqiprefix = tmp;
tmp = tricat(multiquote(qs, 1), compisuffix, compqisuffix);
zsfree(compqisuffix);
compqisuffix = tmp;
zsfree(compiprefix);
compiprefix = ztrdup("");
zsfree(compisuffix);
compisuffix = ztrdup("");
freearray(compwords);
i = countlinknodes(foo);
compwords = (char **) zalloc((i + 1) * sizeof(char *));
for (n = firstnode(foo), i = 0; n; incnode(n), i++) {
p = compwords[i] = (char *) getdata(n);
untokenize(p);
}
/* The current position shouldn't exceed the new word count */
if ((compcurrent = cur + 1) > i) {
DPUTS2(1, "compcurrent=%d > number_of_words=%d", compcurrent, i);
compcurrent = i;
}
compwords[i] = NULL;
}
instring = ois;
inbackt = oib;
return 0;
}
/* This stores the strings from the list in an array. */
/**/
mod_export void
set_list_array(char *name, LinkList l)
{
setaparam(name, zlinklist2array(l, 1));
}
/* Get the words from a variable or a (list of words). */
/**/
mod_export char **
get_user_var(char *nam)
{
if (!nam)
return NULL;
else if (*nam == '(') {
/* It's a (...) list, not a parameter name. */
char *ptr, *s, **uarr, **aptr;
int count = 0, notempty = 0, brk = 0;
LinkList arrlist = newlinklist();
ptr = dupstring(nam);
s = ptr + 1;
while (*++ptr) {
if (*ptr == '\\' && ptr[1])
chuck(ptr), notempty = 1;
else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
if (*ptr == ')')
brk++;
if (notempty) {
*ptr = '\0';
count++;
if (*s == '\n')
s++;
addlinknode(arrlist, s);
}
s = ptr + 1;
notempty = 0;
} else {
notempty = 1;
if (*ptr == Meta)
ptr++;
}
if (brk)
break;
}
if (!brk || !count)
return NULL;
*ptr = '\0';
aptr = uarr = (char **) zhalloc(sizeof(char *) * (count + 1));
while ((*aptr++ = (char *)ugetnode(arrlist)));
uarr[count] = NULL;
return uarr;
} else {
/* Otherwise it should be a parameter name. */
char **arr = NULL, *val;
queue_signals();
if ((arr = getaparam(nam)) || (arr = gethparam(nam)))
arr = (incompfunc ? arrdup(arr) : arr);
else if ((val = getsparam(nam))) {
arr = (char **) zhalloc(2*sizeof(char *));
arr[0] = (incompfunc ? dupstring(val) : val);
arr[1] = NULL;
}
unqueue_signals();
return arr;
}
}
/*
* If KEYS, then NAME is an associative array; return its keys.
* Else, NAME is a plain array; return its elements.
*/
static char **
get_data_arr(char *name, int keys)
{
struct value vbuf;
char **ret;
Value v;
queue_signals();
if (!(v = fetchvalue(&vbuf, &name, 1,
(keys ? SCANPM_WANTKEYS : SCANPM_WANTVALS) |
SCANPM_MATCHMANY)))
ret = NULL;
else
ret = getarrvalue(v);
unqueue_signals();
return ret;
}
static void
addmatch(char *str, int flags, char ***dispp, int line)
{
Cmatch cm = (Cmatch) zhalloc(sizeof(struct cmatch));
char **disp = *dispp;
memset(cm, 0, sizeof(struct cmatch));
cm->str = dupstring(str);
cm->flags = (flags |
(complist ?
((strstr(complist, "packed") ? CMF_PACKED : 0) |
(strstr(complist, "rows") ? CMF_ROWS : 0)) : 0));
if (disp) {
if (!*++disp)
disp = NULL;
if (disp)
cm->disp = dupstring(*disp);
} else if (line) {
cm->disp = dupstring("");
cm->flags |= CMF_DISPLINE;
}
mnum++;
ainfo->count++;
if (curexpl)
curexpl->count++;
addlinknode(matches, cm);
newmatches = 1;
mgroup->new = 1;
*dispp = disp;
}
/* This is used by compadd to add a couple of matches. The arguments are
* the strings given via options. The last argument is the array with
* the matches. */
/**/
int
addmatches(Cadata dat, char **argv)
{
/* ms: "match string" - string to use as completion.
* Overloaded at one place as a temporary. */
char *s, *ms, *lipre = NULL, *lisuf = NULL, *lpre = NULL, *lsuf = NULL;
char **aign = NULL, **dparr = NULL, *oaq = autoq, *oppre = dat->ppre;
char *oqp = qipre, *oqs = qisuf, qc, **disp = NULL, *ibuf = NULL;
char **arrays = NULL;
int lpl, lsl, bcp = 0, bcs = 0, bpadd = 0, bsadd = 0;
int ppl = 0, psl = 0, ilen = 0;
int llpl = 0, llsl = 0, nm = mnum, gflags = 0, ohp = haspattern;
int isexact, doadd, ois = instring, oib = inbackt;
Cline lc = NULL, pline = NULL, sline = NULL;
struct cmlist mst;
Cmlist oms = mstack;
Patprog cp = NULL, *pign = NULL;
LinkList aparl = NULL, oparl = NULL, dparl = NULL;
Brinfo bp, bpl = brbeg, obpl, bsl = brend, obsl;
Heap oldheap;
SWITCHHEAPS(oldheap, compheap) {
if (dat->dummies >= 0)
dat->aflags = ((dat->aflags | CAF_NOSORT | CAF_UNIQCON) &
~CAF_UNIQALL);
/* Select the group in which to store the matches. */
gflags = (((dat->aflags & CAF_NOSORT ) ? CGF_NOSORT : 0) |
((dat->aflags & CAF_MATSORT) ? CGF_MATSORT : 0) |
((dat->aflags & CAF_NUMSORT) ? CGF_NUMSORT : 0) |
((dat->aflags & CAF_REVSORT) ? CGF_REVSORT : 0) |
((dat->aflags & CAF_UNIQALL) ? CGF_UNIQALL : 0) |
((dat->aflags & CAF_UNIQCON) ? CGF_UNIQCON : 0));
if (dat->group) {
endcmgroup(NULL);
begcmgroup(dat->group, gflags);
} else {
endcmgroup(NULL);
begcmgroup("default", 0);
}
if (dat->mesg || dat->exp) {
curexpl = (Cexpl) zhalloc(sizeof(struct cexpl));
curexpl->always = !!dat->mesg;
curexpl->count = curexpl->fcount = 0;
curexpl->str = dupstring(dat->mesg ? dat->mesg : dat->exp);
if (dat->mesg)
addexpl(1);
} else
curexpl = NULL;
} SWITCHBACKHEAPS(oldheap);
if (!*argv && !dat->dummies && !(dat->aflags & CAF_ALL))
return 1;
for (bp = brbeg; bp; bp = bp->next)
bp->curpos = ((dat->aflags & CAF_QUOTE) ? bp->pos : bp->qpos);
for (bp = brend; bp; bp = bp->next)
bp->curpos = ((dat->aflags & CAF_QUOTE) ? bp->pos : bp->qpos);
if (dat->flags & CMF_ISPAR)
dat->flags |= parflags;
if (compquote && (qc = *compquote)) {
if (qc == '`') {
instring = QT_NONE;
/*
* Yes, inbackt has always been set to zero here. I'm
* sure there's a simple explanation.
*/
inbackt = 0;
autoq = "";
} else {
switch (qc) {
case '\'':
instring = QT_SINGLE;
break;
case '"':
instring = QT_DOUBLE;
break;
case '$':
instring = QT_DOLLARS;
break;
}
inbackt = 0;
autoq = multiquote(*compquote == '$' ? compquote+1 : compquote, 1);
}
} else {
instring = QT_NONE;
inbackt = 0;
autoq = NULL;
}
qipre = ztrdup(compqiprefix ? compqiprefix : "");
qisuf = ztrdup(compqisuffix ? compqisuffix : "");
useexact = (compexact && !strcmp(compexact, "accept"));
/* Switch back to the heap that was used when the completion widget
* was invoked. */
SWITCHHEAPS(oldheap, compheap) {
if ((doadd = (!dat->apar && !dat->opar && !dat->dpar))) {
if (dat->aflags & CAF_MATCH)
hasmatched = 1;
else
hasunmatched = 1;
}
if (dat->apar)
aparl = newlinklist();
if (dat->opar)
oparl = newlinklist();
if (dat->dpar) {
if (*(dat->dpar) == '(')
dparr = NULL;
else if ((dparr = get_user_var(dat->dpar)) && !*dparr)
dparr = NULL;
dparl = newlinklist();
}
/* Store the matcher in our stack of matchers. */
if (dat->match) {
mst.next = mstack;
mst.matcher = dat->match;
mstack = &mst;
add_bmatchers(dat->match);
addlinknode(matchers, dat->match);
dat->match->refc++;
}
if (mnum && (mstack || bmatchers))
update_bmatchers();
/* Get the suffixes to ignore. */
if (dat->ign && (aign = get_user_var(dat->ign))) {
char **ap, **sp, *tmp;
Patprog *pp, prog;
pign = (Patprog *) zhalloc((arrlen(aign) + 1) * sizeof(Patprog));
for (ap = sp = aign, pp = pign; (tmp = *ap); ap++) {
tokenize(tmp);
remnulargs(tmp);
if (((tmp[0] == Quest && tmp[1] == Star) ||
(tmp[1] == Quest && tmp[0] == Star)) &&
tmp[2] && !haswilds(tmp + 2))
untokenize(*sp++ = tmp + 2);
else if ((prog = patcompile(tmp, 0, NULL)))
*pp++ = prog;
}
*sp = NULL;
*pp = NULL;
if (!*aign)
aign = NULL;
if (!*pign)
pign = NULL;
}
/* Get the display strings. */
if (dat->disp)
if ((disp = get_user_var(dat->disp)))
disp--;
/* Get the contents of the completion variables if we have
* to perform matching. */
if (dat->aflags & CAF_MATCH) {
lipre = dupstring(compiprefix);
lisuf = dupstring(compisuffix);
lpre = dupstring(compprefix);
lsuf = dupstring(compsuffix);
llpl = strlen(lpre);
llsl = strlen(lsuf);
if (llpl + (int)strlen(compqiprefix) + (int)strlen(lipre) != origlpre
|| llsl + (int)strlen(compqisuffix) + (int)strlen(lisuf) != origlsuf)
lenchanged = 1;
/* Test if there is an existing -P prefix. */
if (dat->pre && *dat->pre) {
int prefix_length = pfxlen(dat->pre, lpre);
if (dat->pre[prefix_length] == '\0' ||
lpre[prefix_length] == '\0') {
/* $compadd_args[-P] is a prefix of ${PREFIX}, or
* vice-versa. */
llpl -= prefix_length;
lpre += prefix_length;
}
}
}
/* Now duplicate the strings we have from the command line. */
if (dat->ipre)
dat->ipre = (lipre ? dyncat(lipre, dat->ipre) :
dupstring(dat->ipre));
else if (lipre)
dat->ipre = lipre;
if (dat->isuf)
dat->isuf = (lisuf ? dyncat(lisuf, dat->isuf) :
dupstring(dat->isuf));
else if (lisuf)
dat->isuf = lisuf;
if (dat->ppre) {
dat->ppre = ((dat->flags & CMF_FILE) ?
tildequote(dat->ppre, !!(dat->aflags & CAF_QUOTE)) :
multiquote(dat->ppre, !!(dat->aflags & CAF_QUOTE)));
lpl = strlen(dat->ppre);
} else
lpl = 0;
if (dat->psuf) {
dat->psuf = multiquote(dat->psuf, !!(dat->aflags & CAF_QUOTE));
lsl = strlen(dat->psuf);
} else
lsl = 0;
if (dat->aflags & CAF_MATCH) {
int ml, gfl = 0;
char *globflag = NULL;
if (comppatmatch && *comppatmatch &&
dat->ppre && lpre[0] == '(' && lpre[1] == '#') {
char *p;
for (p = lpre + 2; *p && *p != ')'; p++);
if (*p == ')') {
char sav = p[1];
p[1] = '\0';
globflag = dupstring(lpre);
gfl = p - lpre + 1;
p[1] = sav;
lpre = p + 1;
llpl -= gfl;
}
}
if ((s = dat->ppre)) {
if ((ml = match_str(lpre, s, &bpl, 0, NULL, 0, 0, 1)) >= 0) {
if (matchsubs) {
Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, 0);
tmp->prefix = matchsubs;
if (matchlastpart)
matchlastpart->next = tmp;
else
matchparts = tmp;
}
pline = matchparts;
lpre += ml;
llpl -= ml;
bcp = ml;
bpadd = strlen(s) - ml;
} else {
if (llpl <= lpl && strpfx(lpre, s))
lpre = dupstring("");
else if (llpl > lpl && strpfx(s, lpre))
lpre += lpl;
else
*argv = NULL;
bcp = lpl;
}
}
if ((s = dat->psuf)) {
if ((ml = match_str(lsuf, s, &bsl, 0, NULL, 1, 0, 1)) >= 0) {
if (matchsubs) {
Cline tmp = get_cline(NULL, 0, NULL, 0, NULL, 0, CLF_SUF);
tmp->suffix = matchsubs;
if (matchlastpart)
matchlastpart->next = tmp;
else
matchparts = tmp;
}
sline = revert_cline(matchparts);
lsuf[llsl - ml] = '\0';
llsl -= ml;
bcs = ml;
bsadd = strlen(s) - ml;
} else {
if (llsl <= lsl && strsfx(lsuf, s))
lsuf = dupstring("");
else if (llsl > lsl && strsfx(s, lsuf))
lsuf[llsl - lsl] = '\0';
else
*argv = NULL;
bcs = lsl;
}
}
if (comppatmatch && *comppatmatch) {
int is = (*comppatmatch == '*');
char *tmp = (char *) zhalloc(2 + llpl + llsl + gfl);
if (gfl) {
strcpy(tmp, globflag);
strcat(tmp, lpre);
} else
strcpy(tmp, lpre);
tmp[llpl + gfl] = 'x';
strcpy(tmp + llpl + gfl + is, lsuf);
tokenize(tmp);
if (haswilds(tmp)) {
if (is)
tmp[llpl + gfl] = Star;
remnulargs(tmp);
if ((cp = patcompile(tmp, 0, NULL)))
haspattern = 1;
}
}
} else {
/*
* (This is called a "comment". Given you've been
* spending your time reading the completion code, you
* may have forgotten what one is. It's used to deconfuse
* the poor so-and-so who's landed up having to maintain
* the code.)
*
* So what's going on here then? I'm glad you asked. To test
* whether we should start menu completion, we test whether
* compstate[insert] has been set to "menu", but only if we found
* patterns in the code. It's not clear to me from the
* documentation why the second condition would apply, but sure
* enough if I remove it the test suite falls over. (Testing
* comppatmatch at the later point doesn't work because compstate
* is likely to have been reset by the point we actually insert
* the completions, after all functions have exited; this is at
* least part of the problem.) In the present case, we are not
* doing matching on the code because all the clever stuff has
* been done over our heads and we've simply between told to
* insert it. However, we still need to take account of ambiguous
* completions properly. To do this, we rely on the caller to
* pass down the same prefix/suffix with the patterns that we
* would get if we were doing matching, and test those for
* patterns. This gets us out of the hole apparently without
* breaking anything. The particular case where this is needed is
* approximate file completion: this does its own matching but
* _approximate still sets the prefix to include the pattern.
*/
if (comppatmatch && *comppatmatch) {
int pflen = strlen(compprefix);
char *tmp = zhalloc(pflen + strlen(compsuffix) + 1);
strcpy(tmp, compprefix);
strcpy(tmp + pflen, compsuffix);
tokenize(tmp);
remnulargs(tmp);
if (haswilds(tmp))
haspattern = 1;
}
}
if (*argv) {
if (dat->pre)
dat->pre = dupstring(dat->pre);
if (dat->suf)
dat->suf = dupstring(dat->suf);
if (!dat->prpre && (dat->prpre = dupstring(oppre))) {
singsub(&(dat->prpre));
untokenize(dat->prpre);
} else
dat->prpre = dupstring(dat->prpre);
/* Select the set of matches. */
if (dat->remf) {
dat->remf = dupstring(dat->remf);
dat->rems = NULL;
} else if (dat->rems)
dat->rems = dupstring(dat->rems);
if (lpre)
lpre = ((!(dat->aflags & CAF_QUOTE) &&
(!dat->ppre && (dat->flags & CMF_FILE))) ?
tildequote(lpre, 1) : multiquote(lpre, 1));
if (lsuf)
lsuf = multiquote(lsuf, 1);
}
/* Walk through the matches given. */
obpl = bpl;
obsl = bsl;
if (dat->aflags & CAF_ARRAYS) {
Heap oldheap2;
SWITCHHEAPS(oldheap2, oldheap) {
arrays = argv;
argv = NULL;
while (*arrays &&
(!(argv = get_data_arr(*arrays,
(dat->aflags & CAF_KEYS))) ||
!*argv))
arrays++;
arrays++;
if (!argv) {
ms = NULL;
argv = &ms;
}
} SWITCHBACKHEAPS(oldheap2);
}
if (dat->ppre)
ppl = strlen(dat->ppre);
if (dat->psuf)
psl = strlen(dat->psuf);
for (; (s = *argv); argv++) {
int sl;
bpl = obpl;
bsl = obsl;
if (disp) {
if (!*++disp)
disp = NULL;
}
sl = strlen(s);
if (aign || pign) {
int il = ppl + sl + psl, addit = 1;
if (il + 1> ilen)
ibuf = (char *) zhalloc((ilen = il) + 2);
if (ppl)
memcpy(ibuf, dat->ppre, ppl);
strcpy(ibuf + ppl, s);
if (psl)
strcpy(ibuf + ppl + sl, dat->psuf);
if (aign) {
/* Do the suffix-test. If the match has one of the
* suffixes from aign, we put it in the alternate set. */
char **pt = aign;
int filell;
for (; addit && *pt; pt++)
addit = !((filell = strlen(*pt)) < il &&
!strcmp(*pt, ibuf + il - filell));
}
if (addit && pign) {
Patprog *pt = pign;
for (; addit && *pt; pt++)
addit = !pattry(*pt, ibuf);
}
if (!addit) {
compignored++;
if (dparr && !*++dparr)
dparr = NULL;
goto next_array;
}
}
if (!(dat->aflags & CAF_MATCH)) {
if (dat->aflags & CAF_QUOTE)
ms = dupstring(s);
else
sl = strlen(ms = multiquote(s, 0));
lc = bld_parts(ms, sl, -1, NULL, NULL);
isexact = 0;
} else if (!(ms = comp_match(lpre, lsuf, s, cp, &lc,
(!(dat->aflags & CAF_QUOTE) ?
(dat->ppre ||
!(dat->flags & CMF_FILE) ? 1 : 2) : 0),
&bpl, bcp, &bsl, bcs,
&isexact))) {
if (dparr && !*++dparr)
dparr = NULL;
goto next_array;
}
if (doadd) {
Cmatch cm;
Brinfo bp;
for (bp = obpl; bp; bp = bp->next)
bp->curpos += bpadd;
for (bp = obsl; bp; bp = bp->next)
bp->curpos += bsadd;
if ((cm = add_match_data(0, ms, s, lc, dat->ipre, NULL,
dat->isuf, dat->pre, dat->prpre,
dat->ppre, pline,
dat->psuf, sline,
dat->suf, dat->flags, isexact))) {
cm->rems = dat->rems;
cm->remf = dat->remf;
if (disp)
cm->disp = dupstring(*disp);
}
} else {
if (dat->apar)
addlinknode(aparl, ms);
if (dat->opar)
addlinknode(oparl, s);
if (dat->dpar && dparr) {
addlinknode(dparl, *dparr);
if (!*++dparr)
dparr = NULL;
}
free_cline(lc);
}
next_array:
if ((dat->aflags & CAF_ARRAYS) && !argv[1]) {
Heap oldheap2;
SWITCHHEAPS(oldheap2, oldheap) {
argv = NULL;
while (*arrays &&
(!(argv = get_data_arr(*arrays,
(dat->aflags & CAF_KEYS))) ||
!*argv))
arrays++;
arrays++;
if (!argv) {
ms = NULL;
argv = &ms;
}
argv--;
} SWITCHBACKHEAPS(oldheap2);
}
}
if (dat->apar)
set_list_array(dat->apar, aparl);
if (dat->opar)
set_list_array(dat->opar, oparl);
if (dat->dpar)
set_list_array(dat->dpar, dparl);
if (dat->exp)
addexpl(0);
if (!hasallmatch && (dat->aflags & CAF_ALL)) {
addmatch("<all>", dat->flags | CMF_ALL, &disp, 1);
hasallmatch = 1;
}
while (dat->dummies-- > 0)
addmatch("", dat->flags | CMF_DUMMY, &disp, 0);
} SWITCHBACKHEAPS(oldheap);
/* We switched back to the current heap, now restore the stack of
* matchers. */
mstack = oms;
instring = ois;
inbackt = oib;
autoq = oaq;
zsfree(qipre);
zsfree(qisuf);
qipre = oqp;
qisuf = oqs;
if (mnum == nm)
haspattern = ohp;
return (mnum == nm);
}
/* This adds all the data we have for a match. */
/**/
mod_export Cmatch
add_match_data(int alt, char *str, char *orig, Cline line,
char *ipre, char *ripre, char *isuf,
char *pre, char *prpre,
char *ppre, Cline pline,
char *psuf, Cline sline,
char *suf, int flags, int exact)
{
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
char curchar, *t, *f, *fs, *fe, *new_str = NULL;
size_t cnt;
wchar_t wc;
#endif
Cmatch cm;
Aminfo ai = (alt ? fainfo : ainfo);
int palen, salen, qipl, ipl, pl, ppl, qisl, isl, psl;
int stl, lpl, lsl, ml;
palen = salen = qipl = ipl = pl = ppl = qisl = isl = psl = 0;
DPUTS(!line, "BUG: add_match_data() without cline");
cline_matched(line);
if (pline)
cline_matched(pline);
if (sline)
cline_matched(sline);
/* If there is a path suffix, we build a cline list for it and
* append it to the list for the match itself. */
if (!sline && psuf)
salen = (psl = strlen(psuf));
if (isuf)
salen += (isl = strlen(isuf));
if (qisuf)
salen += (qisl = strlen(qisuf));
if (salen) {
Cline pp, p, s, sl = NULL;
for (pp = NULL, p = line; p->next; pp = p, p = p->next);
if (psl) {
s = bld_parts(psuf, psl, psl, &sl, NULL);
if (sline) {
Cline sp;
sline = cp_cline(sline, 1);
for (sp = sline; sp->next; sp = sp->next);
sp->next = s;
s = sline;
sline = NULL;
}
if (!(p->flags & (CLF_SUF | CLF_MID)) &&
!p->llen && !p->wlen && !p->olen) {
if (p->prefix) {
Cline q;
for (q = p->prefix; q->next; q = q->next);
q->next = s->prefix;
s->prefix = p->prefix;
p->prefix = NULL;
}
s->flags |= (p->flags & CLF_MATCHED) | CLF_MID;
free_cline(p);
if (pp)
pp->next = s;
else
line = s;
} else
p->next = s;
}
if (isl) {
Cline tsl;
s = bld_parts(isuf, isl, isl, &tsl, NULL);
if (sl)
sl->next = s;
else if (sline) {
Cline sp;
sline = cp_cline(sline, 1);
for (sp = sline; sp->next; sp = sp->next);
sp->next = s;
p->next = sline;
sline = NULL;
} else
p->next = s;
sl = tsl;
}
if (qisl) {
Cline qsl = bld_parts(dupstring(qisuf), qisl, qisl, NULL, NULL);
qsl->flags |= CLF_SUF;
qsl->suffix = qsl->prefix;
qsl->prefix = NULL;
if (sl)
sl->next = qsl;
else if (sline) {
Cline sp;
sline = cp_cline(sline, 1);
for (sp = sline; sp->next; sp = sp->next);
sp->next = qsl;
p->next = sline;
} else
p->next = qsl;
}
} else if (sline) {
Cline p;
for (p = line; p->next; p = p->next);
p->next = cp_cline(sline, 1);
}
/* The prefix is handled differently because the completion code
* is much more eager to insert the -P prefix than it is to insert
* the -S suffix. */
if (qipre)
palen = (qipl = strlen(qipre));
if (ipre)
palen += (ipl = strlen(ipre));
if (pre)
palen += (pl = strlen(pre));
if (!pline && ppre)
palen += (ppl = strlen(ppre));
if (pl) {
if (ppl || pline) {
Cline lp, p;
if (pline)
for (p = cp_cline(pline, 1), lp = p; lp->next; lp = lp->next);
else
p = bld_parts(ppre, ppl, ppl, &lp, NULL);
if (lp->prefix && !(line->flags & (CLF_SUF | CLF_MID)) &&
!lp->llen && !lp->wlen && !lp->olen) {
Cline lpp;
for (lpp = lp->prefix; lpp->next; lpp = lpp->next);
lpp->next = line->prefix;
line->prefix = lp->prefix;
lp->prefix = NULL;
free_cline(lp);
if (p != lp) {
Cline q;
for (q = p; q->next != lp; q = q->next);
q->next = line;
line = p;
}
} else {
lp->next = line;
line = p;
}
}
if (pl) {
Cline lp, p = bld_parts(pre, pl, pl, &lp, NULL);
lp->next = line;
line = p;
}
if (ipl) {
Cline lp, p = bld_parts(ipre, ipl, ipl, &lp, NULL);
lp->next = line;
line = p;
}
if (qipl) {
Cline lp, p = bld_parts(dupstring(qipre), qipl, qipl, &lp, NULL);
lp->next = line;
line = p;
}
} else if (palen || pline) {
Cline p, lp;
if (palen) {
char *apre = (char *) zhalloc(palen);
if (qipl)
memcpy(apre, qipre, qipl);
if (ipl)
memcpy(apre + qipl, ipre, ipl);
if (pl)
memcpy(apre + qipl + ipl, pre, pl);
if (ppl)
memcpy(apre + qipl + ipl + pl, ppre, ppl);
p = bld_parts(apre, palen, palen, &lp, NULL);
if (pline)
for (lp->next = cp_cline(pline, 1); lp->next; lp = lp->next);
} else
for (p = lp = cp_cline(pline, 1); lp->next; lp = lp->next);
if (lp->prefix && !(line->flags & CLF_SUF) &&
!lp->llen && !lp->wlen && !lp->olen) {
Cline lpp;
for (lpp = lp->prefix; lpp->next; lpp = lpp->next);
lpp->next = line->prefix;
line->prefix = lp->prefix;
lp->prefix = NULL;
free_cline(lp);
if (p != lp) {
Cline q;
for (q = p; q->next != lp; q = q->next);
q->next = line;
line = p;
}
} else {
lp->next = line;
line = p;
}
}
stl = strlen(str);
#ifdef MULTIBYTE_SUPPORT
/* If "str" contains a character that won't convert into a wide
* character, change it into a $'\123' sequence. */
memset(&mbs, '\0', sizeof mbs);
for (t = f = fs = str, fe = f + stl; fs < fe; ) {
if ((curchar = *f++) == Meta)
curchar = *f++ ^ 32;
cnt = mbrtowc(&wc, &curchar, 1, &mbs);
switch (cnt) {
case MB_INCOMPLETE:
if (f < fe)
continue;
/* FALL THROUGH */
case MB_INVALID:
/* Get mbs out of its undefined state. */
memset(&mbs, '\0', sizeof mbs);
if (!new_str) {
/* Be very pessimistic about how much space we'll need. */
new_str = zhalloc((t - str) + (fe - fs)*7 + 1);
memcpy(new_str, str, t - str);
t = new_str + (t - str);
}
/* Output one byte from the start of this invalid multibyte
* sequence unless we got MB_INCOMPLETE at the end of the
* string, in which case we output all the incomplete bytes. */
do {
if ((curchar = *fs++) == Meta)
curchar = *fs++ ^ 32;
*t++ = '$';
*t++ = '\'';
*t++ = '\\';
*t++ = '0' + ((STOUC(curchar) >> 6) & 7);
*t++ = '0' + ((STOUC(curchar) >> 3) & 7);
*t++ = '0' + (STOUC(curchar) & 7);
*t++ = '\'';
} while (cnt == MB_INCOMPLETE && fs < fe);
/* Scanning restarts from the spot after the char we skipped. */
f = fs;
break;
default:
while (fs < f)
*t++ = *fs++;
break;
}
}
if (new_str) {
*t = '\0';
str = new_str;
stl = t - str;
}
#endif
/* Allocate and fill the match structure. */
cm = (Cmatch) zhalloc(sizeof(struct cmatch));
cm->str = str;
cm->orig = dupstring(orig);
cm->ppre = (ppre && *ppre ? ppre : NULL);
cm->psuf = (psuf && *psuf ? psuf : NULL);
cm->prpre = ((flags & CMF_FILE) && prpre && *prpre ? prpre : NULL);
if (qipre && *qipre)
cm->ipre = (ipre && *ipre ? dyncat(qipre, ipre) : dupstring(qipre));
else
cm->ipre = (ipre && *ipre ? ipre : NULL);
cm->ripre = (ripre && *ripre ? ripre : NULL);
if (qisuf && *qisuf)
cm->isuf = (isuf && *isuf ? dyncat(isuf, qisuf) : dupstring(qisuf));
else
cm->isuf = (isuf && *isuf ? isuf : NULL);
cm->pre = pre;
cm->suf = suf;
cm->flags = (flags |
(complist ?
((strstr(complist, "packed") ? CMF_PACKED : 0) |
(strstr(complist, "rows") ? CMF_ROWS : 0)) : 0));
cm->mode = cm->fmode = 0;
cm->modec = cm->fmodec = '\0';
if ((flags & CMF_FILE) && orig[0] && orig[strlen(orig) - 1] != '/') {
struct stat buf;
char *pb;
pb = (char *) zhalloc((cm->prpre ? strlen(cm->prpre) : 0) +
3 + strlen(orig));
sprintf(pb, "%s%s", (cm->prpre ? cm->prpre : "./"), orig);
if (!ztat(pb, &buf, 1)) {
cm->mode = buf.st_mode;
if ((cm->modec = file_type(buf.st_mode)) == ' ')
cm->modec = '\0';
}
if (!ztat(pb, &buf, 0)) {
cm->fmode = buf.st_mode;
if ((cm->fmodec = file_type(buf.st_mode)) == ' ')
cm->fmodec = '\0';
}
}
if ((*compqstack == QT_BACKSLASH && compqstack[1]) ||
(autoq && *compqstack && compqstack[1] == QT_BACKSLASH))
cm->flags |= CMF_NOSPACE;
if (nbrbeg) {
int *p;
Brinfo bp;
cm->brpl = (int *) zhalloc(nbrbeg * sizeof(int));
for (p = cm->brpl, bp = brbeg; bp; p++, bp = bp->next)
*p = bp->curpos;
} else
cm->brpl = NULL;
if (nbrend) {
int *p;
Brinfo bp;
cm->brsl = (int *) zhalloc(nbrend * sizeof(int));
for (p = cm->brsl, bp = brend; bp; p++, bp = bp->next)
*p = bp->curpos;
} else
cm->brsl = NULL;
cm->qipl = qipl;
cm->qisl = qisl;
cm->autoq = dupstring(autoq ? autoq : (inbackt ? "`" : NULL));
cm->rems = cm->remf = cm->disp = NULL;
if ((lastprebr || lastpostbr) && !hasbrpsfx(cm, lastprebr, lastpostbr))
return NULL;
/* Then build the unambiguous cline list. */
ai->line = join_clines(ai->line, line);
mnum++;
ai->count++;
addlinknode((alt ? fmatches : matches), cm);
newmatches = 1;
mgroup->new = 1;
if (alt)
compignored++;
if (!complastprompt || !*complastprompt)
dolastprompt = 0;
/* One more match for this explanation. */
if (curexpl) {
if (alt)
curexpl->fcount++;
else
curexpl->count++;
}
if (!ai->firstm)
ai->firstm = cm;
lpl = (cm->ppre ? strlen(cm->ppre) : 0);
lsl = (cm->psuf ? strlen(cm->psuf) : 0);
ml = stl + lpl + lsl;
if (ml < minmlen)
minmlen = ml;
if (ml > maxmlen)
maxmlen = ml;
/* Do we have an exact match? More than one? */
if (exact) {
if (!ai->exact) {
ai->exact = useexact;
if (incompfunc && (!compexactstr || !*compexactstr)) {
/* If a completion widget is active, we make the exact
* string available in `compstate'. */
char *e;
zsfree(compexactstr);
compexactstr = e = (char *) zalloc(ml + 1);
if (cm->ppre) {
strcpy(e, cm->ppre);
e += lpl;
}
strcpy(e, str);
e += stl;
if (cm->psuf)
strcpy(e, cm->psuf);
comp_setunset(0, 0, CP_EXACTSTR, 0);
}
ai->exactm = cm;
} else if (useexact && (!ai->exactm || !matcheq(cm, ai->exactm))) {
ai->exact = 2;
ai->exactm = NULL;
if (incompfunc)
comp_setunset(0, 0, 0, CP_EXACTSTR);
}
}
return cm;
}
/* This begins a new group of matches. */
/**/
mod_export void
begcmgroup(char *n, int flags)
{
if (n) {
/* If a group named <n> already exists, reuse it. */
Cmgroup p;
for (p = amatches; p; p = p->next) {
#ifdef ZSH_HEAP_DEBUG
if (memory_validate(p->heap_id)) {
HEAP_ERROR(p->heap_id);
}
#endif
if (p->name && flags ==
(p->flags & (CGF_NOSORT|CGF_UNIQALL|CGF_UNIQCON|
CGF_MATSORT|CGF_NUMSORT|CGF_REVSORT)) &&
!strcmp(n, p->name)) {
mgroup = p;
expls = p->lexpls;
matches = p->lmatches;
fmatches = p->lfmatches;
allccs = p->lallccs;
return;
}
}
}
/* Create a new group. */
mgroup = (Cmgroup) zhalloc(sizeof(struct cmgroup));
#ifdef ZSH_HEAP_DEBUG
mgroup->heap_id = last_heap_id;
#endif
mgroup->name = dupstring(n);
mgroup->lcount = mgroup->llcount = mgroup->mcount = mgroup->ecount =
mgroup->ccount = 0;
mgroup->flags = flags;
mgroup->matches = NULL;
mgroup->ylist = NULL;
mgroup->expls = NULL;
mgroup->perm = NULL;
mgroup->new = mgroup->num = mgroup->nbrbeg = mgroup->nbrend = 0;
mgroup->lexpls = expls = newlinklist();
mgroup->lmatches = matches = newlinklist();
mgroup->lfmatches = fmatches = newlinklist();
mgroup->lallccs = allccs = ((flags & CGF_NOSORT) ? NULL : newlinklist());
if ((mgroup->next = amatches))
amatches->prev = mgroup;
mgroup->prev = NULL;
amatches = mgroup;
}
/* End the current group for now. */
/**/
mod_export void
endcmgroup(char **ylist)
{
mgroup->ylist = ylist;
}
/* Add an explanation string to the current group, joining duplicates. */
/**/
mod_export void
addexpl(int always)
{
LinkNode n;
Cexpl e;
for (n = firstnode(expls); n; incnode(n)) {
e = (Cexpl) getdata(n);
if (!strcmp(curexpl->str, e->str)) {
e->count += curexpl->count;
e->fcount += curexpl->fcount;
if (always) {
e->always = 1;
nmessages++;
newmatches = 1;
mgroup->new = 1;
}
return;
}
}
addlinknode(expls, curexpl);
newmatches = 1;
if (always) {
mgroup->new = 1;
nmessages++;
}
}
/* The comparison function for matches (used for sorting). */
static int matchorder;
/**/
static int
matchcmp(Cmatch *a, Cmatch *b)
{
const char *as, *bs;
int cmp = !!(*b)->disp - !!(*a)->disp;
int sortdir = (matchorder & CGF_REVSORT) ? -1 : 1;
/* if match sorting selected or we have no display strings */
if ((matchorder & CGF_MATSORT) || (!cmp && !(*a)->disp)) {
as = (*a)->str;
bs = (*b)->str;
} else {
if (cmp) /* matches with display strings come first */
return cmp;
cmp = ((*b)->flags & CMF_DISPLINE) - ((*a)->flags & CMF_DISPLINE);
if (cmp) /* sort one-per-line display strings first */
return cmp;
as = (*a)->disp;
bs = (*b)->disp;
}
return sortdir * zstrcmp(as, bs, SORTIT_IGNORING_BACKSLASHES|
((isset(NUMERICGLOBSORT) ||
matchorder & CGF_NUMSORT) ? SORTIT_NUMERICALLY : 0));
}
/* This tests whether two matches are equal (would produce the same
* strings on the command line). */
#define matchstreq(a, b) ((!(a) && !(b)) || ((a) && (b) && !strcmp((a), (b))))
/**/
static int
matcheq(Cmatch a, Cmatch b)
{
return matchstreq(a->ipre, b->ipre) &&
matchstreq(a->pre, b->pre) &&
matchstreq(a->ppre, b->ppre) &&
matchstreq(a->psuf, b->psuf) &&
matchstreq(a->suf, b->suf) &&
matchstreq(a->str, b->str);
}
/* Make an array from a linked list. The second argument says whether *
* the array should be sorted. The third argument is used to return *
* the number of elements in the resulting array. The fourth argument *
* is used to return the number of NOLIST elements. */
/**/
static Cmatch *
makearray(LinkList l, int type, int flags, int *np, int *nlp, int *llp)
{
Cmatch *ap, *bp, *cp, *rp;
LinkNode nod;
int n, nl = 0, ll = 0;
/* Build an array for the matches. */
rp = ap = (Cmatch *) hcalloc(((n = countlinknodes(l)) + 1) *
sizeof(Cmatch));
/* And copy them into it. */
for (nod = firstnode(l); nod; incnode(nod))
*ap++ = (Cmatch) getdata(nod);
*ap = NULL;
if (!type) {
if (flags) {
char **ap, **bp, **cp;
/* Now sort the array (it contains strings). */
strmetasort((char **)rp, SORTIT_IGNORING_BACKSLASHES |
(isset(NUMERICGLOBSORT) ? SORTIT_NUMERICALLY : 0),
NULL);
/* And delete the ones that occur more than once. */
for (ap = cp = (char **) rp; *ap; ap++) {
*cp++ = *ap;
for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++, n--);
ap = bp;
}
*cp = NULL;
}
} else {
if (!(flags & CGF_NOSORT)) {
/* Now sort the array (it contains matches). */
matchorder = flags;
qsort((void *) rp, n, sizeof(Cmatch),
(int (*) _((const void *, const void *)))matchcmp);
if (!(flags & CGF_UNIQCON)) {
int dup;
/* And delete the ones that occur more than once. */
for (ap = cp = rp; *ap; ap++) {
*cp++ = *ap;
for (bp = ap; bp[1] && matcheq(*ap, bp[1]); bp++, n--);
ap = bp;
/* Mark those, that would show the same string in the list. */
for (dup = 0; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
!strcmp((*ap)->str, (bp[1])->str); bp++) {
(bp[1])->flags |= CMF_MULT;
dup = 1;
}
if (dup)
(*ap)->flags |= CMF_FMULT;
}
*cp = NULL;
}
for (ap = rp; *ap; ap++) {
if ((*ap)->disp && ((*ap)->flags & CMF_DISPLINE))
ll++;
if ((*ap)->flags & (CMF_NOLIST | CMF_MULT))
nl++;
}
} else {
if (!(flags & CGF_UNIQALL) && !(flags & CGF_UNIQCON)) {
int dup;
for (ap = rp; *ap; ap++) {
for (bp = cp = ap + 1; *bp; bp++) {
if (!matcheq(*ap, *bp))
*cp++ = *bp;
else
n--;
}
*cp = NULL;
if (!(*ap)->disp) {
for (dup = 0, bp = ap + 1; *bp; bp++)
if (!(*bp)->disp &&
!((*bp)->flags & CMF_MULT) &&
!strcmp((*ap)->str, (*bp)->str)) {
(*bp)->flags |= CMF_MULT;
dup = 1;
}
if (dup)
(*ap)->flags |= CMF_FMULT;
}
}
} else if (!(flags & CGF_UNIQCON)) {
int dup;
for (ap = cp = rp; *ap; ap++) {
*cp++ = *ap;
for (bp = ap; bp[1] && matcheq(*ap, bp[1]); bp++, n--);
ap = bp;
for (dup = 0; bp[1] && !(*ap)->disp && !(bp[1])->disp &&
!strcmp((*ap)->str, (bp[1])->str); bp++) {
(bp[1])->flags |= CMF_MULT;
dup = 1;
}
if (dup)
(*ap)->flags |= CMF_FMULT;
}
*cp = NULL;
}
for (ap = rp; *ap; ap++) {
if ((*ap)->disp && ((*ap)->flags & CMF_DISPLINE))
ll++;
if ((*ap)->flags & (CMF_NOLIST | CMF_MULT))
nl++;
}
}
}
if (np)
*np = n;
if (nlp)
*nlp = nl;
if (llp)
*llp = ll;
return rp;
}
/* This duplicates one match. */
/**/
static Cmatch
dupmatch(Cmatch m, int nbeg, int nend)
{
Cmatch r;
r = (Cmatch) zshcalloc(sizeof(struct cmatch));
r->str = ztrdup(m->str);
r->orig = ztrdup(m->orig);
r->ipre = ztrdup(m->ipre);
r->ripre = ztrdup(m->ripre);
r->isuf = ztrdup(m->isuf);
r->ppre = ztrdup(m->ppre);
r->psuf = ztrdup(m->psuf);
r->prpre = ztrdup(m->prpre);
r->pre = ztrdup(m->pre);
r->suf = ztrdup(m->suf);
r->flags = m->flags;
if (m->brpl) {
int *p, *q, i;
r->brpl = (int *) zalloc(nbeg * sizeof(int));
for (p = r->brpl, q = m->brpl, i = nbeg; i--; p++, q++)
*p = *q;
} else
r->brpl = NULL;
if (m->brsl) {
int *p, *q, i;
r->brsl = (int *) zalloc(nend * sizeof(int));
for (p = r->brsl, q = m->brsl, i = nend; i--; p++, q++)
*p = *q;
} else
r->brsl = NULL;
r->rems = ztrdup(m->rems);
r->remf = ztrdup(m->remf);
r->autoq = ztrdup(m->autoq);
r->qipl = m->qipl;
r->qisl = m->qisl;
r->disp = ztrdup(m->disp);
r->mode = m->mode;
r->modec = m->modec;
r->fmode = m->fmode;
r->fmodec = m->fmodec;
return r;
}
/* This duplicates all groups of matches. */
/**/
mod_export int
permmatches(int last)
{
Cmgroup g = amatches, n;
Cmatch *p, *q;
Cexpl *ep, *eq, e, o;
LinkList mlist;
static int fi = 0;
int nn, nl, ll, gn = 1, mn = 1, rn, ofi = fi;
if (pmatches && !newmatches) {
if (last && fi)
ainfo = fainfo;
return fi;
}
newmatches = fi = 0;
pmatches = lmatches = NULL;
nmatches = smatches = diffmatches = 0;
if (!ainfo->count) {
if (last)
ainfo = fainfo;
fi = 1;
}
while (g) {
#ifdef ZSH_HEAP_DEBUG
if (memory_validate(g->heap_id)) {
HEAP_ERROR(g->heap_id);
}
#endif
if (fi != ofi || !g->perm || g->new) {
if (fi)
/* We have no matches, try ignoring fignore. */
mlist = g->lfmatches;
else
mlist = g->lmatches;
g->matches = makearray(mlist, 1, g->flags, &nn, &nl, &ll);
g->mcount = nn;
if ((g->lcount = nn - nl) < 0)
g->lcount = 0;
g->llcount = ll;
if (g->ylist) {
g->lcount = arrlen(g->ylist);
smatches = 2;
}
g->expls = (Cexpl *) makearray(g->lexpls, 0, 0, &(g->ecount),
NULL, NULL);
g->ccount = 0;
nmatches += g->mcount;
smatches += g->lcount;
if (g->mcount > 1)
diffmatches = 1;
n = (Cmgroup) zshcalloc(sizeof(struct cmgroup));
#ifdef ZSH_HEAP_DEBUG
n->heap_id = HEAPID_PERMANENT;
#endif
if (g->perm) {
g->perm->next = NULL;
freematches(g->perm, 0);
}
g->perm = n;
if (!lmatches)
lmatches = n;
if (pmatches)
pmatches->prev = n;
n->next = pmatches;
pmatches = n;
n->prev = NULL;
n->num = gn++;
n->flags = g->flags;
n->mcount = g->mcount;
n->matches = p = (Cmatch *) zshcalloc((n->mcount + 1) * sizeof(Cmatch));
n->name = ztrdup(g->name);
for (q = g->matches; *q; q++, p++)
*p = dupmatch(*q, nbrbeg, nbrend);
*p = NULL;
n->lcount = g->lcount;
n->llcount = g->llcount;
if (g->ylist)
n->ylist = zarrdup(g->ylist);
else
n->ylist = NULL;
if ((n->ecount = g->ecount)) {
n->expls = ep = (Cexpl *) zshcalloc((n->ecount + 1) * sizeof(Cexpl));
for (eq = g->expls; (o = *eq); eq++, ep++) {
*ep = e = (Cexpl) zshcalloc(sizeof(struct cexpl));
e->count = (fi ? o->fcount : o->count);
e->always = o->always;
e->fcount = 0;
e->str = ztrdup(o->str);
}
*ep = NULL;
} else
n->expls = NULL;
n->widths = NULL;
} else {
if (!lmatches)
lmatches = g->perm;
if (pmatches)
pmatches->prev = g->perm;
g->perm->next = pmatches;
pmatches = g->perm;
g->perm->prev = NULL;
nmatches += g->mcount;
smatches += g->lcount;
if (g->mcount > 1)
diffmatches = 1;
g->num = gn++;
}
g->new = 0;
g = g->next;
}
for (g = pmatches, p = NULL; g; g = g->next) {
g->nbrbeg = nbrbeg;
g->nbrend = nbrend;
for (rn = 1, q = g->matches; *q; q++) {
(*q)->rnum = rn++;
(*q)->gnum = mn++;
}
if (!diffmatches && *g->matches) {
if (p) {
if (!matcheq(*g->matches, *p))
diffmatches = 1;
} else
p = g->matches;
}
}
hasperm = 1;
permmnum = mn - 1;
permgnum = gn - 1;
listdat.valid = 0;
return fi;
}
/* This frees one match. */
/**/
static void
freematch(Cmatch m, int nbeg, int nend)
{
if (!m) return;
zsfree(m->str);
zsfree(m->orig);
zsfree(m->ipre);
zsfree(m->ripre);
zsfree(m->isuf);
zsfree(m->ppre);
zsfree(m->psuf);
zsfree(m->pre);
zsfree(m->suf);
zsfree(m->prpre);
zsfree(m->rems);
zsfree(m->remf);
zsfree(m->disp);
zsfree(m->autoq);
if (m->brpl)
zfree(m->brpl, nbeg * sizeof(int));
if (m->brsl)
zfree(m->brsl, nend * sizeof(int));
zfree(m, sizeof(*m));
}
/* This frees the groups of matches. */
/**/
mod_export void
freematches(Cmgroup g, int cm)
{
Cmgroup n;
Cmatch *m;
Cexpl *e;
while (g) {
n = g->next;
for (m = g->matches; *m; m++)
freematch(*m, g->nbrbeg, g->nbrend);
free(g->matches);
if (g->ylist)
freearray(g->ylist);
if ((e = g->expls)) {
while (*e) {
zsfree((*e)->str);
free(*e);
e++;
}
free(g->expls);
}
if (g->widths)
free(g->widths);
zsfree(g->name);
free(g);
g = n;
}
if (cm)
minfo.cur = NULL;
}