summaryrefslogtreecommitdiff
path: root/Src/hist.c
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
commite74702b467171dbdafb56dfe354794a212e020d9 (patch)
treec295b3e9b2e93e2de10331877442615b0f37e779 /Src/hist.c
parentc175751b501a3a4cb40ad4787340a597ea769be4 (diff)
downloadzsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz
zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip
Initial revision
Diffstat (limited to 'Src/hist.c')
-rw-r--r--Src/hist.c1670
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;
+}
+