mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-19 21:44:11 +01:00
1671 lines
34 KiB
C
1671 lines
34 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|