about summary refs log tree commit diff
path: root/Src/Zle/zle_hist.c
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2008-04-26 19:51:08 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2008-04-26 19:51:08 +0000
commit234c6ed193fb61379d8d7bb1ee44793ed75e15fb (patch)
tree052258e4845fba674a418729491bbc7409def824 /Src/Zle/zle_hist.c
parent607eac6efbf6b7344529c3b5c0f52f98691b41b9 (diff)
downloadzsh-234c6ed193fb61379d8d7bb1ee44793ed75e15fb.tar.gz
zsh-234c6ed193fb61379d8d7bb1ee44793ed75e15fb.tar.xz
zsh-234c6ed193fb61379d8d7bb1ee44793ed75e15fb.zip
24878: add incremental pattern searches
Diffstat (limited to 'Src/Zle/zle_hist.c')
-rw-r--r--Src/Zle/zle_hist.c578
1 files changed, 405 insertions, 173 deletions
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
index 91d2d1016..281de2e3b 100644
--- a/Src/Zle/zle_hist.c
+++ b/Src/Zle/zle_hist.c
@@ -52,47 +52,12 @@ int previous_search_len = 0;
 
 /*** History text manipulation utilities ***/
 
-
-struct zle_text {
-    /* Metafied, NULL-terminated string */
-    char *text;
-    /* 1 if we have allocated space for text */
-    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.)
+ * Text for the line:  anything previously modified within zle since
+ * the last time the line editor was started, else what was originally
+ * put in the history.
  */
-
-static void
-zletext(Histent ent, struct zle_text *zt)
-{
-    if (ent->zle_text) {
-	zt->text = ent->zle_text;
-	zt->alloced = 0;
-	return;
-    }
-
-    zt->text = ztrdup(ent->node.nam);
-    zt->alloced = 1;
-}
-
-/* See above. */
-
-static void
-zletextfree(struct zle_text *zt)
-{
-    if (zt->alloced) {
-	free(zt->text);
-	zt->alloced = 0;
-    }
-}
+#define GETZLETEXT(ent)	((ent)->zle_text ? (ent)->zle_text : (ent)->node.nam)
 
 /**/
 void
@@ -100,7 +65,7 @@ remember_edits(void)
 {
     Histent ent = quietgethist(histline);
     if (ent) {
-	char *line = 
+	char *line =
 	    zlemetaline ? zlemetaline :
 	    zlelineasstring(zleline, zlell, 0, NULL, NULL, 0);
 	if (!ent->zle_text || strcmp(line, ent->zle_text) != 0) {
@@ -464,7 +429,7 @@ historysearchbackward(char **args)
     Histent he;
     int n = zmult;
     char *str;
-    struct zle_text zt;
+    char *zt;
 
     if (zmult < 0) {
 	int ret;
@@ -499,18 +464,16 @@ historysearchbackward(char **args)
     while ((he = movehistent(he, -1, hist_skip_flags))) {
 	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
 	    continue;
-	zletext(he, &zt);
-	if (zlinecmp(zt.text, str) < 0 &&
-	    (*args || strcmp(zt.text, str) != 0)) {
+	zt = GETZLETEXT(he);
+	if (zlinecmp(zt, str) < 0 &&
+	    (*args || strcmp(zt, str) != 0)) {
 	    if (--n <= 0) {
 		zle_setline(he);
 		srch_hl = histline;
 		srch_cs = zlecs;
-		zletextfree(&zt);
 		return 0;
 	    }
 	}
-	zletextfree(&zt);
     }
     return 1;
 }
@@ -522,7 +485,7 @@ historysearchforward(char **args)
     Histent he;
     int n = zmult;
     char *str;
-    struct zle_text zt;
+    char *zt;
 
     if (zmult < 0) {
 	int ret;
@@ -555,18 +518,16 @@ historysearchforward(char **args)
     while ((he = movehistent(he, 1, hist_skip_flags))) {
 	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
 	    continue;
-	zletext(he, &zt);
-	if (zlinecmp(zt.text, str) < (he->histnum == curhist) &&
-	    (*args || strcmp(zt.text, str) != 0)) {
+	zt = GETZLETEXT(he);
+	if (zlinecmp(zt, str) < (he->histnum == curhist) &&
+	    (*args || strcmp(zt, str) != 0)) {
 	    if (--n <= 0) {
 		zle_setline(he);
 		srch_hl = histline;
 		srch_cs = zlecs;
-		zletextfree(&zt);
 		return 0;
 	    }
 	}
-	zletextfree(&zt);
     }
     return 1;
 }
@@ -780,7 +741,7 @@ zle_setline(Histent he)
     mkundoent();
     histline = he->histnum;
 
-    setline(he->zle_text ? he->zle_text : he->node.nam, ZSL_COPY|ZSL_TOEND);
+    setline(GETZLETEXT(he), ZSL_COPY|ZSL_TOEND);
     setlastline();
     clearlist = 1;
     if (remetafy)
@@ -809,15 +770,11 @@ zle_goto_hist(int ev, int n, int skipdups)
     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, line);
-	    zletextfree(&zt);
+	    ret = zlinecmp(GETZLETEXT(he), line);
 	    if (ret)
 		break;
 	    he = movehistent(he, n, hist_skip_flags);
@@ -917,7 +874,7 @@ zgetline(UNUSED(char **args))
 int
 historyincrementalsearchbackward(char **args)
 {
-    doisearch(args, -1);
+    doisearch(args, -1, 0);
     return 0;
 }
 
@@ -925,18 +882,36 @@ historyincrementalsearchbackward(char **args)
 int
 historyincrementalsearchforward(char **args)
 {
-    doisearch(args, 1);
+    doisearch(args, 1, 0);
+    return 0;
+}
+
+/**/
+int
+historyincrementalpatternsearchbackward(char **args)
+{
+    doisearch(args, -1, 1);
+    return 0;
+}
+
+/**/
+int
+historyincrementalpatternsearchforward(char **args)
+{
+    doisearch(args, 1, 1);
     return 0;
 }
 
 static struct isrch_spot {
     int hl;			/* This spot's histline */
+    int pat_hl;			/* histline where pattern search started */
     unsigned short pos;		/* The search position in our metafied str */
+    unsigned short pat_pos;     /* pos where pattern search started */
     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
+#define ISS_FORWARD	1
+#define ISS_NOMATCH_SHIFT 1
 } *isrch_spots;
 
 static int max_spot = 0;
@@ -952,7 +927,8 @@ free_isrch_spots(void)
 
 /**/
 static void
-set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch)
+set_isrch_spot(int num, int hl, int pos, int pat_hl, int pat_pos,
+	       int cs, int len, int dir, int nomatch)
 {
     if (num >= max_spot) {
 	if (!isrch_spots) {
@@ -966,43 +942,161 @@ set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch)
 
     isrch_spots[num].hl = hl;
     isrch_spots[num].pos = (unsigned short)pos;
+    isrch_spots[num].pat_hl = pat_hl;
+    isrch_spots[num].pat_pos = (unsigned short)pat_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);
+			   + (nomatch << ISS_NOMATCH_SHIFT);
 }
 
 /**/
 static void
-get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch)
+get_isrch_spot(int num, int *hlp, int *posp, int *pat_hlp, int *pat_posp,
+	       int *csp, int *lenp, int *dirp, int *nomatch)
 {
     *hlp = isrch_spots[num].hl;
     *posp = (int)isrch_spots[num].pos;
+    *pat_hlp = isrch_spots[num].pat_hl;
+    *pat_posp = (int)isrch_spots[num].pat_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);
+    *nomatch = (int)(isrch_spots[num].flags >> ISS_NOMATCH_SHIFT);
+}
+
+/*
+ * In pattern search mode, look through the list for a match at, or
+ * before or after the given position, according to the direction.
+ * Return new position or -1.
+ *
+ * Note this handles curpos out of range correctly, i.e. curpos < 0
+ * never matches when searching backwards and curpos > length of string
+ * never matches when searching forwards.
+ */
+static int
+isearch_newpos(LinkList matchlist, int curpos, int dir)
+{
+    LinkNode node;
+
+    if (dir < 0) {
+	for (node = lastnode(matchlist);
+	     node != (LinkNode)matchlist; decnode(node)) {
+	    Repldata rdata = (Repldata)getdata(node);
+	    if (rdata->b <= curpos)
+		return rdata->b;
+	}
+    } else {
+	for (node = firstnode(matchlist);
+	     node; incnode(node)) {
+	    Repldata rdata = (Repldata)getdata(node);
+	    if (rdata->b >= curpos)
+		return rdata->b;
+	}
+    }
+
+    return -1;
 }
 
-#define ISEARCH_PROMPT		"failing XXX-i-search: "
-#define NORM_PROMPT_POS		8
+#define ISEARCH_PROMPT		"XXXXXXX XXX-i-search: "
+#define FAILING_TEXT		"failing"
+#define INVALID_TEXT		"invalid"
+#define BAD_TEXT_LEN		7
+#define NORM_PROMPT_POS		(BAD_TEXT_LEN+1)
 #define FIRST_SEARCH_CHAR	(NORM_PROMPT_POS + 14)
 
 /**/
 static void
-doisearch(char **args, int dir)
+doisearch(char **args, int dir, int pattern)
 {
+    /* The full search buffer, including space for all prompts */
     char *ibuf = zhalloc(80);
+    /* The part of the search buffer with the search string */
     char *sbuf = ibuf + FIRST_SEARCH_CHAR;
+    /* The previous line shown to the user */
     char *last_line = NULL;
-    struct zle_text zt;
-    int sbptr = 0, top_spot = 0, pos, sibuf = 80;
+    /* Text of the history line being examined */
+    char *zt;
+    /*
+     * sbptr: index into sbuf.
+     * top_spot: stack index into the "isrch_spot" stack.
+     * sibuf: allocation size for ibuf
+     */
+    int sbptr = 0, top_spot = 0, sibuf = 80;
+    /*
+     * nomatch = 1: failing isearch
+     * nomatch = 2: invalid pattern
+     * skip_line: finished with current line, skip to next
+     * skip_pos: keep current line but try before/after current position.
+     */
     int nomatch = 0, skip_line = 0, skip_pos = 0;
+    /*
+     * odir: original search direction
+     * sens: limit for zlinecmp to allow (3) or disallow (1) lower case
+     *       matching upper case.
+     */
     int odir = dir, sens = zmult == 1 ? 3 : 1;
-    int hl = histline, savekeys = -1, feep = 0;
+    /*
+     * The number of the history line we are looking at and the
+     * character position into it, essentially the cursor position
+     * except we don't update that as frequently.
+     */
+    int hl = histline, pos;
+    /*
+     * The value of hl and pos at which the last pattern match
+     * search started.  We need to record these because there's
+     * a pathology with pattern matching.  Here's an example.  Suppose
+     * the history consists of:
+     *  echo '*OH NO*'
+     *  echo '\n'
+     *  echo "*WHAT?*"
+     *  <...backward pattern search starts here...>
+     * The user types "\".  As there's nothing after it it's treated
+     * literally (and I certainly don't want to change that).  This
+     * goes to the second line.  Then the user types "*".  This
+     * ought to match the "*" in the line immediately before where the
+     * search started.  However, unless we return to that line for the
+     * new search it will instead carry on to the first line.  This is
+     * different from straight string matching where we never have
+     * to backtrack.
+     *
+     * I think these need resetting to the current hl and pos when
+     * we start a new search or repeat a search.  It seems to work,
+     * anyway.
+     *
+     * We could optimize this more, but I don't think there's a lot
+     * of point.  (Translation:  it's difficult.)
+     */
+    int pat_hl = hl, pat_pos;
+    /*
+     * This is the flag that we need to revert the positions to
+     * the above for the next pattern search.
+     */
+    int revert_patpos = 0;
+    /*
+     * savekeys records the unget buffer, so that if we have arguments
+     * they don't pollute the input.
+     * feep indicates we should feep.  This is a well-known word
+     * meaning "to indicate an error in the zsh line editor".
+     */
+    int savekeys = -1, feep = 0;
+    /* Flag that we are at an old position, no need to search again */
+    int nosearch = 0;
+    /* Command read as input:  we don't read characters directly. */
     Thingy cmd;
+    /* Save the keymap if necessary */
     char *okeymap;
+    /* The current history entry, corresponding to hl */
     Histent he;
+    /* When pattern matching, the compiled pattern */
+    Patprog patprog = NULL;
+    /* When pattern matching, the list of match positions */
+    LinkList matchlist = NULL;
+    /*
+     * When we exit isearching this may be a zle command to
+     * execute.  We save it and execute it after unmetafying the
+     * command line.
+     */
     ZleIntFunc exitfn = (ZleIntFunc)0;
 
     if (!(he = quietgethist(hl)))
@@ -1026,67 +1120,179 @@ doisearch(char **args, int dir)
 
     metafy_line();
     remember_edits();
-    zletext(he, &zt);
-    pos = zlemetacs;
+    zt = GETZLETEXT(he);
+    pat_pos = pos = zlemetacs;
     for (;;) {
 	/* Remember the current values in case search fails (doesn't push). */
-	set_isrch_spot(top_spot, hl, pos, zlemetacs, sbptr, dir, nomatch);
+	set_isrch_spot(top_spot, hl, pos, pat_hl, pat_pos,
+		       zlemetacs, sbptr, dir, nomatch);
 	if (sbptr == 1 && sbuf[0] == '^') {
 	    zlemetacs = 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 = ztrdup(zt.text);
+	    char *t = NULL;
+	    last_line = zt;
 
 	    sbuf[sbptr] = '\0';
-	    for (;;) {
-		char *t;
-
+	    if (pattern && !patprog && !nosearch) {
+		/* avoid too much heap use, can get heavy round here... */
+		char *patbuf = ztrdup(sbuf);
+		char *patstring;
 		/*
-		 * If instructed, move past a match position:
-		 * backwards if searching backwards (skipping
-		 * the line if we're at the start), forwards
-		 * if searching forwards (skipping a line if we're
-		 * at the end).
+		 * Use static pattern buffer since we don't need
+		 * to maintain it and won't call other pattern functions
+		 * meanwhile.
+		 * Use PAT_NOANCH because we don't need the match
+		 * anchored to the end, even if it is at the start.
 		 */
-		if (skip_pos) {
-		    if (dir < 0) {
-			if (pos == 0)
-			    skip_line = 1;
-			else
-			    pos = backwardmetafiedchar(zlemetaline,
-						       zlemetaline + pos,
-						       NULL) - zlemetaline;
-		    } else if (sbuf[0] != '^') {
-			if (pos >= strlen(zt.text) - 1)
-			    skip_line = 1;
-			else
-			    pos += 1;
-		    } else
-			skip_line = 1;
+		int patflags = PAT_STATIC|PAT_NOANCH;
+		if (sbuf[0] == '^') {
+		    /*
+		     * We'll handle the anchor later when
+		     * we call into the globbing code.
+		     */
+		    patstring = patbuf + 1;
+		} else {
+		    /* Scanning for multiple matches per line */
+		    patflags |= PAT_SCAN;
+		    patstring = patbuf;
+		}
+		if (sens == 3)
+		    patflags |= PAT_LCMATCHUC;
+		tokenize(patstring);
+		remnulargs(patstring);
+		patprog = patcompile(patstring, patflags, NULL);
+		free(patbuf);
+		if (matchlist) {
+		    freematchlist(matchlist);
+		    matchlist = NULL;
+		}
+		if (patprog) {
+		    revert_patpos = 1;
+		} else {
+		    handlefeep(zlenoargs);
+		    nomatch = 2;
+		    /* indicate "invalid" in status line */
+		    memcpy(ibuf, INVALID_TEXT, BAD_TEXT_LEN);
+		    statusline = ibuf;
+		}
+	    }
+	    /*
+	     * skip search if pattern compilation failed, or
+	     * if we back somewhere we already searched.
+	     */
+	    while ((!pattern || patprog) && !nosearch) {
+		if (patprog) {
+		    /*
+		     * We are pattern matching against the current
+		     * line.  If anchored at the start, this is
+		     * easy; a single test suffices.
+		     *
+		     * Otherwise, our strategy is to retrieve a linked
+		     * list of all matches within the current line and
+		     * scan through it as appropriate.  This isn't
+		     * actually significantly more efficient, but
+		     * it is algorithmically easier since we just
+		     * need a single one-off line-matching interface
+		     * to the pattern code.  We use a variant of
+		     * the code used for replacing within parameters
+		     * which for historical reasons is in glob.c rather
+		     * than pattern.c.
+		     *
+		     * The code for deciding whether to skip something
+		     * is a bit icky but that sort of code always is.
+		     */
+		    if (!skip_line) {
+			if (sbuf[0] == '^') {
+			    /*
+			     * skip_pos applies to the whole line in
+			     * this mode.
+			     */
+			    if (!skip_pos && pattry(patprog, zt))
+				t = zt;
+			} else {
+			    if (!matchlist && !skip_pos) {
+				if (revert_patpos) {
+				    /*
+				     * Search from where the previous
+				     * search started; see note above.
+				     */
+				    revert_patpos = 0;
+				    he = quietgethist(hl = pat_hl);
+				    zt = GETZLETEXT(he);
+				    pos = pat_pos;
+				}
+				if (!getmatchlist(zt, patprog, &matchlist) ||
+				    !firstnode(matchlist)) {
+				    if (matchlist) {
+					freematchlist(matchlist);
+					matchlist = NULL;
+				    }
+				}
+			    }
+			    if (matchlist) {
+				int newpos;
+				if (!skip_pos) {
+				    /* OK to match at current pos */
+				    newpos = pos;
+				} else {
+				    if (dir < 0)
+					newpos = pos - 1;
+				    else
+					newpos = pos + 1;
+				}
+				newpos = isearch_newpos(matchlist, newpos,
+							dir);
+				/* need a new list next time if off the end */
+				if (newpos < 0) {
+				    freematchlist(matchlist);
+				    matchlist = NULL;
+				} else
+				    t = zt + newpos;
+			    }
+			}
+		    }
 		    skip_pos = 0;
+		} else {
+		    /*
+		     * If instructed, move past a match position:
+		     * backwards if searching backwards (skipping
+		     * the line if we're at the start), forwards
+		     * if searching forwards (skipping a line if we're
+		     * at the end).
+		     */
+		    if (skip_pos) {
+			if (dir < 0) {
+			    if (pos == 0)
+				skip_line = 1;
+			    else
+				pos = backwardmetafiedchar(zlemetaline,
+							   zlemetaline + pos,
+							   NULL) - zlemetaline;
+			} else if (sbuf[0] != '^') {
+			    if (pos >= strlen(zt) - 1)
+				skip_line = 1;
+			    else
+				pos += 1;
+			} else
+			    skip_line = 1;
+			skip_pos = 0;
+		    }
+		    /*
+		     * First search for a(nother) match within the
+		     * current line, unless we've been told to skip it.
+		     */
+		    if (!skip_line) {
+			if (sbuf[0] == '^') {
+			    if (zlinecmp(zt, sbuf + 1) < sens)
+				t = zt;
+			} else
+			    t = zlinefind(zt, pos, sbuf, dir, sens);
+		    }
 		}
-		/*
-		 * First search for a(nother) match within the
-		 * current line, unless we've been told to skip it.
-		 */
-		if (!skip_line && ((sbuf[0] == '^') ?
-				   (t = (zlinecmp(zt.text, sbuf + 1) < sens
-					 ? zt.text : NULL)) :
-		    (t = zlinefind(zt.text, pos, sbuf, dir, sens)))) {
-		    zle_setline(he);
-		    pos = t - zt.text;
-		    zlemetacs = pos +
-			(dir == 1 ? sbptr - (sbuf[0] == '^') : 0);
-	    	    nomatch = 0;
-		    statusline = ibuf + NORM_PROMPT_POS;
+		if (t) {
+		    pos = t - zt;
 		    break;
 		}
 		/*
@@ -1096,45 +1302,59 @@ doisearch(char **args, int dir)
 		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))
+		     && (isrch_spots[top_spot-1].flags >> ISS_NOMATCH_SHIFT))
 			top_spot--;
-		    get_isrch_spot(top_spot, &hl, &pos, &zlemetacs, &sbptr,
-				   &dir, &nomatch);
+		    get_isrch_spot(top_spot, &hl, &pos, &pat_hl, &pat_pos,
+				   &zlemetacs, &sbptr, &dir, &nomatch);
 		    if (!nomatch) {
 			feep = 1;
 			nomatch = 1;
 		    }
 		    he = quietgethist(hl);
-		    zletextfree(&zt);
-		    zletext(he, &zt);
+		    zt = GETZLETEXT(he);
 		    skip_line = 0;
+		    /* indicate "failing" in status line */
+		    memcpy(ibuf, nomatch == 2 ? INVALID_TEXT :FAILING_TEXT,
+			   BAD_TEXT_LEN);
 		    statusline = ibuf;
 		    break;
 		}
 		hl = he->histnum;
-		zletextfree(&zt);
-		zletext(he, &zt);
-		pos = (dir == 1) ? 0 : strlen(zt.text);
+		zt = GETZLETEXT(he);
+		pos = (dir == 1) ? 0 : strlen(zt);
 		skip_line = isset(HISTFINDNODUPS)
 		    ? !!(he->node.flags & HIST_DUP)
-		    : !strcmp(zt.text, last_line);
+		    : !strcmp(zt, last_line);
+	    }
+	    /*
+	     * If we matched above (t set), set the new line.
+	     * If we didn't, but are here because we are on a previous
+	     * match (nosearch set and nomatch not, set the line again).
+	     */
+	    if (t || (nosearch && !nomatch)) {
+		zle_setline(he);
+		zlemetacs = pos +
+		    (dir == 1 ? sbptr - (sbuf[0] == '^') : 0);
+		statusline = ibuf + NORM_PROMPT_POS;
+		nomatch = 0;
 	    }
 	} else {
 	    top_spot = 0;
     	    nomatch = 0;
 	    statusline = ibuf + NORM_PROMPT_POS;
 	}
+	nosearch = 0;
 	sbuf[sbptr] = '_';
 	sbuf[sbptr+1] = '\0';
     ref:
 	zrefresh();
 	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
 	    int i;
-	    get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch);
+	    get_isrch_spot(0, &hl, &pos, &pat_hl, &pat_pos,
+			   &i, &sbptr, &dir, &nomatch);
 	    he = quietgethist(hl);
 	    zle_setline(he);
-	    zletextfree(&zt);
-	    zletext(he, &zt);
+	    zt = GETZLETEXT(he);
 	    zlemetacs = i;
 	    break;
 	}
@@ -1150,18 +1370,26 @@ doisearch(char **args, int dir)
 	    goto ref;
 	} else if(cmd == Th(z_vibackwarddeletechar) ||
 	    	cmd == Th(z_backwarddeletechar)) {
-	    if (top_spot)
-		get_isrch_spot(--top_spot, &hl, &pos, &zlemetacs, &sbptr,
-			       &dir, &nomatch);
-	    else
+	    if (top_spot) {
+		get_isrch_spot(--top_spot, &hl, &pos, &pat_hl, &pat_pos,
+			       &zlemetacs, &sbptr, &dir, &nomatch);
+		patprog = NULL;
+		nosearch = 1;
+	    } else
 		feep = 1;
 	    if (nomatch) {
+		memcpy(ibuf, nomatch == 2 ? INVALID_TEXT : FAILING_TEXT,
+		       BAD_TEXT_LEN);
 		statusline = ibuf;
 		skip_pos = 1;
 	    }
 	    he = quietgethist(hl);
-	    zletextfree(&zt);
-	    zletext(he, &zt);
+	    zt = GETZLETEXT(he);
+	    /*
+	     * Set the line for the cases where we won't go passed
+	     * the usual line-setting logic:  if we're not on a match,
+	     * or if we don't have enough to search for.
+	     */
 	    if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) {
 		int i = zlemetacs;
 		zle_setline(he);
@@ -1182,27 +1410,41 @@ doisearch(char **args, int dir)
 	} else if(cmd == Th(z_acceptline)) {
 	    exitfn = acceptline;
 	    break;
-	} else if(cmd == Th(z_historyincrementalsearchbackward)) {
-	    set_isrch_spot(top_spot++, hl, pos, zlemetacs, sbptr, dir, nomatch);
+	} else if(cmd == Th(z_historyincrementalsearchbackward) ||
+		  cmd == Th(z_historyincrementalpatternsearchbackward)) {
+	    pat_hl = hl;
+	    pat_pos = pos;
+	    set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos,
+			   zlemetacs, 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, zlemetacs, sbptr, dir, nomatch);
+	} else if(cmd == Th(z_historyincrementalsearchforward) ||
+		  cmd == Th(z_historyincrementalpatternsearchforward)) {
+	    pat_hl = hl;
+	    pat_pos = pos;
+	    set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos,
+			   zlemetacs, 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, zlemetacs, sbptr, dir, nomatch);
+	    pat_hl = hl;
+	    pat_pos = pos;
+	    set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos,
+			   zlemetacs, sbptr, dir, nomatch);
 	    dir = -odir;
 	    skip_pos = 1;
 	    goto rpt;
 	} else if(cmd == Th(z_virepeatsearch)) {
-	    set_isrch_spot(top_spot++, hl, pos, zlemetacs, sbptr, dir, nomatch);
+	    pat_hl = hl;
+	    pat_pos = pos;
+	    set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos,
+			   zlemetacs, sbptr, dir, nomatch);
 	    dir = odir;
 	    skip_pos = 1;
 	rpt:
@@ -1254,7 +1496,8 @@ doisearch(char **args, int dir)
 		feep = 1;
 		continue;
 	    }
-	    set_isrch_spot(top_spot++, hl, pos, zlemetacs, sbptr, dir, nomatch);
+	    set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos,
+			   zlemetacs, sbptr, dir, nomatch);
 	    if (sbptr >= sibuf - FIRST_SEARCH_CHAR - 2 
 #ifdef MULTIBYTE_SUPPORT
 		- 2 * MB_CUR_MAX
@@ -1269,6 +1512,7 @@ doisearch(char **args, int dir)
 	     * always valid at this point.
 	     */
 	    sbptr += zlecharasstring(LASTFULLCHAR, sbuf + sbptr);
+	    patprog = NULL;
 	}
 	if (feep)
 	    handlefeep(zlenoargs);
@@ -1285,15 +1529,14 @@ doisearch(char **args, int dir)
 	exitfn(zlenoargs);
     selectkeymap(okeymap, 1);
     zsfree(okeymap);
+    if (matchlist)
+	freematchlist(matchlist);
     /*
      * 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
@@ -1302,15 +1545,10 @@ infernexthist(Histent he, UNUSED(char **args))
     metafy_line();
     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, zlemetaline)) {
+	if (!zlinecmp(GETZLETEXT(he), zlemetaline)) {
 	    unmetafy_line();
-	    zletextfree(&zt);
 	    return movehistent(he, 1, HIST_FOREIGN);
 	}
-	zletextfree(&zt);
     }
     unmetafy_line();
     return NULL;
@@ -1541,7 +1779,7 @@ virepeatsearch(UNUSED(char **args))
 {
     Histent he;
     int n = zmult;
-    struct zle_text zt;
+    char *zt;
 
     if (!visrchstr)
 	return 1;
@@ -1555,18 +1793,16 @@ virepeatsearch(UNUSED(char **args))
     while ((he = movehistent(he, visrchsense, hist_skip_flags))) {
 	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
 	    continue;
-	zletext(he, &zt);
-	if (zlinecmp(zt.text, zlemetaline) &&
-	    (*visrchstr == '^' ? strpfx(zt.text, visrchstr + 1) :
-	     zlinefind(zt.text, 0, visrchstr, 1, 1) != 0)) {
+	zt = GETZLETEXT(he);
+	if (zlinecmp(zt, zlemetaline) &&
+	    (*visrchstr == '^' ? strpfx(zt, visrchstr + 1) :
+	     zlinefind(zt, 0, visrchstr, 1, 1) != 0)) {
 	    if (--n <= 0) {
 		unmetafy_line();
-		zletextfree(&zt);
 		zle_setline(he);
 		return 0;
 	    }
 	}
-	zletextfree(&zt);
     }
     unmetafy_line();
     return 1;
@@ -1594,7 +1830,7 @@ historybeginningsearchbackward(char **args)
     Histent he;
     int cpos = zlecs;		/* save cursor position */
     int n = zmult;
-    struct zle_text zt;
+    char *zt;
 
     if (zmult < 0) {
 	int ret;
@@ -1611,22 +1847,20 @@ historybeginningsearchbackward(char **args)
 	char sav;
 	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
 	    continue;
-	zletext(he, &zt);
+	zt = GETZLETEXT(he);
 	sav = zlemetaline[zlemetacs];
 	zlemetaline[zlemetacs] = '\0';
-	tst = zlinecmp(zt.text, zlemetaline);
+	tst = zlinecmp(zt, zlemetaline);
 	zlemetaline[zlemetacs] = sav;
-	if (tst < 0 && zlinecmp(zt.text, zlemetaline)) {
+	if (tst < 0 && zlinecmp(zt, zlemetaline)) {
 	    if (--n <= 0) {
 		unmetafy_line();
-		zletextfree(&zt);
 		zle_setline(he);
 		zlecs = cpos;
 		CCRIGHT();
 		return 0;
 	    }
 	}
-	zletextfree(&zt);
     }
     unmetafy_line();
     return 1;
@@ -1642,7 +1876,7 @@ historybeginningsearchforward(char **args)
     Histent he;
     int cpos = zlecs;		/* save cursor position */
     int n = zmult;
-    struct zle_text zt;
+    char *zt;
 
     if (zmult < 0) {
 	int ret;
@@ -1659,22 +1893,20 @@ historybeginningsearchforward(char **args)
 	int tst;
 	if (isset(HISTFINDNODUPS) && he->node.flags & HIST_DUP)
 	    continue;
-	zletext(he, &zt);
+	zt = GETZLETEXT(he);
 	sav = zlemetaline[zlemetacs];
 	zlemetaline[zlemetacs] = '\0';
-	tst = zlinecmp(zt.text, zlemetaline) < (he->histnum == curhist);
+	tst = zlinecmp(zt, zlemetaline) < (he->histnum == curhist);
 	zlemetaline[zlemetacs] = sav;
-	if (tst && zlinecmp(zt.text, zlemetaline)) {
+	if (tst && zlinecmp(zt, zlemetaline)) {
 	    if (--n <= 0) {
 		unmetafy_line();
-		zletextfree(&zt);
 		zle_setline(he);
 		zlecs = cpos;
 		CCRIGHT();
 		return 0;
 	    }
 	}
-	zletextfree(&zt);
     }
     unmetafy_line();
     return 1;