/* * 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. The technique used is quite simple: on * * entry to the expansion/completion system, we metafy the line in * * place, adjusting ll and cs to match. All completion and expansion * * is done on the metafied line. Immediately before returning, the * * line is unmetafied again, changing ll and cs back. (ll and cs 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. */ #define inststr(X) inststrlen((X),1,-1) /* 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. May be == 2 to force * * menu completion even if using different widgets. */ /**/ 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 completing in quoted from. */ static char *qword; /* 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). */ /**/ static int usetab(void) { unsigned char *s = line + cs - 1; if (keybuf[0] != '\t' || keybuf[1]) return 0; for (; s >= line && *s != '\n'; s--) if (*s != '\t' && *s != ' ') 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 (c == '\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 (c == '\t' && usetab()) return selfinsert(args); else return docomplete(COMP_COMPLETE); } /**/ int listchoices(char **args) { usemenu = !!isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); wouldinstab = 0; return docomplete(COMP_LIST_COMPLETE); } /**/ int spellword(char **args) { usemenu = useglob = 0; wouldinstab = 0; return docomplete(COMP_SPELL); } /**/ int deletecharorlist(char **args) { usemenu = !!isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); wouldinstab = 0; if (cs != ll) { fixsuffix(); invalidatelist(); return deletechar(args); } return docomplete(COMP_LIST_COMPLETE); } /**/ int expandword(char **args) { usemenu = useglob = 0; wouldinstab = 0; if (c == '\t' && usetab()) return selfinsert(args); else return docomplete(COMP_EXPAND); } /**/ int expandorcomplete(char **args) { usemenu = !!isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); wouldinstab = 0; if (c == '\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 (c == '\t' && usetab()) return selfinsert(args); else return docomplete(COMP_EXPAND_COMPLETE); } /**/ int listexpand(char **args) { usemenu = !!isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); wouldinstab = 0; return docomplete(COMP_LIST_EXPAND); } /**/ mod_export int reversemenucomplete(char **args) { wouldinstab = 0; if (!menucmp) return menucomplete(args); runhookdef(REVERSEMENUHOOK, NULL); return 0; } /**/ 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; /* 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 */ /**/ mod_export int insubscr; /* Parameter pointer for completing keys of an assoc array. */ /**/ mod_export Param keypm; /* 1 if we are completing in a quoted string (or inside `...`) */ /**/ mod_export int instring, inbackt; /* Convenience macro for calling bslashquote() (formerly quotename()). * * This uses the instring variable above. */ #define quotename(s, e) bslashquote(s, e, 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 (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) { 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; 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) { /* This is really a parameter expression (not $(...) or $[...]). */ char *b = p + 1, *e = b; int n = 0, br = 1, nest = 0; if (*b == Inbrace) { char *tb = b; /* If this is a ${...}, see if we are before the '}'. */ if (!skipparens(Inbrace, Outbrace, &tb)) return NULL; /* Ignore the possible (...) flags. */ b++, br++; n = skipparens(Inpar, Outpar, &b); for (tb = p - 1; tb > s && *tb != Outbrace && *tb != Inbrace; tb--); if (tb > s && *tb == Inbrace && (tb[-1] == String || *tb == Qstring)) nest = 1; } /* Ignore the stuff before the parameter name. */ for (; *b; b++) if (*b != '^' && *b != Hat && *b != '=' && *b != Equals && *b != '~' && *b != Tilde) break; if (*b == '#' || *b == Pound || *b == '+') b++; e = b; if (br) { while (*e == 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 if (iident(*e)) while (iident(*e)) e++; /* 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)", NULL, 0); return 1; } active = 1; comprecursive = 0; if (undoing) setlastline(); /* 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) load_module("zsh/compctl"); 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 = cs; origline = dupstring((char *) line); origcs = cs; origll = ll; if (!isfirstln && chline != NULL) { /* If we are completing in a multi-line buffer (which was not * * taken from the history), we have to prepend the stuff saved * * in chline to the contents of line. */ ol = dupstring((char *)line); /* Make sure that chline is zero-terminated. */ *hptr = '\0'; cs = 0; inststr(chline); chl = cs; cs += ocs; } else ol = NULL; inwhat = IN_NOTHING; qword = NULL; 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(); DPUTS(wb < 0 || cs < wb || cs > we, "BUG: 0 <= wb <= cs <= we is not true!"); 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((char *) line + wb, (char *) line)) viinsbegin = ztrsub((char *) line + wb, (char *) line); /* If we added chline to the line buffer, reset the original contents. */ if (ol) { cs -= chl; wb -= chl; we -= chl; if (wb < 0) { strcpy((char *) line, ol); ll = strlen((char *) line); cs = ocs; popheap(); unmetafy_line(); zsfree(s); zsfree(qword); active = 0; return 1; } ocs = cs; cs = 0; foredel(chl); cs = ocs; } freeheap(); /* Save the lexer state, in case the completion code uses the lexer * * somewhere (e.g. when processing a compctl -s flag). */ lexsave(); 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)) 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] != Inbrack) { if (*++q == Inbrace) { if (! skipparens(Inbrace, Outbrace, &q) && q == s + cs - 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 while (iident(*q)) q++; sav = *q; *q = '\0'; if (cs - 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 + cs - 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; cs = wb; foredel(we - wb); 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((char *)line) : (char *)line; int ocs = cs, 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, (char *)line)) { cs = ocs; errflag = 0; 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, (char *)line)) { /* 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. */ cs = 0; foredel(ll); spaceinline(origll); memcpy(line, origline, origll); cs = origcs; } } } else /* Just do completion. */ ret = docompletion(s, lst, lincmd); zsfree(s); } else ret = 1; /* Reset the lexer state, pop the heap. */ lexrestore(); popheap(); zsfree(qword); unmetafy_line(); dat[0] = lst; dat[1] = ret; runhookdef(AFTERCOMPLETEHOOK, (void *) dat); active = 0; 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 (!line[cs] || line[cs] == '\n' || (iblank(line[cs]) && (!cs || line[cs-1] != '\\')) || line[cs] == ')' || line[cs] == '`' || line[cs] == '}' || line[cs] == ';' || line[cs] == '|' || line[cs] == '&' || (instring && (line[cs] == '"' || line[cs] == '\'')) || (addspace = (comppref && !iblank(line[cs])))) { *ptmp = (char *)line; line = (unsigned char *)zhalloc(strlen((char *)line) + 3 + addspace); memcpy(line, *ptmp, cs); line[cs] = 'x'; if (addspace) line[cs+1] = ' '; strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs); 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((char *)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. Note that ll and cs are translated. They *must* be * * called in matching pairs, around all the expansion/completion code. * * Currently, there are four pairs: in history expansion, in the main * * completion function, and one in each of the middle-of-menu-completion * * functions (there's one for each direction). */ /**/ mod_export void metafy_line(void) { int len = ll; char *s; for (s = (char *) line; s < (char *) line + ll;) if (imeta(*s++)) len++; sizeline(len); (void) metafy((char *) line, ll, META_NOALLOC); ll = len; cs = metalen((char *) line, cs); } /**/ mod_export void unmetafy_line(void) { cs = ztrsub((char *) line + cs, (char *) line); (void) unmetafy((char *) line, &ll); } /* 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) { 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) { int t0, tt0, i, j, k, cp, rd, sl, ocs, ins, oins, ia, parct, varq = 0; int ona = noaliases; char *s = NULL, *linptr, *tmp, *p, *tt = NULL; freebrinfo(brbeg); freebrinfo(brend); brbeg = lastbrbeg = brend = lastbrend = NULL; nbrbeg = nbrend = 0; zsfree(lastprebr); zsfree(lastpostbr); lastprebr = lastpostbr = 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, p = (char *)line; p < (char *)line + cs; p++) if (*p == '`' && !(k & 1)) i++; else if (*p == '\"' && !(k & 1) && !(i & 1)) j++; else if (*p == '\'' && !(j & 1)) k++; else if (*p == '\\' && p[1] && !(k & 1)) p++; inbackt = (i & 1); instring = 0; addx(&tmp); linptr = (char *)line; pushheap(); start: inwhat = IN_NOTHING; /* Now set up the lexer and start it. */ parbegin = parend = -1; lincmd = incmdpos; linredir = inredir; zsfree(cmdstr); cmdstr = NULL; zsfree(varname); varname = NULL; insubscr = 0; zleparse = 1; clwpos = -1; lexsave(); inpush(dupstrspace((char *) linptr), 0, NULL); strinbeg(0); i = tt0 = cp = rd = ins = oins = linarr = parct = ia = 0; /* 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 { lincmd = ((incmdpos && !ins && !incond) || (oins == 2 && i == 2) || (ins == 3 && i == 1)); linredir = (inredir && !ins); oins = ins; /* Get the next token. */ if (linarr) incmdpos = 0; 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 linarr = 0; } if (inredir) rdstr = tokstrings[tok]; if (tok == DINPAR) tokstr = NULL; /* We reached the end. */ if (tok == ENDINPUT) break; if ((ins && (tok == DO || tok == SEPER)) || (ins == 2 && i == 2) || (ins == 3 && i == 3) || tok == BAR || tok == AMPER || tok == BARAMP || tok == AMPERBANG || ((tok == DBAR || tok == DAMPER) && !incond)) { /* 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; /* Otherwise reset the variables we are collecting data in. */ i = tt0 = cp = rd = ins = 0; } if (lincmd && (tok == STRING || tok == FOR || tok == FOREACH || tok == SELECT || tok == REPEAT || tok == CASE)) { /* 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)); zsfree(cmdstr); cmdstr = ztrdup(tokstr); i = 0; } if (!zleparse && !tt0) { /* This is done when the lexer reached the word the cursor is on. */ tt = tokstr ? dupstring(tokstr) : NULL; /* If we added a `x', remove it. */ if (addedx && tt) chuck(tt + cs - wb); tt0 = tok; /* Store the number of this word. */ clwpos = i; cp = lincmd; rd = linredir; ia = linarr; if (inwhat == IN_NOTHING && incond) inwhat = IN_COND; } else if (linredir) continue; if (incond) { if (tok == DBAR) tokstr = "||"; else if (tok == DAMPER) tokstr = "&&"; } if (!tokstr) continue; /* Hack to allow completion after `repeat n do'. */ if (oins == 2 && !i && !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 (i + 1 == clwsize) { int n; clwords = (char **)realloc(clwords, (clwsize *= 2) * sizeof(char *)); for(n = clwsize; --n > i; ) clwords[n] = NULL; } zsfree(clwords[i]); /* And store the current token string. */ clwords[i] = ztrdup(tokstr); sl = strlen(tokstr); /* Sometimes the lexer gives us token strings ending with * * spaces we delete the spaces. */ while (sl && clwords[i][sl - 1] == ' ' && (sl < 2 || (clwords[i][sl - 2] != Bnull && clwords[i][sl - 2] != Meta))) clwords[i][--sl] = '\0'; /* If this is the word the cursor is in and we added a `x', * * remove it. */ if (clwpos == i++ && addedx) chuck(&clwords[i - 1][((cs - wb) >= sl) ? (sl - 1) : (cs - wb)]); } while (tok != LEXERR && tok != ENDINPUT && (tok != SEPER || (zleparse && !tt0))); /* Calculate the number of words stored in the clwords array. */ clwnum = (tt || !i) ? i : i - 1; zsfree(clwords[clwnum]); clwords[clwnum] = NULL; t0 = tt0; if (ia) { lincmd = linredir = 0; inwhat = IN_ENV; } else { lincmd = cp; linredir = rd; } strinend(); inpop(); errflag = zleparse = 0; if (parbegin != -1) { /* We are in command or process substitution if we are not in * a $((...)). */ if (parend >= 0 && !tmp) line = (unsigned char *) dupstring(tmp = (char *)line); linptr = (char *) line + ll + addedx - parbegin + 1; if ((linptr - (char *) line) < 3 || *linptr != '(' || linptr[-1] != '(' || linptr[-2] != '$') { if (parend >= 0) { ll -= parend; line[ll + addedx] = '\0'; } lexrestore(); tt = NULL; goto start; } } if (inwhat == IN_MATH) s = NULL; else if (!t0 || t0 == ENDINPUT) { /* There was no word (empty line). */ s = ztrdup(""); we = wb = cs; clwpos = clwnum; t0 = STRING; } else if (t0 == STRING) { /* We found a simple string. */ s = ztrdup(clwords[clwpos]); } else if (t0 == ENVSTRING) { char sav; /* The cursor was inside a parameter assignment. */ if (varq) tt = clwords[clwpos]; for (s = tt; iident(*s); s++); sav = *s; *s = '\0'; zsfree(varname); varname = ztrdup(tt); *s = sav; if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) { s = NULL; inwhat = IN_MATH; if ((keypm = (Param) paramtab->getnode(paramtab, varname)) && (keypm->flags & PM_HASHED)) insubscr = 2; else insubscr = 1; } else if (*s == '=' && cs > wb + (s - tt)) { s++; wb += s - tt; t0 = STRING; s = ztrdup(s); inwhat = IN_ENV; } lincmd = 1; } if (we > ll) we = ll; tt = (char *)line; if (tmp) { line = (unsigned char *)tmp; ll = strlen((char *)line); } if (t0 != STRING && inwhat != IN_MATH) { if (tmp) { tmp = NULL; linptr = (char *)line; lexrestore(); addedx = 0; goto start; } noaliases = ona; lexrestore(); 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) { int i = 0; char *nnb = (iident(*s) ? s : s + 1), *nb = NULL, *ne = NULL; for (tt = s; ++tt < s + cs - wb;) if (*tt == Inbrack) { i++; nb = nnb; ne = tt; } else if (i && *tt == Outbrack) i--; else if (!iident(*tt)) nnb = tt + 1; 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->flags & PM_HASHED)) insubscr = 2; } } } if (inwhat == IN_MATH) { if (compfunc || insubscr == 2) { int lev; char *p; for (wb = cs - 1, lev = 0; wb > 0; wb--) if (line[wb] == ']' || line[wb] == ')') lev++; else if (line[wb] == '[') { if (!lev--) break; } else if (line[wb] == '(') { if (!lev && line[wb - 1] == '(') break; if (lev) lev--; } p = (char *) line + wb; wb++; if (wb && (*p == '[' || *p == '(') && !skipparens(*p, (*p == '[' ? ']' : ')'), &p)) { we = (p - (char *) line) - 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. */ for (we = cs; iident(line[we]); we++); for (wb = cs; --wb >= 0 && iident(line[wb]);); wb++; } zsfree(s); s = zalloc(we - wb + 1); strncpy(s, (char *) line + wb, we - wb); s[we - wb] = '\0'; if (wb > 2 && line[wb - 1] == '[' && iident(line[wb - 2])) { int i = wb - 3; unsigned char sav = line[wb - 1]; while (i >= 0 && iident(line[i])) i--; line[wb - 1] = '\0'; zsfree(varname); varname = ztrdup((char *) line + i + 1); line[wb - 1] = sav; if ((keypm = (Param) paramtab->getnode(paramtab, varname)) && (keypm->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. */ qword = ztrdup(s); offs = cs - 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) && !has_real_token(s + 1)) { char *q = (*s == Snull ? "'" : "\""), *n = tricat(qipre, q, ""); int sl = strlen(s); instring = (*s == Snull ? 1 : 2); zsfree(qipre); qipre = n; if (sl > 1 && s[sl - 1] == *s) { n = tricat(q, qisuf, ""); zsfree(qisuf); qisuf = n; } autoq = ztrdup(q); if (instring == 2) { for (q = s; *q; q++) if (*q == '\\' && q[1] == '!') *q = Bnull; } } /* While building the quoted form, we also clean up the command line. */ for (p = s, tt = qword, i = wb, j = 0; *p; p++, tt++, i++) if (INULL(*p)) { if (i < cs) offs--; if (*p == Snull && isset(RCQUOTES)) j = 1-j; if (p[1] || *p != Bnull) { if (*p == Bnull) { *tt = '\\'; if (cs == i + 1) cs++, offs++; } else { ocs = cs; cs = i; foredel(1); chuck(tt--); if ((cs = ocs) > i--) cs--; we--; } } else { ocs = cs; *tt = '\0'; cs = we; backdel(1); if (ocs == we) cs = we - 1; else cs = ocs; we--; } chuck(p--); } else if (j && *p == '\'' && i < cs) offs--; zsfree(origword); origword = ztrdup(s); if (!isset(IGNOREBRACES)) { /* Try and deal with foo{xxx etc. */ char *curs = s + (isset(COMPLETEINWORD) ? offs : 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 { 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 { if (idigit(*tp)) while (idigit(*tp)) tp++; else if (iident(*tp)) while (iident(*tp)) tp++; 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) { cant = 1; break; } if (*p == Inbrace) { char *tp = p; if (!skipparens(Inbrace, Outbrace, &tp)) { i += tp - p - 1; dp += tp - p - 1; p = tp - 1; continue; } 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(bslashquote(new->str, NULL, instring)); untokenize(new->str); new->pos = begi; *dbeg = '\0'; new->qpos = strlen(bslashquote(predup, NULL, instring)); *dbeg = '{'; i -= len; boffs -= len; strcpy(dbeg, 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 { if (*p == Inbrace) { char *tp = p; if (!skipparens(Inbrace, Outbrace, &tp)) { i += tp - p - 1; dp += tp - p - 1; p = tp - 1; continue; } cant = 1; break; } if (p == curs) { 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(bslashquote(new->str, NULL, instring)); untokenize(new->str); new->pos = begi; *dbeg = '\0'; new->qpos = strlen(bslashquote(predup, NULL, instring)); *dbeg = '{'; i -= len; boffs -= len; strcpy(dbeg, dbeg + len); dp -= len; } bbeg = NULL; } if (*p == Comma) { if (!bbeg) bbeg = p; hascom = 2; } else if (*p == Outbrace) { 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(bslashquote(new->str, NULL, instring)); 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(bslashquote(new->str, NULL, instring)); untokenize(new->str); new->pos = begi; *dbeg = '\0'; new->qpos = strlen(bslashquote(predup, NULL, instring)); *dbeg = '{'; boffs -= len; strcpy(dbeg, 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(bslashquote(predup + p + l, NULL, instring)); strcpy(predup + p, predup + p + l); } } 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; } } lexrestore(); 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); spaceinline(len); strncpy((char *)(line + cs), str, len); if (move) cs += 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); if (errflag) goto end; if (lst == COMP_LIST_EXPAND || lst == COMP_EXPAND) { int ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; globlist(vl, 1); 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. */ cs = 0; foredel(ll); spaceinline(origll); memcpy(line, origline, origll); cs = origcs; ret = listlist(vl); showinglist = 0; goto end; } /* Remove the current word and put the expansions there. */ cs = wb; foredel(we - wb); while ((ss = (char *)ugetnode(vl))) { ret = 0; ss = quotename(ss, NULL); untokenize(ss); inststr(ss); #if 0 if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) || (cs && line[cs-1] != '/')) { #endif if (nonempty(vl) || !first) { spaceinline(1); line[cs++] = ' '; } 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. */ /**/ mod_export int pfxlen(char *s, char *t) { int i = 0; while (*s && *s == *t) s++, t++, i++; return i; } /* 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 strcmp with ignoring backslashes. */ /**/ mod_export int strbpcmp(char **aa, char **bb) { char *a = *aa, *b = *bb; while (*a && *b) { if (*a == '\\') a++; if (*b == '\\') b++; if (*a != *b) break; if (*a) a++; if (*b) b++; } if (isset(NUMERICGLOBSORT) && (idigit(*a) || idigit(*b))) { for (; a > *aa && idigit(a[-1]); a--, b--); if (idigit(*a) && idigit(*b)) { while (*a == '0') a++; while (*b == '0') b++; for (; idigit(*a) && *a == *b; a++, b++); if (idigit(*a) || idigit(*b)) { int cmp = (int) STOUC(*a) - (int) STOUC(*b); while (idigit(*a) && idigit(*b)) a++, b++; if (idigit(*a) && !idigit(*b)) return 1; if (idigit(*b) && !idigit(*a)) return -1; return cmp; } } } return (int)(*a - *b); } /* 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; for (; *p; p++) { /* Handle the `%' stuff (%% == %, %n == ). */ if (doesc && *p == '%') { if (*++p) { m = 0; switch (*p) { case '%': if (dopr) putc('%', shout); cc++; break; case 'n': sprintf(nc, "%d", n); if (dopr) fprintf(shout, nc); cc += strlen(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 '{': for (p++; *p && (*p != '%' || p[1] != '}'); p++) 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; } else { cc++; if (*p == '\n') { if (dopr) { if (tccan(TCCLEAREOL)) tcout(TCCLEAREOL); else { int s = columns - 1 - (cc % columns); while (s-- > 0) putc(' ', shout); } } l += 1 + ((cc - 1) / columns); cc = 0; } if (dopr) { putc(*p, shout); if (!(cc % columns)) fputs(" \010", shout); } } } if (dopr) { if (!(cc % columns)) fputs(" \010", shout); if (tccan(TCCLEAREOL)) tcout(TCCLEAREOL); else { int s = columns - 1 - (cc % columns); while (s-- > 0) putc(' ', shout); } } return l + (cc / 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, columns); int longest = 0, shortest = 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; qsort((void *) data, num, sizeof(char *), (int (*) _((const void *, const void *))) strbpcmp); for (p = data, lenp = lens; *p; p++, lenp++) { len = *lenp = niceztrlen(*p) + 2; if (len > longest) longest = len; if (len < shortest) shortest = len; totl += len; } if ((ncols = ((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 = 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) >= 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 < columns) break; } if (tcols > ncols) tlines = maxlines; } else { for (tlines = ((totl + columns) / 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) >= columns) break; widths[tcols++] = maxlen; maxlen = tline = 0; } } if (tline) { widths[tcols++] = maxlen; width += maxlen; } if (nth == num && width < columns) break; } } if ((pack = (tlines < nlines))) { nlines = tlines; ncols = tcols; } } } else { nlines = 0; for (p = data; *p; p++) nlines += 1 + (strlen(*p) / 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 > 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 + columns - 1) / columns) - 1; fflush(shout); if (getzlequery(1) != 'y') { 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); putc('\n', shout); } } if (clearflag) { if ((nlines += nlnct - 1) < 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) { unsigned char *ol; int oll, ocs, ne = noerrs, err, ona = noaliases; pushheap(); metafy_line(); oll = ll; ocs = cs; ol = (unsigned char *)dupstring((char *)line); expanding = 1; excs = cs; ll = cs = 0; lexsave(); /* We push ol as it will remain unchanged */ inpush((char *) ol, 0, NULL); strinbeg(1); noaliases = 1; noerrs = 1; exlast = inbufct; do { ctxtlex(); } while (tok != ENDINPUT && tok != LEXERR); while (!lexstop) hgetc(); /* We have to save errflags because it's reset in lexrestore. 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(); zleparse = 0; lexrestore(); expanding = 0; if (!err) { cs = excs; if (strcmp((char *)line, (char *)ol)) { 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((char *)line, (char *)ol); ll = oll; cs = ocs; unmetafy_line(); popheap(); return 0; } /**/ int magicspace(char **args) { int ret; c = ' '; if (!(ret = selfinsert(args))) doexpandhist(); return ret; } /**/ int expandhistory(char **args) { if (!doexpandhist()) return 1; return 0; } static int cmdwb, cmdwe; /**/ static char * getcurcmd(void) { int curlincmd; char *s = NULL; zleparse = 2; lexsave(); metafy_line(); inpush(dupstrspace((char *) line), 0, NULL); unmetafy_line(); strinbeg(1); pushheap(); do { curlincmd = incmdpos; ctxtlex(); if (tok == ENDINPUT || tok == LEXERR) break; if (tok == STRING && curlincmd) { zsfree(s); s = ztrdup(tokstr); cmdwb = ll - wordbeg; cmdwe = ll + 1 - inbufct; } } while (tok != ENDINPUT && tok != LEXERR && zleparse); popheap(); strinend(); inpop(); errflag = zleparse = 0; lexrestore(); return s; } /**/ int processcmd(char **args) { char *s; int m = zmult; s = getcurcmd(); if (!s) return 1; zmult = 1; pushline(zlenoargs); zmult = m; inststr(bindk->nam); inststr(" "); untokenize(s); inststr(quotename(s, NULL)); zsfree(s); done = 1; return 0; } /**/ int expandcmdpath(char **args) { int oldcs = cs, na = noaliases; char *s, *str; noaliases = 1; s = getcurcmd(); noaliases = na; if (!s || cmdwb < 0 || cmdwe < cmdwb) return 1; str = findcmd(s, 1); zsfree(s); if (!str) return 1; cs = cmdwb; foredel(cmdwe - cmdwb); spaceinline(strlen(str)); strncpy((char *)line + cs, str, strlen(str)); cs = oldcs; if (cs >= cmdwe - 1) cs += cmdwe - cmdwb + strlen(str); if (cs > ll) cs = ll; 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 (cs && line[cs - 1] == ' ') makesuffixstr(NULL, "\\-", 0); comppref = 0; return ret; } /**/ int endoflist(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; }