diff options
author | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
---|---|---|
committer | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
commit | e74702b467171dbdafb56dfe354794a212e020d9 (patch) | |
tree | c295b3e9b2e93e2de10331877442615b0f37e779 /Src/hist.c | |
parent | c175751b501a3a4cb40ad4787340a597ea769be4 (diff) | |
download | zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.xz zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip |
Initial revision
Diffstat (limited to 'Src/hist.c')
-rw-r--r-- | Src/hist.c | 1670 |
1 files changed, 1670 insertions, 0 deletions
diff --git a/Src/hist.c b/Src/hist.c new file mode 100644 index 000000000..a4c5735c1 --- /dev/null +++ b/Src/hist.c @@ -0,0 +1,1670 @@ +/* + * hist.c - history expansion + * + * 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 "zsh.mdh" +#include "hist.pro" + +/* != 0 means history substitution is turned off */ + +/**/ +int stophist; + +/* this line began with a space, so junk it if HISTIGNORESPACE is on */ + +/**/ +int spaceflag; + +/* if != 0, we are expanding the current line */ + +/**/ +int expanding; + +/* these are used to modify the cursor position during expansion */ + +/**/ +int excs, exlast; + +/* + * Current history event number + * + * Note on curhist: with history inactive, this points to the + * last line actually added to the history list. With history active, + * the line does not get added to the list until hend(), if at all. + * However, curhist is incremented to reflect the current line anyway. + * Thus if the line is not added to the list, curhist must be + * decremented in hend(). + */ + +/**/ +int curhist; + +/* number of history entries */ + +/**/ +int histentct; + +/* array of history entries */ + +/**/ +Histent histentarr; + +/* capacity of history lists */ + +/**/ +int histsiz; + +/* if = 1, we have performed history substitution on the current line * + * if = 2, we have used the 'p' modifier */ + +/**/ +int histdone; + +/* state of the history mechanism */ + +/**/ +int histactive; + +/* Bits of histactive variable */ +#define HA_ACTIVE (1<<0) /* History mechanism is active */ +#define HA_NOSTORE (1<<1) /* Don't store the line when finished */ +#define HA_JUNKED (1<<2) /* Last history line was already junked */ +#define HA_NOINC (1<<3) /* Don't store, curhist not incremented */ + +/* Array of word beginnings and endings in current history line. */ + +/**/ +short *chwords; + +/* Max, actual position in chwords. + * nwords = chwordpos/2 because we record beginning and end of words. + */ + +/**/ +int chwordlen, chwordpos; + +/* the last l for s/l/r/ history substitution */ + +/**/ +char *hsubl; + +/* the last r for s/l/r/ history substitution */ + +/**/ +char *hsubr; + +/* pointer into the history line */ + +/**/ +char *hptr; + +/* the current history line */ + +/**/ +char *chline; + +/* true if the last character returned by hgetc was an escaped bangchar * + * if it is set and NOBANGHIST is unset hwaddc escapes bangchars */ + +/**/ +int qbang; + +/* max size of histline */ + +/**/ +int hlinesz; + +/* default event (usually curhist-1, that is, "!!") */ + +static int defev; + +/* add a character to the current history word */ + +/**/ +void +hwaddc(int c) +{ + /* Only if history line exists and lexing has not finished. */ + if (chline && !(errflag || lexstop)) { + /* Quote un-expanded bangs in the history line. */ + if (c == bangchar && stophist < 2 && qbang) + /* If qbang is not set, we do not escape this bangchar as it's * + * not mecessary (e.g. it's a bang in !=, or it is followed * + * by a space). Roughly speaking, qbang is zero only if the * + * history interpreter has already digested this bang and * + * found that it is not necessary to escape it. */ + hwaddc('\\'); + *hptr++ = c; + + /* Resize history line if necessary */ + if (hptr - chline >= hlinesz) { + int oldsiz = hlinesz; + + chline = realloc(chline, hlinesz = oldsiz + 16); + hptr = chline + oldsiz; + } + } +} + +/* This function adds a character to the zle input line. It is used when * + * zsh expands history (see doexpandhist() in zle_tricky.c). It also * + * calculates the new cursor position after the expansion. It is called * + * from hgetc() and from gettok() in lex.c for characters in comments. */ + +/**/ +void +addtoline(int c) +{ + if (! expanding || lexstop) + return; + if (qbang && c == bangchar && stophist < 2) { + exlast--; + spaceinline(1); + line[cs++] = '\\'; + } + if (excs > cs) { + excs += 1 + inbufct - exlast; + if (excs < cs) + /* this case could be handled better but it is * + * so rare that it does not worth it */ + excs = cs; + } + exlast = inbufct; + spaceinline(1); + line[cs++] = itok(c) ? ztokens[c - Pound] : c; +} + +/**/ +int +hgetc(void) +{ + int c = ingetc(); + + qbang = 0; + if (!stophist && !(inbufflags & INP_ALIAS)) { + /* If necessary, expand history characters. */ + c = histsubchar(c); + if (c < 0) { + /* bad expansion */ + errflag = lexstop = 1; + return ' '; + } + } + if ((inbufflags & INP_HIST) && !stophist) { + /* the current character c came from a history expansion * + * (inbufflags && INP_HIST) and history is not disabled * + * (e.g. we are not inside single quotes). In that case, \! * + * should be treated as ! (since this \! came from a previous * + * history line where \ was used to escape the bang). So if * + * c == '\\' we fetch one more character to see if it's a bang, * + * and if it is not, we unget it and reset c back to '\\' */ + qbang = 0; + if (c == '\\' && !(qbang = (c = ingetc()) == bangchar)) + safeinungetc(c), c = '\\'; + } else if (stophist || (inbufflags & INP_ALIAS)) + /* If the result is a bangchar which came from history or alias * + * expansion, we treat it as an escaped bangchar, unless history * + * is disabled. If stophist == 1 it only means that history is * + * temporarily disabled by a !" which won't appear in in the * + * history, so we still have an escaped bang. stophist > 1 if * + * history is disabled with NOBANGHIST or by someone else (e.g. * + * when the lexer scans single quoted text). */ + qbang = c == bangchar && (stophist < 2); + hwaddc(c); + addtoline(c); + + return c; +} + +/**/ +static void +safeinungetc(int c) +{ + if (lexstop) + lexstop = 0; + else + inungetc(c); +} + +/**/ +void +herrflush(void) +{ + while (!lexstop && inbufct) + hwaddc(ingetc()); +} + +/* extract :s/foo/bar/ delimiters and arguments */ + +/**/ +static int +getsubsargs(char *subline) +{ + int del; + char *ptr1, *ptr2; + + del = ingetc(); + ptr1 = hdynread2(del); + if (!ptr1) + return 1; + ptr2 = hdynread2(del); + if (strlen(ptr1)) { + zsfree(hsubl); + hsubl = ptr1; + } + zsfree(hsubr); + hsubr = ptr2; + if (hsubl && !strstr(subline, hsubl)) { + herrflush(); + zerr("substitution failed", NULL, 0); + return 1; + } + return 0; +} + +/* Get the maximum no. of words for a history entry. */ + +/**/ +static int +getargc(Histent ehist) +{ + return ehist->nwords ? ehist->nwords-1 : 0; +} + +/* Perform history substitution, returning the next character afterwards. */ + +/**/ +static int +histsubchar(int c) +{ + int ev, farg, evset = -1, larg, argc, cflag = 0, bflag = 0; + static int mev = -1, marg = -1; + char buf[256], *ptr; + char *sline; + Histent ehist; + + /* look, no goto's */ + if (isfirstch && c == hatchar) { + /* Line begins ^foo^bar */ + isfirstch = 0; + inungetc(hatchar); + if (!(ehist = gethist(defev)) + || !(sline = getargs(ehist, 0, getargc(ehist))) + || getsubsargs(sline) || !hsubl) + return -1; + subst(&sline, hsubl, hsubr, 0); + } else { + /* Line doesn't begin ^foo^bar */ + if (c != ' ') + isfirstch = 0; + if (c == '\\') { + int g = ingetc(); + + if (g != bangchar) + safeinungetc(g); + else { + qbang = 1; + return bangchar; + } + } + if (c != bangchar) + return c; + *hptr = '\0'; + if ((c = ingetc()) == '{') { + bflag = cflag = 1; + c = ingetc(); + } + if (c == '\"') { + stophist = 1; + return ingetc(); + } + if ((!cflag && inblank(c)) || c == '=' || c == '(' || lexstop) { + safeinungetc(c); + return bangchar; + } + cflag = 0; + ptr = buf; + + /* get event number */ + + if (c == '?') { + for (;;) { + c = ingetc(); + if (c == '?' || c == '\n' || lexstop) + break; + else + *ptr++ = c; + } + if (c != '\n' && !lexstop) + c = ingetc(); + *ptr = '\0'; + mev = ev = hconsearch(hsubl = ztrdup(buf), &marg); + evset = 0; + if (ev == -1) { + herrflush(); + zerr("no such event: %s", buf, 0); + return -1; + } + } else { + int t0; + + for (;;) { + if (inblank(c) || c == ';' || c == ':' || c == '^' || + c == '$' || c == '*' || c == '%' || c == '}' || + c == '\'' || c == '"' || c == '`' || lexstop) + break; + if (ptr != buf) { + if (c == '-') + break; + if ((idigit(buf[0]) || buf[0] == '-') && !idigit(c)) + break; + } + *ptr++ = c; + if (c == '#' || c == bangchar) { + c = ingetc(); + break; + } + c = ingetc(); + } + *ptr = 0; + if (!*buf) + if (c != '%') { + if (isset(CSHJUNKIEHISTORY)) + ev = curhist - 1; + else + ev = defev; + if (c == ':' && evset == -1) + evset = 0; + else + evset = 1; + } else { + if (marg != -1) + ev = mev; + else + ev = defev; + evset = 0; + } else if ((t0 = atoi(buf))) { + ev = (t0 < 0) ? curhist + t0 : t0; + evset = 1; + } else if ((unsigned)*buf == bangchar) { + ev = curhist - 1; + evset = 1; + } else if (*buf == '#') { + ev = curhist; + evset = 1; + } else if ((ev = hcomsearch(buf)) == -1) { + herrflush(); + zerr("event not found: %s", buf, 0); + return -1; + } else + evset = 1; + } + + /* get the event */ + + if (!(ehist = gethist(defev = ev))) + return -1; + + /* extract the relevant arguments */ + + argc = getargc(ehist); + if (c == ':') { + cflag = 1; + c = ingetc(); + if (c == '%' && marg != -1) { + if (!evset) { + ehist = gethist(defev = mev); + argc = getargc(ehist); + } else { + herrflush(); + zerr("Ambiguous history reference", NULL, 0); + return -1; + } + + } + } + if (c == '*') { + farg = 1; + larg = argc; + cflag = 0; + } else { + inungetc(c); + larg = farg = getargspec(argc, marg, evset); + if (larg == -2) + return -1; + if (farg != -1) + cflag = 0; + c = ingetc(); + if (c == '*') { + cflag = 0; + larg = argc; + } else if (c == '-') { + cflag = 0; + larg = getargspec(argc, marg, evset); + if (larg == -2) + return -1; + if (larg == -1) + larg = argc - 1; + } else + inungetc(c); + } + if (farg == -1) + farg = 0; + if (larg == -1) + larg = argc; + if (!(sline = getargs(ehist, farg, larg))) + return -1; + } + + /* do the modifiers */ + + for (;;) { + c = (cflag) ? ':' : ingetc(); + cflag = 0; + if (c == ':') { + int gbal = 0; + + if ((c = ingetc()) == 'g') { + gbal = 1; + c = ingetc(); + } + switch (c) { + case 'p': + histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC; + break; + case 'h': + if (!remtpath(&sline)) { + herrflush(); + zerr("modifier failed: h", NULL, 0); + return -1; + } + break; + case 'e': + if (!rembutext(&sline)) { + herrflush(); + zerr("modifier failed: e", NULL, 0); + return -1; + } + break; + case 'r': + if (!remtext(&sline)) { + herrflush(); + zerr("modifier failed: r", NULL, 0); + return -1; + } + break; + case 't': + if (!remlpaths(&sline)) { + herrflush(); + zerr("modifier failed: t", NULL, 0); + return -1; + } + break; + case 's': + if (getsubsargs(sline)) + return -1; /* fall through */ + case '&': + if (hsubl && hsubr) + subst(&sline, hsubl, hsubr, gbal); + else { + herrflush(); + zerr("no previous substitution", NULL, 0); + return -1; + } + break; + case 'q': + quote(&sline); + break; + case 'x': + quotebreak(&sline); + break; + case 'l': + downcase(&sline); + break; + case 'u': + upcase(&sline); + break; + default: + herrflush(); + zerr("illegal modifier: %c", NULL, c); + return -1; + } + } else { + if (c != '}' || !bflag) + inungetc(c); + if (c != '}' && bflag) { + zerr("'}' expected", NULL, 0); + return -1; + } + break; + } + } + + /* + * Push the expanded value onto the input stack, + * marking this as a history word for purposes of the alias stack. + */ + + lexstop = 0; + /* this function is called only called from hgetc and only if * + * !(inbufflags & INP_ALIAS). History expansion should never be * + * done with INP_ALIAS (to prevent recursive history expansion and * + * histoty expansion of aliases). Escapes are not removed here. * + * This is now handled in hgetc. */ + inpush(sline, INP_HIST, NULL); /* sline from heap, don't free */ + histdone |= HISTFLAG_DONE; + if (isset(HISTVERIFY)) + histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL; + + /* Don't try and re-expand line. */ + return ingetc(); +} + +/* unget a char and remove it from chline. It can only be used * + * to unget a character returned by hgetc. */ + +/**/ +void +hungetc(int c) +{ + int doit = 1; + + while (!lexstop) { + if (hptr[-1] != (char) c && stophist < 4 && + hptr > chline + 1 && hptr[-1] == '\n' && hptr[-2] == '\\') + hungetc('\n'), hungetc('\\'); + + if (expanding) { + cs--; + ll--; + exlast++; + } + DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); + hptr--; + DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); + qbang = (c == bangchar && stophist < 2 && + hptr > chline && hptr[-1] == '\\'); + if (doit) + inungetc(c); + if (!qbang) + return; + doit = !stophist && ((inbufflags & INP_HIST) || + !(inbufflags & INP_ALIAS)); + c = '\\'; + } +} + +/* begin reading a string */ + +/**/ +void +strinbeg(void) +{ + strin++; + hbegin(); + lexinit(); +} + +/* done reading a string */ + +/**/ +void +strinend(void) +{ + hend(); + DPUTS(!strin, "BUG: strinend() called without strinbeg()"); + strin--; + isfirstch = 1; + histdone = 0; +} + +/* initialize the history mechanism */ + +/**/ +void +hbegin(void) +{ + Histent curhistent; + + isfirstln = isfirstch = 1; + errflag = histdone = spaceflag = 0; + stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1; + chline = hptr = zcalloc(hlinesz = 16); + chwords = zalloc((chwordlen = 16)*sizeof(short)); + chwordpos = 0; + + if (histactive & HA_JUNKED) + curhist--; + curhistent = gethistent(curhist); + if (!curhistent->ftim) + curhistent->ftim = time(NULL); + histactive = HA_ACTIVE; + if (interact && isset(SHINSTDIN) && !strin) { + attachtty(mypgrp); + defev = curhist; + curhist++; + } else + histactive |= HA_NOINC; +} + +/* compare current line with history entry using only text in words */ + +/**/ +static int +histcmp(Histent he) +{ + int kword, lword; + int nwords = chwordpos/2; + + /* If the history entry came from a file, the words were not + * divided by the lexer so we have to resort to strcmp. + */ + if (he->flags & HIST_READ) + return strcmp(he->text, chline); + + if (nwords != he->nwords) + return 1; + + for (kword = 0; kword < 2*nwords; kword += 2) + if ((lword = chwords[kword+1]-chwords[kword]) + != he->words[kword+1]-he->words[kword] || + memcmp(he->text+he->words[kword], chline+chwords[kword], lword)) + return 1; + + return 0; +} + +/**/ +void +histreduceblanks(void) +{ + int i, len, pos, needblank; + + for (i = 0, len = 0; i < chwordpos; i += 2) { + len += chwords[i+1] - chwords[i] + + (i > 0 && chwords[i] > chwords[i-1]); + } + if (chline[len] == '\0') + return; + + for (i = 0, pos = 0; i < chwordpos; i += 2) { + len = chwords[i+1] - chwords[i]; + needblank = (i < chwordpos-2 && chwords[i+2] > chwords[i+1]); + if (pos != chwords[i]) { + memcpy(chline + pos, chline + chwords[i], len + needblank); + chwords[i] = pos; + chwords[i+1] = chwords[i] + len; + } + pos += len + needblank; + } + chline[pos] = '\0'; +} + +/* say we're done using the history mechanism */ + +/**/ +int +hend(void) +{ + int flag, save = 1; + + DPUTS(!chline, "BUG: chline is NULL in hend()"); + if (histactive & (HA_NOSTORE|HA_NOINC)) { + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + if (!(histactive & HA_NOINC)) + curhist--; + histactive = 0; + return 1; + } + flag = histdone; + histdone = 0; + if (hptr < chline + 1) + save = 0; + else { + *hptr = '\0'; + if (hptr[-1] == '\n') + if (chline[1]) { + *--hptr = '\0'; + } else + save = 0; + if (!*chline || !strcmp(chline, "\n") || + (isset(HISTIGNORESPACE) && spaceflag)) + save = 0; + } + if (flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) { + char *ptr; + + ptr = ztrdup(chline); + if ((flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) == HISTFLAG_DONE) { + zputs(ptr, shout); + fputc('\n', shout); + fflush(shout); + } + if (flag & HISTFLAG_RECALL) { + PERMALLOC { + pushnode(bufstack, ptr); + } LASTALLOC; + save = 0; + } else + zsfree(ptr); + } + if (save) { + Histent he; + int keepflags = 0; + +#ifdef DEBUG + /* debugging only */ + if (chwordpos%2) { + hwend(); + DPUTS(1, "BUG: uncompleted line in history"); + } +#endif + /* get rid of pesky \n which we've already nulled out */ + if (!chline[chwords[chwordpos-2]]) + chwordpos -= 2; + /* strip superfluous blanks, if desired */ + if (isset(HISTREDUCEBLANKS)) + histreduceblanks(); + + if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1)) + && he->text && !histcmp(he)) { + /* This history entry compares the same as the previous. + * In case minor changes were made, we overwrite the + * previous one with the current one. This also gets + * the timestamp right. However, keep the old flags. + */ + keepflags = he->flags; + curhist--; + } + + he = gethistent(curhist); + zsfree(he->text); + he->text = ztrdup(chline); + if (he->nwords) + zfree(he->words, he->nwords*2*sizeof(short)); + he->stim = time(NULL); + he->ftim = 0L; + he->flags = keepflags; + + if ((he->nwords = chwordpos/2)) { + he->words = (short *)zalloc(chwordpos * sizeof(short)); + memcpy(he->words, chwords, chwordpos * sizeof(short)); + } + } else + curhist--; + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + histactive = 0; + return !(flag & HISTFLAG_NOEXEC || errflag); +} + +/* remove the current line from the history List */ + +/**/ +void +remhist(void) +{ + if (!(histactive & HA_ACTIVE)) { + if (!(histactive & HA_JUNKED)) { + /* make sure this doesn't show up when we do firsthist() */ + Histent he = gethistent(curhist); + zsfree(he->text); + he->text = NULL; + histactive |= HA_JUNKED; + /* curhist-- is delayed until the next hbegin() */ + } + } else + histactive |= HA_NOSTORE; +} + +/* Gives current expansion word if not last word before chwordpos. */ + +/**/ +int hwgetword = -1; + +/* begin a word */ + +/**/ +void +hwbegin(int offset) +{ + if (chwordpos%2) + chwordpos--; /* make sure we're on a word start, not end */ + /* If we're expanding an alias, we should overwrite the expansion + * in the history. + */ + if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) + hwgetword = chwordpos; + else + hwgetword = -1; + chwords[chwordpos++] = hptr - chline + offset; +} + +/* add a word to the history List */ + +/**/ +void +hwend(void) +{ + if (chwordpos%2 && chline) { + /* end of word reached and we've already begun a word */ + if (hptr > chline + chwords[chwordpos-1]) { + chwords[chwordpos++] = hptr - chline; + if (chwordpos >= chwordlen) { + chwords = (short *) realloc(chwords, + (chwordlen += 16)*sizeof(short)); + } + if (hwgetword > -1) { + /* We want to reuse the current word position */ + chwordpos = hwgetword; + /* Start from where previous word ended, if possible */ + hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0]; + } + } else { + /* scrub that last word, it doesn't exist */ + chwordpos--; + } + } +} + +/* Go back to immediately after the last word, skipping space. */ + +/**/ +void +histbackword(void) +{ + if (!(chwordpos%2) && chwordpos) + hptr = chline + chwords[chwordpos-1]; +} + +/* Get the start and end point of the current history word */ + +/**/ +static void +hwget(char **startptr) +{ + int pos = hwgetword > -1 ? hwgetword : chwordpos - 2; + +#ifdef DEBUG + /* debugging only */ + if (hwgetword == -1 && !chwordpos) { + /* no words available */ + DPUTS(1, "BUG: hwget() called with no words"); + *startptr = ""; + return; + } + else if (hwgetword == -1 && chwordpos%2) { + DPUTS(1, "BUG: hwget() called in middle of word"); + *startptr = ""; + return; + } +#endif + + *startptr = chline + chwords[pos]; + chline[chwords[++pos]] = '\0'; +} + +/* Replace the current history word with rep, if different */ + +/**/ +void +hwrep(char *rep) +{ + char *start; + hwget(&start); + + if (!strcmp(rep, start)) + return; + + hptr = start; + chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2; + hwbegin(0); + qbang = 1; + while (*rep) + hwaddc(*rep++); + hwend(); +} + +/* Get the entire current line, deleting it in the history. */ + +/**/ +char * +hgetline(void) +{ + /* Currently only used by pushlineoredit(). + * It's necessary to prevent that from getting too pally with + * the history code. + */ + char *ret; + + if (!chline || hptr == chline) + return NULL; + *hptr = '\0'; + ret = dupstring(chline); + + /* reset line */ + hptr = chline; + chwordpos = 0; + hwgetword = -1; + + return ret; +} + +/* get an argument specification */ + +/**/ +static int +getargspec(int argc, int marg, int evset) +{ + int c, ret = -1; + + if ((c = ingetc()) == '0') + return 0; + if (idigit(c)) { + ret = 0; + while (idigit(c)) { + ret = ret * 10 + c - '0'; + c = ingetc(); + } + inungetc(c); + } else if (c == '^') + ret = 1; + else if (c == '$') + ret = argc; + else if (c == '%') { + if (evset) { + herrflush(); + zerr("Ambiguous history reference", NULL, 0); + return -2; + } + if (marg == -1) { + herrflush(); + zerr("%% with no previous word matched", NULL, 0); + return -2; + } + ret = marg; + } else + inungetc(c); + return ret; +} + +/* do ?foo? search */ + +/**/ +static int +hconsearch(char *str, int *marg) +{ + int t0, t1 = 0; + char *s; + Histent he; + + for (t0 = curhist - 1; (he = quietgethist(t0)); t0--) + if ((s = strstr(he->text, str))) { + int pos = s - he->text; + while (t1 < he->nwords && he->words[2*t1] <= pos) + t1++; + *marg = t1 - 1; + return t0; + } + return -1; +} + +/* do !foo search */ + +/**/ +int +hcomsearch(char *str) +{ + int t0; + char *hs; + + for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--) + if (!strncmp(hs, str, strlen(str))) + return t0; + return -1; +} + +/* various utilities for : modifiers */ + +/**/ +int +remtpath(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '/'))) { + if (str != remcut) + *remcut = '\0'; + else + str[1] = '\0'; + return 1; + } + return 0; +} + +/**/ +int +remtext(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '.')) && remcut != str) { + *remcut = '\0'; + return 1; + } + return 0; +} + +/**/ +int +rembutext(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '.')) && remcut != str) { + *junkptr = dupstring(remcut + 1); /* .xx or xx? */ + return 1; + } + return 0; +} + +/**/ +int +remlpaths(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '/'))) { + *remcut = '\0'; + *junkptr = dupstring(remcut + 1); + return 1; + } + return 0; +} + +/**/ +int +makeuppercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tuupper(*str); + return 1; +} + +/**/ +int +makelowercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tulower(*str); + return 1; +} + +/**/ +int +makecapitals(char **junkptr) +{ + char *str = *junkptr; + + for (; *str;) { + for (; *str && !ialnum(*str); str++); + if (*str) + *str = tuupper(*str), str++; + for (; *str && ialnum(*str); str++) + *str = tulower(*str); + } + return 1; +} + +/**/ +void +subst(char **strptr, char *in, char *out, int gbal) +{ + char *str = *strptr, *instr = *strptr, *substcut, *sptr, *oldstr; + int off, inlen, outlen; + + if (!*in) + in = str, gbal = 0; + if (!(substcut = (char *)strstr(str, in))) + return; + inlen = strlen(in); + sptr = convamps(out, in, inlen); + outlen = strlen(sptr); + + do { + *substcut = '\0'; + off = substcut - *strptr + outlen; + substcut += inlen; + *strptr = tricat(oldstr = *strptr, sptr, substcut); + if (oldstr != instr) + zsfree(oldstr); + str = (char *)*strptr + off; + } while (gbal && (substcut = (char *)strstr(str, in))); +} + +/**/ +static char * +convamps(char *out, char *in, int inlen) +{ + char *ptr, *ret, *pp; + int slen, sdup = 0; + + for (ptr = out, slen = 0; *ptr; ptr++, slen++) + if (*ptr == '\\') + ptr++, sdup = 1; + else if (*ptr == '&') + slen += inlen - 1, sdup = 1; + if (!sdup) + return out; + ret = pp = (char *)alloc(slen + 1); + for (ptr = out; *ptr; ptr++) + if (*ptr == '\\') + *pp++ = *++ptr; + else if (*ptr == '&') { + strcpy(pp, in); + pp += inlen; + } else + *pp++ = *ptr; + *pp = '\0'; + return ret; +} + +/**/ +struct histent * +quietgethist(int ev) +{ + static struct histent storehist; + + if (ev < firsthist() || ev > curhist) + return NULL; + if (ev == curhist && (histactive & HA_ACTIVE)) { + /* The current history line has not been stored. Build it up + * from other variables. + */ + storehist.text = chline; + storehist.nwords = chwordpos/2; + storehist.words = chwords; + + return &storehist; + } else + return gethistent(ev); +} + +/**/ +char * +quietgetevent(int ev) +{ + Histent ent = quietgethist(ev); + + return ent ? ent->text : NULL; +} + +/**/ +static Histent +gethist(int ev) +{ + Histent ret; + + ret = quietgethist(ev); + if (!ret) { + herrflush(); + zerr("no such event: %d", NULL, ev); + } + return ret; +} + +/**/ +static char * +getargs(Histent elist, int arg1, int arg2) +{ + short *words = elist->words; + int pos1, nwords = elist->nwords; + + if (arg2 < arg1 || arg1 >= nwords || arg2 >= nwords) { + /* remember, argN is indexed from 0, nwords is total no. of words */ + herrflush(); + zerr("no such word in event", NULL, 0); + return NULL; + } + + pos1 = words[2*arg1]; + return dupstrpfx(elist->text + pos1, words[2*arg2+1] - pos1); +} + +/**/ +void +upcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tuupper(*pp); +} + +/**/ +void +downcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tulower(*pp); +} + +/**/ +int +quote(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + int inquotes = 0; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') { + len += 3; + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') + len += 2; + ptr = *str; + *str = rptr = (char *)alloc(len); + *rptr++ = '\''; + for (; *ptr; ptr++) + if (*ptr == '\'') { + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') { + *rptr++ = '\''; + *rptr++ = *ptr; + *rptr++ = '\''; + } else + *rptr++ = *ptr; + *rptr++ = '\''; + *rptr++ = 0; + str[1] = NULL; + return 0; +} + +/**/ +static int +quotebreak(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') + len += 3; + else if (inblank(*ptr)) + len += 2; + ptr = *str; + *str = rptr = (char *)alloc(len); + *rptr++ = '\''; + for (; *ptr;) + if (*ptr == '\'') { + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + ptr++; + } else if (inblank(*ptr)) { + *rptr++ = '\''; + *rptr++ = *ptr++; + *rptr++ = '\''; + } else + *rptr++ = *ptr++; + *rptr++ = '\''; + *rptr++ = '\0'; + return 0; +} + +#if 0 +/* read an arbitrary amount of data into a buffer until stop is found */ + +/**/ +char * +hdynread(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') { + inungetc('\n'); + zerr("delimiter expected", NULL, 0); + zfree(buf, bsiz); + return NULL; + } + return buf; +} +#endif + +/**/ +static char * +hdynread2(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\n') { + inungetc(c); + break; + } + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') + inungetc('\n'); + return buf; +} + +/**/ +void +inithist(void) +{ + histentct = histsiz; + histentarr = (Histent) zcalloc(histentct * sizeof *histentarr); +} + +/**/ +void +resizehistents(void) +{ + int newentct, t0, t1, firstlex; + Histent newarr; + + newentct = histsiz; + newarr = (Histent) zcalloc(newentct * sizeof *newarr); + firstlex = curhist - histsiz + 1; + t0 = firsthist(); + if (t0 < curhist - newentct) + t0 = curhist - newentct; + t1 = t0 % newentct; + for (; t0 <= curhist; t0++) { + newarr[t1] = *gethistent(t0); + if (t0 < firstlex) { + zsfree(newarr[t1].text); + newarr[t1].text = NULL; + } + t1++; + if (t1 == newentct) + t1 = 0; + } + free(histentarr); + histentarr = newarr; + histentct = newentct; +} + +/**/ +void +readhistfile(char *s, int err) +{ + char *buf; + FILE *in; + Histent ent; + time_t tim = time(NULL); + short *wordlist; + int nwordpos, nwordlist, bufsiz; + + if (!s) + return; + if ((in = fopen(unmeta(s), "r"))) { + nwordlist = 16; + wordlist = (short *)zalloc(nwordlist*sizeof(short)); + bufsiz = 1024; + buf = zalloc(bufsiz); + + while (fgets(buf, bufsiz, in)) { + int l = strlen(buf); + char *pt, *start; + + while (l) { + while (buf[l - 1] != '\n') { + buf = zrealloc(buf, 2 * bufsiz); + bufsiz = 2 * bufsiz; + if (!fgets(buf + l, bufsiz - l, in)) { + l++; + break; + } + l = strlen(buf); + } + buf[l - 1] = '\0'; + if (l > 1 && buf[l - 2] == '\\') { + buf[l - 2] = '\n'; + fgets(buf + l - 1, bufsiz - (l - 1), in); + l = strlen(buf); + } else + break; + } + + ent = gethistent(++curhist); + pt = buf; + if (*pt == ':') { + pt++; + ent->stim = zstrtol(pt, NULL, 0); + for (; *pt != ':' && *pt; pt++); + if (*pt) { + pt++; + ent->ftim = zstrtol(pt, NULL, 0); + for (; *pt != ';' && *pt; pt++); + if (*pt) + pt++; + } else { + ent->ftim = tim; + } + if (ent->stim == 0) + ent->stim = tim; + if (ent->ftim == 0) + ent->ftim = tim; + } else { + ent->ftim = ent->stim = tim; + } + + zsfree(ent->text); + ent->text = ztrdup(pt); + ent->flags = HIST_OLD|HIST_READ; + if (ent->nwords) + zfree(ent->words, ent->nwords*2*sizeof(short)); + + /* Divide up the words. We don't know how it lexes, + so just look for spaces. + */ + nwordpos = 0; + start = pt; + do { + while (*pt == ' ') + pt++; + if (*pt) { + if (nwordpos >= nwordlist) + wordlist = (short *) realloc(wordlist, + (nwordlist += 16)*sizeof(short)); + wordlist[nwordpos++] = pt - start; + while (*pt && *pt != ' ') + pt++; + wordlist[nwordpos++] = pt - start; + } + } while (*pt); + + ent->nwords = nwordpos/2; + if (ent->nwords) { + ent->words = (short *)zalloc(nwordpos*sizeof(short)); + memcpy(ent->words, wordlist, nwordpos*sizeof(short)); + } else + ent->words = (short *)NULL; + } + fclose(in); + + zfree(wordlist, nwordlist*sizeof(short)); + zfree(buf, bufsiz); + } else if (err) + zerr("can't read history file", s, 0); +} + +/**/ +void +savehistfile(char *s, int err, int app) +{ + char *t; + FILE *out; + int ev; + Histent ent; + int savehist = getiparam("SAVEHIST"); + + if (!s || !interact || savehist <= 0) + return; + ev = curhist - savehist + 1; + if (ev < firsthist()) + ev = firsthist(); + if (app & 1) + out = fdopen(open(unmeta(s), + O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a"); + else + out = fdopen(open(unmeta(s), + O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"); + if (out) { + for (; ev <= curhist - !!(histactive & HA_ACTIVE); ev++) { + ent = gethistent(ev); + if (app & 2) { + if (ent->flags & HIST_OLD) + continue; + ent->flags |= HIST_OLD; + } + t = ent->text; + if (isset(EXTENDEDHISTORY)) { + fprintf(out, ": %ld:%ld;", + (long)ent->stim, + (long)ent->ftim); + } else if (*t == ':') + fputc('\\', out); + + for (; *t; t++) { + if (*t == '\n') + fputc('\\', out); + fputc(*t, out); + } + fputc('\n', out); + } + fclose(out); + + if (app & 2 && (out = fopen(unmeta(s), "r"))) { + char **store, buf[1024], **ptr; + int i, l, histnum = 0; + + store = (char **)zcalloc((savehist + 1) * sizeof *store); + while (fgets(buf, sizeof(buf), out)) { + char *t; + + if (store[i = histnum % savehist]) + free(store[i]); + store[i] = ztrdup(buf); + l = strlen(buf); + if (l > 1) { + t = store[i] + l; + while ((t[-1] != '\n' || + (t[-1] == '\n' && t[-2] == '\\')) && + fgets(buf, sizeof(buf), out)) { + l += strlen(buf); + store[i] = zrealloc(store[i], l + 1); + t = store[i] + l; + strcat(store[i], buf); + } + } + histnum++; + } + fclose(out); + if ((out = fdopen(open(unmeta(s), + O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"))) { + if (histnum < savehist) + for (i = 0; i < histnum; i++) + fprintf(out, "%s", store[i]); + else + for (i = histnum; i < histnum + savehist; i++) + fprintf(out, "%s", store[i % savehist]); + fclose(out); + } + for (ptr = store; *ptr; ptr++) + zsfree(*ptr); + free(store); + } + } else if (err) + zerr("can't write history file %s", s, 0); +} + +/**/ +int +firsthist(void) +{ + int ev; + Histent ent; + + ev = curhist - histentct + 1; + if (ev < 1) + ev = 1; + do { + ent = gethistent(ev); + if (ent->text) + break; + ev++; + } + while (ev < curhist); + return ev; +} + |