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

3074 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, INAMESPC, 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, INAMESPC, 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);
untokenize(cmdstr);
remnulargs(cmdstr);
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];
/* The only place we complete namespaces, see IIDENT below */
s = itype_end(tt, INAMESPC, 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();
/* All further uses of IIDENT in this file should change to *
* INAMESPC if this case is changed. Too ugly to risk now. */
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. See above regarding INAMESPC */
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;
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) {
switch (*p) {
case '%':
if (dopr) {
applytextattributes(0);
putc('%', shout);
}
cc++;
break;
case 'n':
sprintf(nc, "%d", n);
if (dopr) {
applytextattributes(0);
fputs(nc, shout);
}
cc += MB_METASTRWIDTH(nc);
break;
case 'B':
if (dopr)
tsetattrs(TXTBOLDFACE);
break;
case 'b':
if (dopr)
tunsetattrs(TXTBOLDFACE);
break;
case 'S':
if (dopr)
tsetattrs(TXTSTANDOUT);
break;
case 's':
if (dopr)
tunsetattrs(TXTSTANDOUT);
break;
case 'U':
if (dopr)
tsetattrs(TXTUNDERLINE);
break;
case 'u':
if (dopr)
tunsetattrs(TXTUNDERLINE);
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)
tsetattrs(atr);
break;
case 'f':
tunsetattrs(TXTFGCOLOUR);
break;
case 'k':
tunsetattrs(TXTBGCOLOUR);
break;
case 'H':
if (p[1] == '{') {
p = parsehighlight(p + 2, '}', &atr);
--p;
if (atr != TXT_ERROR)
treplaceattrs(atr);
}
break;
case '{':
if (arg)
cc += arg;
if (dopr)
applytextattributes(0);
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;
}
} else
break;
p++;
} else {
if (*p == '\n') {
cc++;
if (dopr) {
applytextattributes(0);
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) {
applytextattributes(0);
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) {
treplaceattrs(0);
applytextattributes(0);
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;
}