/* * 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. */ #ifdef HAVE_NIS_PLUS # include #else # ifdef HAVE_NIS # include # include # include # include /* This is used when getting usernames from the NIS. */ typedef struct { int len; char *s; } dopestring; # endif #endif #define inststr(X) inststrlen((X),1,-1) /* wb and we hold the beginning/end position of the word we are completing. */ static int wb, we; /* offs is the cursor position within the tokenized * * current word after removing nulargs. */ static 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. */ static int usemenu, useglob; /* != 0 if we are in the middle of a menu completion */ static int menucmp; /* A pointer to the current position in the menu-completion array (the one * * that was put in the command line last). */ static char **menucur; /* menupos is the point (in the command line) where the menu-completion * * strings are inserted. menulen is the length of the string that was * * inserted last. menuend is the end position of this string in the * * command line. menuwe is non-zero if the cursor was at the end of the * * word (meaning that suffixes should go before the cursor). menuinsc is * * the length of any suffix that has been temporarily added. */ static int menupos, menulen, menuend, menuwe, menuinsc; /* This is used as a flag from get_comp_string() that we are doing * * completion inside a brace expansion. */ static int complinbrace; /* The list of matches. fmatches contains the matches we first ignore * * because of fignore. */ static LinkList matches, fmatches; /* The list of matches turned into an array. This is used to sort this * * list and when menu-completion is used (directly or via automenu). */ static char **amatches; /* The number of matches. */ static int nmatches; /* A list of user-defined explanations for the completions to be shown * * instead of amatches when listing completions. */ static char **aylist; /* !=0 if we have a valid completion list. */ static int validlist; /* This flag is non-zero if we are completing a pattern (with globcomplete) */ static int ispattern; /* Two patterns used when doing glob-completion. The first one is built * * from the whole word we are completing and the second one from that * * part of the word that was identified as a possible filename. */ static Comp patcomp, filecomp; /* We store the following prefixes/suffixes: * * lpre/lsuf -- what's on the line * * rpre/rsuf -- same as lpre/lsuf, but expanded * * * * ... and if we are completing files, too: * * ppre/psuf -- the path prefix/suffix * * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in * * prpre -- ppre in expanded form usable for opendir * * * * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, * * fpre, and fsuf. noreal is non-zero if we have rpre/rsuf. */ static char *lpre, *lsuf; static char *rpre, *rsuf; static char *ppre, *psuf, *prpre; static char *fpre, *fsuf; static int lpl, lsl, rpl, rsl, fpl, fsl; static int noreal; /* This is used when completing after `$' and holds the whole prefix, * * used in do_single() to check whether the word expands to a directory * * name (in that case and if autoparamslash is set, we add a `/'). * * qparampre is the same but quoted. The length of it is in qparprelen. * * parambr is != 0 if the parameter name is in braces. */ static char *parampre = NULL, *qparampre = NULL; static int qparprelen, parambr; /* This is either zero or equal to the special character the word we are * * trying to complete starts with (e.g. Tilde or Equals). */ static char ic; /* These hold the minimum common prefix/suffix lengths (normal and for * * fignore ignored). */ static int ab, ae, fab, fae; /* This variable says what we are currently adding to the list of matches. */ static int addwhat; /* firstm hold the first match we found, shortest contains the shortest * * one (normal and for fignore ignored). */ static char *firstm, *shortest, *ffirstm, *fshortest; /* This holds the word we are completing in quoted from. */ static char *qword; /* This is the length of the shortest match we found (normal and for * * fignore ignored). */ static int shortl, fshortl; /* This is non-zero if we are doing a menu-completion and this is not the * * first call (e.g. when automenu is set and menu-completion was entered * * due to this). */ static int amenu; /* Find out if we have to insert a tab (instead of trying to complete). */ /**/ static int usetab(void) { unsigned char *s = line + cs - 1; for (; s >= line && *s != '\n'; s--) if (*s != '\t' && *s != ' ') return 0; return 1; } #define COMP_COMPLETE 0 #define COMP_LIST_COMPLETE 1 #define COMP_SPELL 2 #define COMP_EXPAND 3 #define COMP_EXPAND_COMPLETE 4 #define COMP_LIST_EXPAND 5 #define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND) /**/ void completeword(void) { usemenu = isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); if (c == '\t' && usetab()) selfinsert(); else docomplete(COMP_COMPLETE); } /**/ void menucomplete(void) { usemenu = 1; useglob = isset(GLOBCOMPLETE); if (c == '\t' && usetab()) selfinsert(); else docomplete(COMP_COMPLETE); } /**/ void listchoices(void) { usemenu = isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); docomplete(COMP_LIST_COMPLETE); } /**/ void spellword(void) { usemenu = useglob = 0; docomplete(COMP_SPELL); } /**/ void deletecharorlist(void) { char **mc = menucur; usemenu = isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); if (cs != ll) deletechar(); else docomplete(COMP_LIST_COMPLETE); menucur = mc; } /**/ void expandword(void) { usemenu = useglob = 0; if (c == '\t' && usetab()) selfinsert(); else docomplete(COMP_EXPAND); } /**/ void expandorcomplete(void) { usemenu = isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); if (c == '\t' && usetab()) selfinsert(); else docomplete(COMP_EXPAND_COMPLETE); } /**/ void menuexpandorcomplete(void) { usemenu = 1; useglob = isset(GLOBCOMPLETE); if (c == '\t' && usetab()) selfinsert(); else docomplete(COMP_EXPAND_COMPLETE); } /**/ void listexpand(void) { usemenu = isset(MENUCOMPLETE); useglob = isset(GLOBCOMPLETE); docomplete(COMP_LIST_EXPAND); } /**/ void reversemenucomplete(void) { if (!menucmp) { menucomplete(); return; } HEAPALLOC { if (menucur == amatches) menucur = amatches + nmatches - 1; else menucur--; metafy_line(); do_single(*menucur); unmetafy_line(); } LASTALLOC; } /* Accepts the current completion and starts a new arg, * * with the next completions. This gives you a way to * * accept several selections from the list of matches. */ /**/ void acceptandmenucomplete(void) { if (!menucmp) { feep(); return; } cs = menuend + menuinsc; inststrlen(" ", 1, 1); if (qparampre) inststrlen(qparampre, 1, qparprelen); if (lpre && !ispattern) inststrlen(lpre, 1, -1); if (lsuf && !ispattern) inststrlen(lsuf, 0, -1); menupos = cs; menuend = cs + (lsuf ? strlen(lsuf) : 0); menulen = 0; menuinsc = 0; menuwe = 1; menucomplete(); } /* These are flags saying if we are completing in the command * * position or in a redirection. */ static int lincmd, linredir; /* 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. */ static int lastambig; /* This describes some important things collected during the last * * completion. Its value is zero or the inclusive OR of some of * * the HAS_* things below. */ static int haswhat; /* We have a suffix to add (given with compctl -S). */ #define HAS_SUFFIX 1 /* We have filenames in the completion list. */ #define HAS_FILES 2 /* We have other things than files in the completion list. If this is * * not set but HAS_FILES is, we probably put the file type characters * * in the completion list (if listtypes is set) and we attempt to add * * a slash to completed directories. */ #define HAS_MISC 4 /* This is set if we have filenames in the completion list that were * * generated by a globcompletion pattern. */ #define HAS_PATHPAT 8 /* This holds the naem of the current command (used to find the right * * compctl). */ static char *cmdstr; /* 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 && 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; } /* The main entry point for completion. */ /**/ static void docomplete(int lst) { char *s, *ol; int olst = lst, chl = 0, ne = noerrs, ocs; /* If we are doing a menu-completion... */ if (menucmp && lst != COMP_LIST_EXPAND) { do_menucmp(lst); return; } /* Check if we have to start a menu-completion (via automenu). */ if ((amenu = (isset(AUTOMENU) && lastambig))) usemenu = 1; /* Expand history references before starting completion. If anything * * changed, do no more. */ if (doexpandhist()) return; metafy_line(); ocs = cs; 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; /* Get the word to complete. */ 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; unmetafy_line(); feep(); return; } 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 (isset(RECEXACT)) lst = COMP_EXPAND; else { int t0, n = 0; char *fc; 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) && (fc = findcmd(hn->nam))) { zsfree(fc); n++; } if (n == 2) break; } if (n == 1) lst = COMP_EXPAND; } } if (lst == COMP_EXPAND_COMPLETE) do { /* check if there is a parameter expresiion. */ 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 *x, *q; for (q = s; *q; q++) if (INULL(*q)) *q = Nularg; cs = wb; foredel(we - wb); HEAPALLOC { untokenize(x = dupstring(s)); if (*s == Tilde || *s == Equals || *s == String) *x = *s; spckword(&x, 0, lincmd, 0); } LASTALLOC; untokenize(x); inststr(x); } else if (COMP_ISEXPAND(lst)) { /* Do expansion. */ char *ol = (olst == COMP_EXPAND_COMPLETE) ? dupstring((char *)line) : (char *)line; int ocs = cs, ne = noerrs; noerrs = 1; doexpansion(s, 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)) { char *p; cs = ocs; errflag = 0; 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); docompletion(s, lst, lincmd, 1); } } else /* Just do completion. */ docompletion(s, lst, lincmd, 0); zsfree(s); } /* Reset the lexer state, pop the heap. */ lexrestore(); popheap(); zsfree(qword); unmetafy_line(); } /* Do completion, given that we are in the middle of a menu completion. We * * don't need to generate a list of matches, because that's already been * * done by previous commands. We will either list the completions, or * * insert the next completion. */ /**/ static void do_menucmp(int lst) { /* Just list the matches if the list was requested. */ if (lst == COMP_LIST_COMPLETE) { showinglist = -2; return; } /* Otherwise go to the next match in the array... */ HEAPALLOC { if (!*++menucur) menucur = amatches; /* ... and insert it into the command line. */ metafy_line(); do_single(*menucur); unmetafy_line(); } LASTALLOC; } /* 1 if we are completing in a string */ static int instring; /* 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] == '`' || (instring && (line[cs] == '"' || line[cs] == '\'')) || (addspace = (comppref && !iblank(line[cs])))) { *ptmp = (char *)line; line = (unsigned char *)halloc(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. */ /**/ static char * dupstrspace(const char *str) { int len = strlen((char *)str); char *t = (char *)ncalloc(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). */ /**/ static 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); } /**/ static void unmetafy_line(void) { cs = ztrsub((char *) line + cs, (char *) line); (void) unmetafy((char *) line, &ll); } /* 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; char *s = NULL, *linptr, *tmp, *p, *tt = NULL; complinbrace = 0; /* 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 ((...)). */ 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++; instring = (j & 1) ? 2 : (k & 1); addx(&tmp); if (instring) { /* Yes, we are in a string. */ if (!tmp) { tmp = (char *)line; line = (unsigned char *) dupstring((char *) line); } /* Now remove the quotes. * * What?? Why that?? Well, we want to be able to complete * * inside strings. The lexer code gives us no help here, * * so we have to cheat. We remove the quotes, the lexer * * will than treat the words in the strings normally and we * * can complete them. * * This is completely the wrong thing to do, but it's * * occasionally useful, and we can't handle quotes properly * * yet anyway. */ for (p = (char *)line; *p; p++) if (*p == '"' || *p == '\'') *p = ' '; } linptr = (char *)line; pushheap(); HEAPALLOC { start: inwhat = IN_NOTHING; /* Now set up the lexer and start it. */ parbegin = parend = -1; lincmd = incmdpos; linredir = inredir; zsfree(cmdstr); cmdstr = NULL; zleparse = 1; clwpos = -1; lexsave(); inpush(dupstrspace((char *) linptr), 0, NULL); strinbeg(); stophist = 2; i = tt0 = cp = rd = 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. It's a * * simple way to do things, but suffers from an inability to * * distinguish actual command arguments from, for example, * * filenames in redirections. (But note that code elsewhere checks * * if we are completing *in* a redirection.) The only way to fix * * this would be to pass the command line through the parser too, * * and get the arguments that way. Maybe in 3.1... */ do { lincmd = incmdpos; linredir = inredir; /* Get the next token. */ ctxtlex(); if (tok == DINPAR) tokstr = NULL; /* We reached the end. */ if (tok == ENDINPUT) break; if (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 = 0; } if (lincmd && tok == STRING) { /* The lexer says, this token is in command position, so * * store the token string (to find the right compctl). */ 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; if (inwhat == IN_NOTHING && incond) inwhat = IN_COND; } if (!tokstr) continue; /* 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; lincmd = cp; linredir = rd; strinend(); inpop(); errflag = zleparse = 0; if (parbegin != -1) { /* We are in command or process substitution */ if (parend >= 0 && !tmp) line = (unsigned char *) dupstring(tmp = (char *)line); linptr = (char *) line + ll + addedx - parbegin + 1; if (parend >= 0) { ll -= parend; line[ll + addedx] = '\0'; } lexrestore(); 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) { /* The cursor was inside a parameter assignment. */ for (s = tt; iident(*s); s++); if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) s = NULL, inwhat = IN_MATH; else if (*s == '=') { 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(); goto start; } feep(); noaliases = 0; lexrestore(); LASTALLOC_RETURN NULL; } noaliases = 0; /* 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; for (tt = s; ++tt < s + cs - wb;) if (*tt == Inbrack) i++; else if (i && *tt == Outbrack) i--; if (i) inwhat = IN_MATH; } if (inwhat == IN_MATH) { /* 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'; } /* This variable will hold the current word in quoted form. */ qword = ztrdup(s); /* While building the quoted form, we also clean up the command line. */ offs = cs - wb; for (p = s, tt = qword, i = wb; *p; p++, tt++, i++) if (INULL(*p)) { if (i < cs) offs--; 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--); } if (!isset(IGNOREBRACES)) { /* Try and deal with foo{xxx etc.; only simple cases * (only one inbrace, completion after inbrace and before outbrace * if present). */ int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s); tt = NULL; /* First check the conditions mentioned above * and locate opening brace */ for (i = 0, p = s; *p; p++, i++) { /* careful, ${... is not a brace expansion... * in fact, if it's got a substitution in it's too * hard for us anyway. sorry. */ if (*p == String || *p == Qstring) { tt = NULL; break; } else if (*p == Inbrace) { if (tt) { /* too many inbraces */ tt = NULL; break; } tt = p; } else if (*p == Outbrace && i < myoffs) { /* outbrace is before cursor pos, so nothing to complete */ tt = NULL; break; } } if (tt && tt < s + myoffs) { /* Braces are go: delete opening brace */ char *com = NULL; chuck(tt); offs--; myoffs--; /* Look for text up to comma before cursor and delete it */ for (i = tt - s, p = tt; *p && i < myoffs; p++, i++) if (*p == Comma) com = p; if (com) { i = com - tt + 1; while (i--) chuck(tt), offs--, myoffs--; } /* Look for text between subsequent comma * and closing brace or end of string and delete it */ for (p = s + myoffs; *p && *p != Outbrace; p++) if (*p == Comma) { while (*p && *p != Outbrace) chuck(p); break; } if (*p == Outbrace) chuck(p); else { /* we are still waiting for an outbrace and maybe commas */ complinbrace = 1; } } } } LASTALLOC; lexrestore(); return (char *)s; } /* Expand the current word. */ /**/ static void doexpansion(char *s, int lst, int olst, int explincmd) { LinkList vl; char *ss; DPUTS(useheap, "BUG: useheap in doexpansion()"); HEAPALLOC { pushheap(); vl = newlinklist(); ss = dupstring(s); 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); opts[NULLGLOB] = ng; } if (errflag) goto end; if (empty(vl) || !*(char *)peekfirst(vl)) { if (!noerrs) feep(); 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, 0); else feep(); goto end; } if (lst == COMP_LIST_EXPAND) { /* Only the list of expansions was requested. */ listlist(vl); goto end; } /* Remove the current word and put the expansions there. */ cs = wb; foredel(we - wb); while ((ss = (char *)ugetnode(vl))) { untokenize(ss); ss = quotename(ss, NULL, NULL, NULL); inststr(ss); #if 0 if (nonempty(vl)) { spaceinline(1); line[cs++] = ' '; } #endif if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) || (cs && line[cs-1] != '/')) { spaceinline(1); line[cs++] = ' '; } } end: popheap(); } LASTALLOC; } /* This is called from the lexer to give us word positions. */ /**/ void gotword(void) { we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0); if (cs <= we) { wb = ll - wordbeg + addedx; zleparse = 0; } } /* 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). */ /**/ static void inststrlen(char *str, int move, int len) { if (!len) return; if (len == -1) len = strlen(str); spaceinline(len); strncpy((char *)(line + cs), str, len); if (move) cs += len; } /* Quote the string s and return the result. If e is non-zero, it the * * pointer it points to may point to aposition in s and in e the position * * of the corresponding character in the quoted string is returned. Like * * e, te may point to a position in the string and pl is used to return * * the position of the character pointed to by te in the quoted string. * * The string is metafied and may contain tokens. */ /**/ static char * quotename(const char *s, char **e, char *te, int *pl) { const char *u, *tt; char *v, buf[PATH_MAX * 2]; int sf = 0; tt = v = buf; u = s; for (; *u; u++) { if (e && *e == u) *e = v, sf |= 1; if (te == u) *pl = v - tt, sf |= 2; if (ispecial(*u) && (!instring || (isset(BANGHIST) && *u == (char)bangchar) || (instring == 2 && (*u == '$' || *u == '`' || *u == '\"')) || (instring == 1 && *u == '\''))) if (*u == '\n' || (instring == 1 && *u == '\'')) { if (unset(RCQUOTES)) { *v++ = '\''; if (*u == '\'') *v++ = '\\'; *v++ = *u; *v++ = '\''; } else if (*u == '\n') *v++ = '"', *v++ = '\n', *v++ = '"'; else *v++ = '\'', *v++ = '\''; continue; } else *v++ = '\\'; if(*u == Meta) *v++ = *u++; *v++ = *u; } *v = '\0'; if (strcmp(buf, s)) tt = dupstring(buf); else tt = s; v += tt - buf; if (e && (sf & 1)) *e += tt - buf; if (e && *e == u) *e = v; if (te == u) *pl = v - tt; return (char *) tt; } /* This adds a match to the list of matches. The string to add is given * * in s, the type of match is given in the global variable addwhat and * * the parameter t (if not NULL) is a pointer to a hash node node which * * may be used to give other information to this function. * * * * addwhat contains either one of the special values (negative, see below) * * or the inclusive OR of some of the CC_* flags used for compctls. */ /**/ static void addmatch(char *s, char *t) { int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp; char *e = NULL, *tt, *te, *fc, **fm; Comp cp = patcomp; HashNode hn; Param pm; LinkList l = matches; /* * addwhat: -5 is for files, * -6 is for glob expansions, * -8 is for executable files (e.g. command paths), * -9 is for parameters * -7 is for command names (from cmdnamtab) * -4 is for a cdable parameter * -3 is for executable command names. * -2 is for anything unquoted * -1 is for other file specifications * (things with `~' of `=' at the beginning, ...). */ /* Just to make the code cleaner */ hn = (HashNode) t; pm = (Param) t; if (!addwhat) { test = 1; } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 || addwhat == CC_FILES || addwhat == -7 || addwhat == -8) { if (sl < fpl + fsl) return; if ((addwhat == CC_FILES || addwhat == -5) && !*psuf && !*fsuf) { /* If this is a filename, do the fignore check. */ char **pt = fignore; int filell; for (test = 1; test && *pt; pt++) if ((filell = strlen(*pt)) < sl && !strcmp(*pt, s + sl - filell)) test = 0; if (!test) l = fmatches; } pl = fpl; if (addwhat == -5 || addwhat == -8) { test = 1; cp = filecomp; cc = cp || ispattern; e = s + sl - fsl; } else { if ((cp = filecomp)) { if ((test = domatch(s, filecomp, 0))) cc = 1; } else { e = s + sl - fsl; if ((test = !strncmp(s, fpre, fpl))) test = !strcmp(e, fsuf); if (ispattern) cc = 1; } } if (test) { fc = NULL; if (addwhat == -7 && !(fc = findcmd(s))) return; if (fc) zsfree(fc); haswhat |= HAS_FILES; if (addwhat == CC_FILES || addwhat == -6 || addwhat == -5 || addwhat == -8) { te = s + pl; s = quotename(s, &e, te, &pl); sl = strlen(s); } else if (!cc) { s = dupstring(t = s); e += s - t; } if (cc) { tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1); strcpy(tt, ppre); strcat(tt, s); strcat(tt, psuf); untokenize(s = tt); } } } else if (addwhat == CC_QUOTEFLAG || addwhat == -2 || (addwhat == -3 && !(hn->flags & DISABLED)) || (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) && (tt = pm->gets.cfn(pm)) && *tt == '/') || (addwhat == -9 && !(hn->flags & PM_UNSET)) || (addwhat > 0 && ((!(hn->flags & PM_UNSET) && (((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) || ((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) || ((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) || ((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) || ((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) || ((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) || ((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED)))) || ((( addwhat & CC_SHFUNCS) || ( addwhat & CC_BUILTINS) || ( addwhat & CC_EXTCMDS) || ( addwhat & CC_RESWDS) || ((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) || ((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) && (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) || ((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED)))) || ((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) { if (sl >= rpl + rsl) { if (cp) test = domatch(s, patcomp, 0); else { e = s + sl - rsl; if ((test = !strncmp(s, rpre, rpl))) test = !strcmp(e, rsuf); } } if (!test && sl < lpl + lsl) return; if (!test && lpre && lsuf && sl >= lpl + lsl) { e = s + sl - lsl; if ((test = !strncmp(s, lpre, lpl))) test = !strcmp(e, lsuf); pl = lpl; } if (addwhat == CC_QUOTEFLAG) { te = s + pl; s = quotename(s, &e, te, &pl); sl = strlen(s); } if (test) haswhat |= HAS_MISC; } if (!test) return; if (ispattern) { t = s; } else { t = s += pl; if (*e) t = s = dupstrpfx(t, e - t); } if (l == fmatches) { bp = &fab; ep = &fae; sp = &fshortl; fm = &ffirstm; } else { bp = &ab; ep = &ae; sp = &shortl; fm = &firstm; } if (!ispattern && *fm) { if ((test = pfxlen(*fm, s)) < *bp) *bp = test; if ((test = sfxlen(*fm, s)) < *ep) *ep = test; if (*ep > *sp - *bp) *ep = *sp - *bp; } /* If we are doing a glob completion we store the whole string in * * the list. Otherwise only the part that fits between the prefix * * and the suffix is stored. */ addlinknode(l, t); if (!*fm) { *bp = *ep = 10000; *fm = t; *sp = 100000; } if (!ispattern && (sl = strlen(t)) < *sp) { *sp = sl; if (l == fmatches) fshortest = t; else shortest = t; } } #ifdef HAVE_NIS_PLUS static int match_username(nis_name table, nis_object *object, void *userdata) { if (errflag) return 1; else { static char buf[40]; register entry_col *ec = object->zo_data.objdata_u.en_data.en_cols.en_cols_val; register int l = minimum(ec->ec_value.ec_value_len, 39); memcpy(buf, ec->ec_value.ec_value_val, l); buf[l] = '\0'; addmatch(dupstring(buf), NULL); } return 0; } #else # ifdef HAVE_NIS static int match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data) { if (errflag || status != YP_TRUE) return 1; if (vallen > keylen && val[keylen] == ':') { val[keylen] = '\0'; addmatch(dupstring(val), NULL); } return 0; } # endif /* HAVE_NIS */ #endif /* HAVE_NIS_PLUS */ /**/ static void maketildelist(void) { #if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS) FILE *pwf; char buf[BUFSIZ], *p; int skipping; # ifndef HAVE_NIS_PLUS char domain[YPMAXDOMAIN]; struct ypall_callback cb; dopestring data; data.s = fpre; data.len = fpl; /* Get potential matches from NIS and cull those without local accounts */ if (getdomainname(domain, YPMAXDOMAIN) == 0) { cb.foreach = (int (*)()) match_username; cb.data = (char *)&data; yp_all(domain, PASSWD_MAP, &cb); } # else /* HAVE_NIS_PLUS */ /* Maybe we should turn this string into a #define'd constant...? */ nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH, match_username, 0); # endif /* Don't forget the non-NIS matches from the flat passwd file */ if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) { skipping = 0; while (fgets(buf, BUFSIZ, pwf) != NULL) { if (strchr(buf, '\n') != NULL) { if (!skipping) { if ((p = strchr(buf, ':')) != NULL) { *p = '\0'; addmatch(dupstring(buf), NULL); } } else skipping = 0; } else skipping = 1; } fclose(pwf); } #else /* no NIS or NIS_PLUS */ /* add all the usernames to the named directory table */ nameddirtab->filltable(nameddirtab); #endif scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0, addhnmatch, 0); } /* Copy the given string and remove backslashes from the copy and return it. */ /**/ static char * rembslash(char *s) { char *t = s = dupstring(s); while (*s) if (*s == '\\') { chuck(s); if (*s) s++; } else s++; return t; } /* This does the check for compctl -x `n' and `N' patterns. */ /**/ static int getcpat(char *wrd, int cpatindex, char *cpat, int class) { char *str, *s, *t, *p; int d = 0; if (!wrd || !*wrd) return -1; cpat = rembslash(cpat); str = ztrdup(wrd); untokenize(str); if (!cpatindex) cpatindex++, d = 0; else if ((d = (cpatindex < 0))) cpatindex = -cpatindex; for (s = d ? str + strlen(str) - 1 : str; d ? (s >= str) : *s; d ? s-- : s++) { for (t = s, p = cpat; *t && *p; p++) { if (class) { if (*p == *s && !--cpatindex) { zsfree(str); return (int)(s - str + 1); } } else if (*t++ != *p) break; } if (!class && !*p && !--cpatindex) { zsfree(str); t += wrd - str; for (d = 0; --t >= wrd;) if (! INULL(*t)) d++; return d; } } zsfree(str); return -1; } /* This holds a pointer to the compctl we are using. */ static Compctl ccmain; /* Find the compctl to use and return it. The first argument gives a * * compctl to start searching with (if it is zero, the hash table is * * searched). compadd is used to return a number of characters that * * should be ignored at the beginning of the word and incmd is * * non-zero if we are in command position. */ /**/ static Compctl get_ccompctl(Compctl occ, int *compadd, int incmd) { Compctl compc, ret; Compctlp ccp; int t, i, a, b, tt, ra, rb, j, isf = 1; Compcond or, cc; char *s, *ss, *sc, *cmd = dupstring(cmdstr); Comp comp; first_rec: *compadd = 0; ra = 0; rb = clwnum - 1; sc = NULL; if (!(ret = compc = occ)) { if (isf) { isf = 0; ret = &cc_first; } else if (inwhat == IN_ENV) /* Default completion for parameter values. */ ret = &cc_default; else if (inwhat == IN_MATH) { /* Parameter names inside mathematical expression. */ cc_dummy.mask = CC_PARAMS; ret = &cc_dummy; cc_dummy.refc = 10000; } else if (inwhat == IN_COND) { /* We try to be clever here: in conditions we complete option * * names after a `-o', file names after `-nt', `-ot', and `-ef' * * and file names and parameter names elsewhere. */ s = clwpos ? clwords[clwpos - 1] : ""; cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS : ((*s == '-' && s[1] && !s[2]) || !strcmp("-nt", s) || !strcmp("-ot", s) || !strcmp("-ef", s)) ? CC_FILES : (CC_FILES | CC_PARAMS); ret = &cc_dummy; cc_dummy.refc = 10000; } else if (incmd) ret = &cc_compos; /* And in redirections or if there is no command name (and we are * * not in command position) or if no special compctl was given * * for the command: use default completion. Note that we first * * search the complete command name and than the trailing * * pathname component. */ else if (linredir || !(cmd && (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) && (compc = ret = ccp->cc)) || ((s = dupstring(cmd)) && remlpaths(&s) && (ccp = (Compctlp) compctltab->getnode(compctltab, s)) && (compc = ret = ccp->cc))))) ret = &cc_default; ccmain = compc = ret; ccmain->refc++; } /* The compctl we found has extended completion patterns, check them. */ if (compc && compc->ext) { compc = compc->ext; /* This loops over the patterns separated by `--'. */ for (t = 0; compc && !t; compc = compc->next) { /* This loops over OR'ed patterns. */ for (cc = compc->cond; cc && !t; cc = or) { or = cc->or; /* This loops over AND'ed patterns. */ for (t = 1; cc && t; cc = cc->and) { /* And this loops of [...] pairs. */ for (t = i = 0; i < cc->n && !t; i++) { s = NULL; ra = 0; rb = clwnum - 1; switch (cc->type) { case CCT_POS: tt = clwpos; goto cct_num; case CCT_NUMWORDS: tt = clwnum; cct_num: if ((a = cc->u.r.a[i]) < 0) a += clwnum; if ((b = cc->u.r.b[i]) < 0) b += clwnum; if (cc->type == CCT_POS) ra = a, rb = b; t = (tt >= a && tt <= b); break; case CCT_CURSUF: case CCT_CURPRE: s = ztrdup(clwpos < clwnum ? clwords[clwpos] : ""); untokenize(s); sc = rembslash(cc->u.s.s[i]); a = strlen(sc); if (!strncmp(s, sc, a)) { *compadd = (cc->type == CCT_CURSUF ? a : 0); t = 1; } break; case CCT_CURSUB: case CCT_CURSUBC: if (clwpos < 0 || clwpos > clwnum) t = 0; else { a = getcpat(clwords[clwpos], cc->u.s.p[i], cc->u.s.s[i], cc->type == CCT_CURSUBC); if (a != -1) *compadd = a, t = 1; } break; case CCT_CURPAT: case CCT_CURSTR: tt = clwpos; goto cct_str; case CCT_WORDPAT: case CCT_WORDSTR: tt = 0; cct_str: if ((a = tt + cc->u.s.p[i]) < 0) a += clwnum; s = ztrdup((a < 0 || a >= clwnum) ? "" : clwords[a]); untokenize(s); if (cc->type == CCT_CURPAT || cc->type == CCT_WORDPAT) { tokenize(ss = dupstring(cc->u.s.s[i])); t = ((comp = parsereg(ss)) && domatch(s, comp, 0)); } else t = (!strcmp(s, rembslash(cc->u.s.s[i]))); break; case CCT_RANGESTR: case CCT_RANGEPAT: if (cc->type == CCT_RANGEPAT) tokenize(sc = dupstring(cc->u.l.a[i])); for (j = clwpos; j; j--) { untokenize(s = ztrdup(clwords[j])); if (cc->type == CCT_RANGESTR) sc = rembslash(cc->u.l.a[i]); if (cc->type == CCT_RANGESTR ? !strncmp(s, sc, strlen(sc)) : ((comp = parsereg(sc)) && domatch(s, comp, 0))) { zsfree(s); ra = j + 1; t = 1; break; } zsfree(s); } if (t) { if (cc->type == CCT_RANGEPAT) tokenize(sc = dupstring(cc->u.l.b[i])); for (j++; j < clwnum; j++) { untokenize(s = ztrdup(clwords[j])); if (cc->type == CCT_RANGESTR) sc = rembslash(cc->u.l.b[i]); if (cc->type == CCT_RANGESTR ? !strncmp(s, sc, strlen(sc)) : ((comp = parsereg(sc)) && domatch(s, comp, 0))) { zsfree(s); rb = j - 1; t = clwpos <= rb; break; } zsfree(s); } } s = NULL; } zsfree(s); } } } if (t) break; } if (compc) /* We found a matching pattern, we may return it. */ ret = compc; } if (ret->subcmd) { /* The thing we want to return has a subcmd flag (-l). */ char **ow = clwords, *os = cmdstr, *ops = NULL; int oldn = clwnum, oldp = clwpos; /* So we restrict the words-array. */ if (ra >= clwnum) ra = clwnum - 1; if (ra < 1) ra = 1; if (rb >= clwnum) rb = clwnum - 1; if (rb < 1) rb = 1; clwnum = rb - ra + 1; clwpos = clwpos - ra; if (ret->subcmd[0]) { /* And probably put the command name given to the flag * * in the array. */ clwpos++; clwnum++; incmd = 0; ops = clwords[ra - 1]; clwords[ra - 1] = cmdstr = ret->subcmd; clwords += ra - 1; } else { cmdstr = clwords[ra]; incmd = !clwpos; clwords += ra; } *compadd = 0; if (ccmain != &cc_dummy) freecompctl(ccmain); /* Then we call this function recursively. */ ret = get_ccompctl(NULL, compadd, incmd); /* And restore the things we changed. */ clwords = ow; cmdstr = os; clwnum = oldn; clwpos = oldp; if (ops) clwords[ra - 1] = ops; } if (ret == &cc_first) goto first_rec; return ret; } /* Dump a hash table (without sorting). For each element the addmatch * * function is called and at the beginning the addwhat variable is set. * * This could be done using scanhashtable(), but this is easy and much * * more efficient. */ /**/ static void dumphashtable(HashTable ht, int what) { HashNode hn; int i; addwhat = what; for (i = 0; i < ht->hsize; i++) for (hn = ht->nodes[i]; hn; hn = hn->next) addmatch(hn->nam, (char *) hn); } /* ScanFunc used by maketildelist() et al. */ /**/ static void addhnmatch(HashNode hn, int flags) { addmatch(hn->nam, NULL); } /* Perform expansion on the given string and return the result. * * During this errors are not reported. */ /**/ static char * getreal(char *str) { LinkList l = newlinklist(); int ne = noerrs; noerrs = 1; addlinknode(l, dupstring(str)); prefork(l, 0); noerrs = ne; if (!errflag && nonempty(l)) return ztrdup(peekfirst(l)); errflag = 0; return ztrdup(str); } /* This reads a directory and adds the files to the list of * * matches. The parameters say which files should be added. */ /**/ static void gen_matches_files(int dirs, int execs, int all) { DIR *d; struct stat buf; char *n, p[PATH_MAX], *q = NULL, *e; LinkList l = NULL; int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat; addwhat = execs ? -8 : -5; opts[NULLGLOB] = 1; if (*psuf) { /* If there is a path suffix, check if it doesn't have a `*' or * * `)' at the end (this is used to determine if we should use * * globbing). */ q = psuf + strlen(psuf) - 1; ns = !(*q == Star || *q == Outpar); l = newlinklist(); /* And generate only directory names. */ dirs = 1; all = execs = 0; } /* Open directory. */ if ((d = opendir((prpre && *prpre) ? prpre : "."))) { /* If we search only special files, prepare a path buffer for stat. */ if (!all && prpre) { strcpy(p, prpre); q = p + strlen(prpre); } /* Fine, now read the directory. */ while ((n = zreaddir(d, 1)) && !errflag) { /* Ignore files beginning with `.' unless the thing we found on * * the command line also starts with a dot or GLOBDOTS is set. */ if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) { if (filecomp) /* If we have a pattern for the filename check, use it. */ test = domatch(n, filecomp, 0); else { /* Otherwise use the prefix and suffix strings directly. */ e = n + strlen(n) - fsl; if ((test = !strncmp(n, fpre, fpl))) test = !strcmp(e, fsuf); } /* Filename didn't match? */ if (!test) continue; if (!all) { /* We still have to check the file type, so prepare * * the path buffer by appending the filename. */ strcpy(q, n); /* And do the stat. */ if (stat(p, &buf) < 0) continue; } if (all || (dirs && S_ISDIR(buf.st_mode)) || (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) { /* If we want all files or the file has the right type... */ if (*psuf) { /* We have to test for a path suffix. */ int o = strlen(p), tt; /* Append it to the path buffer. */ strcpy(p + o, psuf); /* Do we have to use globbing? */ if (ispattern || (ns && isset(GLOBCOMPLETE))) { /* Yes, so append a `*' if needed. */ if (ns) { int tl = strlen(p); p[tl] = Star; p[tl + 1] = '\0'; } /* Do the globbing... */ remnulargs(p); addlinknode(l, p); globlist(l); /* And see if that produced a filename. */ tt = nonempty(l); while (ugetnode(l)); } else /* Otherwise just check, if we have access * * to the file. */ tt = !access(p, F_OK); p[o] = '\0'; if (tt) /* Ok, we can add the filename to the * * list of matches. */ addmatch(dupstring(n), NULL); } else /* We want all files, so just add the name * * to the matches. */ addmatch(dupstring(n), NULL); } } } closedir(d); } opts[NULLGLOB] = ng; addwhat = aw; } /* This holds the explanation string we have to print. */ static char *expl; /* This holds the suffix to add (given with compctl -S). */ static char *ccsuffix; /* This s non-zero if the compctl -q flag was given (the suffix should * * be removed when a space or something like that is typed next). */ static int remsuffix; /**/ static void quotepresuf(char **ps) { if (*ps) { char *p = quotename(*ps, NULL, NULL, NULL); if (p != *ps) { zsfree(*ps); *ps = ztrdup(p); } } } /**/ static void docompletion(char *s, int lst, int incmd, int untokenized) { static int delit, compadd; fixsuffix(); HEAPALLOC { pushheap(); /* Make sure we have the completion list and compctl. */ if(makecomplist(s, incmd, &delit, &compadd, untokenized)) { /* Error condition: feeeeeeeeeeeeep(). */ feep(); goto compend; } if (lst == COMP_LIST_COMPLETE) /* All this and the guy only wants to see the list, sigh. */ showinglist = -2; else { /* We have matches. */ if (delit) { /* If we have to delete the word from the command line, * * do it now. */ wb -= compadd; strcpy((char *)line + wb, (char *)line + we); we = cs = wb; } if (nmatches > 1) /* There are more than one match. */ do_ambiguous(); else if (nmatches == 1) { /* Only one match. */ do_single(amatches[0]); invalidatelist(); } } /* Print the explanation string if needed. */ if (!showinglist && expl && nmatches != 1) { int up; if (!nmatches) feep(); trashzle(); clearflag = (isset(USEZLE) && !termflags && (isset(ALWAYSLASTPROMPT) && zmult == 1)) || (unset(ALWAYSLASTPROMPT) && zmult != 1); up = printfmt(expl, nmatches, 1); if (clearflag) tcmultout(TCUP, TCMULTUP, up + nlnct); else putc('\n', shout); fflush(shout); } compend: ll = strlen((char *)line); if (cs > ll) cs = ll; popheap(); } LASTALLOC; } /* Create the completion list. This is called whenever some bit of * * completion code needs the list. If the list is already available * * (validlist!=0), this function doesn't do anything. Along with * * the list is maintained the prefixes/suffixes etc. When any of * * this becomes invalid -- e.g. if some text is changed on the * * command line -- invalidatelist() should be called, to set * * validlist to zero and free up the memory used. This function * * returns non-zero on error. delit and compadd return information * * about bits of the command line that need to be deleted. */ /**/ static int makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized) { Compctl cc = NULL; int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1; int t, sf1, sf2, ooffs; char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL; unsigned char *ol = NULL; /* If we already have a list from a previous execution of this * * function, skip the list building code. */ if (validlist) return !nmatches; os = dupstring(s); ol = (unsigned char *)dupstring((char *)line); xorrec: DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)"); /* Go to the end of the word if complete_in_word is not set. */ if (unset(COMPLETEINWORD) && cs != we) cs = we, offs = strlen(s); ispattern = haswhat = lastambig = 0; patcomp = filecomp = NULL; menucur = NULL; shortest = NULL; fshortest = NULL; rpre = rsuf = lpre = lsuf = ppre = psuf = prpre = fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL; /* Blank out the lists. */ matches = newlinklist(); fmatches = newlinklist(); /* If we don't have a compctl definition yet or we have a compctl * * with extended completion, get it (or the next one, resp.). */ if (!cc || cc->ext) cc = get_ccompctl(cc, compadd, incmd); /* *compadd is the number of characters we have to ignore at the * * beginning of the word. */ wb += *compadd; s += *compadd; if ((offs -= *compadd) < 0) /* It's bigger than our word prefix, so we can't help here... */ return 1; /* Insert the prefix (compctl -P), if any. */ if (cc->prefix) { int pl = 0, sl = strlen(cc->prefix); if (*s) { /* First find out how much of the prefix is already on the line. */ sd = dupstring(s); untokenize(sd); pl = pfxlen(cc->prefix, sd); s += pl; } if (pl < sl) { int savecs = cs; /* Then insert the prefix. */ cs = wb + pl; inststrlen(cc->prefix + pl, 0, sl - pl); cs = savecs + sl - pl; } /* And adjust the word beginning/end variables. */ wb += sl; we += sl - pl; offs -= pl; } /* Does this compctl have a suffix (compctl -S)? */ if ((ccsuffix = cc->suffix) && *ccsuffix) { char *sdup = dupstring(ccsuffix); int sl = strlen(sdup), suffixll; /* Ignore trailing spaces. */ for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--); p[1] = '\0'; if (!sd) { sd = dupstring(s); untokenize(sd); } /* If the suffix is already there, ignore it (and don't add * * it again). */ if (*sd && (suffixll = strlen(sd)) >= sl && offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) { ccsuffix = NULL; haswhat |= HAS_SUFFIX; s[suffixll - sl] = '\0'; } } /* Do we have one of the special characters `~' and `=' at the beginning? */ if ((ic = *s) != Tilde && ic != Equals) ic = 0; /* Check if we have to complete a parameter name... */ /* Try to find a `$'. */ for (p = s + offs; p > s && *p != String; p--); if (*p == String) { /* Handle $$'s */ while (p > s && p[-1] == String) p--; while (p[1] == String && p[2] == String) p += 2; } if (*p == String && 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; if (*b == Inbrace) { /* If this is a ${...}, 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; /* 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) || (useglob && (*e == Star || *e == Quest))) e++; /* Now make sure that the cursor is inside the name. */ if (offs <= e - s && offs >= b - s && n <= 0) { /* It is. */ parambr = br - 1; /* Get the prefix (anything up to the character before the name). */ *e = '\0'; parampre = ztrduppfx(s, b - s); qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL)); untokenize(qparampre); qparprelen = strlen(qparampre); /* And adjust wb, we, and offs again. */ offs -= b - s; wb = cs - offs; we = wb + e - b; s = b; /* And now make sure that we complete parameter names. */ cc = ccmain = &cc_dummy; cc_dummy.refc = 10000; cc_dummy.mask = CC_PARAMS | CC_ENVVARS; } } ooffs = offs; /* If we have to ignore the word, do that. */ if (cc->mask & CC_DELETE) { *delit = 1; *s = '\0'; offs = 0; } else *delit = 0; /* Compute line prefix/suffix. */ lpl = offs; lpre = zalloc(lpl + 1); memcpy(lpre, s, lpl); lpre[lpl] = '\0'; p = quotename(lpre, NULL, NULL, NULL); if (strcmp(p, lpre) && !strpfx(p, qword)) { int l1, l2; backdel(l1 = cs - wb); untokenize(p); inststrlen(p, 1, l2 = strlen(p)); we += l2 - l1; } lsuf = ztrdup(s + offs); lsl = strlen(lsuf); if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) && (strcmp(p, lsuf) && !strsfx(p, qword))) { int l1, l2; foredel(l1 = strlen(s + offs)); untokenize(p); inststrlen(p, 0, l2 = strlen(p)); we += l2 - l1; } /* First check for ~.../... */ if (ic == Tilde) { for (p = lpre + lpl; p > lpre; p--) if (*p == '/') break; if (*p == '/') ic = 0; } /* Compute real prefix/suffix. */ noreal = !*delit; for (p = lpre; *p && *p != String && *p != Tick; p++); tt = ic && !parampre ? lpre + 1 : lpre; rpre = (*p || *lpre == Tilde || *lpre == Equals) ? (noreal = 0, getreal(tt)) : ztrdup(tt); for (p = lsuf; *p && *p != String && *p != Tick; p++); rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf); /* Check if word is a pattern. */ for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1; p >= rpre && (ispattern != 3 || !sf1); p--) if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde))) ispattern |= sf1 ? 1 : 2; else if (*p == '/') { sf1++; if (!s1) s1 = p; } for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++) if (itok(*p)) t |= sf2 ? 4 : 2; else if (*p == '/') { sf2++; if (!s2) s2 = p; } ispattern = ispattern | t; /* But if we were asked not to do glob completion, we never treat the * * thing as a pattern. */ if (!useglob) ispattern = 0; if (ispattern) { /* The word should be treated as a pattern, so compute the matcher. */ p = (char *)ncalloc(rpl + rsl + 2); strcpy(p, rpre); if (rpl && p[rpl - 1] != Star) { p[rpl] = Star; strcpy(p + rpl + 1, rsuf); } else strcpy(p + rpl, rsuf); patcomp = parsereg(p); } if (!patcomp) { untokenize(rpre); untokenize(rsuf); rpl = strlen(rpre); rsl = strlen(rsuf); } untokenize(lpre); untokenize(lsuf); /* Handle completion of files specially (of course). */ if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) { /* s1 and s2 point to the last/first slash in the prefix/suffix. */ if (!s1) s1 = rpre; if (!s2) s2 = rsuf + rsl; /* Compute the path prefix/suffix. */ if (*s1 != '/') ppre = ztrdup(""); else ppre = ztrduppfx(rpre, s1 - rpre + 1); psuf = ztrdup(s2); /* And get the file prefix. */ fpre = ztrdup(((s1 == s || s1 == rpre || ic) && (*s != '/' || cs == wb)) ? s1 : s1 + 1); /* And the suffix. */ fsuf = ztrduppfx(rsuf, s2 - rsuf); if (useglob && (ispattern & 2)) { int t2; /* We have to use globbing, so compute the pattern from * * the file prefix and suffix with a `*' between them. */ p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2); strcpy(p, fpre); if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star) p[t2++] = Star; strcpy(p + t2, fsuf); filecomp = parsereg(p); } if (!filecomp) { untokenize(fpre); untokenize(fsuf); fpl = strlen(fpre); fsl = strlen(fsuf); } addwhat = -1; /* Completion after `~', maketildelist adds the usernames * * and named directories. */ if (ic == Tilde) maketildelist(); else if (ic == Equals) { /* Completion after `=', get the command names from * * the cmdnamtab and aliases from aliastab. */ if (isset(HASHLISTALL)) cmdnamtab->filltable(cmdnamtab); dumphashtable(cmdnamtab, -7); dumphashtable(aliastab, -2); } else { /* Normal file completion... */ if (ispattern & 1) { /* But with pattern matching. */ LinkList l = newlinklist(); LinkNode n; int ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; addwhat = 0; p = (char *)ncalloc(lpl + lsl + 3); strcpy(p, lpre); if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*') strcat(p, "*"); strcat(p, lsuf); if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')') strcat(p, "*"); /* Do the globbing. */ tokenize(p); remnulargs(p); addlinknode(l, p); globlist(l); if (nonempty(l)) { /* And add the resulting words. */ haswhat |= HAS_PATHPAT; for (n = firstnode(l); n; incnode(n)) addmatch(getdata(n), NULL); } opts[NULLGLOB] = ng; } else { /* No pattern matching. */ addwhat = CC_FILES; if (cc->withd) { prpre = tricat(cc->withd, "/", ppre); } else prpre = ztrdup(ppre); if (sf2) /* We are in the path, so add only directories. */ gen_matches_files(1, 0, 0); else { if (cc->mask & CC_FILES) /* Add all files. */ gen_matches_files(0, 0, 1); else if (cc->mask & CC_COMMPATH) { /* Completion of command paths. */ if (sf1 || cc->withd) /* There is a path prefix, so add * * directories and executables. */ gen_matches_files(1, 1, 0); else { /* No path prefix, so add the things * * reachable via the PATH variable. */ char **pc = path, *pp = prpre; for (; *pc; pc++) if (!**pc || (pc[0][0] == '.' && !pc[0][1])) break; if (*pc) { prpre = "./"; gen_matches_files(1, 1, 0); prpre = pp; } } } else if (cc->mask & CC_DIRS) gen_matches_files(1, 0, 0); /* The compctl has a glob pattern (compctl -g). */ if (cc->glob) { int ns, pl = strlen(prpre), o; char *g = dupstring(cc->glob), pa[PATH_MAX]; char *p2, *p3; int ne = noerrs, md = opts[MARKDIRS]; /* These are used in the globbing code to make * * things a bit faster. */ glob_pre = fpre; glob_suf = fsuf; noerrs = 1; addwhat = -6; strcpy(pa, prpre); o = strlen(pa); opts[MARKDIRS] = 0; /* The compctl -g string may contain more than * * one pattern, so we need a loop. */ while (*g) { LinkList l = newlinklist(); int ng; /* Find the blank terminating the pattern. */ while (*g && inblank(*g)) g++; /* Oops, we already reached the end of the string. */ if (!*g) break; for (p = g + 1; *p && !inblank(*p); p++) if (*p == '\\' && p[1]) p++; /* Get the pattern string. */ tokenize(g = dupstrpfx(g, p - g)); if (*g == '=') *g = Equals; if (*g == '~') *g = Tilde; remnulargs(g); if ((*g == Equals || *g == Tilde) && !cc->withd) { /* The pattern has a `~' or `=' at the * * beginning, so we expand this and use * * the result. */ filesub(&g, 0); addlinknode(l, dupstring(g)); } else if (*g == '/' && !cc->withd) /* The pattern is a full path (starting * * with '/'), so add it unchanged. */ addlinknode(l, dupstring(g)); else { /* It's a simple pattern, so append it to * * the path we have on the command line. */ strcpy(pa + o, g); addlinknode(l, dupstring(pa)); } /* Do the globbing. */ ng = opts[NULLGLOB]; opts[NULLGLOB] = 1; globlist(l); opts[NULLGLOB] = ng; /* Get the results. */ if (nonempty(l) && peekfirst(l)) { for (p2 = (char *)peekfirst(l); *p2; p2++) if (itok(*p2)) break; if (!*p2) { if ((*g == Equals || *g == Tilde || *g == '/') || cc->withd) { /* IF the pattern started with `~', * * `=', or `/', add the result only, * * if it really matches what we have * * on the line. * * Do this if an initial directory * * was specified, too. */ while ((p2 = (char *)ugetnode(l))) if (strpfx(prpre, p2)) addmatch(p2 + pl, NULL); } else { /* Otherwise ignore the path we * * prepended to the pattern. */ while ((p2 = p3 = (char *)ugetnode(l))) { for (ns = sf1; *p3 && ns; p3++) if (*p3 == '/') ns--; addmatch(p3, NULL); } } } } pa[o] = '\0'; g = p; } glob_pre = glob_suf = NULL; noerrs = ne; opts[MARKDIRS] = md; } } } } } /* Use tricat() instead of dyncat() to get zalloc()'d memory. */ if (ic) { /* Now change the `~' and `=' tokens to the real characters so * * that things starting with these characters will be added. */ char *orpre = rpre; rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre); rpl++; zsfree(orpre); } if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) { /* If we have to complete commands, add alias names, * * shell functions and builtins too. */ dumphashtable(aliastab, -3); dumphashtable(reswdtab, -3); dumphashtable(shfunctab, -3); dumphashtable(builtintab, -3); if (isset(HASHLISTALL)) cmdnamtab->filltable(cmdnamtab); dumphashtable(cmdnamtab, -3); /* And parameter names if autocd and cdablevars are set. */ if (isset(AUTOCD) && isset(CDABLEVARS)) dumphashtable(paramtab, -4); } addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG; if (cc->mask & CC_NAMED) /* Add named directories. */ dumphashtable(nameddirtab, addwhat); if (cc->mask & CC_OPTIONS) /* Add option names. */ dumphashtable(optiontab, addwhat); if (cc->mask & CC_VARS) /* And parameter names. */ dumphashtable(paramtab, -9); if (cc->mask & CC_BINDINGS) /* And zle function names... */ dumphashtable(thingytab, CC_BINDINGS); if (cc->keyvar) { /* This adds things given to the compctl -k flag * * (from a parameter or a list of words). */ char **usr = get_user_var(cc->keyvar); if (usr) while (*usr) addmatch(*usr++, NULL); } if (cc->mask & CC_USERS) /* Add user names. */ maketildelist(); if (cc->func) { /* This handles the compctl -K flag. */ List list; char **r; int lv = lastval; /* Get the function. */ if ((list = getshfunc(cc->func)) != &dummy_list) { /* We have it, so build a argument list. */ LinkList args = newlinklist(); addlinknode(args, cc->func); if (*delit) { p = dupstrpfx(os, ooffs); untokenize(p); addlinknode(args, p); p = dupstring(os + ooffs); untokenize(p); addlinknode(args, p); } else { addlinknode(args, lpre); addlinknode(args, lsuf); } /* This flag allows us to use read -l and -c. */ incompctlfunc = 1; /* Call the function. */ doshfunc(list, args, 0, 1); incompctlfunc = 0; /* And get the result from the reply parameter. */ if ((r = get_user_var("reply"))) while (*r) addmatch(*r++, NULL); } lastval = lv; } if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) { /* Get job names. */ int i; char *j, *jj; for (i = 0; i < MAXJOB; i++) if (jobtab[i].stat & STAT_INUSE) { int stopped = jobtab[i].stat & STAT_STOPPED; j = jj = dupstring(jobtab[i].procs->text); /* Find the first word. */ for (; *jj; jj++) if (*jj == ' ') { *jj = '\0'; break; } if ((cc->mask & CC_JOBS) || (stopped && (cc->mask & CC_STOPPED)) || (!stopped && (cc->mask & CC_RUNNING))) addmatch(j, NULL); } } if (cc->str) { /* Get the stuff from a compctl -s. */ LinkList foo = newlinklist(); LinkNode n; int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb; char *tmpbuf; opts[NULLGLOB] = 1; /* Put the strin in the lexer buffer and call the lexer to * * get the words we have to expand. */ zleparse = 1; lexsave(); tmpbuf = (char *)halloc(strlen(cc->str) + 5); sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */ inpush(tmpbuf, 0, NULL); strinbeg(); noaliases = 1; do { ctxtlex(); if (tok == ENDINPUT || tok == LEXERR) break; if (!first && tokstr && *tokstr) addlinknode(foo, ztrdup(tokstr)); first = 0; } while (tok != ENDINPUT && tok != LEXERR); noaliases = 0; strinend(); inpop(); errflag = zleparse = 0; lexrestore(); /* Fine, now do full expansion. */ prefork(foo, 0); if (!errflag) { globlist(foo); if (!errflag) /* And add the resulting words as matches. */ for (n = firstnode(foo); n; incnode(n)) addmatch((char *)n->dat, NULL); } opts[NULLGLOB] = ng; we = oowe; wb = oowb; } if (cc->hpat) { /* We have a pattern to take things from the history. */ Comp compc = NULL; char *e, *h, hpatsav; Histent he; int i = curhist - 1, n = cc->hnum; /* Parse the pattern, if it isn't the null string. */ if (*(cc->hpat)) { char *thpat = dupstring(cc->hpat); tokenize(thpat); compc = parsereg(thpat); } /* n holds the number of history line we have to search. */ if (!n) n = -1; /* Now search the history. */ while (n-- && (he = quietgethist(i--))) { int iwords; for (iwords = 0; iwords < he->nwords; iwords++) { h = he->text + he->words[iwords*2]; e = he->text + he->words[iwords*2+1]; hpatsav = *e; *e = '\0'; /* We now have a word from the history, ignore it * * if it begins with a quote or `$'. */ if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' && (!compc || domatch(h, compc, 0))) /* Otherwise add it if it was matched. */ addmatch(dupstring(h), NULL); if (hpatsav) *e = hpatsav; } } } if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS | CC_READONLYS | CC_SPECIALS | CC_PARAMS))) /* Add various flavours of parameters. */ dumphashtable(paramtab, t); if ((t = cc->mask & CC_SHFUNCS)) /* Add shell functions. */ dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & CC_BUILTINS)) /* Add builtins. */ dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & CC_EXTCMDS)) /* Add external commands */ dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & CC_RESWDS)) /* Add reserved words */ dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); if ((t = cc->mask & (CC_ALREG | CC_ALGLOB))) /* Add the two types of aliases. */ dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); /* If we have no matches, ignore fignore. */ if (empty(matches)) { matches = fmatches; firstm = ffirstm; shortest = fshortest; ab = fab; ae = fae; shortl = fshortl; } /* Make an array from the list of matches. */ makearray(matches); PERMALLOC { amatches = arrdup(amatches); if (firstm) firstm = ztrdup(firstm); /* And quote the prefixes/suffixes. */ if (hasspecial(s)) { zfree(lpre, lpl); zfree(lsuf, lsl); lpre = zalloc(lpl + 1); memcpy(lpre, s, lpl); lpre[lpl] = '\0'; lsuf = ztrdup(s + offs); quotepresuf(&lpre); quotepresuf(&lsuf); untokenize(lpre); untokenize(lsuf); } quotepresuf(&fpre); quotepresuf(&fsuf); quotepresuf(&ppre); quotepresuf(&psuf); } LASTALLOC; if (!errflag && cc->ylist) { /* generate the user-defined display list: if anything fails, * * we silently allow the normal completion list to be used. */ char **yaptr, *uv = NULL; List list; if (cc->ylist[0] == '$' || cc->ylist[0] == '(') { /* from variable */ uv = cc->ylist + (cc->ylist[0] == '$'); } else if ((list = getshfunc(cc->ylist)) != &dummy_list) { /* from function: pass completions as arg list */ LinkList args = newlinklist(); int addlen = strlen(rpre) + strlen(rsuf) + 1; addlinknode(args, cc->ylist); for (yaptr = amatches; *yaptr; yaptr++) { /* can't use tricat(). rats. */ char *ptr = (char *)halloc(addlen + strlen(*yaptr)); sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf); addlinknode(args, ptr); } /* No harm in allowing read -l and -c here, too */ incompctlfunc = 1; doshfunc(list, args, 0, 1); incompctlfunc = 0; uv = "reply"; } if (uv && (yaptr = get_user_var(uv))) { PERMALLOC { aylist = arrdup(yaptr); } LASTALLOC; } } /* Get the explanation string we will have to print: * * do this here in case a -y function alters the messge */ if ((expl = cc->explain)) { if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) { singsub(&expl); untokenize(expl); } expl = ztrdup(expl); } remsuffix = (cc->mask & CC_REMOVE); ccsuffix = cc->suffix; validlist = 1; if (nmatches && !errflag) return 0; if ((isf || cc->xor) && !parampre) { /* We found no matches, but there is a xor'ed completion: * * fine, so go back and continue with that compctl. */ errflag = 0; cc = cc->xor; isf = 0; wb = owb; we = owe; cs = ocs; ll = oll; strcpy((char *)line, (char *)ol); offs = oloffs; s = dupstring(os); free(amatches); zsfree(rpre); zsfree(rsuf); zsfree(lpre); zsfree(lsuf); zsfree(ppre); zsfree(psuf); zsfree(fpre); zsfree(fsuf); zsfree(prpre); zsfree(parampre); zsfree(qparampre); zsfree(firstm); if (expl) zsfree(expl); expl = NULL; if (aylist) freearray(aylist); aylist = NULL; goto xorrec; } /* No matches and xor'ed completion: restore the command line if * * it was alredy quoted, which is the case when s is untokenized. */ if (untokenized) strcpy((char *)line, (char *)ol); return 1; } /* Invalidate the completion list. */ /**/ void invalidatelist(void) { if(showinglist == -2) listmatches(); if(validlist) { freearray(amatches); if (aylist) freearray(aylist); aylist = NULL; if (expl) zsfree(expl); expl = 0; zsfree(rpre); zsfree(rsuf); zsfree(lpre); zsfree(lsuf); zsfree(ppre); zsfree(psuf); zsfree(fpre); zsfree(fsuf); zsfree(prpre); zsfree(parampre); zsfree(qparampre); zsfree(firstm); if (ccmain != &cc_dummy) freecompctl(ccmain); } lastambig = menucmp = showinglist = validlist = 0; menucur = NULL; } /* Get the words from a variable or a compctl -k list. */ /**/ static char ** get_user_var(char *nam) { if (!nam) return NULL; else if (*nam == '(') { /* It's a (...) list, not a parameter name. */ char *ptr, *s, **uarr, **aptr; int count = 0, notempty = 0, brk = 0; LinkList arrlist = newlinklist(); ptr = dupstring(nam); s = ptr + 1; while (*++ptr) { if (*ptr == '\\' && ptr[1]) chuck(ptr), notempty = 1; else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') { if (*ptr == ')') brk++; if (notempty) { *ptr = '\0'; count++; if (*s == '\n') s++; addlinknode(arrlist, s); } s = ptr + 1; notempty = 0; } else { notempty = 1; if(*ptr == Meta) ptr++; } if (brk) break; } if (!brk || !count) return NULL; *ptr = '\0'; aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1)); while ((*aptr++ = (char *)ugetnode(arrlist))); uarr[count] = NULL; return uarr; } else { /* Otherwise it should be a parameter name. */ char **arr = NULL, *val; if (!(arr = getaparam(nam)) && (val = getsparam(nam))) { arr = (char **)ncalloc(2*sizeof(char *)); arr[0] = val; arr[1] = NULL; } return arr; } } /* This is strcmp with ignoring backslashes. */ /**/ static int strbpcmp(const void *a, const void *b) { char *aa = *((char **)a), *bb = *((char **)b); while (*aa && *bb) { if (*aa == '\\') aa++; if (*bb == '\\') bb++; if (*aa != *bb) return (int)(*aa - *bb); if (*aa) aa++; if (*bb) bb++; } return (int)(*aa - *bb); } /* Make an array from a linked list */ /**/ static void makearray(LinkList l) { char **ap, **bp, **cp; LinkNode nod; /* Build an array for the matches. */ ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) * sizeof(char *)); /* And copy them into it. */ for (nod = firstnode(l); nod; incnode(nod)) *ap++ = (char *)getdata(nod); *ap = NULL; /* Now sort the array. */ qsort((void *) amatches, nmatches, sizeof(char *), (int (*) _((const void *, const void *)))strbpcmp); /* And delete the ones that occur more than once. */ for (ap = cp = amatches; *ap; ap++) { *cp++ = *ap; for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++); ap = bp; } *cp = NULL; nmatches = arrlen(amatches); } /* Handle the case were we found more than one match. */ /**/ static void do_ambiguous(void) { int p = (usemenu || ispattern), atend = (cs == we); int inv = 0; menucmp = 0; /* If we have to insert the first match, call do_single(). This is * * how REC_EXACT takes effect. We effectively turn the ambiguous * * completion into an unambiguous one. */ if (shortest && shortl == 0 && isset(RECEXACT) && (usemenu == 0 || unset(AUTOMENU))) { do_single(shortest); invalidatelist(); return; } /* Setting lastambig here means that the completion is ambiguous and * * AUTO_MENU might want to start a menu completion next time round, * * but this might be overridden below if we can complete an * * unambiguous prefix. */ lastambig = 1; if(p) { /* p is set if we are in a position to start using menu completion * * due to one of the menu completion options, or due to the * * menu-complete-word command, or due to using GLOB_COMPLETE which * * does menu-style completion regardless of the setting of the * * normal menu completion options. */ do_ambig_menu(); } else { /* Sort-of general case: we have an ambiguous completion, and aren't * * starting menu completion or doing anything really weird. We need * * to insert any unambiguous prefix and suffix, if possible. */ if(ab) inststrlen(firstm, 1, ab); if(ae && !atend) inststrlen(firstm + strlen(firstm) - ae, 0, ae); if(ab || (ae && !atend)) inv = 1; /* If the LIST_AMBIGUOUS option (meaning roughly `show a list only * * if the completion is completely ambiguous') is set, and some * * prefix was inserted, return now, bypassing the list-displaying * * code. On the way, invalidate the list and note that we don't * * want to enter an AUTO_MENU imediately. */ if(isset(LISTAMBIGUOUS) && inv) { invalidatelist(); lastambig = 0; return; } } /* At this point, we might want a completion listing. Show the listing * * if it is needed. */ if (isset(LISTBEEP)) feep(); if (isset(AUTOLIST) && !amenu && !showinglist) showinglist = -2; if(inv) invalidatelist(); } /* This is a stat that ignores backslashes in the filename. The `ls' * * parameter says if we have to do lstat() or stat(). I think this * * should instead be done by use of a general function to expand a * * filename (stripping backslashes), combined with the actual * * (l)stat(). */ /**/ static int ztat(char *nam, struct stat *buf, int ls) { char b[PATH_MAX], *p; for (p = b; p < b + sizeof(b) - 1 && *nam; nam++) if (*nam == '\\' && nam[1]) *p++ = *++nam; else *p++ = *nam; *p = '\0'; return ls ? lstat(b, buf) : stat(b, buf); } /* Insert a single match in the command line. */ /**/ static void do_single(char *str) { int l; int havesuff = 0; fixsuffix(); if (!menucur) { /* We are currently not in a menu-completion, * * so set the position variables. */ if (ispattern) { cs = we; menupos = wb; } else menupos = cs; menuwe = (cs == we) || isset(ALWAYSTOEND); menuend = we; } /* If we are already in a menu-completion or if we have done a * * glob completion, we have to delete some of the stuff on the * * command line. */ if (menucur) { if (menuinsc) { cs = menuend + lsl; foredel(menuinsc); } l = menulen; } else if (ispattern) l = we - wb; else l = 0; menuinsc = 0; cs = menupos; foredel(l); /* And than we insert the new string. */ inststrlen(str, 1, menulen = strlen(str)); menuend = cs; cs += lsl; if (ccsuffix) { /* There is a compctl -S suffix. Add it. */ if (!(haswhat & HAS_SUFFIX) && *ccsuffix) { havesuff = 1; inststr(ccsuffix); menuinsc = ztrlen(ccsuffix); if (remsuffix && menuwe) makesuffix(menuinsc); } havesuff = 1; } else { /* There is no user-specified suffix, * * so generate one automagically. */ if(parampre && parambr) { /*{{*/ /* Completing a parameter in braces. Add a removable `}' suffix. */ inststrlen("}", 1, 1); menuinsc++; } if(!(haswhat & HAS_MISC) || (parampre && isset(AUTOPARAMSLASH))) { /* If we have only filenames or we completed a parameter name * * and AUTO_PARAM_SLASH is set, lets see if it is a directory. * * If it is, we append a slash. */ char *p; struct stat buf; /* Build the path name. */ if (ispattern || ic || parampre) { int ne = noerrs; noerrs = 1; if (parampre) { int pl = strlen(parampre); p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) + strlen(lsuf) + 1); sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf); if (pl && p[pl-1] == Inbrace) strcpy(p+pl-1, p+pl); } else if (ic) { p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) + strlen(fsuf) + strlen(psuf) + 2); sprintf(p, "%c%s%s%s%s%s", ic, ppre, fpre, str, fsuf, psuf); } else p = dupstring(str); parsestr(p); if (ic) *p = ic; singsub(&p); noerrs = ne; } else { p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) + strlen(str) + strlen(fsuf) + strlen(psuf) + 3); sprintf(p, "%s%s%s%s%s", (prpre && *prpre) ? prpre : "./", fpre, str, fsuf, psuf); } /* And do the stat. */ if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) { /* It is a directory, so add the slash. */ havesuff = 1; inststrlen("/", 1, 1); menuinsc++; if(menuwe && isset(AUTOREMOVESLASH)) { makesuffix(1); suffixlen['/'] = 1; } } } } /* If completing in a brace expansion... */ if(complinbrace) { if(havesuff) { /*{{*/ /* If a suffix was added, and is removable, let * * `,' and `}' remove it. */ if(isset(AUTOPARAMKEYS)) suffixlen[','] = suffixlen['}'] = suffixlen[256]; } else { /*{{*/ /* Otherwise, add a `,' suffix, and let `}' remove it. */ havesuff = 1; inststrlen(",", 1, 1); menuinsc++; if(menuwe && isset(AUTOPARAMKEYS)) suffixlen[','] = suffixlen['}'] = 1; } } else if(!menucmp && !havesuff) { /* If we didn't add a suffix, add a space, unless we are * * doing menu completion. */ inststrlen(" ", 1, 1); menuinsc++; if(menuwe) makesuffix(1); } if(menuwe && parampre && isset(AUTOPARAMKEYS)) makeparamsuffix(parambr, menuinsc); if (!menuwe) cs = menuend; } /* This handles the beginning of menu-completion. */ /**/ static void do_ambig_menu(void) { menucmp = 1; menucur = NULL; do_single(amatches[0]); menucur = amatches; } /* Return the length of the common prefix of s and t. */ /**/ 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. */ /**/ 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; } /* This is used to print the explanation string. * * It returns the number of lines printed. */ /**/ static int printfmt(char *fmt, int n, int dopr) { char *p = fmt, nc[DIGBUFSIZE]; int l = 0, cc = 0; for (; *p; p++) { /* Handle the `%' stuff (%% == %, %n == ). */ if (*p == '%') { if (*++p) { switch (*p) { case '%': if (dopr) putc('%', shout); cc++; break; case 'n': sprintf(nc, "%d", n); if (dopr) fprintf(shout, nc); cc += strlen(nc); break; } } else break; } else { cc++; if (*p == '\n') { l += 1 + (cc / columns); cc = 0; } if (dopr) putc(*p, shout); } } return l + (cc / columns); } /* List the matches. Note that the list entries are metafied. */ /**/ void listmatches(void) { int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0; int off = 0, boff = 0, nboff = 0; int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC)); char **arr, **ap, sav; int nfpl, nfsl, nlpl, nlsl; int listmax = getiparam("LISTMAX"), litnl = 0; size_t (*strlenfn) _((char const *)); #ifdef DEBUG /* Sanity check */ if(!validlist) { showmsg("BUG: listmatches called with bogus list"); return; } #endif /* Calculate lengths of prefixes/suffixes to be added */ nfpl = fpre ? niceztrlen(fpre) : 0; nfsl = fsuf ? niceztrlen(fsuf) : 0; nlpl = lpre ? niceztrlen(lpre) : 0; nlsl = lsuf ? niceztrlen(lsuf) : 0; /* Calculate the lengths of the prefixes/suffixes we have to ignore during printing. */ if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) { if (ppre && *ppre) off = strlen(ppre); if (psuf && *psuf) { boff = strlen(psuf); nboff = niceztrlen(psuf); } } /* Set the cursor below the prompt. */ trashzle(); showinglist = 0; clearflag = (isset(USEZLE) && !termflags && (isset(ALWAYSLASTPROMPT) && zmult == 1)) || (unset(ALWAYSLASTPROMPT) && zmult != 1); /* just to keep gcc happy */ fw = colsz = up = 0; if (aylist) { arr = aylist; /* If no literal newlines, the remaining code should use strlen() */ strlenfn = (size_t (*) _((char const *)))strlen; /* The hard bit here is that we are handling newlines literally. * * In fact, we are in principle handling all characters literally, * * but it's quite enough work with just newlines. * * If there are such, we give up trying to print the list as * * columns and print as rows, counting the extra newlines. */ ct = 0; for (ap = arr; *ap; ap++) { ct++; if (strchr(*ap, '\n')) litnl++; } if (litnl) { colsz = ct; up = colsz + nlnct - clearflag; /* Count real newlines, as well as overflowing lines. */ for (ap = arr; *ap; ap++) { char *nlptr, *sptr = *ap; while (sptr && *sptr) { up += (nlptr = strchr(sptr, '\n')) ? 1 + (nlptr-sptr)/columns : strlen(sptr)/columns; sptr = nlptr ? nlptr+1 : NULL; } } } } else { arr = amatches; ct = nmatches; strlenfn = niceztrlen; } if (!litnl) { /* Calculate the column width, the number of columns and the number of lines. */ for (ap = arr; *ap; ap++) if ((cl = strlenfn(*ap + off) - nboff + ((ispattern || aylist) ? 0 : (!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) > longest) longest = cl; if (of) longest++; fw = longest + 2; fct = (columns + 1) / fw; if (fct == 0) { fct = 1; colsz = ct; up = colsz + nlnct - clearflag; for (ap = arr; *ap; ap++) up += (strlenfn(*ap + off) - nboff + of + ((ispattern || aylist) ? 0 : (!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) / columns; } else { colsz = (ct + fct - 1) / fct; up = colsz + nlnct - clearflag + (ct == 0); } } /* Print the explanation string, if any. */ if (expl) { xup = printfmt(expl, ct, 1) + 1; putc('\n', shout); up += xup; } /* Maybe we have to ask if the user wants to see the list. */ if ((listmax && ct > listmax) || (!listmax && up >= lines)) { int qup; setterm(); qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1); fflush(shout); if (getzlequery() != 'y') { if (clearflag) { putc('\r', shout); tcmultout(TCUP, TCMULTUP, qup); if (tccan(TCCLEAREOD)) tcout(TCCLEAREOD); tcmultout(TCUP, TCMULTUP, nlnct + xup); } else putc('\n', shout); return; } if (clearflag) { putc('\r', shout); tcmultout(TCUP, TCMULTUP, qup); if (tccan(TCCLEAREOD)) tcout(TCCLEAREOD); } else putc('\n', shout); settyinfo(&shttyinfo); } /* Now print the matches. */ for (t1 = 0; t1 != colsz; t1++) { ap = arr + t1; if (of) { /* We have to print the file types. */ while (*ap) { int t2; char *pb; struct stat buf; /* Build the path name for the stat. */ if (ispattern) { int cut = strlen(*ap) - boff; sav = ap[0][cut]; ap[0][cut] = '\0'; nicezputs(*ap + off, shout); t2 = niceztrlen(*ap + off); ap[0][cut] = sav; pb = *ap; } else { nicezputs(fpre, shout); nicezputs(*ap, shout); nicezputs(fsuf, shout); t2 = nfpl + niceztrlen(*ap) + nfsl; pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 + strlen(fpre) + strlen(*ap) + strlen(fsuf)); sprintf(pb, "%s%s%s%s", (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf); } if (ztat(pb, &buf, 1)) putc(' ', shout); else /* Print the file type character. */ putc(file_type(buf.st_mode), shout); for (t0 = colsz; t0 && *ap; t0--, ap++); if (*ap) /* And add spaces to make the columns aligned. */ for (++t2; t2 < fw; t2++) putc(' ', shout); } } else while (*ap) { int t2; if (aylist) { zputs(*ap, shout); t2 = strlen(*ap); } else if (ispattern) { int cut = strlen(*ap) - boff; sav = ap[0][cut]; ap[0][cut] = '\0'; nicezputs(*ap + off, shout); t2 = niceztrlen(*ap + off); ap[0][cut] = sav; } else if (!(haswhat & HAS_MISC)) { nicezputs(fpre, shout); nicezputs(*ap, shout); nicezputs(fsuf, shout); t2 = nfpl + niceztrlen(*ap) + nfsl; } else { nicezputs(lpre, shout); nicezputs(*ap, shout); nicezputs(lsuf, shout); t2 = nlpl + niceztrlen(*ap) + nlsl; } for (t0 = colsz; t0 && *ap; t0--, ap++); if (*ap) for (; t2 < fw; t2++) putc(' ', shout); } if (t1 != colsz - 1 || !clearflag) putc('\n', shout); } if (clearflag) /* Move the cursor up to the prompt, if always_last_prompt * * is set and all that... */ if (up < lines) { tcmultout(TCUP, TCMULTUP, up); showinglist = -1; } else clearflag = 0, putc('\n', shout); } /* This is used to print expansions. */ /**/ void listlist(LinkList l) { int hw = haswhat, ip = ispattern; char *lp = lpre, *ls = lsuf; int nm = nmatches, vl = validlist; char **am = amatches, **ay = aylist; char *ex = expl; haswhat = HAS_MISC; ispattern = 0; validlist = 1; lpre = lsuf = ""; aylist = NULL; expl = NULL; makearray(l); listmatches(); showinglist = 0; expl = ex; amatches = am; aylist = ay; nmatches = nm; validlist = vl; lpre = lp; lsuf = ls; ispattern = ip; haswhat = hw; } /* Expand the history references. */ /**/ int doexpandhist(void) { unsigned char *ol; int oll, ocs, ne = noerrs, err; DPUTS(useheap, "BUG: useheap in doexpandhist()"); HEAPALLOC { 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(); noaliases = 1; noerrs = 1; exlast = inbufct; do { ctxtlex(); } while (tok != ENDINPUT && tok != LEXERR); stophist = 2; 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 = 0; 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(); LASTALLOC_RETURN 1; } } strcpy((char *)line, (char *)ol); ll = oll; cs = ocs; unmetafy_line(); popheap(); } LASTALLOC; return 0; } /**/ void magicspace(void) { c = ' '; selfinsert(); doexpandhist(); } /**/ void expandhistory(void) { if (!doexpandhist()) feep(); } static int cmdwb, cmdwe; /**/ static char * getcurcmd(void) { int curlincmd; char *s = NULL; DPUTS(useheap, "BUG: useheap in getcurcmd()"); HEAPALLOC { zleparse = 2; lexsave(); metafy_line(); inpush(dupstrspace((char *) line), 0, NULL); unmetafy_line(); strinbeg(); 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(); } LASTALLOC; return s; } /**/ void processcmd(void) { char *s; int m = zmult; s = getcurcmd(); if (!s) { feep(); return; } zmult = 1; pushline(); zmult = m; inststr(bindk->nam); inststr(" "); untokenize(s); HEAPALLOC { inststr(quotename(s, NULL, NULL, NULL)); } LASTALLOC; zsfree(s); done = 1; } /**/ void expandcmdpath(void) { int oldcs = cs, na = noaliases; char *s, *str; noaliases = 1; s = getcurcmd(); noaliases = na; if (!s || cmdwb < 0 || cmdwe < cmdwb) { feep(); return; } str = findcmd(s); zsfree(s); if (!str) { feep(); return; } 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; zsfree(str); } /* Extra function added by AR Iano-Fletcher. */ /* This is a expand/complete in the vein of wash. */ /**/ void expandorcompleteprefix(void) { comppref = 1; expandorcomplete(); comppref = 0; }