zsh/Src/Zle/zle_tricky.c
Peter Stephenson c557cee1a6 24581: Fix array assignments in shell word splitting and completion.
Assignments after the first were not recognised as such as without
the full parser the state didn't return to command position.
Fix this in bufferwords() and the completion miniparser.
2019-12-31 18:35:06 +00:00

3066 lines
75 KiB
C

/*
* zle_tricky.c - expansion and completion
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1997 Paul Falstad
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Paul Falstad or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Paul Falstad and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Paul Falstad and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Paul Falstad and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "zle.mdh"
#include "zle_tricky.pro"
/*
* The main part of ZLE maintains the line being edited as binary data,
* but here, where we interface with the lexer and other bits of zsh, we
* need the line metafied and, if necessary, converted from wide
* characters into multibyte strings. On entry to the
* expansion/completion system, we metafy the line from zleline into
* zlemetaline, with zlell and zlecs adjusted into zlemetall zlemetacs
* to match. zlemetall and zlemetacs refer to raw character positions,
* in other words a metafied character contributes 2 to each. All
* completion and expansion is done on the metafied line. Immediately
* before returning, the line is unmetafied again, so that zleline,
* zlell and zlecs are once again valid. (zlell and zlecs might have
* changed during completion, so they can't be merely saved and
* restored.) The various indexes into the line that are used in this
* file only are not translated: they remain indexes into the metafied
* line.
*
* zlemetaline is always NULL when not in use and non-NULL when in use.
* This can be used to test if the line is metafied. It would be
* possible to use zlecs and zlell directly, updated as appropriate when
* metafying and unmetafying, instead of zlemetacs and zlemetall,
* however the current system seems clearer.
*/
#define inststr(X) inststrlen((X),1,-1)
/*
* The state of the line being edited between metafy_line()
* unmetafy_line().
*
* zlemetacs and zlemetall are defined in lex.c.
*/
/**/
mod_export char *zlemetaline;
/**/
mod_export int metalinesz;
/* The line before completion was tried. */
/**/
mod_export char *origline;
/**/
mod_export int origcs, origll;
/* Words on the command line, for use in completion */
/**/
mod_export int clwsize, clwnum, clwpos;
/**/
mod_export char **clwords;
/* offs is the cursor position within the tokenized *
* current word after removing nulargs. */
/**/
mod_export int offs;
/* These control the type of completion that will be done. They are *
* affected by the choice of ZLE command and by relevant shell options. *
* usemenu is set to 2 if we have to start automenu and 3 if we have to *
* insert a match as if for menucompletion but without really starting it. */
/**/
mod_export int usemenu, useglob;
/* != 0 if we would insert a TAB if we weren't calling a completion widget. */
/**/
mod_export int wouldinstab;
/* != 0 if we are in the middle of a menu completion. */
/**/
mod_export int menucmp;
/* Lists of brace-infos before/after cursor (first and last for each). */
/**/
mod_export Brinfo brbeg, lastbrbeg, brend, lastbrend;
/**/
mod_export int nbrbeg, nbrend;
/**/
mod_export char *lastprebr, *lastpostbr;
/* !=0 if we have a valid completion list. */
/**/
mod_export int validlist;
/* Non-zero if we have to redisplay the list of matches. */
/**/
mod_export int showagain = 0;
/* This holds the word we are working on without braces removed. */
static char *origword;
/* The quoted prefix/suffix and a flag saying if we want to add the
* closing quote. */
/**/
mod_export char *qipre, *qisuf, *autoq;
/* This contains the name of the function to call if this is for a new *
* style completion. */
/**/
mod_export char *compfunc = NULL;
/* Non-zero if the last completion done was ambiguous (used to find *
* out if AUTOMENU should start). More precisely, it's nonzero after *
* successfully doing any completion, unless the completion was *
* unambiguous and did not cause the display of a completion list. *
* From the other point of view, it's nonzero iff AUTOMENU (if set) *
* should kick in on another completion. *
* *
* If both AUTOMENU and BASHAUTOLIST are set, then we get a listing *
* on the second tab, a` la bash, and then automenu kicks in when *
* lastambig == 2. */
/**/
mod_export int lastambig, bashlistfirst;
/* Arguments for and return value of completion widget. */
/**/
mod_export char **cfargs;
/**/
mod_export int cfret;
/* != 0 if recursive calls to completion are (temporarily) allowed */
/**/
mod_export int comprecursive;
/* != 0 if there are any defined completion widgets. */
/**/
int hascompwidgets;
/*
* Find out if we have to insert a tab (instead of trying to complete).
* The line is not metafied here.
*/
/**/
static int
usetab(void)
{
ZLE_STRING_T s = zleline + zlecs - 1;
if (keybuf[0] != '\t' || keybuf[1])
return 0;
for (; s >= zleline && *s != ZWC('\n'); s--)
if (*s != ZWC('\t') && *s != ZWC(' '))
return 0;
if (compfunc) {
wouldinstab = 1;
return 0;
}
return 1;
}
/**/
int
completecall(char **args)
{
cfargs = args;
cfret = 0;
compfunc = compwidget->u.comp.func;
if (compwidget->u.comp.fn(zlenoargs) && !cfret)
cfret = 1;
compfunc = NULL;
return cfret;
}
/**/
int
completeword(char **args)
{
usemenu = !!isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
if (lastchar == '\t' && usetab())
return selfinsert(args);
else {
int ret;
if (lastambig == 1 && isset(BASHAUTOLIST) && !usemenu && !menucmp) {
bashlistfirst = 1;
ret = docomplete(COMP_LIST_COMPLETE);
bashlistfirst = 0;
lastambig = 2;
} else
ret = docomplete(COMP_COMPLETE);
return ret;
}
}
/**/
mod_export int
menucomplete(char **args)
{
usemenu = 1;
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
if (lastchar == '\t' && usetab())
return selfinsert(args);
else
return docomplete(COMP_COMPLETE);
}
/**/
int
listchoices(UNUSED(char **args))
{
usemenu = !!isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
return docomplete(COMP_LIST_COMPLETE);
}
/**/
int
spellword(UNUSED(char **args))
{
usemenu = useglob = 0;
wouldinstab = 0;
return docomplete(COMP_SPELL);
}
/**/
int
deletecharorlist(char **args)
{
usemenu = !!isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
/* Line not yet metafied */
if (zlecs != zlell) {
fixsuffix();
invalidatelist();
return deletechar(args);
}
return docomplete(COMP_LIST_COMPLETE);
}
/**/
int
expandword(char **args)
{
usemenu = useglob = 0;
wouldinstab = 0;
if (lastchar == '\t' && usetab())
return selfinsert(args);
else
return docomplete(COMP_EXPAND);
}
/**/
int
expandorcomplete(char **args)
{
usemenu = !!isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
if (lastchar == '\t' && usetab())
return selfinsert(args);
else {
int ret;
if (lastambig == 1 && isset(BASHAUTOLIST) && !usemenu && !menucmp) {
bashlistfirst = 1;
ret = docomplete(COMP_LIST_COMPLETE);
bashlistfirst = 0;
lastambig = 2;
} else
ret = docomplete(COMP_EXPAND_COMPLETE);
return ret;
}
}
/**/
int
menuexpandorcomplete(char **args)
{
usemenu = 1;
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
if (lastchar == '\t' && usetab())
return selfinsert(args);
else
return docomplete(COMP_EXPAND_COMPLETE);
}
/**/
int
listexpand(UNUSED(char **args))
{
usemenu = !!isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
wouldinstab = 0;
return docomplete(COMP_LIST_EXPAND);
}
/**/
mod_export int
reversemenucomplete(char **args)
{
wouldinstab = 0;
zmult = -zmult;
return menucomplete(args);
}
/**/
int
acceptandmenucomplete(char **args)
{
wouldinstab = 0;
if (!menucmp)
return 1;
runhookdef(ACCEPTCOMPHOOK, NULL);
return menucomplete(args);
}
/* These are flags saying if we are completing in the command *
* position, in a redirection, or in a parameter expansion. */
/**/
mod_export int lincmd, linredir, linarr;
/* The string for the redirection operator. */
/**/
mod_export char *rdstr;
static char rdstrbuf[20];
/* The list of redirections on the line. */
/**/
mod_export LinkList rdstrs;
/* This holds the name of the current command (used to find the right *
* compctl). */
/**/
mod_export char *cmdstr;
/* This hold the name of the variable we are working on. */
/**/
mod_export char *varname;
/*
* != 0 if we are in a subscript.
* Of course, this being the completion code, you're expected to guess
* what the different numbers actually mean, but here's a cheat:
* 1: Key of an ordinary array
* 2: Key of a hash
* 3: Ummm.... this appears to be a special case of 2. After a lot
* of uncommented code looking for groups of brackets, we suddenly
* decide to set it to 2. The only upshot seems to be that compctl
* then doesn't add a matching ']' at the end, so I think it means
* there's one there already.
*/
/**/
mod_export int insubscr;
/* Parameter pointer for completing keys of an assoc array. */
/**/
mod_export Param keypm;
/*
* instring takes one of the QT_* values defined in zsh.h.
* It's never QT_TICK, instead we use inbackt.
* TODO: can we combine the two?
*/
/**/
mod_export int instring, inbackt;
/*
* Convenience macro for calling quotestring (formerly bslashquote() (formerly
* quotename())).
* This uses the instring variable above.
*/
#define quotename(s) \
quotestring(s, instring == QT_NONE ? QT_BACKSLASH : instring)
/* Check if the given string is the name of a parameter and if this *
* parameter is one worth expanding. */
/**/
static int
checkparams(char *p)
{
int t0, n, l = strlen(p), e = 0;
struct hashnode *hn;
for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--)
for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next)
if (pfxlen(p, hn->nam) == l) {
n++;
if ((int)strlen(hn->nam) == l)
e = 1;
}
return (n == 1) ? (getsparam(p) != NULL) :
(!menucmp && e && (!hascompmod || isset(RECEXACT)));
}
/* Check if the given string has wildcards. The difficulty is that we *
* have to treat things like job specifications (%...) and parameter *
* expressions correctly. */
/**/
static int
cmphaswilds(char *str)
{
char *ptr;
if ((*str == Inbrack || *str == Outbrack) && !str[1])
return 0;
/* If a leading % is immediately followed by ?, then don't *
* treat that ? as a wildcard. This is so you don't have *
* to escape job references such as %?foo. */
if (str[0] == '%' && str[1] ==Quest)
str += 2;
/*
* In ~[foo], the square brackets are not wild cards.
* This test matches the master one in filesubstr().
*/
if (*str == Tilde && str[1] == Inbrack &&
(ptr = strchr(str+2, Outbrack)))
str = ptr + 1;
for (; *str;) {
if (*str == String || *str == Qstring) {
/* A parameter expression. */
if (*++str == Inbrace)
skipparens(Inbrace, Outbrace, &str);
else if (*str == String || *str == Qstring)
str++;
else {
/* Skip all the things a parameter expression might start *
* with (before we come to the parameter name). */
for (; *str; str++)
if (*str != '^' && *str != Hat &&
*str != '=' && *str != Equals &&
*str != '~' && *str != Tilde)
break;
if (*str == '#' || *str == Pound)
str++;
/* Star and Quest are parameter names here, not wildcards */
if (*str == Star || *str == Quest)
str++;
}
} else {
/* Not a parameter expression so we check for wildcards */
if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) ||
*str == Star || *str == Bar || *str == Quest ||
!skipparens(Inbrack, Outbrack, &str) ||
!skipparens(Inang, Outang, &str) ||
(unset(IGNOREBRACES) &&
!skipparens(Inbrace, Outbrace, &str)) ||
(*str == Inpar && str[1] == ':' &&
!skipparens(Inpar, Outpar, &str)))
return 1;
if (*str)
str++;
}
}
return 0;
}
/* Check if we have to complete a parameter name. */
/**/
char *
parambeg(char *s)
{
char *p;
/* Try to find a `$'. */
for (p = s + offs; p > s && *p != String && *p != Qstring; p--);
if (*p == String || *p == Qstring) {
/* Handle $$'s */
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 ((*p == String || *p == Qstring) &&
p[1] != Inpar && p[1] != Inbrack && p[1] != '\'') {
/*
* This is really a parameter expression (not $(...) or $[...]
* or $'...').
*/
char *b = p + 1, *e = b;
int n = 0, br = 1;
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++;
n = skipparens(Inpar, Outpar, &b);
}
/* 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 == Dnull)
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
e = itype_end(e, IIDENT, 0);
/* Now make sure that the cursor is inside the name. */
if (offs <= e - s && offs >= b - s && n <= 0) {
if (br) {
p = e;
while (*p == Dnull)
p++;
}
/* It is. */
return b;
}
}
return NULL;
}
/* The main entry point for completion. */
/**/
static int
docomplete(int lst)
{
static int active = 0;
char *s, *ol;
int olst = lst, chl = 0, ne = noerrs, ocs, ret = 0, dat[2];
if (active && !comprecursive) {
zwarn("completion cannot be used recursively (yet)");
return 1;
}
active = 1;
comprecursive = 0;
makecommaspecial(0);
/* From the C-code's point of view, we can only use compctl as a default
* type of completion. Load it if it hasn't been loaded already and
* no completion widgets are defined. */
if (!module_loaded("zsh/compctl") && !hascompwidgets)
(void)load_module("zsh/compctl", NULL, 0);
if (runhookdef(BEFORECOMPLETEHOOK, (void *) &lst)) {
active = 0;
return 0;
}
/* Expand history references before starting completion. If anything *
* changed, do no more. */
if (doexpandhist()) {
active = 0;
return 0;
}
metafy_line();
ocs = zlemetacs;
zsfree(origline);
origline = ztrdup(zlemetaline);
origcs = zlemetacs;
origll = zlemetall;
if (!isfirstln && (chline != NULL || zle_chline != NULL)) {
ol = dupstring(zlemetaline);
/*
* Make sure that chline is zero-terminated.
* zle_chline always is and hptr doesn't point into it anyway.
*/
if (!zle_chline)
*hptr = '\0';
zlemetacs = 0;
inststr(zle_chline ? zle_chline : chline);
chl = zlemetacs;
zlemetacs += ocs;
} else
ol = NULL;
inwhat = IN_NOTHING;
zsfree(qipre);
qipre = ztrdup("");
zsfree(qisuf);
qisuf = ztrdup("");
zsfree(autoq);
autoq = NULL;
/* Get the word to complete.
* NOTE: get_comp_string() calls pushheap(), but not popheap(). */
noerrs = 1;
s = get_comp_string();
DPUTS3(wb < 0 || zlemetacs < wb || zlemetacs > we,
"BUG: 0 <= wb (%d) <= zlemetacs (%d) <= we (%d) is not true!",
wb, zlemetacs, we);
noerrs = ne;
/* For vi mode, reset the start-of-insertion pointer to the beginning *
* of the word being completed, if it is currently later. Vi itself *
* would never change the pointer in the middle of an insertion, but *
* then vi doesn't have completion. More to the point, this is only *
* an emulation. */
if (viinsbegin > ztrsub(zlemetaline + wb, zlemetaline))
viinsbegin = ztrsub(zlemetaline + wb, zlemetaline);
/* If we added chline to the line buffer, reset the original contents. */
if (ol) {
zlemetacs -= chl;
wb -= chl;
we -= chl;
if (wb < 0) {
strcpy(zlemetaline, ol);
zlemetall = strlen(zlemetaline);
zlemetacs = ocs;
popheap();
unmetafy_line();
zsfree(s);
active = 0;
makecommaspecial(0);
return 1;
}
ocs = zlemetacs;
zlemetacs = 0;
foredel(chl, CUT_RAW);
zlemetacs = ocs;
}
freeheap();
/* Save the lexer state, in case the completion code uses the lexer *
* somewhere (e.g. when processing a compctl -s flag). */
zcontext_save();
if (inwhat == IN_ENV)
lincmd = 0;
if (s) {
if (lst == COMP_EXPAND_COMPLETE) {
/* Check if we have to do expansion or completion. */
char *q = s;
if (*q == Equals) {
/* The word starts with `=', see if we can expand it. */
q = s + 1;
if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked)) {
if (!hascompmod || isset(RECEXACT))
lst = COMP_EXPAND;
else {
int t0, n = 0;
struct hashnode *hn;
for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
for (hn = cmdnamtab->nodes[t0]; hn;
hn = hn->next) {
if (strpfx(q, hn->nam) &&
findcmd(hn->nam, 0, 0))
n++;
if (n == 2)
break;
}
if (n == 1)
lst = COMP_EXPAND;
}
}
}
if (lst == COMP_EXPAND_COMPLETE) {
do {
/* Check if there is a parameter expression. */
for (; *q && *q != String; q++);
if (*q == String && q[1] != Inpar && q[1] != Inparmath &&
q[1] != Inbrack) {
if (*++q == Inbrace) {
if (! skipparens(Inbrace, Outbrace, &q) &&
q == s + zlemetacs - wb)
lst = COMP_EXPAND;
} else {
char *t, sav, sav2;
/* Skip the things parameter expressions might *
* start with (the things before the parameter *
* name). */
for (; *q; q++)
if (*q != '^' && *q != Hat &&
*q != '=' && *q != Equals &&
*q != '~' && *q != Tilde)
break;
if ((*q == '#' || *q == Pound || *q == '+') &&
q[1] != String)
q++;
sav2 = *(t = q);
if (*q == Quest || *q == Star || *q == String ||
*q == Qstring)
*q = ztokens[*q - Pound], ++q;
else if (*q == '?' || *q == '*' || *q == '$' ||
*q == '-' || *q == '!' || *q == '@')
q++;
else if (idigit(*q))
do q++; while (idigit(*q));
else
q = itype_end(q, IIDENT, 0);
sav = *q;
*q = '\0';
if (zlemetacs - wb == q - s &&
(idigit(sav2) || checkparams(t)))
lst = COMP_EXPAND;
*q = sav;
*t = sav2;
}
if (lst != COMP_EXPAND)
lst = COMP_COMPLETE;
} else
break;
} while (q < s + zlemetacs - wb);
}
if (lst == COMP_EXPAND_COMPLETE) {
/* If it is still not clear if we should use expansion or *
* completion and there is a `$' or a backtick in the word, *
* than do expansion. */
for (q = s; *q; q++)
if (*q == Tick || *q == Qtick ||
*q == String || *q == Qstring)
break;
lst = *q ? COMP_EXPAND : COMP_COMPLETE;
}
/* And do expansion if there are wildcards and globcomplete is *
* not used. */
if (unset(GLOBCOMPLETE) && cmphaswilds(s))
lst = COMP_EXPAND;
}
if (lincmd && (inwhat == IN_NOTHING))
inwhat = IN_CMD;
if (lst == COMP_SPELL) {
char *w = dupstring(origword), *x, *q, *ox;
for (q = w; *q; q++)
if (inull(*q))
*q = Nularg;
zlemetacs = wb;
foredel(we - wb, CUT_RAW);
untokenize(x = ox = dupstring(w));
if (*w == Tilde || *w == Equals || *w == String)
*x = *w;
spckword(&x, 0, lincmd, 0);
ret = !strcmp(x, ox);
untokenize(x);
inststr(x);
} else if (COMP_ISEXPAND(lst)) {
/* Do expansion. */
char *ol = (olst == COMP_EXPAND ||
olst == COMP_EXPAND_COMPLETE) ?
dupstring(zlemetaline) : zlemetaline;
int ocs = zlemetacs, ne = noerrs;
noerrs = 1;
ret = doexpansion(origword, lst, olst, lincmd);
lastambig = 0;
noerrs = ne;
/* If expandorcomplete was invoked and the expansion didn't *
* change the command line, do completion. */
if (olst == COMP_EXPAND_COMPLETE &&
!strcmp(ol, zlemetaline)) {
zlemetacs = ocs;
errflag &= ~ERRFLAG_ERROR;
if (!compfunc) {
char *p;
p = s;
if (*p == Tilde || *p == Equals)
p++;
for (; *p; p++)
if (itok(*p)) {
if (*p != String && *p != Qstring)
*p = ztokens[*p - Pound];
else if (p[1] == Inbrace)
p++, skipparens(Inbrace, Outbrace, &p);
}
}
ret = docompletion(s, lst, lincmd);
} else {
if (ret)
clearlist = 1;
if (!strcmp(ol, zlemetaline)) {
/* We may have removed some quotes. For completion, other
* parts of the code re-install them, but for expansion
* we have to do it here. */
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
spaceinline(origll);
memcpy(zlemetaline, origline, origll);
zlemetacs = origcs;
}
}
} else
/* Just do completion. */
ret = docompletion(s, lst, lincmd);
zsfree(s);
} else
ret = 1;
/* Reset the lexer state, pop the heap. */
zcontext_restore();
popheap();
dat[0] = lst;
dat[1] = ret;
runhookdef(AFTERCOMPLETEHOOK, (void *) dat);
unmetafy_line();
active = 0;
makecommaspecial(0);
/*
* As a special case, we reset user interrupts here.
* That's because completion is an intensive piece of
* computation that the user might want to interrupt separately
* from anything else going on. If they do, they probably
* want to keep the line edit buffer intact.
*
* There's a race here that the user might hit ^C just
* after completion exited anyway, but that's inevitable.
*/
errflag &= ~ERRFLAG_INT;
return dat[1];
}
/* 1 if we are completing the prefix */
static int comppref;
/* This function inserts an `x' in the command line at the cursor position. *
* *
* Oh, you want to know why? Well, if completion is tried somewhere on an *
* empty part of the command line, the lexer code would normally not be *
* able to give us the `word' we want to complete, since there is no word. *
* But we need to call the lexer to find out where we are (and for which *
* command we are completing and such things). So we temporarily add a `x' *
* (any character without special meaning would do the job) at the cursor *
* position, than the lexer gives us the word `x' and its beginning and end *
* positions and we can remove the `x'. *
* *
* If we are just completing the prefix (comppref set), we also insert a *
* space after the x to end the word. We never need to remove the space: *
* anywhere we are able to retrieve a word for completion it will be *
* discarded as whitespace. It has the effect of making any suffix *
* referrable to as the next word on the command line when indexing *
* from a completion function. */
/**/
static void
addx(char **ptmp)
{
int addspace = 0;
if (!zlemetaline[zlemetacs] || zlemetaline[zlemetacs] == '\n' ||
(iblank(zlemetaline[zlemetacs]) &&
(!zlemetacs || zlemetaline[zlemetacs-1] != '\\')) ||
zlemetaline[zlemetacs] == ')' || zlemetaline[zlemetacs] == '`' ||
zlemetaline[zlemetacs] == '}' ||
zlemetaline[zlemetacs] == ';' || zlemetaline[zlemetacs] == '|' ||
zlemetaline[zlemetacs] == '&' ||
zlemetaline[zlemetacs] == '>' || zlemetaline[zlemetacs] == '<' ||
(instring != QT_NONE && (zlemetaline[zlemetacs] == '"' ||
zlemetaline[zlemetacs] == '\'')) ||
(addspace = (comppref && !iblank(zlemetaline[zlemetacs])))) {
*ptmp = zlemetaline;
zlemetaline = zhalloc(strlen(zlemetaline) + 3 + addspace);
memcpy(zlemetaline, *ptmp, zlemetacs);
zlemetaline[zlemetacs] = 'x';
if (addspace)
zlemetaline[zlemetacs+1] = ' ';
strcpy(zlemetaline + zlemetacs + 1 + addspace, (*ptmp) + zlemetacs);
addedx = 1 + addspace;
} else {
addedx = 0;
*ptmp = NULL;
}
}
/* Like dupstring, but add an extra space at the end of the string. */
/**/
mod_export char *
dupstrspace(const char *str)
{
int len = strlen(str);
char *t = (char *) hcalloc(len + 2);
strcpy(t, str);
strcpy(t+len, " ");
return t;
}
/*
* These functions metafy and unmetafy the ZLE buffer, as described at
* the top of this file. They *must* be called in matching pairs,
* around all the expansion/completion code.
*
* The variables zleline, zlell and zlecs are metafied into
* zlemetaline, zlemetall and zlemetacs. Only the latter variables
* should be referred to from above zle (i.e. in the main shell),
* or when using the completion API (if that's not too strong a
* way of referring to it).
*/
/**/
mod_export void
metafy_line(void)
{
UNMETACHECK();
zlemetaline = zlelineasstring(zleline, zlell, zlecs,
&zlemetall, &zlemetacs, 0);
metalinesz = zlemetall;
/*
* We will always allocate a new zleline based on zlemetaline.
*/
free(zleline);
zleline = NULL;
}
/**/
mod_export void
unmetafy_line(void)
{
METACHECK();
/* paranoia */
zlemetaline[zlemetall] = '\0';
zleline = stringaszleline(zlemetaline, zlemetacs, &zlell, &linesz, &zlecs);
free(zlemetaline);
zlemetaline = NULL;
/*
* If we inserted combining characters under the cursor we
* won't have tested the effect yet. So fix it up now.
*/
CCRIGHT();
}
/* Free a brinfo list. */
/**/
mod_export void
freebrinfo(Brinfo p)
{
Brinfo n;
while (p) {
n = p->next;
zsfree(p->str);
zfree(p, sizeof(*p));
p = n;
}
}
/* Duplicate a brinfo list. */
/**/
mod_export Brinfo
dupbrinfo(Brinfo p, Brinfo *last, int heap)
{
Brinfo ret = NULL, *q = &ret, n = NULL;
while (p) {
n = *q = (heap ? (Brinfo) zhalloc(sizeof(*n)) :
(Brinfo) zalloc(sizeof(*n)));
q = &(n->next);
n->next = NULL;
n->str = (heap ? dupstring(p->str) : ztrdup(p->str));
n->pos = p->pos;
n->qpos = p->qpos;
n->curpos = p->curpos;
p = p->next;
}
if (last)
*last = n;
return ret;
}
/* This is a bit like has_token(), but ignores nulls. */
static int
has_real_token(const char *s)
{
while (*s) {
/*
* Special action required for $' strings, which
* need to be treated like nulls.
*/
if ((*s == Qstring && s[1] == '\'') ||
(*s == String && s[1] == Snull))
{
s += 2;
continue;
}
if (itok(*s) && !inull(*s))
return 1;
s++;
}
return 0;
}
/* Lasciate ogni speranza. *
* This function is a nightmare. It works, but I'm sure that nobody really *
* understands why. The problem is: to make it cleaner we would need *
* changes in the lexer code (and then in the parser, and then...). */
/**/
static char *
get_comp_string(void)
{
enum lextok t0, tt0, cmdtok;
int i, j, k, cp, rd, sl, ocs, ins, oins, ia, parct, varq = 0;
int ona = noaliases;
/*
* Index of word being considered
*/
int wordpos;
/*
* qsub fixes up the offset into the current completion word
* for changes made by the lexer. That currently means the
* effect of RCQUOTES on embedded pairs of single quotes.
* zlemetacs_qsub takes account of the effect of this offset
* on the cursor position; it's only needed when using the
* word we got from the lexer, which we only do sometimes because
* otherwise it would be too easy. If looking at zlemetaline we
* still use zlemetacs.
*/
int qsub, zlemetacs_qsub = 0;
/*
* redirpos is used to record string arguments for redirection
* when they occur at the start of the line. In this case
* the command word is not at index zero in the array.
*/
int redirpos;
int noword;
char *s = NULL, *tmp, *p, *tt = NULL, rdop[20];
char *linptr, *u;
METACHECK();
freebrinfo(brbeg);
freebrinfo(brend);
brbeg = lastbrbeg = brend = lastbrend = NULL;
nbrbeg = nbrend = 0;
zsfree(lastprebr);
zsfree(lastpostbr);
lastprebr = lastpostbr = NULL;
if (rdstrs)
freelinklist(rdstrs, freestr);
rdstrs = znewlinklist();
rdop[0] = '\0';
rdstr = NULL;
/* This global flag is used to signal the lexer code if it should *
* expand aliases or not. */
noaliases = isset(COMPLETEALIASES);
/* Find out if we are somewhere in a `string', i.e. inside '...', *
* "...", `...`, or ((...)). Nowadays this is only used to find *
* out if we are inside `...`. */
for (i = j = k = 0, u = zlemetaline; u < zlemetaline + zlemetacs; u++) {
if (*u == '`' && !(k & 1))
i++;
else if (*u == '\"' && !(k & 1) && !(i & 1))
j++;
else if (*u == '\'' && !(j & 1))
k++;
else if (*u == '\\' && u[1] && !(k & 1))
u++;
}
inbackt = (i & 1);
instring = QT_NONE;
addx(&tmp);
linptr = zlemetaline;
pushheap();
start:
inwhat = IN_NOTHING;
/* Now set up the lexer and start it. */
parbegin = parend = -1;
lincmd = incmdpos;
linredir = inredir;
zsfree(cmdstr);
cmdstr = NULL;
cmdtok = NULLTOK;
zsfree(varname);
varname = NULL;
insubscr = 0;
clwpos = -1;
zcontext_save();
lexflags = LEXFLAGS_ZLE;
inpush(dupstrspace(linptr), 0, NULL);
strinbeg(0);
wordpos = cp = rd = ins = oins = linarr = parct = ia = redirpos = 0;
we = wb = zlemetacs;
tt0 = NULLTOK;
/* This loop is possibly the wrong way to do this. It goes through *
* the previously massaged command line using the lexer. It stores *
* each token in each command (commands being regarded, roughly, as *
* being separated by tokens | & &! |& || &&). The loop stops when *
* the end of the command containing the cursor is reached. What *
* makes this messy is checking for things like redirections, loops *
* and whatnot. */
do {
qsub = noword = 0;
/*
* pws: added cmdtok == NULLTOK test as fallback for detecting
* we haven't had a command yet. This is a cop out: it's needed
* after SEPER because of bizarre and incomprehensible dance
* that we otherwise do involving the "ins" flag when you might
* have thought we'd just reset everything because we're now
* considering a new command. Consequently, although this looks
* relatively harmless by itself, it's probably incomplete.
*/
linredir = (inredir && !ins);
lincmd = !inredir &&
((incmdpos && !ins && !incond) ||
(oins == 2 && wordpos == 2) ||
(ins == 3 && wordpos == 1) ||
(cmdtok == NULLTOK && !incond));
oins = ins;
/* Get the next token. */
if (linarr)
incmdpos = 0;
/*
* Arrange to parse assignments after typeset etc...
* but not if we're already in an array.
*/
if (cmdtok == TYPESET)
intypeset = !linarr;
ctxtlex();
if (tok == LEXERR) {
if (!tokstr)
break;
for (j = 0, p = tokstr; *p; p++)
if (*p == Snull || *p == Dnull)
j++;
if (j & 1) {
if (lincmd && strchr(tokstr, '=')) {
varq = 1;
tok = ENVSTRING;
} else
tok = STRING;
}
} else if (tok == ENVSTRING)
varq = 0;
if (tok == ENVARRAY) {
linarr = 1;
zsfree(varname);
varname = ztrdup(tokstr);
} else if (tok == INPAR)
parct++;
else if (tok == OUTPAR) {
if (parct)
parct--;
else if (linarr) {
linarr = 0;
incmdpos = 1;
}
}
if (inredir && IS_REDIROP(tok)) {
rdstr = rdstrbuf;
if (tokfd >= 0)
sprintf(rdop, "%d%s", tokfd, tokstrings[tok]);
else
strcpy(rdop, tokstrings[tok]);
strcpy(rdstr, rdop);
/* Record if we haven't had the command word yet */
if (wordpos == redirpos)
redirpos++;
if (zlemetacs < (zlemetall - inbufct) &&
zlemetacs >= wordbeg && wb == we) {
/* Cursor is in the middle of a redirection, treat as a word */
we = zlemetall - (inbufct + addedx);
if (addedx && we > wb) {
/* Assume we are in {param}> form, wb points at "{" */
wb++;
/* Should complete parameter names here */
} else {
/* In "2>" form, zlemetacs points at "2" */
wb = zlemetacs;
/* Should insert a space under cursor here */
}
}
}
if (tok == DINPAR)
tokstr = NULL;
/* We reached the end. */
if (tok == ENDINPUT)
break;
if ((ins && (tok == DOLOOP || tok == SEPER)) ||
(ins == 2 && wordpos == 2) || (ins == 3 && wordpos == 3) ||
tok == BAR || tok == AMPER ||
tok == BARAMP || tok == AMPERBANG ||
((tok == DBAR || tok == DAMPER) && !incond) ||
/*
* Special case: we might reach a new command (incmdpos set)
* if we've already found the string we're completing (tt set)
* without hitting one of the above if we're using one of
* the special zsh forms of delimiting for conditions and
* loops that I really loathe having to support.
*/
(tt && incmdpos)) {
/* This is one of the things that separate commands. If we *
* already have the things we need (e.g. the token strings), *
* leave the loop. */
if (tt)
break;
if (ins < 2) {
/*
* Don't add this as a word, because we're about to start
* a new command line: pretend there's no string here.
* We don't dare do this if we're using one of the
* *really* gross hacks with ins to get later words
* to look like command words, because we don't
* understand how they work. Quite possibly we
* should be using a mechanism like the one here rather
* than the ins thing.
*/
noword = 1;
}
/* Otherwise reset the variables we are collecting data in. */
wordpos = cp = rd = ins = redirpos = 0;
tt0 = NULLTOK;
}
if (lincmd && (tok == STRING || tok == FOR || tok == FOREACH ||
tok == SELECT || tok == REPEAT || tok == CASE ||
tok == TYPESET)) {
/* The lexer says, this token is in command position, so *
* store the token string (to find the right compctl). */
ins = (tok == REPEAT ? 2 : (tok != STRING && tok != TYPESET));
zsfree(cmdstr);
cmdstr = ztrdup(tokstr);
cmdtok = tok;
/*
* If everything before is a redirection, or anything
* complicated enough that we've seen the word the
* cursor is on, don't reset the index.
*/
if (wordpos != redirpos && clwpos == -1)
wordpos = redirpos = 0;
} else if (tok == SEPER) {
/*
* A following DOLOOP should cause us to reset to the start
* of the command line. For some reason we only recognise
* DOLOOP for this purpose (above) if ins is set. Why? To
* handle completing multiple SEPER-ated command positions on
* the same command line, e.g., pipelines.
*/
ins = (cmdtok != STRING && cmdtok != TYPESET);
}
if (!lexflags && tt0 == NULLTOK) {
/* This is done when the lexer reached the word the cursor is on. */
tt = tokstr ? dupstring(tokstr) : NULL;
/*
* If there was a proper interface between this
* function and the lexical analyser, we wouldn't
* have to fix things up.
*
* Fix up backslash-newline pairs in zlemetaline
* that the lexer will have removed. As we're
* looking back at the zlemetaline version it's
* still using untokenized quotes.
*/
for (i = j = k = 0, u = zlemetaline + wb;
u < zlemetaline + we; u++) {
if (*u == '`' && !(k & 1))
i++;
else if (*u == '\"' && !(k & 1) && !(i & 1))
j++;
else if (*u == '\'' && !(j & 1))
k++;
else if (*u == '\\' && u[1] && !(k & 1))
{
if (u[1] == '\n') {
/* Removed by lexer in tt */
qsub += 2;
}
u++;
}
}
/*
* Fix up RCQUOTES quotes that the
* the lexer will also have removed.
*/
if (isset(RCQUOTES) && tt) {
char *tt1, *e = tt + zlemetacs - wb - qsub;
for (tt1 = tt; *tt1; tt1++) {
if (*tt1 == Snull) {
char *p;
for (p = tt1; *p && p < e; p++)
if (*p == '\'')
qsub++;
}
}
}
/* If we added a `x', remove it. */
if (addedx && tt)
chuck(tt + zlemetacs - wb - qsub);
tt0 = tok;
/* Store the number of this word. */
clwpos = wordpos;
cp = lincmd;
rd = linredir;
ia = linarr;
if (inwhat == IN_NOTHING && incond)
inwhat = IN_COND;
} else if (linredir) {
if (rdop[0] && tokstr)
zaddlinknode(rdstrs, tricat(rdop, ":", tokstr));
continue;
}
if (incond) {
if (tok == DBAR)
tokstr = "||";
else if (tok == DAMPER)
tokstr = "&&";
}
if (!tokstr || noword)
continue;
/* Hack to allow completion after `repeat n do'. */
if (oins == 2 && !wordpos && !strcmp(tokstr, "do"))
ins = 3;
/* We need to store the token strings of all words (for some of *
* the more complicated compctl -x things). They are stored in *
* the clwords array. Make this array big enough. */
if (wordpos + 1 == clwsize) {
int n;
clwords = (char **)realloc(clwords,
(clwsize *= 2) * sizeof(char *));
for(n = clwsize; --n > wordpos; )
clwords[n] = NULL;
}
zsfree(clwords[wordpos]);
/* And store the current token string. */
clwords[wordpos] = ztrdup(tokstr);
sl = strlen(tokstr);
/* Sometimes the lexer gives us token strings ending with *
* spaces we delete the spaces. */
while (sl && clwords[wordpos][sl - 1] == ' ' &&
(sl < 2 || (clwords[wordpos][sl - 2] != Bnull &&
clwords[wordpos][sl - 2] != Meta)))
clwords[wordpos][--sl] = '\0';
/* If this is the word the cursor is in and we added a `x', *
* remove it. */
if (clwpos == wordpos++ && addedx) {
int chuck_at, word_diff;
zlemetacs_qsub = zlemetacs - qsub;
word_diff = zlemetacs_qsub - wb;
/* Ensure we chuck within the word... */
if (word_diff >= sl)
chuck_at = sl -1;
else if (word_diff < 0)
chuck_at = 0;
else
chuck_at = word_diff;
chuck(&clwords[wordpos - 1][chuck_at]);
}
} while (tok != LEXERR && tok != ENDINPUT &&
(tok != SEPER || (lexflags && tt0 == NULLTOK)));
/* Calculate the number of words stored in the clwords array. */
clwnum = (tt || !wordpos) ? wordpos : wordpos - 1;
zsfree(clwords[clwnum]);
clwords[clwnum] = NULL;
t0 = tt0;
if (ia) {
lincmd = linredir = 0;
inwhat = IN_ENV;
} else {
lincmd = cp;
linredir = rd;
}
strinend();
inpop();
lexflags = 0;
errflag &= ~ERRFLAG_ERROR;
if (parbegin != -1) {
/* We are in command or process substitution if we are not in
* a $((...)). */
if (parend >= 0 && !tmp)
zlemetaline = dupstring(tmp = zlemetaline);
linptr = zlemetaline + zlemetall + addedx - parbegin + 1;
if ((linptr - zlemetaline) < 3 || *linptr != '(' ||
linptr[-1] != '(' || linptr[-2] != '$') {
if (parend >= 0) {
zlemetall -= parend;
zlemetaline[zlemetall + addedx] = '\0';
}
zcontext_restore();
tt = NULL;
goto start;
}
}
if (inwhat == IN_MATH)
s = NULL;
else if (t0 == NULLTOK || t0 == ENDINPUT) {
/* There was no word (empty line). */
s = ztrdup("");
we = wb = zlemetacs;
clwpos = clwnum;
t0 = STRING;
} else if (t0 == STRING || t0 == TYPESET) {
/* We found a simple string. */
s = clwords[clwpos];
DPUTS(!s, "Completion word has disappeared!");
s = ztrdup(s ? s : "");
} else if (t0 == ENVSTRING) {
char sav;
/* The cursor was inside a parameter assignment. */
if (varq)
tt = clwords[clwpos];
s = itype_end(tt, IIDENT, 0);
sav = *s;
*s = '\0';
zsfree(varname);
varname = ztrdup(tt);
*s = sav;
if (*s == '+')
s++;
if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt +
zlemetacs_qsub - wb) {
s = NULL;
inwhat = IN_MATH;
if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
(keypm->node.flags & PM_HASHED))
insubscr = 2;
else
insubscr = 1;
} else if (*s == '=') {
if (zlemetacs_qsub > wb + (s - tt)) {
s++;
wb += s - tt;
s = ztrdup(s);
inwhat = IN_ENV;
} else {
char *p = s;
if (p[-1] == '+')
p--;
sav = *p;
*p = '\0';
inwhat = IN_PAR;
s = ztrdup(tt);
*p = sav;
we = wb + p - tt;
}
t0 = STRING;
}
lincmd = 1;
}
if (we > zlemetall)
we = zlemetall;
tt = zlemetaline;
if (tmp) {
zlemetaline = tmp;
zlemetall = strlen(zlemetaline);
}
if (t0 != STRING && t0 != TYPESET && inwhat != IN_MATH) {
if (tmp) {
tmp = NULL;
linptr = zlemetaline;
zcontext_restore();
addedx = 0;
goto start;
}
noaliases = ona;
zcontext_restore();
return NULL;
}
noaliases = ona;
/* Check if we are in an array subscript. We simply assume that *
* we are in a subscript if we are in brackets. Correct solution *
* is very difficult. This is quite close, but gets things like *
* foo[_ wrong (note no $). If we are in a subscript, treat it *
* as being in math. */
if (inwhat != IN_MATH) {
char *nnb, *nb = NULL, *ne = NULL;
i = 0;
MB_METACHARINIT();
if (itype_end(s, IIDENT, 1) == s)
nnb = s + MB_METACHARLEN(s);
else
nnb = s;
tt = s;
if (lincmd)
{
/*
* Ignore '['s at the start of a command as they're not
* matched by closing brackets.
*/
while (*tt == Inbrack && tt < s + zlemetacs_qsub - wb)
tt++;
}
while (tt < s + zlemetacs_qsub - wb) {
if (*tt == Inbrack) {
i++;
nb = nnb;
ne = tt;
tt++;
} else if (i && *tt == Outbrack) {
i--;
tt++;
} else {
int nclen = MB_METACHARLEN(tt);
if (itype_end(tt, IIDENT, 1) == tt)
nnb = tt + nclen;
tt += nclen;
}
}
if (i) {
inwhat = IN_MATH;
insubscr = 1;
if (nb < ne) {
char sav = *ne;
*ne = '\0';
zsfree(varname);
varname = ztrdup(nb);
*ne = sav;
if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
(keypm->node.flags & PM_HASHED))
insubscr = 2;
}
}
}
if (inwhat == IN_MATH) {
if (compfunc || insubscr == 2) {
int lev;
char *p;
for (wb = zlemetacs - 1, lev = 0; wb > 0; wb--)
if (zlemetaline[wb] == ']' || zlemetaline[wb] == ')')
lev++;
else if (zlemetaline[wb] == '[') {
if (!lev--)
break;
} else if (zlemetaline[wb] == '(') {
if (!lev && zlemetaline[wb - 1] == '(')
break;
if (lev)
lev--;
}
p = zlemetaline + wb;
wb++;
if (wb && (*p == '[' || *p == '(') &&
!skipparens(*p, (*p == '[' ? ']' : ')'), &p)) {
we = (p - zlemetaline) - 1;
if (insubscr == 2)
insubscr = 3;
}
} else {
/* In mathematical expression, we complete parameter names *
* (even if they don't have a `$' in front of them). So we *
* have to find that name. */
char *cspos = zlemetaline + zlemetacs, *wptr, *cptr;
we = itype_end(cspos, IIDENT, 0) - zlemetaline;
/*
* With multibyte characters we need to go forwards,
* so start at the beginning of the line and continue
* until cspos.
*/
wptr = cptr = zlemetaline;
for (;;) {
cptr = itype_end(wptr, IIDENT, 0);
if (cptr == wptr) {
/* not an ident character */
wptr = (cptr += MB_METACHARLEN(cptr));
}
if (cptr >= cspos) {
wb = wptr - zlemetaline;
break;
}
wptr = cptr;
}
}
zsfree(s);
s = zalloc(we - wb + 1);
strncpy(s, zlemetaline + wb, we - wb);
s[we - wb] = '\0';
if (wb > 2 && zlemetaline[wb - 1] == '[') {
char *sqbr = zlemetaline + wb - 1, *cptr, *wptr;
/* Need to search forward for word characters */
cptr = wptr = zlemetaline;
for (;;) {
cptr = itype_end(wptr, IIDENT, 0);
if (cptr == wptr) {
/* not an ident character */
wptr = (cptr += MB_METACHARLEN(cptr));
}
if (cptr >= sqbr)
break;
wptr = cptr;
}
if (wptr < sqbr) {
zsfree(varname);
varname = ztrduppfx(wptr, sqbr - wptr);
if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
(keypm->node.flags & PM_HASHED)) {
if (insubscr != 3)
insubscr = 2;
} else
insubscr = 1;
}
}
parse_subst_string(s);
}
/* This variable will hold the current word in quoted form. */
offs = zlemetacs - wb;
if ((p = parambeg(s))) {
for (p = s; *p; p++)
if (*p == Dnull)
*p = '"';
else if (*p == Snull)
*p = '\'';
} else {
int level = 0;
for (p = s; *p; p++) {
if (level && *p == Snull)
*p = '\'';
else if (level && *p == Dnull)
*p = '"';
else if ((*p == String || *p == Qstring) && p[1] == Inbrace)
level++;
else if (*p == Outbrace)
level--;
}
}
if ((*s == Snull || *s == Dnull ||
((*s == String || *s == Qstring) && s[1] == Snull))
&& !has_real_token(s + 1)) {
int sl = strlen(s);
char *q, *qtptr = s, *n;
switch (*s) {
case Snull:
q = "'";
instring = QT_SINGLE;
break;
case Dnull:
q = "\"";
instring = QT_DOUBLE;
break;
default:
q = "$'";
instring = QT_DOLLARS;
qtptr++;
sl--;
break;
}
n = tricat(qipre, q, "");
zsfree(qipre);
qipre = n;
/*
* TODO: it's certainly the case that the suffix for
* $' is ', but exactly what does that affect?
*/
if (*q == '$')
q++;
if (sl > 1 && qtptr[sl - 1] == *qtptr) {
n = tricat(q, qisuf, "");
zsfree(qisuf);
qisuf = n;
}
autoq = ztrdup(q);
/*
* \! in double quotes is extracted by the history code before normal
* parsing, so sanitize it here, too.
*/
if (instring == QT_DOUBLE) {
for (q = s; *q; q++)
if (*q == '\\' && q[1] == '!')
*q = Bnull;
}
}
/*
* Leading "=" gets tokenized in case the EQUALS options
* changes afterwards. It's too late for that now, so restore it
* to a plain "=" if the option is unset.
*/
if (*s == Equals && !isset(EQUALS))
*s = '=';
/* While building the quoted form, we also clean up the command line. */
for (p = s, i = wb, j = 0; *p; p++, i++) {
int skipchars;
if (*p == String && p[1] == Snull) {
char *pe;
for (pe = p + 2; *pe && *pe != Snull && i + (pe - p) < zlemetacs;
pe++)
;
if (*pe != Snull) {
/* no terminating Snull, can't substitute */
skipchars = 2;
if (*pe)
j = 1;
} else {
/*
* Try and substitute the $'...' expression.
*/
int len, tlen;
char *t = getkeystring(p + 2, &len, GETKEYS_DOLLARS_QUOTE,
NULL);
len += 2;
tlen = strlen(t);
skipchars = len - tlen;
/*
* If this makes the line longer, we don't attempt
* to substitute it. This is because "we" don't
* really understand what the heck is going on anyway
* and have blindly copied the code here from
* the sections below.
*/
if (skipchars >= 0) {
/* Update the completion string */
memcpy(p, t, tlen);
/* Update the version of the line we are operating on */
ocs = zlemetacs;
zlemetacs = i;
if (skipchars > 0) {
/* Move the tail of the completion string up. */
char *dptr = p + tlen;
char *sptr = p + len;
while ((*dptr++ = *sptr++))
;
/*
* If the character is before the cursor, we need to
* update the offset into the completion string to the
* cursor position, too. (Use ocs since we've hacked
* zlemetacs at this point.)
*/
if (i < ocs)
offs -= skipchars;
/* Move the tail of the line up */
foredel(skipchars, CUT_RAW);
/*
* Update the offset into the command line to the
* cursor position if that's after the current position.
*/
if ((zlemetacs = ocs) > i)
zlemetacs -= skipchars;
/* Always update the word end. */
we -= skipchars;
}
/*
* Copy the unquoted string into place, which
* now has the correct size.
*/
memcpy(zlemetaline + i, t, tlen);
/*
* Move both the completion string pointer
* and the command line offset to the end of
* the chunk we've copied in (minus 1 for
* the end of loop increment). The line
* and completion string chunks are now the
* same length.
*/
p += tlen - 1;
i += tlen - 1;
continue;
} else {
/*
* We give up if the expansion is longer the original
* string. That's because "we" don't really have the
* first clue how the completion system actually works.
*/
skipchars = 2;
/*
* Also pretend we're in single quotes.
*/
j = 1;
}
}
}
else if (*p == Qstring && p[1] == Snull)
skipchars = 2;
else if (inull(*p))
skipchars = 1;
else
skipchars = 0;
if (skipchars) {
if (i < zlemetacs)
offs -= skipchars;
if (*p == Snull && isset(RCQUOTES))
j = 1-j;
if (p[1] || *p != Bnull) {
if (*p == Bnull) {
if (zlemetacs == i + 1)
zlemetacs++, offs++;
} else {
ocs = zlemetacs;
zlemetacs = i;
foredel(skipchars, CUT_RAW);
if ((zlemetacs = ocs) > --i) {
zlemetacs -= skipchars;
/* do not skip past the beginning of the word */
if (wb > zlemetacs)
zlemetacs = wb;
}
we -= skipchars;
}
} else {
ocs = zlemetacs;
zlemetacs = we;
backdel(skipchars, CUT_RAW);
if (ocs == we)
zlemetacs = we - skipchars;
else
zlemetacs = ocs;
/* do not skip past the beginning of the word */
if (wb > zlemetacs)
zlemetacs = wb;
we -= skipchars;
}
/* we need to get rid of all the quotation bits... */
while (skipchars--)
chuck(p);
/* but we only decrement once to confuse the loop increment. */
p--;
} else if (j && *p == '\'' && i < zlemetacs)
offs--;
}
zsfree(origword);
origword = ztrdup(s);
if (!isset(IGNOREBRACES)) {
/* Try and deal with foo{xxx etc. */
/*}*/
char *curs = s + (isset(COMPLETEINWORD) ? offs : (int)strlen(s));
char *predup = dupstring(s), *dp = predup;
char *bbeg = NULL, *bend = NULL, *dbeg = NULL;
char *lastp = NULL, *firsts = NULL;
int cant = 0, begi = 0, boffs = offs, hascom = 0;
for (i = 0, p = s; *p; p++, dp++, i++) {
/* careful, ${... is not a brace expansion...
* we try to get braces after a parameter expansion right,
* but this may fail sometimes. sorry.
*/
/*}*/
if (*p == String || *p == Qstring) {
if (p[1] == Inbrace || p[1] == Inpar || p[1] == Inbrack) {
char *tp = p + 1;
if (skipparens(*tp, (*tp == Inbrace ? Outbrace :
(*tp == Inpar ? Outpar : Outbrack)),
&tp)) {
tt = NULL;
break;
}
i += tp - p;
dp += tp - p;
p = tp;
} else if (p[1] != Snull /* paranoia: should be gone now */) {
char *tp = p + 1;
for (; *tp == '^' || *tp == Hat ||
*tp == '=' || *tp == Equals ||
*tp == '~' || *tp == Tilde ||
*tp == '#' || *tp == Pound || *tp == '+';
tp++);
if (*tp == Quest || *tp == Star || *tp == String ||
*tp == Qstring || *tp == '?' || *tp == '*' ||
*tp == '$' || *tp == '-' || *tp == '!' ||
*tp == '@')
p++, i++;
else {
char *ie;
if (idigit(*tp))
while (idigit(*tp))
tp++;
else if ((ie = itype_end(tp, IIDENT, 0)) != tp)
tp = ie;
else {
tt = NULL;
break;
}
if (*tp == Inbrace) {
cant = 1;
break;
}
tp--;
i += tp - p;
dp += tp - p;
p = tp;
}
}
} else if (p < curs) {
if (*p == Outbrace) {
/*
* HERE: strip and remember code from last
* comma to here.
*/
cant = 1;
break;
}
if (*p == Inbrace) {
char *tp = p;
if (!skipparens(Inbrace, Outbrace, &tp)) {
/*
* Balanced brace: skip.
* We only deal with unfinished braces, so
* something{foo<x>bar,morestuff}else
* doesn't work
*
* HERE: instead, continue, look for a comma.
* Stack tp and brace for popping when we
* find a comma at each level.
*/
i += tp - p - 1;
dp += tp - p - 1;
p = tp - 1;
continue;
}
makecommaspecial(1);
if (bbeg) {
Brinfo new;
int len = bend - bbeg;
new = (Brinfo) zalloc(sizeof(*new));
nbrbeg++;
new->next = NULL;
if (lastbrbeg)
lastbrbeg->next = new;
else
brbeg = new;
lastbrbeg = new;
new->next = NULL;
new->str = dupstrpfx(bbeg, len);
new->str = ztrdup(quotename(new->str));
untokenize(new->str);
new->pos = begi;
*dbeg = '\0';
new->qpos = strlen(quotename(predup));
*dbeg = '{';
i -= len;
boffs -= len;
memmove(dbeg, dbeg + len, 1+strlen(dbeg+len));
dp -= len;
}
bbeg = lastp = p;
dbeg = dp;
bend = p + 1;
begi = i;
} else if (*p == Comma && bbeg) {
bend = p + 1;
hascom = 1;
}
} else {
/* On or after the cursor position */
if (*p == Inbrace) {
char *tp = p;
if (!skipparens(Inbrace, Outbrace, &tp)) {
/*
* Balanced braces after the cursor.
* Could do the same with these as
* those before the cursor.
*/
i += tp - p - 1;
dp += tp - p - 1;
p = tp - 1;
continue;
}
cant = 1;
makecommaspecial(1);
break;
}
if (p == curs) {
/*
* We've reached the cursor position.
* If there's a pending open brace at this
* point we need to stack the text.
* We've marked the bit we don't want from
* bbeg to bend, which might be a comma
* between the opening brace and us.
*/
if (bbeg) {
Brinfo new;
int len = bend - bbeg;
new = (Brinfo) zalloc(sizeof(*new));
nbrbeg++;
new->next = NULL;
if (lastbrbeg)
lastbrbeg->next = new;
else
brbeg = new;
lastbrbeg = new;
new->str = dupstrpfx(bbeg, len);
new->str = ztrdup(quotename(new->str));
untokenize(new->str);
new->pos = begi;
*dbeg = '\0';
new->qpos = strlen(quotename(predup));
*dbeg = '{';
i -= len;
boffs -= len;
memmove(dbeg, dbeg + len, 1+strlen(dbeg+len));
dp -= len;
}
bbeg = NULL;
}
if (*p == Comma) {
/*
* Comma on or after cursor.
* We set bbeg to NULL at the cursor; here
* it's being used to find the first comma
* afterwards.
*/
if (!bbeg)
bbeg = p;
hascom = 2;
} else if (*p == Outbrace) {
/*
* Closing brace on or after the cursor.
* Not sure how this can be after the cursor;
* if it was matched, wouldn't we have skipped
* over the group, and if it wasn't, surely we're
* not interested in it?
*/
Brinfo new;
int len;
if (!bbeg)
bbeg = p;
len = p + 1 - bbeg;
if (!firsts)
firsts = p + 1;
new = (Brinfo) zalloc(sizeof(*new));
nbrend++;
if (!lastbrend)
lastbrend = new;
new->next = brend;
brend = new;
new->str = dupstrpfx(bbeg, len);
new->str = ztrdup(quotename(new->str));
untokenize(new->str);
new->pos = dp - predup - len + 1;
new->qpos = len;
bbeg = NULL;
}
}
}
if (cant) {
freebrinfo(brbeg);
freebrinfo(brend);
brbeg = lastbrbeg = brend = lastbrend = NULL;
nbrbeg = nbrend = 0;
} else {
if (p == curs && bbeg) {
Brinfo new;
int len = bend - bbeg;
new = (Brinfo) zalloc(sizeof(*new));
nbrbeg++;
new->next = NULL;
if (lastbrbeg)
lastbrbeg->next = new;
else
brbeg = new;
lastbrbeg = new;
new->str = dupstrpfx(bbeg, len);
new->str = ztrdup(quotename(new->str));
untokenize(new->str);
new->pos = begi;
*dbeg = '\0';
new->qpos = strlen(quotename(predup));
*dbeg = '{';
boffs -= len;
memmove(dbeg, dbeg + len, 1+strlen(dbeg+len));
}
if (brend) {
Brinfo bp, prev = NULL;
int p, l;
for (bp = brend; bp; bp = bp->next) {
bp->prev = prev;
prev = bp;
p = bp->pos;
l = bp->qpos;
bp->pos = strlen(predup + p + l);
bp->qpos = strlen(quotename(predup + p + l));
memmove(predup + p, predup + p + l, 1+bp->pos);
}
}
if (hascom) {
if (lastp) {
char sav = *lastp;
*lastp = '\0';
untokenize(lastprebr = ztrdup(s));
*lastp = sav;
}
if ((lastpostbr = ztrdup(firsts)))
untokenize(lastpostbr);
}
zsfree(s);
s = ztrdup(predup);
offs = boffs;
}
}
zcontext_restore();
return (char *)s;
}
/* Insert the given string into the command line. If move is non-zero, *
* the cursor position is changed and len is the length of the string *
* to insert (if it is -1, the length is calculated here). *
* The last argument says if we should quote the string. */
/**/
mod_export int
inststrlen(char *str, int move, int len)
{
if (!len || !str)
return 0;
if (len == -1)
len = strlen(str);
if (zlemetaline != NULL) {
spaceinline(len);
strncpy(zlemetaline + zlemetacs, str, len);
if (move)
zlemetacs += len;
} else {
char *instr;
ZLE_STRING_T zlestr;
int zlelen;
instr = ztrduppfx(str, len);
zlestr = stringaszleline(instr, 0, &zlelen, NULL, NULL);
spaceinline(zlelen);
ZS_strncpy(zleline + zlecs, zlestr, zlelen);
free(zlestr);
zsfree(instr);
if (move)
zlecs += len;
}
return len;
}
/* Expand the current word. */
/**/
static int
doexpansion(char *s, int lst, int olst, int explincmd)
{
int ret = 1, first = 1;
LinkList vl;
char *ss, *ts;
pushheap();
vl = newlinklist();
ss = dupstring(s);
/* get_comp_string() leaves these quotes unchanged when they are
* inside parameter expansions. */
for (ts = ss; *ts; ts++)
if (*ts == '"')
*ts = Dnull;
else if (*ts == '\'')
*ts = Snull;
addlinknode(vl, ss);
prefork(vl, 0, NULL);
if (errflag)
goto end;
if (lst == COMP_LIST_EXPAND || lst == COMP_EXPAND) {
int ng = opts[NULLGLOB];
opts[NULLGLOB] = 1;
globlist(vl, PREFORK_NO_UNTOK);
opts[NULLGLOB] = ng;
}
if (errflag)
goto end;
if (empty(vl) || !*(char *)peekfirst(vl))
goto end;
if (peekfirst(vl) == (void *) ss ||
(olst == COMP_EXPAND_COMPLETE &&
!nextnode(firstnode(vl)) && *s == Tilde &&
(ss = dupstring(s), filesubstr(&ss, 0)) &&
!strcmp(ss, (char *)peekfirst(vl)))) {
/* If expansion didn't change the word, try completion if *
* expandorcomplete was called, otherwise, just beep. */
if (lst == COMP_EXPAND_COMPLETE)
docompletion(s, COMP_COMPLETE, explincmd);
goto end;
}
if (lst == COMP_LIST_EXPAND) {
/* Only the list of expansions was requested. Restore the
* command line. */
zlemetacs = 0;
foredel(zlemetall, CUT_RAW);
spaceinline(origll);
memcpy(zlemetaline, origline, origll);
zlemetacs = origcs;
ret = listlist(vl);
showinglist = 0;
goto end;
}
/* Remove the current word and put the expansions there. */
zlemetacs = wb;
foredel(we - wb, CUT_RAW);
while ((ss = (char *)ugetnode(vl))) {
ret = 0;
ss = quotename(ss);
untokenize(ss);
inststr(ss);
if (nonempty(vl) || !first) {
spaceinline(1);
zlemetaline[zlemetacs++] = ' ';
}
first = 0;
}
end:
popheap();
return ret;
}
/**/
static int
docompletion(char *s, int lst, int incmd)
{
struct compldat dat;
dat.s = s;
dat.lst = lst;
dat.incmd = incmd;
return runhookdef(COMPLETEHOOK, (void *) &dat);
}
/*
* Return the length of the common prefix of s and t.
* s and t are both metafied; the length returned is a raw byte count
* into both strings, excluding any common bytes that form less than
* a complete wide character.
*/
/**/
mod_export int
pfxlen(char *s, char *t)
{
int i = 0;
#ifdef MULTIBYTE_SUPPORT
wchar_t wc;
mbstate_t mbs;
size_t cnt;
int lasti = 0;
char inc;
memset(&mbs, 0, sizeof mbs);
while (*s) {
if (*s == Meta) {
if (*t != Meta || t[1] != s[1])
break;
inc = s[1] ^ 32;
i += 2;
s += 2;
t += 2;
} else {
if (*s != *t)
break;
inc = *s;
i++;
s++;
t++;
}
cnt = mbrtowc(&wc, &inc, 1, &mbs);
if (cnt == MB_INVALID) {
/* error */
break;
}
if (cnt != MB_INCOMPLETE) {
/* successfully found complete character, record position */
lasti = i;
}
/* Otherwise, not found a complete character: keep trying. */
}
return lasti;
#else
while (*s && *s == *t)
s++, t++, i++;
return i;
#endif
}
/* Return the length of the common suffix of s and t. */
#if 0
static int
sfxlen(char *s, char *t)
{
if (*s && *t) {
int i = 0;
char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1;
while (s2 >= s && t2 >= t && *s2 == *t2)
s2--, t2--, i++;
return i;
} else
return 0;
}
#endif
/* This is used to print the strings (e.g. explanations). *
* It returns the number of lines printed. */
/**/
mod_export int
printfmt(char *fmt, int n, int dopr, int doesc)
{
char *p = fmt, nc[DIGBUFSIZE];
int l = 0, cc = 0, b = 0, s = 0, u = 0, m;
MB_METACHARINIT();
for (; *p; ) {
/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
if (doesc && *p == '%') {
int arg = 0, is_fg;
zattr atr;
if (idigit(*++p))
arg = zstrtol(p, &p, 10);
if (*p) {
m = 0;
switch (*p) {
case '%':
if (dopr)
putc('%', shout);
cc++;
break;
case 'n':
sprintf(nc, "%d", n);
if (dopr)
fputs(nc, shout);
cc += MB_METASTRWIDTH(nc);
break;
case 'B':
b = 1;
if (dopr)
tcout(TCBOLDFACEBEG);
break;
case 'b':
b = 0; m = 1;
if (dopr)
tcout(TCALLATTRSOFF);
break;
case 'S':
s = 1;
if (dopr)
tcout(TCSTANDOUTBEG);
break;
case 's':
s = 0; m = 1;
if (dopr)
tcout(TCSTANDOUTEND);
break;
case 'U':
u = 1;
if (dopr)
tcout(TCUNDERLINEBEG);
break;
case 'u':
u = 0; m = 1;
if (dopr)
tcout(TCUNDERLINEEND);
break;
case 'F':
case 'K':
is_fg = (*p == 'F');
if (p[1] == '{') {
p += 2;
atr = match_colour((const char **)&p, is_fg, 0);
if (*p != '}')
p--;
} else
atr = match_colour(NULL, is_fg, arg);
if (atr != TXT_ERROR)
set_colour_attribute(atr, is_fg ? COL_SEQ_FG :
COL_SEQ_BG, 0);
break;
case 'f':
set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, 0);
break;
case 'k':
set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, 0);
break;
case '{':
if (arg)
cc += arg;
for (p++; *p && (*p != '%' || p[1] != '}'); p++) {
if (*p == Meta) {
p++;
if (dopr)
putc(*p ^ 32, shout);
}
else if (dopr)
putc(*p, shout);
}
if (*p)
p++;
else
p--;
break;
}
if (dopr && m) {
if (b)
tcout(TCBOLDFACEBEG);
if (s)
tcout(TCSTANDOUTBEG);
if (u)
tcout(TCUNDERLINEBEG);
}
} else
break;
p++;
} else {
if (*p == '\n') {
cc++;
if (dopr) {
if (tccan(TCCLEAREOL))
tcout(TCCLEAREOL);
else {
int s = zterm_columns - 1 - (cc % zterm_columns);
while (s-- > 0)
putc(' ', shout);
}
}
l += 1 + ((cc - 1) / zterm_columns);
cc = 0;
if (dopr)
putc('\n', shout);
p++;
} else {
convchar_t cchar;
int clen = MB_METACHARLENCONV(p, &cchar);
if (dopr) {
while (clen--) {
if (*p == Meta) {
p++;
clen--;
putc(*p++ ^ 32, shout);
} else
putc(*p++, shout);
}
} else
p += clen;
cc += WCWIDTH_WINT(cchar);
if (dopr && !(cc % zterm_columns))
fputs(" \010", shout);
}
}
}
if (dopr) {
if (!(cc % zterm_columns))
fputs(" \010", shout);
if (tccan(TCCLEAREOL))
tcout(TCCLEAREOL);
else {
int s = zterm_columns - 1 - (cc % zterm_columns);
while (s-- > 0)
putc(' ', shout);
}
}
/*
* Experiments suggest that at this point not subtracting 1 from
* cc is correct, i.e. if just misses wrapping we still add 1.
* (Why?)
*/
return l + (cc / zterm_columns);
}
/* This is used to print expansions. */
/**/
int
listlist(LinkList l)
{
int num = countlinknodes(l);
VARARR(char *, data, (num + 1));
LinkNode node;
char **p;
VARARR(int, lens, num);
VARARR(int, widths, zterm_columns);
int longest = 0, shortest = zterm_columns, totl = 0;
int len, ncols, nlines, tolast, col, i, max, pack = 0, *lenp;
for (node = firstnode(l), p = data; node; incnode(node), p++)
*p = (char *) getdata(node);
*p = NULL;
strmetasort((char **)data, SORTIT_IGNORING_BACKSLASHES |
(isset(NUMERICGLOBSORT) ? SORTIT_NUMERICALLY : 0), NULL);
for (p = data, lenp = lens; *p; p++, lenp++) {
len = *lenp = ZMB_nicewidth(*p) + 2;
if (len > longest)
longest = len;
if (len < shortest)
shortest = len;
totl += len;
}
if ((ncols = ((zterm_columns + 2) / longest))) {
int tlines = 0, tline, tcols = 0, maxlen, nth, width;
nlines = (num + ncols - 1) / ncols;
if (isset(LISTPACKED)) {
if (isset(LISTROWSFIRST)) {
int count, tcol, first, maxlines = 0, llines;
for (tcols = zterm_columns / shortest; tcols > ncols;
tcols--) {
for (nth = first = maxlen = width = maxlines =
llines = tcol = 0,
count = num;
count > 0; count--) {
if (!(nth % tcols))
llines++;
if (lens[nth] > maxlen)
maxlen = lens[nth];
nth += tcols;
tlines++;
if (nth >= num) {
if ((width += maxlen) >= zterm_columns)
break;
widths[tcol++] = maxlen;
maxlen = 0;
nth = ++first;
if (llines > maxlines)
maxlines = llines;
llines = 0;
}
}
if (nth < num) {
widths[tcol++] = maxlen;
width += maxlen;
}
if (!count && width < zterm_columns)
break;
}
if (tcols > ncols)
tlines = maxlines;
} else {
for (tlines = ((totl + zterm_columns) / zterm_columns);
tlines < nlines; tlines++) {
for (p = data, nth = tline = width =
maxlen = tcols = 0;
*p; nth++, p++) {
if (lens[nth] > maxlen)
maxlen = lens[nth];
if (++tline == tlines) {
if ((width += maxlen) >= zterm_columns)
break;
widths[tcols++] = maxlen;
maxlen = tline = 0;
}
}
if (tline) {
widths[tcols++] = maxlen;
width += maxlen;
}
if (nth == num && width < zterm_columns)
break;
}
}
if ((pack = (tlines < nlines))) {
nlines = tlines;
ncols = tcols;
}
}
} else {
nlines = 0;
for (p = data; *p; p++)
nlines += 1 + (strlen(*p) / zterm_columns);
}
/* Set the cursor below the prompt. */
trashzle();
tolast = ((zmult == 1) == !!isset(ALWAYSLASTPROMPT));
clearflag = (isset(USEZLE) && !termflags && tolast);
max = getiparam("LISTMAX");
if ((max && num > max) || (!max && nlines > zterm_lines)) {
int qup, l;
zsetterm();
l = (num > 0 ?
fprintf(shout, "zsh: do you wish to see all %d possibilities (%d lines)? ",
num, nlines) :
fprintf(shout, "zsh: do you wish to see all %d lines? ", nlines));
qup = ((l + zterm_columns - 1) / zterm_columns) - 1;
fflush(shout);
if (!getzlequery()) {
if (clearflag) {
putc('\r', shout);
tcmultout(TCUP, TCMULTUP, qup);
if (tccan(TCCLEAREOD))
tcout(TCCLEAREOD);
tcmultout(TCUP, TCMULTUP, nlnct);
} else
putc('\n', shout);
return 1;
}
if (clearflag) {
putc('\r', shout);
tcmultout(TCUP, TCMULTUP, qup);
if (tccan(TCCLEAREOD))
tcout(TCCLEAREOD);
} else
putc('\n', shout);
settyinfo(&shttyinfo);
}
lastlistlen = (clearflag ? nlines : 0);
if (ncols) {
if (isset(LISTROWSFIRST)) {
for (col = 1, p = data, lenp = lens; *p;
p++, lenp++, col++) {
nicezputs(*p, shout);
if (col == ncols) {
col = 0;
if (p[1])
putc('\n', shout);
} else {
if ((i = (pack ? widths[col - 1] : longest) - *lenp + 2) > 0)
while (i--)
putc(' ', shout);
}
}
} else {
char **f;
int *fl, line;
for (f = data, fl = lens, line = 0; line < nlines;
f++, fl++, line++) {
for (col = 1, p = f, lenp = fl; *p; col++) {
nicezputs(*p, shout);
if (col == ncols)
break;
if ((i = (pack ? widths[col - 1] : longest) - *lenp + 2) > 0)
while (i--)
putc(' ', shout);
for (i = nlines; i && *p; i--, p++, lenp++);
}
if (line + 1 < nlines)
putc('\n', shout);
}
}
} else {
for (p = data; *p; p++) {
nicezputs(*p, shout);
/* One column: newlines between elements, not after the last */
if (p[1])
putc('\n', shout);
}
}
if (clearflag) {
if ((nlines += nlnct - 1) < zterm_lines) {
tcmultout(TCUP, TCMULTUP, nlines);
showinglist = -1;
} else
clearflag = 0, putc('\n', shout);
} else
putc('\n', shout);
if (listshown)
showagain = 1;
return !num;
}
/* Expand the history references. */
/**/
int
doexpandhist(void)
{
char *ol;
int ne = noerrs, err, ona = noaliases;
UNMETACHECK();
pushheap();
metafy_line();
zle_save_positions();
ol = dupstring(zlemetaline);
expanding = 1;
excs = zlemetacs;
zlemetall = zlemetacs = 0;
zcontext_save();
/* We push ol as it will remain unchanged */
inpush(ol, 0, NULL);
strinbeg(1);
noaliases = 1;
noerrs = 1;
exlast = inbufct;
do {
ctxtlex();
} while (tok != ENDINPUT && tok != LEXERR);
if (tok == LEXERR)
lexstop = 0;
while (!lexstop)
hgetc();
/* We have to save errflags because it's reset in zcontext_restore. Since *
* noerrs was set to 1 errflag is true if there was a habort() which *
* means that the expanded string is unusable. */
err = errflag;
noerrs = ne;
noaliases = ona;
strinend();
inpop();
zcontext_restore();
expanding = 0;
if (!err) {
zlemetacs = excs;
if (strcmp(zlemetaline, ol)) {
zle_free_positions();
unmetafy_line();
/* For vi mode -- reset the beginning-of-insertion pointer *
* to the beginning of the line. This seems a little silly, *
* if we are, for example, expanding "exec !!". */
if (viinsbegin > findbol())
viinsbegin = findbol();
popheap();
return 1;
}
}
strcpy(zlemetaline, ol);
zle_restore_positions();
unmetafy_line();
popheap();
return 0;
}
/**/
void
fixmagicspace(void)
{
lastchar = ' ';
#ifdef MULTIBYTE_SUPPORT
/*
* This is redundant if the multibyte encoding extends ASCII,
* since lastchar is a full character, but it's safer anyway...
*/
lastchar_wide = L' ';
lastchar_wide_valid = 1;
#endif
}
/**/
int
magicspace(char **args)
{
ZLE_STRING_T bangq;
ZLE_CHAR_T zlebangchar[1];
int ret;
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
#endif
fixmagicspace();
#ifdef MULTIBYTE_SUPPORT
/*
* Use mbrtowc() here for consistency and to ensure the
* state is initialised properly. bangchar is unsigned char,
* but must be ASCII, so we simply cast the pointer.
*/
memset(&mbs, 0, sizeof(mbs));
if (mbrtowc(zlebangchar, (char *)&bangchar, 1, &mbs) == MB_INVALID)
return selfinsert(args);
#else
zlebangchar[0] = bangchar;
#endif
for (bangq = zleline; bangq < zleline + zlell; bangq++) {
if (*bangq != zlebangchar[0])
continue;
if (bangq[1] == ZWC('"') &&
(bangq == zleline || bangq[-1] == ZWC('\\')))
break;
}
if (!(ret = selfinsert(args)) &&
(!bangq || bangq + 2 > zleline + zlecs))
doexpandhist();
return ret;
}
/**/
int
expandhistory(UNUSED(char **args))
{
if (!doexpandhist())
return 1;
return 0;
}
static int cmdwb, cmdwe;
/**/
static char *
getcurcmd(void)
{
int curlincmd;
char *s = NULL;
zcontext_save();
lexflags = LEXFLAGS_ZLE;
metafy_line();
inpush(dupstrspace(zlemetaline), 0, NULL);
strinbeg(1);
pushheap();
do {
curlincmd = incmdpos;
ctxtlex();
if (tok == ENDINPUT || tok == LEXERR)
break;
if (tok == STRING && curlincmd) {
zsfree(s);
s = ztrdup(tokstr);
cmdwb = zlemetall - wordbeg;
cmdwe = zlemetall + 1 - inbufct;
}
}
while (tok != ENDINPUT && tok != LEXERR && lexflags);
popheap();
strinend();
inpop();
errflag &= ~ERRFLAG_ERROR;
unmetafy_line();
zcontext_restore();
return s;
}
/* Run '$WIDGET $commandword' and then restore the command-line using push-line.
*/
/**/
int
processcmd(UNUSED(char **args))
{
char *s;
int m = zmult, na = noaliases;
noaliases = 1;
s = getcurcmd();
noaliases = na;
if (!s)
return 1;
zmult = 1;
pushline(zlenoargs);
zmult = m;
inststr(bindk->nam);
inststr(" ");
untokenize(s);
inststr(quotename(s));
zsfree(s);
done = 1;
return 0;
}
/**/
int
expandcmdpath(UNUSED(char **args))
{
/*
* zleline is not metafied for most of this function
* (that happens within getcurcmd()).
*/
int oldcs = zlecs, na = noaliases, strll;
char *s, *str;
ZLE_STRING_T zlestr;
noaliases = 1;
s = getcurcmd();
noaliases = na;
if (!s)
return 1;
if (cmdwb < 0 || cmdwe < cmdwb) {
zsfree(s);
return 1;
}
str = findcmd(s, 1, 0);
zsfree(s);
if (!str)
return 1;
zlecs = cmdwb;
foredel(cmdwe - cmdwb, CUT_RAW);
zlestr = stringaszleline(str, 0, &strll, NULL, NULL);
spaceinline(strll);
ZS_strncpy(zleline + zlecs, zlestr, strll);
free(zlestr);
zlecs = oldcs;
if (zlecs >= cmdwe - 1)
zlecs += cmdwe - cmdwb + strlen(str);
if (zlecs > zlell)
zlecs = zlell;
return 0;
}
/* Extra function added by AR Iano-Fletcher. */
/* This is a expand/complete in the vein of wash. */
/**/
int
expandorcompleteprefix(char **args)
{
int ret;
comppref = 1;
ret = expandorcomplete(args);
if (zlecs && zleline[zlecs - 1] == ZWC(' '))
makesuffixstr(NULL, "\\-", 0);
comppref = 0;
return ret;
}
/**/
int
endoflist(UNUSED(char **args))
{
if (lastlistlen > 0) {
int i;
clearflag = 0;
trashzle();
for (i = lastlistlen; i > 0; i--)
putc('\n', shout);
showinglist = lastlistlen = 0;
if (sfcontext)
zrefresh();
return 0;
}
return 1;
}