/*
 * zle_hist.c - history editing
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1997 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zle.mdh"
#include "zle_hist.pro"

/* Column position of vi ideal cursor.  -1 if it is unknown -- most *
 * movements and changes do this.                                   */

/**/
int lastcol;

/* current history line number */

/**/
int histline;

/* Previous search string use in an incremental search */

/**/
ZLE_STRING_T previous_search = NULL;

/**/
int previous_search_len = 0;

struct zle_text {
    ZLE_STRING_T text;
    int len;
    int alloced;
};

/*
 * Fetch the text of a history line in internal ZLE format.
 * If the line has been edited, returns that, else allocates
 * a converted line.
 *
 * Each use of this must have a matching zletextfree() in order
 * to free up the allocated line, if any.  (N.B.: each use *of
 * the function*, not just each use of a struct zle_text.)
 *
 * TODO: This is quite inefficient.  We could convert zlinecmp and
 * zlinefind to take a metafied string as input and acquire a (wide)
 * character from it whenever needed, which would also require storing
 * zle_text as a metafied string in remember_edits().  However, the
 * following is good enough for now (although searching a really huge
 * history might not be so much fun).
 */

static void
zletext(Histent ent, struct zle_text *zt)
{
    char *duptext;

    if (ent->zle_text) {
	zt->text = ent->zle_text;
	zt->len = ent->zle_len;
	zt->alloced = 0;
	return;
    }

    duptext = ztrdup(ent->node.nam);
    zt->text = stringaszleline(duptext, 0, &zt->len, NULL, NULL);
    zsfree(duptext);
    zt->alloced = 1;
}

/* See above. */

static void
zletextfree(struct zle_text *zt)
{
    if (zt->alloced) {
	free(zt->text);
	zt->alloced = 0;
    }
}

/**/
void
remember_edits(void)
{
    Histent ent = quietgethist(histline);
    if (ent) {
	if (!ent->zle_text || ent->zle_len != zlell ||
	    ZS_memcmp(ent->zle_text, zleline, zlell) != 0) {
	    if (ent->zle_text)
		free(ent->zle_text);
	    ent->zle_text = zalloc(zlell * ZLE_CHAR_SIZE);
	    ent->zle_len = zlell;
	    ZS_memcpy(ent->zle_text, zleline, zlell);
	}
    }
}

/**/
void
forget_edits(void)
{
    Histent he;

    for (he = hist_ring; he; he = up_histent(he)) {
	if (he->zle_text) {
	    free(he->zle_text);
	    he->zle_text = NULL;
	    he->zle_len = 0;
	}
    }
}

/**/
int
uphistory(UNUSED(char **args))
{
    int nodups = isset(HISTIGNOREDUPS);
    if (!zle_goto_hist(histline, -zmult, nodups) && isset(HISTBEEP))
	return 1;
    return 0;
}

/**/
static int
upline(void)
{
    int n = zmult;

    if (n < 0) {
	zmult = -zmult;
	n = -downline();
	zmult = -zmult;
	return n;
    }
    if (lastcol == -1)
	lastcol = zlecs - findbol();
    zlecs = findbol();
    while (n) {
	if (!zlecs)
	    break;
	zlecs--;
	zlecs = findbol();
	n--;
    }
    if (!n) {
	int x = findeol();

	if ((zlecs += lastcol) >= x) {
	    zlecs = x;
	    if (zlecs > findbol() && invicmdmode())
		zlecs--;
	}
    }
    return n;
}

/**/
int
uplineorhistory(char **args)
{
    int ocs = zlecs;
    int n = upline();
    if (n) {
	int m = zmult, ret;

	zlecs = ocs;
	if (virangeflag || !(zlereadflags & ZLRF_HISTORY))
	    return 1;
	zmult = n;
	ret = uphistory(args);
	zmult = m;
	return ret;
    }
    return 0;
}

/**/
int
viuplineorhistory(char **args)
{
    int col = lastcol;
    uplineorhistory(args);
    lastcol = col;
    return vifirstnonblank(args);
}

/**/
int
uplineorsearch(char **args)
{
    int ocs = zlecs;
    int n = upline();
    if (n) {
	int m = zmult, ret;

	zlecs = ocs;
	if (virangeflag || !(zlereadflags & ZLRF_HISTORY))
	    return 1;
	zmult = n;
	ret = historysearchbackward(args);
	zmult = m;
	return ret;
    }
    return 0;
}

/**/
static int
downline(void)
{
    int n = zmult;

    if (n < 0) {
	zmult = -zmult;
	n = -upline();
	zmult = -zmult;
	return n;
    }
    if (lastcol == -1)
	lastcol = zlecs - findbol();
    while (n) {
	int x = findeol();

	if (x == zlell)
	    break;
	zlecs = x + 1;
	n--;
    }
    if (!n) {
	int x = findeol();

	if ((zlecs += lastcol) >= x) {
	    zlecs = x;
	    if (zlecs > findbol() && invicmdmode())
		zlecs--;
	}
    }
    return n;
}

/**/
int
downlineorhistory(char **args)
{
    int ocs = zlecs;
    int n = downline();
    if (n) {
	int m = zmult, ret;

	zlecs = ocs;
	if (virangeflag || !(zlereadflags & ZLRF_HISTORY))
	    return 1;
	zmult = n;
	ret = downhistory(args);
	zmult = m;
	return ret;
    }
    return 0;
}

/**/
int
vidownlineorhistory(char **args)
{
    int col = lastcol;
    downlineorhistory(args);
    lastcol = col;
    return vifirstnonblank(zlenoargs);
}

/**/
int
downlineorsearch(char **args)
{
    int ocs = zlecs;
    int n = downline();
    if (n) {
	int m = zmult, ret;

	zlecs = ocs;
	if (virangeflag || !(zlereadflags & ZLRF_HISTORY))
	    return 1;
	zmult = n;
	ret = historysearchforward(args);
	zmult = m;
	return ret;
    }
    return 0;
}

/**/
int
acceptlineanddownhistory(UNUSED(char **args))
{
    Histent he = quietgethist(histline);

    if (he && (he = movehistent(he, 1, HIST_FOREIGN))) {
	zpushnode(bufstack, ztrdup(he->node.nam));
	stackhist = he->histnum;
    }
    done = 1;
    return 0;
}

/**/
int
downhistory(UNUSED(char **args))
{
    int nodups = isset(HISTIGNOREDUPS);
    if (!zle_goto_hist(histline, zmult, nodups) && isset(HISTBEEP))
	return 1;
    return 0;
}

static int histpos, srch_hl, srch_cs = -1;
static ZLE_STRING_T srch_str;

/**/
int
historysearchbackward(char **args)
{
    Histent he;
    int n = zmult, hp;
    ZLE_STRING_T str;
    struct zle_text zt;

    if (zmult < 0) {
	int ret;
	zmult = -n;
	ret = historysearchforward(args);
	zmult = n;
	return ret;
    }
    if (*args)
	str = stringaszleline(*args, 0, &hp, NULL, NULL);
    else {
	if (histline == curhist || histline != srch_hl || zlecs != srch_cs ||
	    mark != 0 || ZS_memcmp(srch_str, zleline, histpos) != 0) {
	    zfree(srch_str, histpos);
	    for (histpos = 0; histpos < zlell && !ZC_iblank(zleline[histpos]); histpos++) ;
	    if (histpos < zlell)
		histpos++;
	    srch_str = zalloc(histpos * ZLE_CHAR_SIZE);
	    ZS_memcpy(srch_str, zleline, histpos);
	}
	str = srch_str;
	hp = histpos;
    }
    if (!(he = quietgethist(histline))) {
	if (*args)
	    free(str);
	return 1;
    }
    while ((he = movehistent(he, -1, hist_skip_flags))) {
	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
	    continue;
	zletext(he, &zt);
	if (zlinecmp(zt.text, zt.len, str, hp) < 0 &&
	    (*args || zlell != zt.len || ZS_memcmp(zt.text, str, zlell))) {
	    if (--n <= 0) {
		zle_setline(he);
		srch_hl = histline;
		srch_cs = zlecs;
		if (*args)
		    free(str);
		zletextfree(&zt);
		return 0;
	    }
	}
	zletextfree(&zt);
    }
    if (*args)
	free(str);
    return 1;
}

/**/
int
historysearchforward(char **args)
{
    Histent he;
    int n = zmult, hp;
    ZLE_STRING_T str;
    struct zle_text zt;

    if (zmult < 0) {
	int ret;
	zmult = -n;
	ret = historysearchbackward(args);
	zmult = n;
	return ret;
    }
    if (*args)
	str = stringaszleline(*args, 0, &hp, NULL, NULL);
    else {
	if (histline == curhist || histline != srch_hl || zlecs != srch_cs ||
	    mark != 0 || ZS_memcmp(srch_str, zleline, histpos) != 0) {
	    zfree(srch_str, histpos * ZLE_CHAR_SIZE);
	    for (histpos = 0; histpos < zlell && !ZC_iblank(zleline[histpos]); histpos++) ;
	    if (histpos < zlell)
		histpos++;
	    srch_str = zalloc(histpos * ZLE_CHAR_SIZE);
	    ZS_memcpy(srch_str, zleline, histpos);
	}
	str = srch_str;
	hp = histpos;
    }
    if (!(he = quietgethist(histline))) {
	if (*args)
	    free(str);
	return 1;
    }
    while ((he = movehistent(he, 1, hist_skip_flags))) {
	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
	    continue;
	zletext(he, &zt);
	if (zlinecmp(zt.text, zt.len, str, hp) < (he->histnum == curhist) &&
	    (*args || zlell != zt.len || ZS_memcmp(zt.text, str, zlell))) {
	    if (--n <= 0) {
		zle_setline(he);
		srch_hl = histline;
		srch_cs = zlecs;
		if (*args)
		    free(str);
		zletextfree(&zt);
		return 0;
	    }
	}
	zletextfree(&zt);
    }
    if (*args)
	free(str);
    return 1;
}

/**/
int
beginningofbufferorhistory(char **args)
{
    if (findbol())
	zlecs = 0;
    else
	return beginningofhistory(args);
    return 0;
}

/**/
int
beginningofhistory(UNUSED(char **args))
{
    if (!zle_goto_hist(firsthist(), 0, 0) && isset(HISTBEEP))
	return 1;
    return 0;
}

/**/
int
endofbufferorhistory(char **args)
{
    if (findeol() != zlell)
	zlecs = zlell;
    else
	return endofhistory(args);
    return 0;
}

/**/
int
endofhistory(UNUSED(char **args))
{
    zle_goto_hist(curhist, 0, 0);
    return 0;
}

/**/
int
insertlastword(char **args)
{
    int n, nwords, histstep = -1, wordpos = 0, deleteword = 0, len;
    char *s, *t;
    Histent he = NULL;
    LinkList l = NULL;
    LinkNode node;
    ZLE_STRING_T zs;

    static char *lastinsert;
    static int lasthist, lastpos, lastlen;
    int evhist, save;

    /*
     * If we have at least one argument, the first is the history
     * step.  The default is -1 (go back).  Repeated calls take
     * a step in this direction.  A value of 0 is allowed and doesn't
     * move the line.
     *
     * If we have two arguments, the second is the position of
     * the word to extract, 1..N.  The default is to use the
     * numeric argument, or the last word if that is not set.
     *
     * If we have three arguments, we reset the history pointer to
     * the current history event before applying the history step.
     */
    if (*args)
    {
	histstep = (int)zstrtol(*args, NULL, 10);
	if (*++args)
	{
	    wordpos = (int)zstrtol(*args, NULL, 10);
	    if (*++args)
		lasthist = curhist;
	}
    }

    metafy_line();
    if (lastinsert && lastlen &&
	lastpos <= zlemetacs &&
	lastlen == zlemetacs - lastpos &&
	memcmp(lastinsert, &zlemetaline[lastpos], lastlen) == 0)
	deleteword = 1;
    else
	lasthist = curhist;
    evhist = histstep ? addhistnum(lasthist, histstep, HIST_FOREIGN) :
	lasthist;

    if (evhist == curhist) {
	/*
	 * The line we are currently editing.  If we are going to
	 * replace an existing word, delete the old one now to avoid
	 * confusion.
	 */
	if (deleteword) {
	    int pos = zlemetacs;
	    zlemetacs = lastpos;
	    foredel(pos - zlemetacs);
	    /*
	     * Mark that this has been deleted.
	     * For consistency with history lines, we really ought to
	     * insert it back if the current command later fails. But
	     * - we can't be bothered
	     * - the problem that this can screw up going to other
	     *   lines in the history because we don't update
	     *   the history line isn't really relevant
	     * - you can see what you're copying, dammit, so you
	     *   shouldn't make errors.
	     * Of course, I could have implemented it in the time
	     * it took to say why I haven't.
	     */
	    deleteword = 0;
	}
	/*
	 * Can only happen fail if the line is empty, I hope.
	 * In that case, we don't need to worry about restoring
	 * a deleted word, because that can only have come
	 * from a non-empty line.  I think.
	 */
	if (!(l = bufferwords(NULL, NULL, NULL))) {
	    unmetafy_line();
	    return 1;
	}
	nwords = countlinknodes(l);
    } else {
	/* Some stored line. */
	if (!(he = quietgethist(evhist)) || !he->nwords) {
	    unmetafy_line();
	    return 1;
	}
	nwords = he->nwords;
    }
    if (wordpos) {
	n = (wordpos > 0) ? wordpos : nwords + wordpos + 1;
    } else if (zmult > 0) {
	n = nwords - (zmult - 1);
    } else {
	n = 1 - zmult;
    }
    if (n < 1 || n > nwords) {
	/*
	 * We can't put in the requested word, but we did find the
	 * history entry, so we remember the position in the history
	 * list.  This avoids getting stuck on a history line with
	 * fewer words than expected.  The cursor location cs
	 * has not changed, and lastinsert is still valid.
	 */
	lasthist = evhist;
	unmetafy_line();
	return 1;
    }
    /*
     * Only remove the old word from the command line if we have
     * successfully found a new one to insert.
     */
    if (deleteword > 0) {
	int pos = zlemetacs;
	zlemetacs = lastpos;
	foredel(pos - zlemetacs);
    }
    if (lastinsert) {
	zfree(lastinsert, lastlen);
	lastinsert = NULL;
    }
    if (l) {
	for (node = firstnode(l); --n; incnode(node))
	    ;
	s = (char *)getdata(node);
	t = s + strlen(s);
    } else {
	s = he->node.nam + he->words[2*n-2];
	t = he->node.nam + he->words[2*n-1];
    }

    save = *t;
    *t = '\0';			/* ignore trailing whitespace */
    lasthist = evhist;
    lastpos = zlemetacs;
    lastlen = t - s;
    lastinsert = zalloc(t - s);
    memcpy(lastinsert, s, lastlen);
    n = zmult;
    zmult = 1;

    unmetafy_line();

    zs = stringaszleline(s, 0, &len, NULL, NULL);
    doinsert(zs, len);
    free(zs);
    zmult = n;
    *t = save;
    return 0;
}

/**/
void
zle_setline(Histent he)
{
    remember_edits();
    mkundoent();
    histline = he->histnum;

    if (he->zle_text) {
	/*
	 * Optimise out conversion to metafied string and back.
	 * Remember convention of extra 2 characters spare.
	 */
	free(zleline);
	linesz = zlell = he->zle_len;
	zleline = zalloc((zlell + 2) * ZLE_CHAR_SIZE);
	ZS_memcpy(zleline, he->zle_text, zlell);

	if ((zlecs = zlell) && invicmdmode())
	    zlecs--;
    } else {
	setline(he->node.nam, ZSL_COPY|ZSL_TOEND);
    }
    setlastline();
    clearlist = 1;
}

/**/
int
setlocalhistory(UNUSED(char **args))
{
    if (zmod.flags & MOD_MULT) {
	hist_skip_flags = zmult? HIST_FOREIGN : 0;
    } else {
	hist_skip_flags ^= HIST_FOREIGN;
    }
    return 0;
}

/**/
int
zle_goto_hist(int ev, int n, int skipdups)
{
    Histent he = quietgethist(ev);
    if (!he || !(he = movehistent(he, n, hist_skip_flags)))
	return 1;
    if (skipdups && n) {
	struct zle_text zt;

	n = n < 0? -1 : 1;
	while (he) {
	    int ret;

	    zletext(he, &zt);
	    ret = zlinecmp(zt.text, zt.len, zleline, zlell);
	    zletextfree(&zt);
	    if (ret)
		break;
	    he = movehistent(he, n, hist_skip_flags);
	}
    }
    if (!he)
	return 0;
    zle_setline(he);
    return 1;
}

/**/
int
pushline(UNUSED(char **args))
{
    int n = zmult;

    if (n < 0)
	return 1;
    zpushnode(bufstack, zlelineasstring(zleline, zlell, 0, NULL, NULL, 0));
    while (--n)
	zpushnode(bufstack, ztrdup(""));
    stackcs = zlecs;
    *zleline = ZWC('\0');
    zlell = zlecs = 0;
    clearlist = 1;
    return 0;
}

/**/
int
pushlineoredit(char **args)
{
    int ics, ret;
    ZLE_STRING_T s;
    char *hline = hgetline();

    if (zmult < 0)
	return 1;
    if (hline && *hline) {
	ZLE_STRING_T zhline = stringaszleline(hline, 0, &ics, NULL, NULL);

	sizeline(ics + zlell + 1);
	/* careful of overlapping copy */
	for (s = zleline + zlell; --s >= zleline; s[ics] = *s)
	    ;
	ZS_memcpy(zleline, zhline, ics);
	zlell += ics;
	zlecs += ics;
	free(zhline);
    }
    ret = pushline(args);
    if (!isfirstln)
	errflag = done = 1;
    clearlist = 1;
    return ret;
}

/**/
int
pushinput(char **args)
{
    int i, ret;

    if (zmult < 0)
	return 1;
    zmult += i = !isfirstln;
    ret = pushlineoredit(args);
    zmult -= i;
    return ret;
}

/* Renamed to avoid clash with library function */
/**/
int
zgetline(UNUSED(char **args))
{
    char *s = getlinknode(bufstack);

    if (!s) {
	return 1;
    } else {
	int cc;
	ZLE_STRING_T lineadd = stringaszleline(s, 0, &cc, NULL, NULL);

	spaceinline(cc);
	ZS_memcpy(zleline + zlecs, lineadd, cc);
	zlecs += cc;
	free(s);
	free(lineadd);
	clearlist = 1;
    }
    return 0;
}

/**/
int
historyincrementalsearchbackward(char **args)
{
    doisearch(args, -1);
    return 0;
}

/**/
int
historyincrementalsearchforward(char **args)
{
    doisearch(args, 1);
    return 0;
}

static struct isrch_spot {
    int hl;			/* This spot's histline */
    unsigned short pos;		/* The search position in our metafied str */
    unsigned short cs;		/* The visible search position to the user */
    unsigned short len;		/* The search string's length */
    unsigned short flags;	/* This spot's flags */
#define ISS_FAILING	1
#define ISS_FORWARD	2
} *isrch_spots;

static int max_spot = 0;

/**/
void
free_isrch_spots(void)
{
    zfree(isrch_spots, max_spot * sizeof(*isrch_spots));
    max_spot = 0;
    isrch_spots = NULL;
}

/**/
static void
set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch)
{
    if (num >= max_spot) {
	if (!isrch_spots) {
	    isrch_spots = (struct isrch_spot*)
			    zalloc((max_spot = 64) * sizeof *isrch_spots);
	} else {
	    isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots,
			    (max_spot += 64) * sizeof *isrch_spots);
	}
    }

    isrch_spots[num].hl = hl;
    isrch_spots[num].pos = (unsigned short)pos;
    isrch_spots[num].cs = (unsigned short)cs;
    isrch_spots[num].len = (unsigned short)len;
    isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0)
			   + (nomatch? ISS_FAILING : 0);
}

/**/
static void
get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch)
{
    *hlp = isrch_spots[num].hl;
    *posp = (int)isrch_spots[num].pos;
    *csp = (int)isrch_spots[num].cs;
    *lenp = (int)isrch_spots[num].len;
    *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1;
    *nomatch = (isrch_spots[num].flags & ISS_FAILING);
}

#define ISEARCH_PROMPT		ZWS("failing XXX-i-search: ")
#define NORM_PROMPT_POS		8
#define FIRST_SEARCH_CHAR	(NORM_PROMPT_POS + 14)

/**/
static void
doisearch(char **args, int dir)
{
    ZLE_STRING_T ibuf = zhalloc(80 * ZLE_CHAR_SIZE);
    ZLE_STRING_T sbuf = ibuf + FIRST_SEARCH_CHAR;
    ZLE_STRING_T last_line = NULL;
    struct zle_text zt;
    int sbptr = 0, top_spot = 0, pos, sibuf = 80;
    int nomatch = 0, skip_line = 0, skip_pos = 0;
    int odir = dir, sens = zmult == 1 ? 3 : 1;
    int hl = histline, savekeys = -1, feep = 0, last_len;
    Thingy cmd;
    char *okeymap;
    Histent he;

    if (!(he = quietgethist(hl)))
	return;

    clearlist = 1;

    if (*args) {
	int len;
	char *arg;
	savekeys = kungetct;
	arg = getkeystring(*args, &len, GETKEYS_BINDKEY, NULL);
	ungetbytes(arg, len);
    }

    ZS_strcpy(ibuf, ISEARCH_PROMPT);
    ZS_memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? ZWS("fwd") : ZWS("bck"), 3);
    remember_edits();
    okeymap = ztrdup(curkeymapname);
    zletext(he, &zt);
    selectkeymap("main", 1);
    pos = zlecs;
    for (;;) {
	/* Remember the current values in case search fails (doesn't push). */
	set_isrch_spot(top_spot, hl, pos, zlecs, sbptr, dir, nomatch);
	if (sbptr == 1 && sbuf[0] == ZWC('^')) {
	    zlecs = 0;
    	    nomatch = 0;
	    statusline = ibuf + NORM_PROMPT_POS;
	} else if (sbptr > 0) {
	    /*
	     * As we may free zt.text as soon as we switch to a new
	     * line, we can't keep the pointer to it.  This is a bit
	     * ghastly.
	     */
	    if (last_line)
		free(last_line);
	    last_line = zalloc(zt.len * ZLE_CHAR_SIZE);
	    ZS_memcpy(last_line, zt.text, zt.len);
	    last_len = zt.len;

	    for (;;) {
		ZLE_STRING_T t;

		if (skip_pos) {
		    if (dir < 0) {
			if (pos == 0)
			    skip_line = 1;
			else
			    pos -= 1;
		    } else if (sbuf[0] != ZWC('^')) {
			if (pos >= zt.len - 1)
			    skip_line = 1;
			else
			    pos += 1;
		    } else
			skip_line = 1;
		    skip_pos = 0;
		}
		if (!skip_line && ((sbuf[0] == ZWC('^')) ?
		    (t = zlinecmp(zt.text, zt.len, sbuf + 1, sbptr - 1) < sens
		     ? zt.text : NULL) :
		    (t = zlinefind(zt.text, zt.len, pos, sbuf,
				   sbptr, dir, sens)))) {
		    zle_setline(he);
		    pos = t - zt.text;
		    zlecs = pos +
			(dir == 1 ? sbptr - (sbuf[0] == ZWC('^')) : 0);
	    	    nomatch = 0;
		    statusline = ibuf + NORM_PROMPT_POS;
		    break;
		}
		if (!(zlereadflags & ZLRF_HISTORY)
		 || !(he = movehistent(he, dir, hist_skip_flags))) {
		    if (sbptr == (int)isrch_spots[top_spot-1].len
		     && (isrch_spots[top_spot-1].flags & ISS_FAILING))
			top_spot--;
		    get_isrch_spot(top_spot, &hl, &pos, &zlecs, &sbptr,
				   &dir, &nomatch);
		    if (!nomatch) {
			feep = 1;
			nomatch = 1;
		    }
		    he = quietgethist(hl);
		    zletextfree(&zt);
		    zletext(he, &zt);
		    skip_line = 0;
		    statusline = ibuf;
		    break;
		}
		hl = he->histnum;
		zletextfree(&zt);
		zletext(he, &zt);
		pos = (dir == 1) ? 0 : zt.len;
		skip_line = isset(HISTFINDNODUPS) ? !!(he->node.flags & HIST_DUP)
		    : (zt.len == last_len &&
		       !ZS_memcmp(zt.text, last_line, zt.len));
	    }
	} else {
	    top_spot = 0;
    	    nomatch = 0;
	    statusline = ibuf + NORM_PROMPT_POS;
	}
	sbuf[sbptr] = ZWC('_');
	statusll = sbuf - statusline + sbptr + 1;
    ref:
	zrefresh();
	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
	    int i;
	    get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch);
	    he = quietgethist(hl);
	    zle_setline(he);
	    zletextfree(&zt);
	    zletext(he, &zt);
	    zlecs = i;
	    break;
	}
	if(cmd == Th(z_clearscreen)) {
	    clearscreen(zlenoargs);
	    goto ref;
	} else if(cmd == Th(z_redisplay)) {
	    redisplay(zlenoargs);
	    goto ref;
	} else if(cmd == Th(z_vicmdmode)) {
	    if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0))
		feep = 1;
	    goto ref;
	} else if(cmd == Th(z_vibackwarddeletechar) ||
	    	cmd == Th(z_backwarddeletechar)) {
	    if (top_spot)
		get_isrch_spot(--top_spot, &hl, &pos, &zlecs, &sbptr,
			       &dir, &nomatch);
	    else
		feep = 1;
	    if (nomatch) {
		statusline = ibuf;
		skip_pos = 1;
	    }
	    he = quietgethist(hl);
	    zletextfree(&zt);
	    zletext(he, &zt);
	    if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == ZWC('^'))) {
		int i = zlecs;
		zle_setline(he);
		zlecs = i;
	    }
	    ZS_memcpy(ibuf + NORM_PROMPT_POS,
		      (dir == 1) ? ZWS("fwd") : ZWS("bck"), 3);
	    continue;
	} else if(cmd == Th(z_acceptandhold)) {
	    acceptandhold(zlenoargs);
	    break;
	} else if(cmd == Th(z_acceptandinfernexthistory)) {
	    acceptandinfernexthistory(zlenoargs);
	    break;
	} else if(cmd == Th(z_acceptlineanddownhistory)) {
	    acceptlineanddownhistory(zlenoargs);
	    break;
	} else if(cmd == Th(z_acceptline)) {
	    acceptline(zlenoargs);
	    break;
	} else if(cmd == Th(z_historyincrementalsearchbackward)) {
	    set_isrch_spot(top_spot++, hl, pos, zlecs, sbptr, dir, nomatch);
	    if (dir != -1)
		dir = -1;
	    else
		skip_pos = 1;
	    goto rpt;
	} else if(cmd == Th(z_historyincrementalsearchforward)) {
	    set_isrch_spot(top_spot++, hl, pos, zlecs, sbptr, dir, nomatch);
	    if (dir != 1)
		dir = 1;
	    else
		skip_pos = 1;
	    goto rpt;
	} else if(cmd == Th(z_virevrepeatsearch)) {
	    set_isrch_spot(top_spot++, hl, pos, zlecs, sbptr, dir, nomatch);
	    dir = -odir;
	    skip_pos = 1;
	    goto rpt;
	} else if(cmd == Th(z_virepeatsearch)) {
	    set_isrch_spot(top_spot++, hl, pos, zlecs, sbptr, dir, nomatch);
	    dir = odir;
	    skip_pos = 1;
	rpt:
	    if (!sbptr && previous_search_len) {
		if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) {
		    ibuf = hrealloc((char *)ibuf, sibuf * ZLE_CHAR_SIZE,
				    (sibuf + previous_search_len)
				    * ZLE_CHAR_SIZE);
		    sbuf = ibuf + FIRST_SEARCH_CHAR;
		    sibuf += previous_search_len;
		}
		ZS_memcpy(sbuf, previous_search, sbptr = previous_search_len);
	    }
	    ZS_memcpy(ibuf + NORM_PROMPT_POS,
		      (dir == 1) ? ZWS("fwd") : ZWS("bck"), 3);
	    continue;
	} else if(cmd == Th(z_viquotedinsert) ||
	    	cmd == Th(z_quotedinsert)) {
	    if(cmd == Th(z_viquotedinsert)) {
		sbuf[sbptr] = ZWC('^');
		zrefresh();
	    }
	    if (getfullchar(0) == ZLEEOF)
		feep = 1;
	    else
		goto ins;
	} else {
	    if(cmd == Th(z_selfinsertunmeta)) {
		fixunmeta();
	    } else if (cmd == Th(z_magicspace)) {
		fixmagicspace();
	    } else if (cmd == Th(z_selfinsert)) {
#ifdef MULTIBYTE_SUPPORT
		if (!lastchar_wide_valid)
		    if (getrestchar(lastchar) == WEOF) {
			handlefeep(zlenoargs);
			continue;
		    }
#else
		;
#endif
	    } else {
		ungetkeycmd();
		if (cmd == Th(z_sendbreak))
		    sbptr = 0;
		break;
	    }
	ins:
	    if (sbptr == PATH_MAX) {
		feep = 1;
		continue;
	    }
	    set_isrch_spot(top_spot++, hl, pos, zlecs, sbptr, dir, nomatch);
	    if (sbptr >= sibuf - FIRST_SEARCH_CHAR - 2) {
		ibuf = hrealloc((char *)ibuf, sibuf * ZLE_CHAR_SIZE,
				sibuf * 2 * ZLE_CHAR_SIZE);
		sbuf = ibuf + FIRST_SEARCH_CHAR;
		sibuf *= 2;
	    }
	    /*
	     * We've supposedly arranged above that lastchar_wide is
	     * always valid at this point.
	     */
	    sbuf[sbptr++] = LASTFULLCHAR;
	}
	if (feep)
	    handlefeep(zlenoargs);
	feep = 0;
    }
    if (sbptr) {
	zfree(previous_search, previous_search_len);
	previous_search = zalloc(sbptr * ZLE_CHAR_SIZE);
	ZS_memcpy(previous_search, sbuf, previous_search_len = sbptr);
    }
    statusline = NULL;
    selectkeymap(okeymap, 1);
    zsfree(okeymap);
    /*
     * Don't allow unused characters provided as a string to the
     * widget to overflow and be used as separated commands.
     */
    if (savekeys >= 0 && kungetct > savekeys)
	kungetct = savekeys;
    if (last_line)
	free(last_line);
    zletextfree(&zt);
}

static Histent
infernexthist(Histent he, UNUSED(char **args))
{
    for (he = movehistent(he, -2, HIST_FOREIGN);
	 he; he = movehistent(he, -1, HIST_FOREIGN)) {
	struct zle_text zt;
	zletext(he, &zt);

	if (!zlinecmp(zt.text, zt.len, zleline, zlell)) {
	    zletextfree(&zt);
	    return movehistent(he, 1, HIST_FOREIGN);
	}
	zletextfree(&zt);
    }
    return NULL;
}

/**/
int
acceptandinfernexthistory(char **args)
{
    Histent he;

    if (!(he = infernexthist(hist_ring, args)))
	return 1;
    zpushnode(bufstack, ztrdup(he->node.nam));
    done = 1;
    stackhist = he->histnum;
    return 0;
}

/**/
int
infernexthistory(char **args)
{
    Histent he = quietgethist(histline);

    if (!he || !(he = infernexthist(he, args)))
	return 1;
    zle_setline(he);
    return 0;
}

/**/
int
vifetchhistory(UNUSED(char **args))
{
    if (zmult < 0)
	return 1;
    if (histline == curhist) {
	if (!(zmod.flags & MOD_MULT)) {
	    zlecs = zlell;
	    zlecs = findbol();
	    return 0;
	}
    }
    if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist, 0, 0) &&
	isset(HISTBEEP)) {
	return 1;
    }
    return 0;
}

/* the last vi search */

static char *visrchstr, *vipenultsrchstr;
static int visrchsense;

/**/
static int
getvisrchstr(void)
{
    ZLE_STRING_T sbuf = zhalloc(80 * ZLE_CHAR_SIZE);
    int sptr = 1, ret = 0, ssbuf = 80, feep = 0;
    Thingy cmd;
    char *okeymap = ztrdup(curkeymapname);

    if (vipenultsrchstr) {
	zsfree(vipenultsrchstr);
	vipenultsrchstr = NULL;
    }

    if (visrchstr) {
	vipenultsrchstr = visrchstr;
	visrchstr = NULL;
    }
    clearlist = 1;
    statusline = sbuf;
    sbuf[0] = (visrchsense == -1) ? ZWC('?') : ZWC('/');
    selectkeymap("main", 1);
    while (sptr) {
	sbuf[sptr] = ZWC('_');
	statusll = sptr + 1;
	zrefresh();
	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
	    ret = 0;
	    break;
	}
	if(cmd == Th(z_magicspace)) {
	    fixmagicspace();
	    cmd = Th(z_selfinsert);
	}
	if(cmd == Th(z_redisplay)) {
	    redisplay(zlenoargs);
	} else if(cmd == Th(z_clearscreen)) {
	    clearscreen(zlenoargs);
	} else if(cmd == Th(z_acceptline) ||
	    	cmd == Th(z_vicmdmode)) {
	    int newlen;
	    sbuf[sptr] = ZWC('\0');
	    visrchstr = zlelineasstring(sbuf+1, sptr-1, 0, &newlen, NULL, 0);
	    if (!newlen) {
	        zsfree(visrchstr);
		visrchstr = ztrdup(vipenultsrchstr);
	    }
	    ret = 1;
	    sptr = 0;
	} else if(cmd == Th(z_backwarddeletechar) ||
	    	cmd == Th(z_vibackwarddeletechar)) {
	    sptr--;
	} else if(cmd == Th(z_backwardkillword) ||
	    	cmd == Th(z_vibackwardkillword)) {
	    while(sptr != 1 && ZC_iblank(sbuf[sptr - 1]))
		sptr--;
	    if(ZC_iident(sbuf[sptr - 1]))
		while(sptr != 1 && ZC_iident(sbuf[sptr - 1]))
		    sptr--;
	    else
		while(sptr != 1 && !ZC_iident(sbuf[sptr - 1]) &&
		      !ZC_iblank(sbuf[sptr - 1]))
		    sptr--;
	} else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) {
	    if(cmd == Th(z_viquotedinsert)) {
		sbuf[sptr] = ZWC('^');
		zrefresh();
	    }
	    if (getfullchar(0) == ZLEEOF)
		feep = 1;
	    else
		goto ins;
	} else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) {
	    if(cmd == Th(z_selfinsertunmeta)) {
		fixunmeta();
	    } else {
#ifdef MULTIBYTE_SUPPORT
		if (!lastchar_wide_valid)
		    if (getrestchar(lastchar) == WEOF) {
			handlefeep(zlenoargs);
			continue;
		    }
#else
		;
#endif
	    }
	  ins:
	    if (sptr == ssbuf - 1) {
		ZLE_STRING_T newbuf =
		    (ZLE_STRING_T) zhalloc((ssbuf *= 2) * ZLE_CHAR_SIZE);
		ZS_strcpy(newbuf, sbuf);
		statusline = sbuf = newbuf;
	    }
	    sbuf[sptr++] = LASTFULLCHAR;
	} else {
	    feep = 1;
	}
	if (feep)
	    handlefeep(zlenoargs);
	feep = 0;
    }
    statusline = NULL;
    selectkeymap(okeymap, 1);
    zsfree(okeymap);
    return ret;
}

/**/
int
vihistorysearchforward(char **args)
{
    if (*args) {
	int ose = visrchsense, ret;
	char *ost = visrchstr;

	visrchsense = 1;
	visrchstr = *args;
	ret = virepeatsearch(zlenoargs);
	visrchsense = ose;
	visrchstr = ost;
	return ret;
    }
    visrchsense = 1;
    if (getvisrchstr())
	return virepeatsearch(zlenoargs);
    return 1;
}

/**/
int
vihistorysearchbackward(char **args)
{
    if (*args) {
	int ose = visrchsense, ret;
	char *ost = visrchstr;

	visrchsense = -1;
	visrchstr = *args;
	ret = virepeatsearch(zlenoargs);
	visrchsense = ose;
	visrchstr = ost;
	return ret;
    }
    visrchsense = -1;
    if (getvisrchstr())
	return virepeatsearch(zlenoargs);
    return 1;
}

/**/
int
virepeatsearch(UNUSED(char **args))
{
    Histent he;
    ZLE_STRING_T srcstr;
    int srclen;
    int n = zmult;
    struct zle_text zt;

    if (!visrchstr)
	return 1;
    if (zmult < 0) {
	n = -n;
	visrchsense = -visrchsense;
    }
    srcstr = stringaszleline(visrchstr, 0, &srclen, NULL, NULL);
    if (!(he = quietgethist(histline)))
	return 1;
    while ((he = movehistent(he, visrchsense, hist_skip_flags))) {
	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
	    continue;
	zletext(he, &zt);
	if (zlinecmp(zt.text, zt.len, zleline, zlell) &&
	    (*visrchstr == '^'?
	     (zt.len == srclen - 1 &&
	      ZS_memcmp(zt.text, srcstr + 1, zt.len) == 0) :
	     zlinefind(zt.text, zt.len, 0, srcstr, srclen, 1, 1) != 0)) {
	    if (--n <= 0) {
		zletextfree(&zt);
		zle_setline(he);
		free(srcstr);
		return 0;
	    }
	}
	zletextfree(&zt);
    }
    free(srcstr);
    return 1;
}

/**/
int
virevrepeatsearch(char **args)
{
    int ret;
    visrchsense = -visrchsense;
    ret = virepeatsearch(args);
    visrchsense = -visrchsense;
    return ret;
}

/* Extra function added by A.R. Iano-Fletcher.	*/
/*The extern variable "zlecs" is the position of the cursor. */
/* history-beginning-search-backward */

/**/
int
historybeginningsearchbackward(char **args)
{
    Histent he;
    int cpos = zlecs;		/* save cursor position */
    int n = zmult;
    struct zle_text zt;

    if (zmult < 0) {
	int ret;
	zmult = -n;
	ret = historybeginningsearchforward(args);
	zmult = n;
	return ret;
    }
    if (!(he = quietgethist(histline)))
	return 1;
    while ((he = movehistent(he, -1, hist_skip_flags))) {
	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
	    continue;
	zletext(he, &zt);
	if (zlinecmp(zt.text, zt.len, zleline, zlecs) < 0 &&
	    zlinecmp(zt.text, zt.len, zleline, zlell)) {
	    if (--n <= 0) {
		zletextfree(&zt);
		zle_setline(he);
		zlecs = cpos;
		return 0;
	    }
	}
	zletextfree(&zt);
    }
    return 1;
}

/* Extra function added by A.R. Iano-Fletcher.	*/

/* history-beginning-search-forward */
/**/
int
historybeginningsearchforward(char **args)
{
    Histent he;
    int cpos = zlecs;		/* save cursor position */
    int n = zmult;
    struct zle_text zt;

    if (zmult < 0) {
	int ret;
	zmult = -n;
	ret = historybeginningsearchbackward(args);
	zmult = n;
	return ret;
    }
    if (!(he = quietgethist(histline)))
	return 1;
    while ((he = movehistent(he, 1, hist_skip_flags))) {
	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
	    continue;
	zletext(he, &zt);
	if (zlinecmp(zt.text, zt.len, zleline, zlecs) <
	    (he->histnum == curhist) &&
	    zlinecmp(zt.text, zt.len, zleline, zlell)) {
	    if (--n <= 0) {
		zletextfree(&zt);
		zle_setline(he);
		zlecs = cpos;
		return 0;
	    }
	}
	zletextfree(&zt);
    }
    return 1;
}
