about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-05-12 04:49:46 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-05-12 04:49:46 +0000
commitea0ddb0fc6073be3d7d289e59b083f564dbd761f (patch)
treed1e3f1be8624d47e7e8a75838f9e84885a17a484 /Src
parent53d36e795b26a945048e7a87a1a91224f8e1663a (diff)
downloadzsh-ea0ddb0fc6073be3d7d289e59b083f564dbd761f.tar.gz
zsh-ea0ddb0fc6073be3d7d289e59b083f564dbd761f.tar.xz
zsh-ea0ddb0fc6073be3d7d289e59b083f564dbd761f.zip
Diffstat (limited to 'Src')
-rw-r--r--Src/Builtins/rlimits.c4
-rw-r--r--Src/Zle/comp1.c3
-rw-r--r--Src/Zle/iwidgets.list1
-rw-r--r--Src/Zle/zle_hist.c318
-rw-r--r--Src/Zle/zle_keymap.c2
-rw-r--r--Src/Zle/zle_main.c42
-rw-r--r--Src/Zle/zle_move.c15
-rw-r--r--Src/Zle/zle_thingy.c17
-rw-r--r--Src/Zle/zle_tricky.c63
-rw-r--r--Src/Zle/zle_utils.c12
-rw-r--r--Src/builtin.c71
-rw-r--r--Src/exec.c6
-rw-r--r--Src/hashtable.c176
-rw-r--r--Src/hist.c747
-rw-r--r--Src/init.c17
-rw-r--r--Src/jobs.c34
-rw-r--r--Src/module.c39
-rw-r--r--Src/options.c13
-rw-r--r--Src/params.c25
-rw-r--r--Src/signals.c5
-rw-r--r--Src/subst.c25
-rw-r--r--Src/text.c3
-rw-r--r--Src/utils.c61
-rw-r--r--Src/zsh.export6
-rw-r--r--Src/zsh.h36
25 files changed, 1164 insertions, 577 deletions
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
index 7825a900d..34344f5c1 100644
--- a/Src/Builtins/rlimits.c
+++ b/Src/Builtins/rlimits.c
@@ -43,14 +43,14 @@ zstrtorlimt(const char *s, char **t, int base)
 {
     rlim_t ret = 0;
  
-    if (!base)
+    if (!base) {
 	if (*s != '0')
 	    base = 10;
 	else if (*++s == 'x' || *s == 'X')
 	    base = 16, s++;
 	else
 	    base = 8;
- 
+    } 
     if (base <= 10)
 	for (; *s >= '0' && *s < ('0' + base); s++)
 	    ret = ret * base + *s - '0';
diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c
index c51aad297..f32e5f5c0 100644
--- a/Src/Zle/comp1.c
+++ b/Src/Zle/comp1.c
@@ -47,7 +47,7 @@ Cmlist cmatcher;
 void (*makecompparamsptr) _((void));
 
 /**/
-void (*comp_setunsetptr) _((int, int));
+void (*comp_setunsetptr) _((unsigned int, unsigned int));
 
 /* pointers to functions required by compctl and defined by zle */
 
@@ -147,6 +147,7 @@ createcompctltable(void)
     compctltab->hash        = hasher;
     compctltab->emptytable  = emptyhashtable;
     compctltab->filltable   = NULL;
+    compctltab->cmpnodes    = strcmp;
     compctltab->addnode     = addhashnode;
     compctltab->getnode     = gethashnode2;
     compctltab->getnode2    = gethashnode2;
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
index 61ad0e24a..e0c93bd03 100644
--- a/Src/Zle/iwidgets.list
+++ b/Src/Zle/iwidgets.list
@@ -91,6 +91,7 @@
 "send-break", sendbreak, 0
 "set-mark-command", setmarkcommand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "spell-word", spellword, 0
+"set-local-history", setlocalhistory, 0
 "transpose-chars", transposechars, 0
 "transpose-words", transposewords, 0
 "undefined-key", undefinedkey, 0
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
index 0064d4ef0..d2b9a3233 100644
--- a/Src/Zle/zle_hist.c
+++ b/Src/Zle/zle_hist.c
@@ -47,27 +47,16 @@ int lastcol;
 /**/
 int histline;
 
-/* the last line in the history (the current one), metafied */
-
-/**/
-char *curhistline;
+#define ZLETEXT(X) ((X)->zle_text ? (X)->zle_text : (X)->text)
 
 /**/
 void
 remember_edits(void)
 {
-    if (histline == curhist) {
-	zsfree(curhistline);
-	curhistline = metafy((char *) line, ll, META_DUP);
-    }
-    else {
-	Histent ent = gethistent(histline);
-
-	if (metadiffer(ent->zle_text ? ent->zle_text : ent->text,
-		       (char *) line, ll)) {
-	    zsfree(ent->zle_text);
-	    ent->zle_text = metafy((char *) line, ll, META_DUP);
-	}
+    Histent ent = quietgethist(histline);
+    if (metadiffer(ZLETEXT(ent), (char *) line, ll)) {
+	zsfree(ent->zle_text);
+	ent->zle_text = metafy((char *) line, ll, META_DUP);
     }
 }
 
@@ -75,11 +64,11 @@ remember_edits(void)
 void
 forget_edits(void)
 {
-    int i;
+    Histent he;
 
-    for (i = 0; i < histentct; i++) {
-	zsfree(histentarr[i].zle_text);
-	histentarr[i].zle_text = NULL;
+    for (he = hist_ring; he; he = up_histent(he)) {
+	zsfree(he->zle_text);
+	he->zle_text = NULL;
     }
 }
 
@@ -87,16 +76,12 @@ forget_edits(void)
 void
 uphistory(void)
 {
-    if (zmult < 0) {
-	zmult = -zmult;
-	downhistory();
-	zmult = -zmult;
-    } else if(!zle_goto_hist(histline - zmult) && isset(HISTBEEP))
+    if (!zle_goto_hist(histline, -zmult) && isset(HISTBEEP))
 	feep();
 }
 
 /**/
-int
+static int
 upline(void)
 {
     int n = zmult;
@@ -159,7 +144,6 @@ viuplineorhistory(void)
     vifirstnonblank();
 }
 
-
 /**/
 void
 uplineorsearch(void)
@@ -181,7 +165,7 @@ uplineorsearch(void)
 }
 
 /**/
-int
+static int
 downline(void)
 {
     int n = zmult;
@@ -268,26 +252,22 @@ downlineorsearch(void)
 void
 acceptlineanddownhistory(void)
 {
-    char *s;
+    Histent he;
 
-    if (!(s = zle_get_event(histline + 1))) {
+    if (!(he = movehistent(quietgethist(histline), 1, HIST_FOREIGN))) {
 	feep();
 	return;
     }
-    pushnode(bufstack, ztrdup(s));
+    pushnode(bufstack, ztrdup(ZLETEXT(he)));
     done = 1;
-    stackhist = histline + 1;
+    stackhist = he->histnum;
 }
 
 /**/
 void
 downhistory(void)
 {
-    if (zmult < 0) {
-	zmult = -zmult;
-	uphistory();
-	zmult = -zmult;
-    } else if(!zle_goto_hist(histline + zmult) && isset(HISTBEEP))
+    if (!zle_goto_hist(histline, zmult) && isset(HISTBEEP))
 	feep();
 }
 
@@ -298,7 +278,7 @@ static char *srch_str;
 void
 historysearchbackward(void)
 {
-    int hl = histline;
+    Histent he;
     int n = zmult;
     char *s;
 
@@ -308,7 +288,7 @@ historysearchbackward(void)
 	zmult = n;
 	return;
     }
-    if (hl == curhist || hl != srch_hl || cs != srch_cs || mark != 0
+    if (histline == curhist || histline != srch_hl || cs != srch_cs || mark != 0
      || memcmp(srch_str, line, histpos) != 0) {
 	zfree(srch_str, histpos);
 	for (histpos = 0; histpos < ll && !iblank(line[histpos]); histpos++) ;
@@ -317,28 +297,29 @@ historysearchbackward(void)
 	srch_str = zalloc(histpos);
 	memcpy(srch_str, line, histpos);
     }
-    for (;;) {
-	hl--;
-	if (!(s = zle_get_event(hl))) {
-	    feep();
-	    return;
-	}
+    he = quietgethist(histline);
+    while ((he = movehistent(he, -1, hist_skip_flags))) {
+	if (isset(HISTFINDNODUPS) && he->flags & HIST_DUP)
+	    continue;
+	s = ZLETEXT(he);
 	if (metadiffer(s, srch_str, histpos) < 0 &&
 	    metadiffer(s, srch_str, ll)) {
-	    if (--n <= 0)
-		break;
+	    if (--n <= 0) {
+		zle_setline(he);
+		srch_hl = histline;
+		srch_cs = cs;
+		return;
+	    }
 	}
     }
-    zle_goto_hist(hl);
-    srch_hl = hl;
-    srch_cs = cs;
+    feep();
 }
 
 /**/
 void
 historysearchforward(void)
 {
-    int hl = histline;
+    Histent he;
     int n = zmult;
     char *s;
 
@@ -348,7 +329,7 @@ historysearchforward(void)
 	zmult = n;
 	return;
     }
-    if (hl == curhist || hl != srch_hl || cs != srch_cs || mark != 0
+    if (histline == curhist || histline != srch_hl || cs != srch_cs || mark != 0
      || memcmp(srch_str, line, histpos) != 0) {
 	zfree(srch_str, histpos);
 	for (histpos = 0; histpos < ll && !iblank(line[histpos]); histpos++) ;
@@ -357,20 +338,22 @@ historysearchforward(void)
 	srch_str = zalloc(histpos);
 	memcpy(srch_str, line, histpos);
     }
-    for (;;) {
-	hl++;
-	if (!(s = zle_get_event(hl))) {
-	    feep();
-	    return;
+    he = quietgethist(histline);
+    while ((he = movehistent(he, 1, hist_skip_flags))) {
+	if (isset(HISTFINDNODUPS) && he->flags & HIST_DUP)
+	    continue;
+	s = ZLETEXT(he);
+	if (metadiffer(s, srch_str, histpos) < (he->histnum == curhist) &&
+	    metadiffer(s, srch_str, ll)) {
+	    if (--n <= 0) {
+		zle_setline(he);
+		srch_hl = histline;
+		srch_cs = cs;
+		return;
+	    }
 	}
-	if (metadiffer(s, srch_str, histpos) < (hl == curhist) &&
-	    metadiffer(s, srch_str, ll))
-	    if (--n <= 0)
-		break;
     }
-    zle_goto_hist(hl);
-    srch_hl = hl;
-    srch_cs = cs;
+    feep();
 }
 
 /**/
@@ -387,7 +370,7 @@ beginningofbufferorhistory(void)
 void
 beginningofhistory(void)
 {
-    if (!zle_goto_hist(firsthist()) && isset(HISTBEEP))
+    if (!zle_goto_hist(firsthist(), 0) && isset(HISTBEEP))
 	feep();
 }
 
@@ -405,7 +388,7 @@ endofbufferorhistory(void)
 void
 endofhistory(void)
 {
-    zle_goto_hist(curhist);
+    zle_goto_hist(curhist, 0);
 }
 
 /**/
@@ -419,7 +402,7 @@ insertlastword(void)
 /* multiple calls will now search back through the history, pem */
     static char *lastinsert;
     static int lasthist, lastpos;
-    int evhist = curhist - 1, save;
+    int evhist = addhistnum(curhist, -1, HIST_FOREIGN), save;
 
     if (lastinsert) {
 	int lastlen = ztrlen(lastinsert);
@@ -428,7 +411,7 @@ insertlastword(void)
 	if (lastpos <= pos &&
 	    lastlen == pos - lastpos &&
 	    memcmp(lastinsert, (char *)&line[lastpos], lastlen) == 0) {
-	    evhist = --lasthist;
+	    evhist = addhistnum(lasthist, -1, HIST_FOREIGN);
 	    cs = lastpos;
 	    foredel(pos - cs);
 	}
@@ -464,41 +447,37 @@ insertlastword(void)
 }
 
 /**/
-char *
-qgetevent(int ev)
+void
+zle_setline(Histent he)
 {
-    return ((ev == curhist) ? curhistline : quietgetevent(ev));
+    remember_edits();
+    mkundoent();
+    histline = he->histnum;
+    setline(ZLETEXT(he));
+    setlastline();
+    clearlist = 1;
 }
 
 /**/
-char *
-zle_get_event(int ev)
+void
+setlocalhistory(void)
 {
-    Histent ent;
-
-    if (ev == curhist)
-	return curhistline;
-    if (! (ent = quietgethist(ev)))
-	return NULL;
-    if (ent->zle_text)
-	return ent->zle_text;
-    return ent->text;
+    if (zmod.flags & MOD_MULT) {
+	hist_skip_flags = zmult? HIST_FOREIGN : 0;
+    }
+    else {
+	hist_skip_flags ^= HIST_FOREIGN;
+    }
 }
 
 /**/
-static int
-zle_goto_hist(int ev)
+int
+zle_goto_hist(int ev, int n)
 {
-    char *t;
-
-    remember_edits();
-    if(!(t = zle_get_event(ev)))
+    Histent he = movehistent(quietgethist(ev), n, hist_skip_flags);
+    if (!he)
 	return 0;
-    mkundoent();
-    histline = ev;
-    setline(t);
-    setlastline();
-    clearlist = 1;
+    zle_setline(he);
     return 1;
 }
 
@@ -603,6 +582,7 @@ static struct isrch_spot {
 
 static int max_spot = 0;
 
+/**/
 #ifdef MODULE
 
 /**/
@@ -612,6 +592,7 @@ free_isrch_spots(void)
     zfree(isrch_spots, max_spot * sizeof(*isrch_spots));
 }
 
+/**/
 #endif /* MODULE */
 
 /**/
@@ -665,13 +646,15 @@ doisearch(int dir)
     char *okeymap = curkeymapname;
     static char *previous_search = NULL;
     static int previous_search_len = 0;
+    Histent he;
 
     clearlist = 1;
 
     strcpy(ibuf, ISEARCH_PROMPT);
     memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
     remember_edits();
-    s = zle_get_event(hl);
+    he = quietgethist(hl);
+    s = ZLETEXT(he);
     selectkeymap("main", 1);
     pos = metalen(s, cs);
     for (;;) {
@@ -705,15 +688,14 @@ doisearch(int dir)
 		if (!skip_line && ((sbuf[0] == '^') ?
 		    (t = metadiffer(s, sbuf + 1, sbptr - 1) < sens ? s : NULL) :
 		    (t = hstrnstr(s, pos, sbuf, sbptr, dir, sens)))) {
-		    zle_goto_hist(hl);
+		    zle_setline(he);
 		    pos = t - s;
 		    cs = ztrsub(t, s) + (dir == 1? sbptr - (sbuf[0]=='^') : 0);
 	    	    nomatch = 0;
 		    statusline = ibuf + NORM_PROMPT_POS;
 		    break;
 		}
-		hl += dir;
-		if (!(s = zle_get_event(hl))) {
+		if (!(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--;
@@ -723,13 +705,17 @@ doisearch(int dir)
 			feep();
 			nomatch = 1;
 		    }
-		    s = last_line;
+		    he = quietgethist(hl);
+		    s = ZLETEXT(he);
 		    skip_line = 0;
 		    statusline = ibuf;
 		    break;
 		}
+		hl = he->histnum;
+		s = ZLETEXT(he);
 		pos = dir == 1? 0 : strlen(s);
-		skip_line = !strcmp(last_line, s);
+		skip_line = isset(HISTFINDNODUPS)? !!(he->flags & HIST_DUP)
+						 : !strcmp(last_line, s);
 	    }
 	} else {
 	    top_spot = 0;
@@ -743,8 +729,9 @@ doisearch(int dir)
 	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
 	    int i;
 	    get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch);
-	    s = zle_get_event(hl);
-	    zle_goto_hist(hl);
+	    he = quietgethist(hl);
+	    zle_setline(he);
+	    s = ZLETEXT(he);
 	    cs = i;
 	    break;
 	}
@@ -769,10 +756,11 @@ doisearch(int dir)
 		statusline = ibuf;
 		skip_pos = 1;
 	    }
-	    s = zle_get_event(hl);
+	    he = quietgethist(hl);
+	    s = ZLETEXT(he);
 	    if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) {
 		int i = cs;
-		zle_goto_hist(hl);
+		zle_setline(he);
 		cs = i;
 	    }
 	    memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
@@ -874,42 +862,35 @@ doisearch(int dir)
 void
 acceptandinfernexthistory(void)
 {
-    int t0;
-    char *s;
+    Histent he;
 
     done = 1;
-    for (t0 = histline - 2;; t0--) {
-	if (!(s = qgetevent(t0)))
+    for (he = movehistent(quietgethist(histline), -2, HIST_FOREIGN);
+	 he; he = movehistent(he, -1, HIST_FOREIGN)) {
+	if (!metadiffer(ZLETEXT(he), (char *) line, ll)) {
+	    he = movehistent(he, 1, HIST_FOREIGN);
+	    pushnode(bufstack, ztrdup(ZLETEXT(he)));
+	    stackhist = he->histnum;
 	    return;
-	if (!metadiffer(s, (char *) line, ll))
-	    break;
+	}
     }
-    if (!(s = qgetevent(t0 + 1)))
-	return;
-    pushnode(bufstack, ztrdup(s));
-    stackhist = t0 + 1;
 }
 
 /**/
 void
 infernexthistory(void)
 {
-    int t0;
-    char *s;
+    Histent he;
 
-    for (t0 = histline - 2;; t0--) {
-	if (!(s = qgetevent(t0))) {
-	    feep();
+    for (he = movehistent(quietgethist(histline), -2, HIST_FOREIGN);
+	 he; he = movehistent(he, -1, HIST_FOREIGN)) {
+	if (!metadiffer(ZLETEXT(he), (char *) line, ll)) {
+	    he = movehistent(he, 1, HIST_FOREIGN);
+	    zle_setline(he);
 	    return;
 	}
-	if (! metadiffer(s, (char *) line, ll))
-	    break;
     }
-    if (!(s = qgetevent(t0 + 1))) {
-	feep();
-	return;
-    }
-    zle_goto_hist(t0 + 1);
+    feep();
 }
 
 /**/
@@ -925,7 +906,7 @@ vifetchhistory(void)
 	    return;
 	}
     }
-    if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist) &&
+    if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist, 0) &&
 	isset(HISTBEEP))
 	feep();
 }
@@ -1041,7 +1022,8 @@ vihistorysearchbackward(void)
 void
 virepeatsearch(void)
 {
-    int hl = histline, t0;
+    Histent he;
+    int t0;
     int n = zmult;
     char *s;
 
@@ -1054,23 +1036,21 @@ virepeatsearch(void)
 	visrchsense = -visrchsense;
     }
     t0 = strlen(visrchstr);
-    for (;;) {
-	hl += visrchsense;
-	if (!(s = zle_get_event(hl))) {
-	    feep();
-	    return;
-	}
-	if (!metadiffer(s, (char *) line, ll))
-	    continue;
-	if (*visrchstr == '^') {
-	    if (strncmp(s, visrchstr + 1, t0 - 1) != 0)
-		continue;
-	} else if (!hstrnstr(s, 0, visrchstr, t0, 1, 1))
+    he = quietgethist(histline);
+    while ((he = movehistent(he, visrchsense, hist_skip_flags))) {
+	if (isset(HISTFINDNODUPS) && he->flags & HIST_DUP)
 	    continue;
-	if (--n <= 0)
-	    break;
+	s = ZLETEXT(he);
+	if (metadiffer(s, (char *) line, ll)
+	 && (*visrchstr == '^'? strncmp(s, visrchstr + 1, t0 - 1) == 0
+			      : hstrnstr(s, 0, visrchstr, t0, 1, 1) != 0)) {
+	    if (--n <= 0) {
+		zle_setline(he);
+		return;
+	    }
+	}
     }
-    zle_goto_hist(hl);
+    feep();
 }
 
 /**/
@@ -1090,8 +1070,8 @@ virevrepeatsearch(void)
 void
 historybeginningsearchbackward(void)
 {
+    Histent he;
     int cpos = cs;		/* save cursor position */
-    int hl = histline;
     int n = zmult;
     char *s;
 
@@ -1101,20 +1081,21 @@ historybeginningsearchbackward(void)
 	zmult = n;
 	return;
     }
-    for (;;) {
-	hl--;
-	if (!(s = zle_get_event(hl))) {
-	    feep();
-	    return;
-	}
+    he = quietgethist(histline);
+    while ((he = movehistent(he, -1, hist_skip_flags))) {
+	if (isset(HISTFINDNODUPS) && he->flags & HIST_DUP)
+	    continue;
+	s = ZLETEXT(he);
 	if (metadiffer(s, (char *)line, cs) < 0 &&
-	    metadiffer(s, (char *)line, ll))
-	    if (--n <= 0)
-		break;
+	    metadiffer(s, (char *)line, ll)) {
+	    if (--n <= 0) {
+		zle_setline(he);
+		cs = cpos;
+		return;
+	    }
+	}
     }
-
-    zle_goto_hist(hl);
-    cs = cpos;
+    feep();
 }
 
 /* Extra function added by A.R. Iano-Fletcher.	*/
@@ -1124,8 +1105,8 @@ historybeginningsearchbackward(void)
 void
 historybeginningsearchforward(void)
 {
+    Histent he;
     int cpos = cs;		/* save cursor position */
-    int hl = histline;
     int n = zmult;
     char *s;
 
@@ -1135,18 +1116,19 @@ historybeginningsearchforward(void)
 	zmult = n;
 	return;
     }
-    for (;;) {
-	hl++;
-	if (!(s = zle_get_event(hl))) {
-	    feep();
-	    return;
+    he = quietgethist(histline);
+    while ((he = movehistent(he, 1, hist_skip_flags))) {
+	if (isset(HISTFINDNODUPS) && he->flags & HIST_DUP)
+	    continue;
+	s = ZLETEXT(he);
+	if (metadiffer(s, (char *)line, cs) < (he->histnum == curhist) &&
+	    metadiffer(s, (char *)line, ll)) {
+	    if (--n <= 0) {
+		zle_setline(he);
+		cs = cpos;
+		return;
+	    }
 	}
-	if (metadiffer(s, (char *)line, cs) < (hl == curhist) &&
-	    metadiffer(s, (char *)line, ll))
-	    if (--n <= 0)
-		break;
     }
-
-    zle_goto_hist(hl);
-    cs = cpos;
+    feep();
 }
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
index 1baa3a845..7504ed809 100644
--- a/Src/Zle/zle_keymap.c
+++ b/Src/Zle/zle_keymap.c
@@ -130,6 +130,7 @@ createkeymapnamtab(void)
     keymapnamtab->hash        = hasher;
     keymapnamtab->emptytable  = emptyhashtable;
     keymapnamtab->filltable   = NULL;
+    keymapnamtab->cmpnodes    = strcmp;
     keymapnamtab->addnode     = addhashnode;
     keymapnamtab->getnode     = gethashnode2;
     keymapnamtab->getnode2    = gethashnode2;
@@ -172,6 +173,7 @@ newkeytab(char *kmname)
     ht->hash        = hasher;
     ht->emptytable  = emptyhashtable;
     ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
     ht->addnode     = addhashnode;
     ht->getnode     = gethashnode2;
     ht->getnode2    = gethashnode2;
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index a5da84b66..dfb437308 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -111,6 +111,10 @@ int prefixflag;
 /**/
 int feepflag;
 
+#ifdef FIONREAD
+static int delayzsetterm;
+#endif
+
 /* set up terminal */
 
 /**/
@@ -119,12 +123,30 @@ zsetterm(void)
 {
     struct ttyinfo ti;
 
-#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD)
+#if defined(FIONREAD)
     int val;
 
     ioctl(SHTTY, FIONREAD, (char *)&val);
-    if (val)
+    if (val) {
+	/*
+	 * Problems can occur on some systems when switching from
+	 * canonical to non-canonical input.  The former is usually
+	 * set while running programmes, but the latter is necessary
+	 * for zle.  If there is input in canonical mode, then we
+	 * need to read it without setting up the terminal.  Furthermore,
+	 * while that input gets processed there may be more input
+	 * being typed (i.e. further typeahead).  This means that
+	 * we can't set up the terminal for zle *at all* until
+	 * we are sure there is no more typeahead to come.  So
+	 * if there is typeahead, we set the flag delayzsetterm.
+	 * Then getkey() performs another FIONREAD call; if that is
+	 * 0, we have finally used up all the typeahead, and it is
+	 * safe to alter the terminal, which we do at that point.
+	 */
+	delayzsetterm = 1;
 	return;
+    } else
+	delayzsetterm = 0;
 #endif
 
 /* sanitize the tty */
@@ -298,7 +320,19 @@ getkey(int keytmout)
     if (kungetct)
 	ret = STOUC(kungetbuf[--kungetct]);
     else {
-	if (keytmout) {
+#ifdef FIONREAD
+	if (delayzsetterm) {
+	    int val;
+	    ioctl(SHTTY, FIONREAD, (char *)&val);
+	    if (!val)
+		zsetterm();
+	}
+#endif
+	if (keytmout
+#ifdef FIONREAD
+	    && ! delayzsetterm
+#endif
+	    ) {
 	    if (keytimeout > 500)
 		exp100ths = 500;
 	    else if (keytimeout > 0)
@@ -461,7 +495,6 @@ zleread(char *lp, char *rp, int flags)
 	undoing = 1;
 	line = (unsigned char *)zalloc((linesz = 256) + 2);
 	virangeflag = lastcmd = done = cs = ll = mark = 0;
-	curhistline = NULL;
 	vichgflag = 0;
 	viinsbegin = 0;
 	statusline = NULL;
@@ -542,7 +575,6 @@ zleread(char *lp, char *rp, int flags)
 	zleactive = no_restore_tty = 0;
 	alarm(0);
     } LASTALLOC;
-    zsfree(curhistline);
     freeundo();
     if (eofsent) {
 	free(line);
diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c
index d6e9c4b7d..7169f5700 100644
--- a/Src/Zle/zle_move.c
+++ b/Src/Zle/zle_move.c
@@ -476,17 +476,10 @@ vigotomark(void)
 	feep();
 	return;
     }
-    if (curhist != vimarkline[ch]) {
-	char *s;
-
-	remember_edits();
-	if (!(s = qgetevent(vimarkline[ch]))) {
-	    vimarkline[ch] = 0;
-	    feep();
-	    return;
-	}
-	histline = vimarkline[ch];
-	setline(s);
+    if (curhist != vimarkline[ch] && !zle_goto_hist(vimarkline[ch], 0)) {
+	vimarkline[ch] = 0;
+	feep();
+	return;
     }
     cs = vimarkcs[ch];
     if (cs > ll)
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index b879669ac..dbf982899 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -64,6 +64,7 @@ createthingytab(void)
     thingytab->hash        = hasher;
     thingytab->emptytable  = emptythingytab;
     thingytab->filltable   = NULL;
+    thingytab->cmpnodes    = strcmp;
     thingytab->addnode     = addhashnode;
     thingytab->getnode     = gethashnode;
     thingytab->getnode2    = gethashnode2;
@@ -356,7 +357,7 @@ bin_zle(char *name, char **args, char *ops, int func)
 
     /* check number of arguments */
     for(n = 0; args[n]; n++) ;
-    if(!op->o && n != 1) {
+    if(!op->o && n != 1 && n != 2) {
 	zerrnam(name, "wrong number of arguments", NULL, 0);
 	return 1;
     }
@@ -515,17 +516,31 @@ static int
 bin_zle_call(char *name, char **args, char *ops, char func)
 {
     Thingy t;
+    struct modifier modsave;
 
     if(!zleactive || incompctlfunc || incompfunc) {
 	zerrnam(name, "widgets can only be called when ZLE is active",
 	    NULL, 0);
 	return 1;
     }
+    if (args[1]) {
+	modsave = zmod;
+	if (isdigit(*args[1])) {
+	    zmod.mult = atoi(args[1]);
+	    zmod.flags |= MOD_MULT;
+	}
+	else {
+	    zmod.mult = 1;
+	    zmod.flags &= ~MOD_MULT;
+	}
+    }
     t = rthingy(args[0]);
     PERMALLOC {
       execzlefunc(t);
     } LASTALLOC;
     unrefthingy(t);
+    if (args[1])
+	zmod = modsave;
     return 0;
 }
 
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 8db571d0b..bc6cff8b6 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -578,6 +578,10 @@ static char *varname;
 
 static int insubscr;
 
+/* Parameter pointer for completing keys of an assoc array. */
+
+static Param keypm;
+
 /* 1 if we are completing in a quoted string (or inside `...`) */
 
 /**/
@@ -1364,9 +1368,15 @@ get_comp_string(void)
 	    zsfree(varname);
 	    varname = ztrdup(tt);
 	    *s = sav;
-	    if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
-		s = NULL, inwhat = IN_MATH, insubscr = 1;
-	    else if (*s == '=' && cs > wb + (s - tt)) {
+	    if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) {
+		s = NULL;
+		inwhat = IN_MATH;
+		if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
+		    (keypm->flags & PM_HASHED))
+		    insubscr = 2;
+		else
+		    insubscr = 1;
+	    } else if (*s == '=' && cs > wb + (s - tt)) {
 		s++;
 		wb += s - tt;
 		t0 = STRING;
@@ -1424,11 +1434,15 @@ get_comp_string(void)
 		    zsfree(varname);
 		    varname = ztrdup(nb);
 		    *ne = sav;
+		    if ((keypm = (Param) paramtab->getnode(paramtab,
+							   varname)) &&
+			(keypm->flags & PM_HASHED))
+			insubscr = 2;
 		}
 	    }
 	}
 	if (inwhat == IN_MATH) {
-	    if (compfunc) {
+	    if (compfunc || insubscr == 2) {
 		int lev;
 		char *p;
 
@@ -1472,7 +1486,11 @@ get_comp_string(void)
 		zsfree(varname);
 		varname = ztrdup((char *) line + i + 1);
 		line[wb - 1] = sav;
-		insubscr = 1;
+		if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
+		    (keypm->flags & PM_HASHED))
+		    insubscr = 2;
+		else
+		    insubscr = 1;
 	    }
 	}
 	/* This variable will hold the current word in quoted form. */
@@ -3524,7 +3542,7 @@ int
 addmatches(Cadata dat, char **argv)
 {
     char *s, *ms, *lipre = NULL, *lisuf = NULL, *lpre = NULL, *lsuf = NULL;
-    char **aign = NULL, **dparr;
+    char **aign = NULL, **dparr = NULL;
     int lpl, lsl, pl, sl, bpl, bsl, llpl = 0, llsl = 0, nm = mnum;
     int oisalt = 0, isalt, isexact, doadd;
     Cline lc = NULL;
@@ -4360,7 +4378,7 @@ callcompfunc(char *s, char *fn)
 
 	    PERMALLOC {
 		q = compwords = (char **)
-		    zalloc((clwnum - aadd + 1) * sizeof(char *));
+		    zalloc((clwnum + 1) * sizeof(char *));
 		for (p = clwords + aadd; *p; p++, q++) {
 		    tmp = dupstring(*p);
 		    untokenize(tmp);
@@ -4829,13 +4847,22 @@ makecomplistglobal(char *os, int incmd, int lst, int flags)
     char *s;
 
     ccont = CC_CCCONT;
+    cc_dummy.suffix = NULL;
 
     if (linwhat == IN_ENV) {
         /* Default completion for parameter values. */
         cc = &cc_default;
+	keypm = NULL;
     } else if (linwhat == IN_MATH) {
-        /* Parameter names inside mathematical expression. */
-        cc_dummy.mask = CC_PARAMS;
+	if (insubscr == 2) {
+	    /* Inside subscript of assoc array, complete keys. */
+	    cc_dummy.mask = 0;
+	    cc_dummy.suffix = "]";
+	} else {
+	    /* Other math environment, complete paramete names. */
+	    keypm = NULL;
+	    cc_dummy.mask = CC_PARAMS;
+	}
 	cc = &cc_dummy;
 	cc_dummy.refc = 10000;
     } else if (linwhat == IN_COND) {
@@ -4851,13 +4878,16 @@ makecomplistglobal(char *os, int incmd, int lst, int flags)
 	    (CC_FILES | CC_PARAMS);
 	cc = &cc_dummy;
 	cc_dummy.refc = 10000;
-    } else if (linredir)
+	keypm = NULL;
+    } else if (linredir) {
 	/* In redirections use default completion. */
 	cc = &cc_default;
-    else
+	keypm = NULL;
+    } else {
 	/* Otherwise get the matches for the command. */
+	keypm = NULL;
 	return makecomplistcmd(os, incmd, flags);
-
+    }
     if (cc) {
 	/* First, use the -T compctl. */
 	if (!(flags & CFN_FIRST)) {
@@ -5917,7 +5947,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	Comp compc = NULL;
 	char *e, *h, hpatsav;
 	Histent he;
-	int i = curhist - 1, n = cc->hnum;
+	int i = addhistnum(curhist,-1,HIST_FOREIGN), n = cc->hnum;
 
 	/* Parse the pattern, if it isn't the null string. */
 	if (*(cc->hpat)) {
@@ -5971,7 +6001,12 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
     if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
 	/* Add the two types of aliases. */
 	dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
-
+    if (keypm && cc == &cc_dummy) {
+	/* Add the keys of the parameter in keypm. */
+	scanhashtable(keypm->gets.hfn(keypm), 0, 0, PM_UNSET, addhnmatch, 0);
+	keypm = NULL;
+	cc_dummy.suffix = NULL;
+    }
     if (!errflag && cc->ylist) {
 	/* generate the user-defined display list: if anything fails, *
 	 * we silently allow the normal completion list to be used.   */
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 4cdb3c52d..8524fd21e 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -572,10 +572,8 @@ undo(void)
 static void
 unapplychange(struct change *ch)
 {
-    if(ch->hist != histline) {
-	remember_edits();
-	setline(zle_get_event(histline = ch->hist));
-    }
+    if(ch->hist != histline)
+	zle_setline(quietgethist(ch->hist));
     cs = ch->off;
     if(ch->ins)
 	foredel(ztrlen(ch->ins));
@@ -613,10 +611,8 @@ redo(void)
 static void
 applychange(struct change *ch)
 {
-    if(ch->hist != histline) {
-	remember_edits();
-	setline(zle_get_event(histline = ch->hist));
-    }
+    if(ch->hist != histline)
+	zle_setline(quietgethist(ch->hist));
     cs = ch->off;
     if(ch->del)
 	foredel(ztrlen(ch->del));
diff --git a/Src/builtin.c b/Src/builtin.c
index 9ffcad1b3..8436cde33 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -120,7 +120,7 @@ static struct builtin builtins[] =
     BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"),
 
 #ifdef DYNAMIC
-    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "LaudicIp", NULL),
+    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "ILabcdipu", NULL),
 #endif
 };
 
@@ -142,6 +142,7 @@ createbuiltintable(void)
     builtintab->hash        = hasher;
     builtintab->emptytable  = NULL;
     builtintab->filltable   = NULL;
+    builtintab->cmpnodes    = strcmp;
     builtintab->addnode     = addhashnode;
     builtintab->getnode     = gethashnode;
     builtintab->getnode2    = gethashnode2;
@@ -1146,19 +1147,17 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     }
     if (ops['R']) {
 	/* read history from a file */
-	readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1);
+	readhistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
 	return 0;
     }
     if (ops['W']) {
 	/* write history to a file */
-	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
-		     (ops['I'] ? 2 : 0));
+	savehistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
 	return 0;
     }
     if (ops['A']) {
 	/* append history to a file */
-	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
-		     (ops['I'] ? 3 : 1));
+	savehistfile(*argv, 1, HFILE_APPEND | (ops['I'] ? HFILE_SKIPOLD : 0));
 	return 0;
     }
     if (!(ops['l'] && unset(HISTNOSTORE)))
@@ -1200,9 +1199,9 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     }
     /* default values of first and last, and range checking */
     if (first == -1)
-	first = (ops['l']) ? curhist - 16 : curhist - 1;
+	first = ops['l']? addhistnum(curhist,-16,0) : addhistnum(curhist,-1,0);
     if (last == -1)
-	last = (ops['l']) ? curhist - 1 : first;
+	last = ops['l']? addhistnum(curhist,-1,0) : first;
     if (first < firsthist())
 	first = firsthist();
     if (last == -1)
@@ -1267,7 +1266,7 @@ fcgetcomm(char *s)
      * numbers indicate reversed numbering.           */
     if ((cmd = atoi(s))) {
 	if (cmd < 0)
-	    cmd = curhist + cmd;
+	    cmd = addhistnum(curhist,cmd,HIST_FOREIGN);
 	if (cmd >= curhist) {
 	    zwarnnam("fc", "bad history number: %d", 0, cmd);
 	    return -1;
@@ -1331,7 +1330,7 @@ static int
 fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com)
 {
     int fclistdone = 0;
-    char *s, *hs;
+    char *s;
     Histent ent;
 
     /* reverse range if required */
@@ -1344,28 +1343,31 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
     if (!subs)
 	fclistdone = 1;
 
-    for (;;) {
-	hs = quietgetevent(first);
-	if (!hs) {
+    ent = gethistent(first, first < last? GETHIST_DOWNWARD : GETHIST_UPWARD);
+    if (!ent || ent->histnum < first || ent->histnum > last) {
+	if (first == last)
 	    zwarnnam("fc", "no such event: %d", NULL, first);
-	    return 1;
-	}
-	s = dupstring(hs);
+	else
+	    zwarnnam("fc", "no events in that range", NULL, 0);
+	return 1;
+    }
+
+    for (;;) {
+	s = dupstring(ent->text);
 	/* this if does the pattern matching, if required */
 	if (!com || domatch(s, com, 0)) {
 	    /* perform substitution */
 	    fclistdone |= fcsubs(&s, subs);
 
 	    /* do numbering */
-	    if (n)
-		fprintf(f, "%5d  ", first);
-	    ent = NULL;
+	    if (n) {
+		fprintf(f, "%5d%c ", ent->histnum,
+			ent->flags & HIST_FOREIGN? '*' : ' ');
+	    }
 	    /* output actual time (and possibly date) of execution of the
 	    command, if required */
 	    if (d) {
 		struct tm *ltm;
-		if (!ent)
-		    ent = gethistent(first);
 		ltm = localtime(&ent->stim);
 		if (d >= 2) {
 		    if (d >= 8) {
@@ -1387,8 +1389,6 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
 	    /* display the time taken by the command, if required */
 	    if (D) {
 		long diff;
-		if (!ent)
-		    ent = gethistent(first);
 		diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
 		fprintf(f, "%ld:%02ld  ", diff / 60, diff % 60);
 	    }
@@ -1401,12 +1401,14 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
 		fprintf(f, "%s\n", s);
 	}
 	/* move on to the next history line, or quit the loop */
-	if (first == last)
-	    break;
-	else if (first > last)
-	    first--;
-	else
-	    first++;
+	if (first < last) {
+	    if (!(ent = down_histent(ent)) || ent->histnum > last)
+		break;
+	}
+	else {
+	    if (!(ent = up_histent(ent)) || ent->histnum < last)
+		break;
+	}
     }
 
     /* final processing */
@@ -1768,7 +1770,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	    for (i = 0; i < paramtab->hsize; i++) {
 		for (pm = (Param) paramtab->nodes[i]; pm;
 		     pm = (Param) pm->next) {
-		    if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED))
+		    if (((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) ||
+			(pm->flags & PM_UNSET))
 			continue;
 		    if (domatch(pm->nam, com, 0))
 			addlinknode(pmlist, pm);
@@ -2523,10 +2526,7 @@ bin_print(char *name, char **args, char *ops, int func)
 	char **pargs = args;
 
 	PERMALLOC {
-	    ent = gethistent(++curhist);
-	    zsfree(ent->text);
-	    if (ent->nwords)
-		zfree(ent->words, ent->nwords*2*sizeof(short));
+	    ent = prepnexthistent(++curhist);
 	    while (*pargs++)
 		nwords++;
 	    if ((ent->nwords = nwords)) {
@@ -2543,6 +2543,7 @@ bin_print(char *name, char **args, char *ops, int func)
 	    ent->text = zjoin(args, ' ');
 	    ent->stim = ent->ftim = time(NULL);
 	    ent->flags = 0;
+	    addhistnode(histtab, ent->text, ent);
 	} LASTALLOC;
 	return 0;
     }
@@ -2952,7 +2953,7 @@ zexit(int val, int from_signal)
 	    killrunjobs(from_signal);
 	if (isset(RCS) && interact) {
 	    if (!nohistsave)
-		savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+		savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 	    if (islogin && !subsh) {
 		sourcehome(".zlogout");
 #ifdef GLOBAL_ZLOGOUT
diff --git a/Src/exec.c b/Src/exec.c
index 764b7140c..da2bf9fe2 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1862,7 +1862,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		/* If we are exec'ing a command, and we are not in a subshell, *
 		 * then check if we should save the history file.              */
 		if (isset(RCS) && interact && !nohistsave)
-		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 		exit(lastval);
 	    }
 
@@ -1874,7 +1874,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		/* If we are exec'ing a command, and we are not *
 		 * in a subshell, then save the history file.   */
 		if (!subsh && isset(RCS) && interact && !nohistsave)
-		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 	    }
 	    if (type == SIMPLE) {
 		if (cmd->vars) {
@@ -2232,7 +2232,7 @@ getoutput(char *cmd, int qt)
 	zclose(pipes[1]);
 	retval = readoutput(pipes[0], qt);
 	fdtable[pipes[0]] = 0;
-	child_suspend(0);		/* unblocks */
+	waitforpid(pid);		/* unblocks */
 	lastval = cmdoutval;
 	return retval;
     }
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 72e4db21b..0a640349b 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -83,7 +83,7 @@ hasher(char *str)
     unsigned hashval = 0;
 
     while (*str)
-	hashval += (hashval << 5) + ((unsigned) *str++);
+	hashval += (hashval << 5) + *(unsigned char *)str++;
 
     return hashval;
 }
@@ -138,7 +138,7 @@ deletehashtable(HashTable ht)
 }
 
 /* Add a node to a hash table.                          *
- * nam is the key to use in hashing.  dat is a pointer  *
+ * nam is the key to use in hashing.  nodeptr points    *
  * to the node to add.  If there is already a node in   *
  * the table with the same key, it is first freed, and  *
  * then the new node is added.  If the number of nodes  *
@@ -149,6 +149,17 @@ deletehashtable(HashTable ht)
 void
 addhashnode(HashTable ht, char *nam, void *nodeptr)
 {
+    HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+    if (oldnode)
+	ht->freenode(oldnode);
+}
+
+/* Add a node to a hash table, returning the old node on replacment. */
+
+/**/
+HashNode
+addhashnode2(HashTable ht, char *nam, void *nodeptr)
+{
     unsigned hashval;
     HashNode hn, hp, hq;
 
@@ -164,11 +175,11 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
 	ht->nodes[hashval] = hn;
 	if (++ht->ct >= ht->hsize * 2 && !ht->scan)
 	    expandhashtable(ht);
-	return;
+	return NULL;
     }
 
     /* else check if the first node contains the same key */
-    if (!strcmp(hp->nam, hn->nam)) {
+    if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
 	ht->nodes[hashval] = hn;
 	replacing:
 	hn->next = hp->next;
@@ -182,15 +193,14 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
 	    } else if(ht->scan->u.u == hp)
 		ht->scan->u.u = hn;
 	}
-	ht->freenode(hp);
-	return;
+	return hp;
     }
 
     /* else run through the list and check all the keys */
     hq = hp;
     hp = hp->next;
     for (; hp; hq = hp, hp = hp->next) {
-	if (!strcmp(hp->nam, hn->nam)) {
+	if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
 	    hq->next = hn;
 	    goto replacing;
 	}
@@ -201,6 +211,7 @@ addhashnode(HashTable ht, char *nam, void *nodeptr)
     ht->nodes[hashval] = hn;
     if (++ht->ct >= ht->hsize * 2 && !ht->scan)
         expandhashtable(ht);
+    return NULL;
 }
 
 /* Get an enabled entry in a hash table.  *
@@ -217,7 +228,7 @@ gethashnode(HashTable ht, char *nam)
 
     hashval = ht->hash(nam) % ht->hsize;
     for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
-	if (!strcmp(hp->nam, nam)) {
+	if (ht->cmpnodes(hp->nam, nam) == 0) {
 	    if (hp->flags & DISABLED)
 		return NULL;
 	    else
@@ -241,7 +252,7 @@ gethashnode2(HashTable ht, char *nam)
 
     hashval = ht->hash(nam) % ht->hsize;
     for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
-	if (!strcmp(hp->nam, nam))
+	if (ht->cmpnodes(hp->nam, nam) == 0)
 	    return hp;
     }
     return NULL;
@@ -267,7 +278,7 @@ removehashnode(HashTable ht, char *nam)
 	return NULL;
 
     /* else check if the key in the first one matches */
-    if (!strcmp(hp->nam, nam)) {
+    if (ht->cmpnodes(hp->nam, nam) == 0) {
 	ht->nodes[hashval] = hp->next;
 	gotit:
 	ht->ct--;
@@ -288,7 +299,7 @@ removehashnode(HashTable ht, char *nam)
     hq = hp;
     hp = hp->next;
     for (; hp; hq = hp, hp = hp->next) {
-	if (!strcmp(hp->nam, nam)) {
+	if (ht->cmpnodes(hp->nam, nam) == 0) {
 	    hq->next = hp->next;
 	    goto gotit;
 	}
@@ -316,7 +327,7 @@ enablehashnode(HashNode hn, int flags)
     hn->flags &= ~DISABLED;
 }
 
-/* Compare two hash table entries */
+/* Compare two hash table entries by name */
 
 /**/
 static int
@@ -498,6 +509,7 @@ emptyhashtable(HashTable ht)
     resizehashtable(ht, ht->hsize);
 }
 
+/**/
 #ifdef ZSH_HASH_DEBUG
 
 /* Print info about hash table */
@@ -550,6 +562,7 @@ bin_hashinfo(char *nam, char **args, char *ops, int func)
     return 0;
 }
 
+/**/
 #endif /* ZSH_HASH_DEBUG */
 
 /********************************/
@@ -577,6 +590,7 @@ createcmdnamtable(void)
     cmdnamtab->hash        = hasher;
     cmdnamtab->emptytable  = emptycmdnamtable;
     cmdnamtab->filltable   = fillcmdnamtable;
+    cmdnamtab->cmpnodes    = strcmp;
     cmdnamtab->addnode     = addhashnode;
     cmdnamtab->getnode     = gethashnode2;
     cmdnamtab->getnode2    = gethashnode2;
@@ -747,6 +761,7 @@ createshfunctable(void)
     shfunctab->hash        = hasher;
     shfunctab->emptytable  = NULL;
     shfunctab->filltable   = NULL;
+    shfunctab->cmpnodes    = strcmp;
     shfunctab->addnode     = addhashnode;
     shfunctab->getnode     = gethashnode;
     shfunctab->getnode2    = gethashnode2;
@@ -920,6 +935,7 @@ createreswdtable(void)
     reswdtab->hash        = hasher;
     reswdtab->emptytable  = NULL;
     reswdtab->filltable   = NULL;
+    reswdtab->cmpnodes    = strcmp;
     reswdtab->addnode     = addhashnode;
     reswdtab->getnode     = gethashnode;
     reswdtab->getnode2    = gethashnode2;
@@ -980,6 +996,7 @@ createaliastable(void)
     aliastab->hash        = hasher;
     aliastab->emptytable  = NULL;
     aliastab->filltable   = NULL;
+    aliastab->cmpnodes    = strcmp;
     aliastab->addnode     = addhashnode;
     aliastab->getnode     = gethashnode;
     aliastab->getnode2    = gethashnode2;
@@ -1109,6 +1126,7 @@ createnameddirtable(void)
     nameddirtab->hash        = hasher;
     nameddirtab->emptytable  = emptynameddirtable;
     nameddirtab->filltable   = fillnameddirtable;
+    nameddirtab->cmpnodes    = strcmp;
     nameddirtab->addnode     = addnameddirnode;
     nameddirtab->getnode     = gethashnode;
     nameddirtab->getnode2    = gethashnode2;
@@ -1219,3 +1237,137 @@ printnameddirnode(HashNode hn, int printflags)
     quotedzputs(nd->dir, stdout);
     putchar('\n');
 }
+
+/*************************************/
+/* History Line Hash Table Functions */
+/*************************************/
+
+/**/
+void
+createhisttable(void)
+{
+    histtab = newhashtable(599, "histtab", NULL);
+
+    histtab->hash        = histhasher;
+    histtab->emptytable  = emptyhisttable;
+    histtab->filltable   = NULL;
+    histtab->cmpnodes    = histstrcmp;
+    histtab->addnode     = addhistnode;
+    histtab->getnode     = gethashnode2;
+    histtab->getnode2    = gethashnode2;
+    histtab->removenode  = removehashnode;
+    histtab->disablenode = NULL;
+    histtab->enablenode  = NULL;
+    histtab->freenode    = freehistnode;
+    histtab->printnode   = NULL;
+}
+
+/**/
+unsigned
+histhasher(char *str)
+{
+    unsigned hashval = 0;
+
+    while (inblank(*str)) str++;
+
+    while (*str) {
+	if (inblank(*str)) {
+	    do str++; while (inblank(*str));
+	    if (*str)
+		hashval += (hashval << 5) + ' ';
+	}
+	else
+	    hashval += (hashval << 5) + *(unsigned char *)str++;
+    }
+    return hashval;
+}
+
+/**/
+void
+emptyhisttable(HashTable ht)
+{
+    emptyhashtable(ht);
+    if (hist_ring)
+	histremovedups();
+}
+
+/* Compare two strings with normalized white-space */
+
+/**/
+int
+histstrcmp(const char *str1, const char *str2)
+{
+    while (inblank(*str1)) str1++;
+    while (inblank(*str2)) str2++;
+    while (*str1 && *str2) {
+	if (inblank(*str1)) {
+	    if (!inblank(*str2))
+		break;
+	    do str1++; while (inblank(*str1));
+	    do str2++; while (inblank(*str2));
+	}
+	else {
+	    if (*str1 != *str2)
+		break;
+	    str1++;
+	    str2++;
+	}
+    }
+    return *str1 - *str2;
+}
+
+/**/
+void
+addhistnode(HashTable ht, char *nam, void *nodeptr)
+{
+    HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+    Histent he = (Histent)nodeptr;
+    if (oldnode && oldnode != nodeptr) {
+	if (he->flags & HIST_MAKEUNIQUE
+	 || (he->flags & HIST_FOREIGN && (Histent)oldnode == he->up)) {
+	    he->flags |= HIST_DUP;
+	    addhashnode(ht, oldnode->nam, oldnode); /* Remove the new dup */
+	}
+	else {
+	    oldnode->flags |= HIST_DUP;
+	    if (hist_ignore_all_dups)
+		freehistnode(oldnode); /* Remove the old dup */
+	}
+    }
+    else
+	he->flags &= ~HIST_MAKEUNIQUE;
+}
+
+/**/
+void
+freehistnode(HashNode nodeptr)
+{
+    freehistdata((Histent)nodeptr, 1);
+    zfree(nodeptr, sizeof (struct histent));
+}
+
+/**/
+void
+freehistdata(Histent he, int unlink)
+{
+    if (!he)
+	return;
+
+    if (!(he->flags & HIST_DUP))
+	removehashnode(histtab, he->text);
+
+    zsfree(he->text);
+    if (he->nwords)
+	zfree(he->words, he->nwords*2*sizeof(short));
+
+    if (unlink) {
+	if (!--histlinect)
+	    hist_ring = NULL;
+	else {
+	    if (he == hist_ring)
+		hist_ring = hist_ring->up;
+	    he->up->down = he->down;
+	    he->down->up = he->up;
+	}
+    }
+}
diff --git a/Src/hist.c b/Src/hist.c
index e104e1f3e..55209a1c0 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -56,23 +56,29 @@ int excs, exlast;
  * 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
+ * However, curhist is incremented to reflect the current line anyway
+ * and a temporary history entry is inserted while the user is editing.
+ * If the resulting line was not added to the list, curhist is
  * decremented in hend().
  */
  
 /**/
 int curhist;
 
-/* number of history entries */
- 
 /**/
-int histentct;
- 
-/* array of history entries */
- 
+struct histent curline;
+
+/* current line count of allocated history entries */
+
+/**/
+int histlinect;
+
+/* The history lines are kept in a hash, and also doubly-linked in a ring */
+
+/**/
+HashTable histtab;
 /**/
-Histent histentarr;
+Histent hist_ring;
  
 /* capacity of history lists */
  
@@ -90,6 +96,17 @@ int histdone;
 /**/
 int histactive;
 
+/* Current setting of the associated option, but sometimes also includes
+ * the setting of the HIST_SAVE_NO_DUPS option. */
+
+/**/
+int hist_ignore_all_dups;
+
+/* What flags (if any) we should skip when moving through the history */
+
+/**/
+int hist_skip_flags;
+
 /* 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 */
@@ -395,7 +412,7 @@ histsubchar(int c)
 	    if (!*buf) {
 		if (c != '%') {
 		    if (isset(CSHJUNKIEHISTORY))
-			ev = curhist - 1;
+			ev = addhistnum(curhist,-1,HIST_FOREIGN);
 		    else
 			ev = defev;
 		    if (c == ':' && evset == -1)
@@ -410,10 +427,10 @@ histsubchar(int c)
 		    evset = 0;
 		}
 	    } else if ((t0 = atoi(buf))) {
-		ev = (t0 < 0) ? curhist + t0 : t0;
+		ev = (t0 < 0) ? addhistnum(curhist,t0,HIST_FOREIGN) : t0;
 		evset = 1;
 	    } else if ((unsigned)*buf == bangchar) {
-		ev = curhist - 1;
+		ev = addhistnum(curhist,-1,HIST_FOREIGN);
 		evset = 1;
 	    } else if (*buf == '#') {
 		ev = curhist;
@@ -650,8 +667,6 @@ strinend(void)
 void
 hbegin(void)
 {
-    Histent curhistent;
-
     isfirstln = isfirstch = 1;
     errflag = histdone = spaceflag = 0;
     stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1;
@@ -661,43 +676,23 @@ hbegin(void)
 
     if (histactive & HA_JUNKED)
 	curhist--;
-    curhistent = gethistent(curhist);
-    if (!curhistent->ftim)
-	curhistent->ftim = time(NULL);
-    histactive = HA_ACTIVE;
+    if (hist_ring && !hist_ring->ftim)
+	hist_ring->ftim = time(NULL);
     if (interact && isset(SHINSTDIN) && !strin) {
+	histactive = HA_ACTIVE;
 	attachtty(mypgrp);
-	defev = curhist;
-	curhist++;
+	if (!hist_ring)
+	    hist_ring = curline.up = curline.down = &curline;
+	else {
+	    curline.up = hist_ring;
+	    curline.down = hist_ring->down;
+	    hist_ring->down = hist_ring->down->up = &curline;
+	    hist_ring = &curline;
+	}
+	curline.histnum = ++curhist;
+	defev = addhistnum(curhist, -1, HIST_FOREIGN);
     } 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;
+	histactive = HA_ACTIVE | HA_NOINC;
 }
 
 /**/
@@ -726,6 +721,140 @@ histreduceblanks(void)
     chline[pos] = '\0';
 }
 
+/**/
+void
+histremovedups(void)
+{
+    Histent he, next;
+    for (he = hist_ring; he; he = next) {
+	next = up_histent(he);
+	if (he->flags & HIST_DUP)
+	    freehistnode((HashNode)he);
+    }
+}
+
+/**/
+int
+addhistnum(int hl, int n, int xflags)
+{
+    int dir = n < 0? -1 : n > 0? 1 : 0;
+    Histent he = gethistent(hl, dir);
+			     
+    if (!he)
+	return 0;
+    if (he->histnum != hl)
+	n -= dir;
+    if (n)
+	he = movehistent(he, n, xflags);
+    if (!he)
+	return dir < 0? firsthist() : curhist;
+    return he->histnum;
+}
+
+/**/
+Histent
+movehistent(Histent he, int n, int xflags)
+{
+    while (n < 0) {
+	if (!(he = up_histent(he)))
+	    return NULL;
+	if (!(he->flags & xflags))
+	    n++;
+    }
+    while (n > 0) {
+	if (!(he = down_histent(he)))
+	    return NULL;
+	if (!(he->flags & xflags))
+	    n--;
+    }
+    return he;
+}
+
+/**/
+Histent
+up_histent(Histent he)
+{
+    return he->up == hist_ring? NULL : he->up;
+}
+
+/**/
+Histent
+down_histent(Histent he)
+{
+    return he == hist_ring? NULL : he->down;
+}
+
+/**/
+Histent
+gethistent(int ev, int nearmatch)
+{
+    Histent he;
+
+    if (!hist_ring)
+	return NULL;
+
+    if (ev - hist_ring->down->histnum < hist_ring->histnum - ev) {
+	for (he = hist_ring->down; he->histnum <= ev; he = he->down) {
+	    if (he->histnum == ev)
+		return he;
+	}
+	if (nearmatch < 0)
+	    return up_histent(he);
+	if (nearmatch > 0)
+	    return he;
+    }
+    else {
+	for (he = hist_ring; he->histnum >= ev; he = he->up) {
+	    if (he->histnum == ev)
+		return he;
+	}
+	if (nearmatch < 0)
+	    return he;
+	if (nearmatch > 0)
+	    return down_histent(he);
+    }
+
+    return NULL;
+}
+
+/**/
+Histent
+prepnexthistent(int histnum)
+{
+    Histent he;
+
+    if (histlinect < histsiz) {
+	he = (Histent)zcalloc(sizeof *he);
+	if (!hist_ring)
+	    hist_ring = he->up = he->down = he;
+	else {
+	    he->up = hist_ring;
+	    he->down = hist_ring->down;
+	    hist_ring->down = he->down->up = he;
+	    hist_ring = he;
+	}
+	histlinect++;
+    }
+    else {
+	he = hist_ring->down;
+	if (isset(HISTEXPIREDUPSFIRST) && !(he->flags & HIST_DUP)) {
+	    do {
+		he = he->down;
+	    } while (he != hist_ring->down && !(he->flags & HIST_DUP)) ;
+	    if (he != hist_ring->down) {
+		he->up->down = he->down;
+		he->down->up = he->up;
+		he->up = hist_ring;
+		he->down = hist_ring->down;
+		hist_ring->down = he->down->up = he;
+	    }
+	}
+	freehistdata(hist_ring = he, 0);
+    }
+    hist_ring->histnum = histnum;
+    return hist_ring;
+}
+
 /* say we're done using the history mechanism */
 
 /**/
@@ -733,19 +862,35 @@ int
 hend(void)
 {
     int flag, save = 1;
+    char *hf = getsparam("HISTFILE");
 
     DPUTS(!chline, "BUG: chline is NULL in hend()");
     if (histdone & HISTFLAG_SETTY)
 	settyinfo(&shttyinfo);
+    if (!(histactive & HA_NOINC)) {
+	curline.up->down = curline.down;
+	curline.down->up = curline.up;
+	if (hist_ring == &curline) {
+	    if (!histlinect)
+		hist_ring = NULL;
+	    else
+		hist_ring = curline.up;
+	}
+	curhist--;
+    }
     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;
     }
+    if (hist_ignore_all_dups != isset(HISTIGNOREALLDUPS)
+     && (hist_ignore_all_dups = isset(HISTIGNOREALLDUPS)) != 0)
+	histremovedups();
+    /* For history sharing, lock history file once for both read and write */
+    if (isset(SHAREHISTORY) && lockhistfile(hf, 0))
+	readhistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST);
     flag = histdone;
     histdone = 0;
     if (hptr < chline + 1)
@@ -781,8 +926,10 @@ hend(void)
     }
     if (save) {
 	Histent he;
-	int keepflags = 0;
+	int keepflags;
 
+	for (he = hist_ring; he && he->flags & hist_skip_flags;
+	     he = up_histent(he)) ;
 #ifdef DEBUG
 	/* debugging only */
 	if (chwordpos%2) {
@@ -796,23 +943,21 @@ hend(void)
 	/* strip superfluous blanks, if desired */
 	if (isset(HISTREDUCEBLANKS))
 	    histreduceblanks();
-
-	if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1))
-	 && he->text && !histcmp(he)) {
+	if ((isset(HISTIGNOREDUPS) || isset(HISTIGNOREALLDUPS)) && he
+	 && histstrcmp(chline, he->text) == 0) {
 	    /* 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.
+	     * previous one with the current one.  This also gets the
+	     * timestamp right.  Perhaps, preserve the HIST_OLD flag.
 	     */
-	    keepflags = he->flags;
-	    curhist--;
+	    keepflags = he->flags & HIST_OLD; /* Avoid re-saving */
+	    freehistdata(he, 0);
+	} else {
+	    keepflags = 0;
+	    he = prepnexthistent(++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;
@@ -821,12 +966,15 @@ hend(void)
 	    he->words = (short *)zalloc(chwordpos * sizeof(short));
 	    memcpy(he->words, chwords, chwordpos * sizeof(short));
 	}
-    } else
-	curhist--;
+	addhistnode(histtab, he->text, he);
+    }
     zfree(chline, hlinesz);
     zfree(chwords, chwordlen*sizeof(short));
     chline = NULL;
     histactive = 0;
+    if (isset(SHAREHISTORY) || isset(INCREMENTALAPPENDHISTORY))
+	savehistfile(hf, 1, HFILE_USE_OPTIONS | HFILE_FAST);
+    unlockhistfile(hf); /* It's OK to call this even if we aren't locked */
     return !(flag & HISTFLAG_NOEXEC || errflag);
 }
 
@@ -838,10 +986,7 @@ 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;
+	    freehistnode((HashNode)hist_ring);
 	    histactive |= HA_JUNKED;
 	    /* curhist-- is delayed until the next hbegin() */
 	}
@@ -1026,18 +1171,21 @@ getargspec(int argc, int marg, int evset)
 static int
 hconsearch(char *str, int *marg)
 {
-    int t0, t1 = 0;
+    int t1 = 0;
     char *s;
     Histent he;
 
-    for (t0 = curhist - 1; (he = quietgethist(t0)); t0--)
+    for (he = up_histent(hist_ring); he; he = up_histent(he)) {
+	if (he->flags & HIST_FOREIGN)
+	    continue;
 	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 he->histnum;
 	}
+    }
     return -1;
 }
 
@@ -1047,12 +1195,15 @@ hconsearch(char *str, int *marg)
 int
 hcomsearch(char *str)
 {
-    int t0;
-    char *hs;
+    Histent he;
+    int len = strlen(str);
 
-    for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--)
-	if (!strncmp(hs, str, strlen(str)))
-	    return t0;
+    for (he = up_histent(hist_ring); he; he = up_histent(he)) {
+	if (he->flags & HIST_FOREIGN)
+	    continue;
+	if (strncmp(he->text, str, len) == 0)
+	    return he->histnum;
+    }
     return -1;
 }
 
@@ -1206,33 +1357,15 @@ convamps(char *out, char *in, int inlen)
 }
 
 /**/
-struct histent *
+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;
+	curline.text = chline;
+	curline.nwords = chwordpos/2;
+	curline.words = chwords;
+    }
+    return gethistent(ev, GETHIST_EXACT);
 }
 
 /**/
@@ -1362,10 +1495,9 @@ quotebreak(char **tr)
     return 0;
 }
 
-#if 0
 /* read an arbitrary amount of data into a buffer until stop is found */
 
-/**/
+#if 0 /**/
 char *
 hdynread(int stop)
 {
@@ -1424,61 +1556,80 @@ hdynread2(int stop)
 void
 inithist(void)
 {
-    histentct = histsiz;
-    histentarr = (Histent) zcalloc(histentct * sizeof *histentarr);
+    createhisttable();
 }
 
 /**/
 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;
+    while (histlinect > histsiz)
+	freehistnode((HashNode)hist_ring->down);
 }
 
+/* Remember the last line in the history file so we can find it again. */
+static struct {
+    char *text;
+    time_t stim, mtim;
+    off_t fpos, fsiz;
+    int next_write_ev;
+} lasthist;
+
+static int histfile_linect;
+
 /**/
 void
-readhistfile(char *s, int err)
+readhistfile(char *fn, int err, int readflags)
 {
-    char *buf;
+    char *buf, *start = NULL;
     FILE *in;
-    Histent ent;
-    time_t tim = time(NULL);
+    Histent he;
+    time_t stim, ftim, tim = time(NULL);
+    off_t fpos;
     short *wordlist;
+    struct stat sb;
     int nwordpos, nwordlist, bufsiz;
+    int searching, newflags;
 
-    if (!s)
+    if (!fn && !(fn = getsparam("HISTFILE")))
+	return;
+    if (readflags & HFILE_FAST) {
+	if (stat(fn, &sb) < 0
+	 || (lasthist.fsiz == sb.st_size && lasthist.mtim == sb.st_mtime)
+	 || !lockhistfile(fn, 0))
+	    return;
+	lasthist.fsiz = sb.st_size;
+	lasthist.mtim = sb.st_mtime;
+    }
+    else if (!lockhistfile(fn, 1))
 	return;
-    if ((in = fopen(unmeta(s), "r"))) {
+    if ((in = fopen(unmeta(fn), "r"))) {
 	nwordlist = 16;
 	wordlist = (short *)zalloc(nwordlist*sizeof(short));
 	bufsiz = 1024;
 	buf = zalloc(bufsiz);
 
-	while (fgets(buf, bufsiz, in)) {
+	if (readflags & HFILE_FAST && lasthist.text) {
+	    if (lasthist.fpos < lasthist.fsiz) {
+		fseek(in, lasthist.fpos, 0);
+		searching = 1;
+	    }
+	    else {
+		histfile_linect = 0;
+		searching = -1;
+	    }
+	} else
+	    searching = 0;
+
+	newflags = HIST_OLD | HIST_READ;
+	if (readflags & HFILE_FAST)
+	    newflags |= HIST_FOREIGN;
+	if (readflags & HFILE_SKIPOLD
+	 || (hist_ignore_all_dups && newflags & hist_skip_flags))
+	    newflags |= HIST_MAKEUNIQUE;
+	while (fpos = ftell(in), fgets(buf, bufsiz, in)) {
 	    int l = strlen(buf);
-	    char *pt, *start;
+	    char *pt;
 
 	    while (l) {
 		while (buf[l - 1] != '\n') {
@@ -1488,114 +1639,184 @@ readhistfile(char *s, int err)
 			l++;
 			break;
 		    }
-		    l = strlen(buf);
+		    l += strlen(buf+l);
 		}
 		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);
+		    buf[--l - 1] = '\n';
+		    fgets(buf + l, bufsiz - l, in);
+		    l += strlen(buf+l);
 		} else
 		    break;
 	    }
 
-	    ent = gethistent(++curhist);
 	    pt = buf;
 	    if (*pt == ':') {
 		pt++;
-		ent->stim = zstrtol(pt, NULL, 0);
+		stim = zstrtol(pt, NULL, 0);
 		for (; *pt != ':' && *pt; pt++);
 		if (*pt) {
 		    pt++;
-		    ent->ftim = zstrtol(pt, NULL, 0);
+		    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
+		    ftim = stim;
 	    } else {
-		ent->ftim = ent->stim = tim;
+		if (*pt == '\\' && pt[1] == ':')
+		    pt++;
+		stim = ftim = 0;
+	    }
+
+	    if (searching) {
+		if (searching > 0) {
+		    if (stim == lasthist.stim
+		     && histstrcmp(pt, lasthist.text) == 0)
+			searching = 0;
+		    else {
+			fseek(in, 0, 0);
+			histfile_linect = 0;
+			searching = -1;
+		    }
+		    continue;
+		}
+		else if (stim < lasthist.stim) {
+		    histfile_linect++;
+		    continue;
+		}
+		searching = 0;
 	    }
 
-	    zsfree(ent->text);
-	    ent->text = ztrdup(pt);
-	    ent->flags = HIST_OLD|HIST_READ;
-	    if (ent->nwords)
-		zfree(ent->words, ent->nwords*2*sizeof(short));
+	    if (readflags & HFILE_USE_OPTIONS) {
+		histfile_linect++;
+		lasthist.fpos = fpos;
+		lasthist.stim = stim;
+	    }
+
+	    he = prepnexthistent(++curhist);
+	    he->text = ztrdup(pt);
+	    he->flags = newflags;
+	    if ((he->stim = stim) == 0)
+		he->stim = he->ftim = tim;
+	    else if (ftim < stim)
+		he->ftim = stim + ftim;
+	    else
+		he->ftim = ftim;
 
 	    /* Divide up the words.  We don't know how it lexes,
-	       so just look for spaces.
+	       so just look for white-space.
 	       */
 	    nwordpos = 0;
 	    start = pt;
 	    do {
-		while (*pt == ' ')
+		while (inblank(*pt))
 		    pt++;
 		if (*pt) {
 		    if (nwordpos >= nwordlist)
 			wordlist = (short *) realloc(wordlist,
 					(nwordlist += 16)*sizeof(short));
 		    wordlist[nwordpos++] = pt - start;
-		    while (*pt && *pt != ' ')
+		    while (*pt && !inblank(*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));
+	    he->nwords = nwordpos/2;
+	    if (he->nwords) {
+		he->words = (short *)zalloc(nwordpos*sizeof(short));
+		memcpy(he->words, wordlist, nwordpos*sizeof(short));
 	    } else
-		ent->words = (short *)NULL;
+		he->words = (short *)NULL;
+	    addhistnode(histtab, he->text, he);
+	    if (hist_ring != he)
+		curhist--; /* We discarded a foreign duplicate */
+	}
+	if (start && readflags & HFILE_USE_OPTIONS) {
+	    zsfree(lasthist.text);
+	    lasthist.text = ztrdup(start);
 	}
-	fclose(in);
-
 	zfree(wordlist, nwordlist*sizeof(short));
 	zfree(buf, bufsiz);
+
+	fclose(in);
     } else if (err)
-	zerr("can't read history file", s, 0);
+	zerr("can't read history file", fn, 0);
+
+    unlockhistfile(fn);
 }
 
 /**/
 void
-savehistfile(char *s, int err, int app)
+savehistfile(char *fn, int err, int writeflags)
 {
-    char *t;
+    char *t, *start = NULL;
     FILE *out;
-    int ev;
-    Histent ent;
+    Histent he;
+    int xcurhist = curhist - !!(histactive & HA_ACTIVE);
     int savehist = getiparam("SAVEHIST");
+    int extended_history = isset(EXTENDEDHISTORY);
 
-    if (!s || !interact || savehist <= 0)
+    if (!interact || savehist <= 0 || !hist_ring
+     || (!fn && !(fn = getsparam("HISTFILE"))))
 	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 (writeflags & HFILE_FAST) {
+	he = gethistent(lasthist.next_write_ev, GETHIST_DOWNWARD);
+	while (he && he->flags & HIST_OLD) {
+	    lasthist.next_write_ev = he->histnum + 1;
+	    he = down_histent(he);
+	}
+	if (!he || !lockhistfile(fn, 0))
+	    return;
+	if (histfile_linect > savehist + savehist / 5)
+	    writeflags &= ~HFILE_FAST;
+    }
+    else {
+	if (!lockhistfile(fn, 1))
+	    return;
+	he = hist_ring->down;
+    }
+    if (writeflags & HFILE_USE_OPTIONS) {
+	if (isset(APPENDHISTORY) || isset(INCREMENTALAPPENDHISTORY)
+	 || isset(SHAREHISTORY))
+	    writeflags |= HFILE_APPEND | HFILE_SKIPOLD;
+	else
+	    histfile_linect = 0;
+	if (isset(HISTSAVENODUPS))
+	    writeflags |= HFILE_SKIPDUPS;
+	if (isset(SHAREHISTORY))
+	    extended_history = 1;
+    }
+    if (writeflags & HFILE_APPEND) {
+	out = fdopen(open(unmeta(fn),
+			O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a");
+    }
+    else {
+	out = fdopen(open(unmeta(fn),
+			 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)
+	for (; he && he->histnum <= xcurhist; he = down_histent(he)) {
+	    if ((writeflags & HFILE_SKIPDUPS && he->flags & HIST_DUP)
+	     || (writeflags & HFILE_SKIPFOREIGN && he->flags & HIST_FOREIGN))
+		continue;
+	    if (writeflags & HFILE_SKIPOLD) {
+		if (he->flags & HIST_OLD)
 		    continue;
-		ent->flags |= HIST_OLD;
+		he->flags |= HIST_OLD;
+		if (writeflags & HFILE_USE_OPTIONS)
+		    lasthist.next_write_ev = he->histnum + 1;
 	    }
-	    t = ent->text;
-	    if (isset(EXTENDEDHISTORY)) {
-		fprintf(out, ": %ld:%ld;",
-			(long)ent->stim,
-			(long)ent->ftim);
+	    if (writeflags & HFILE_USE_OPTIONS) {
+		lasthist.fpos = ftell(out);
+		lasthist.stim = he->stim;
+		histfile_linect++;
+	    }
+	    t = start = he->text;
+	    if (extended_history) {
+		fprintf(out, ": %ld:%ld;", (long)he->stim,
+			he->ftim? (long)(he->ftim - he->stim) : 0L);
 	    } else if (*t == ':')
 		fputc('\\', out);
 
@@ -1606,69 +1827,109 @@ savehistfile(char *s, int err, int app)
 	    }
 	    fputc('\n', out);
 	}
+	if (start && writeflags & HFILE_USE_OPTIONS) {
+	    struct stat sb;
+	    fflush(out);
+	    if (fstat(fileno(out), &sb) == 0) {
+		lasthist.fsiz = sb.st_size;
+		lasthist.mtim = sb.st_mtime;
+	    }
+	    zsfree(lasthist.text);
+	    lasthist.text = ztrdup(start);
+	}
 	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);
+	if ((writeflags & (HFILE_SKIPOLD | HFILE_FAST)) == HFILE_SKIPOLD) {
+	    HashTable remember_histtab = histtab;
+	    Histent remember_hist_ring = hist_ring;
+	    int remember_histlinect = histlinect;
+	    int remember_curhist = curhist;
+
+	    hist_ring = NULL;
+	    curhist = histlinect = 0;
+	    histsiz = savehist;
+	    createhisttable(); /* sets histtab */
+
+	    hist_ignore_all_dups |= isset(HISTSAVENODUPS);
+	    readhistfile(fn, err, 0);
+	    hist_ignore_all_dups = isset(HISTIGNOREALLDUPS);
+	    savehistfile(fn, err, 0);
+	    deletehashtable(histtab);
+
+	    curhist = remember_curhist;
+	    histlinect = remember_histlinect;
+	    hist_ring = remember_hist_ring;
+	    histtab = remember_histtab;
 	}
     } else if (err)
-	zerr("can't write history file %s", s, 0);
+	zerr("can't write history file %s", fn, 0);
+
+    unlockhistfile(fn);
 }
 
+static int lockhistct;
+
 /**/
 int
-firsthist(void)
+lockhistfile(char *fn, int keep_trying)
 {
-    int ev;
-    Histent ent;
+    int ct = lockhistct;
 
-    ev = curhist - histentct + 1;
-    if (ev < 1)
-	ev = 1;
-    do {
-	ent = gethistent(ev);
-	if (ent->text)
-	    break;
-	ev++;
+    if (!fn && !(fn = getsparam("HISTFILE")))
+	return 0;
+    if (!lockhistct++) {
+	struct stat sb;
+	int fd, len = strlen(fn);
+	char *tmpfile, *lockfile;
+
+	tmpfile = zalloc(len + 10 + 1);
+	sprintf(tmpfile, "%s.%ld", fn, mypid);
+	if ((fd = open(tmpfile, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0) {
+	    write(fd, "0\n", 2);
+	    close(fd);
+	    lockfile = zalloc(len + 5 + 1);
+	    sprintf(lockfile, "%s.LOCK", fn);
+	    while (link(tmpfile, lockfile) < 0) {
+		if (stat(lockfile, &sb) < 0) {
+		    if (errno == ENOENT)
+			continue;
+		}
+		else if (keep_trying) {
+		    if (time(NULL) - sb.st_mtime < 10)
+			sleep(1);
+		    else
+			unlink(lockfile);
+		    continue;
+		}
+		lockhistct--;
+		break;
+	    }
+	    free(lockfile);
+	}
+	unlink(tmpfile);
+	free(tmpfile);
     }
-    while (ev < curhist);
-    return ev;
+    return ct != lockhistct;
 }
 
+/* Unlock the history file if this corresponds to the last nested lock
+ * request.  If we don't have the file locked, just return.
+ */
+
+/**/
+void
+unlockhistfile(char *fn)
+{
+    if (!fn && !(fn = getsparam("HISTFILE")))
+	return;
+    if (--lockhistct) {
+	if (lockhistct < 0)
+	    lockhistct = 0;
+    }
+    else {
+	char *lockfile = zalloc(strlen(fn) + 5 + 1);
+	sprintf(lockfile, "%s.LOCK", fn);
+	unlink(lockfile);
+	free(lockfile);
+    }
+}
diff --git a/Src/init.c b/Src/init.c
index 6ad1e5100..e92a5000f 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -110,15 +110,14 @@ loop(int toplevel, int justonce)
 	    List prelist;
 
 	    if (toplevel && (prelist = getshfunc("preexec")) != &dummy_list) {
-		Histent he = gethistent(curhist);
 		LinkList args;
 		int osc = sfcontext;
 
 		PERMALLOC {
 		    args = newlinklist();
 		    addlinknode(args, "preexec");
-		    if (he && he->text)
-			addlinknode(args, he->text);
+		    if (hist_ring)
+			addlinknode(args, hist_ring->text);
 		} LASTALLOC;
 		sfcontext = SFC_HOOK;
 		doshfunc("preexec", prelist, args, 0, 1);
@@ -642,11 +641,13 @@ setupvals(void)
     wrappers = NULL;
 
 #ifdef TIOCGWINSZ
-    adjustwinsize();
+    adjustwinsize(0);
 #else
-    /* Using zero below sets the defaults from termcap */
-    setiparam("COLUMNS", 0);
-    setiparam("LINES", 0);
+    /* columns and lines are normally zero, unless something different *
+     * was inhereted from the environment.  If either of them are zero *
+     * the setiparam calls below set them to the defaults from termcap */
+    setiparam("COLUMNS", columns);
+    setiparam("LINES", lines);
 #endif
 
 #ifdef HAVE_GETRLIMIT
@@ -828,7 +829,7 @@ init_misc(void)
     }
 
     if (interact && isset(RCS))
-	readhistfile(getsparam("HISTFILE"), 0);
+	readhistfile(NULL, 0, HFILE_USE_OPTIONS);
 }
 
 /* source a file */
diff --git a/Src/jobs.c b/Src/jobs.c
index e94dd77e2..6ddef0f70 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -180,11 +180,14 @@ update_job(Job jn)
     } else {                   /* job is done, so remember return value */
 	lastval2 = val;
 	/* If last process was run in the current shell, keep old status
-	 * and let it handle its own traps
+	 * and let it handle its own traps, but always allow the test
+	 * for the pgrp.
 	 */
-	if (job == thisjob && !(jn->stat & STAT_CURSH)) {
-	  lastval = val;
-	  inforeground = 1;
+	if (jn->stat & STAT_CURSH)
+	    inforeground = 1;
+	else if (job == thisjob) {
+	    lastval = val;
+	    inforeground = 2;
 	}
     }
 
@@ -198,8 +201,20 @@ update_job(Job jn)
 	/* is this job in the foreground of an interactive shell? */
 	if (mypgrp != pgrp && inforeground &&
 	    (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
-	    attachtty(mypgrp);
-	    adjustwinsize();   /* check window size and adjust if necessary */
+	    if (list_pipe) {
+		/*
+		 * Oh, dear, we're right in the middle of some confusion
+		 * of shell jobs on the righthand side of a pipeline, so
+		 * it's death to call attachtty() just yet.  Mark the
+		 * fact in the job, so that the attachtty() will be called
+		 * when the job is finally deleted.
+		 */
+		jn->stat |= STAT_ATTACH;
+	    } else {
+		attachtty(mypgrp);
+		/* check window size and adjust if necessary */
+		adjustwinsize(0);
+	    }
 	}
     }
 
@@ -223,7 +238,7 @@ update_job(Job jn)
      * process group from the shell, so the shell will not receive     *
      * terminal signals, therefore we we pretend that the shell got    *
      * the signal too.                                                 */
-    if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) {
+    if (inforeground == 2 && isset(MONITOR) && WIFSIGNALED(status)) {
 	int sig = WTERMSIG(status);
 
 	if (sig == SIGINT || sig == SIGQUIT) {
@@ -609,6 +624,11 @@ deletejob(Job jn)
 {
     struct process *pn, *nx;
 
+    if (jn->stat & STAT_ATTACH) {
+	attachtty(mypgrp);
+	adjustwinsize(0);
+    }
+
     pn = jn->procs;
     jn->procs = NULL;
     for (; pn; pn = nx) {
diff --git a/Src/module.c b/Src/module.c
index 09f1fd5db..c966d4497 100644
--- a/Src/module.c
+++ b/Src/module.c
@@ -142,6 +142,7 @@ addwrapper(Module m, FuncWrap w)
     return 0;
 }
 
+/**/
 #ifdef DYNAMIC
 
 /* $module_path ($MODULE_PATH) */
@@ -238,6 +239,7 @@ deletewrapper(Module m, FuncWrap w)
     return 1;
 }
 
+/**/
 #ifdef AIXDYNAMIC
 
 #include <sys/ldr.h>
@@ -273,6 +275,7 @@ load_and_bind(const char *fn)
 #define dlclose(X)  unload(X)
 #define dlerror()   (dlerrstr[0])
 
+/**/
 #else
 
 #ifdef HAVE_DLFCN_H
@@ -289,6 +292,7 @@ load_and_bind(const char *fn)
 # endif
 #endif
 
+/**/
 #ifdef HPUXDYNAMIC
 # define dlopen(file,mode) (void *)shl_load((file), (mode), (long) 0)
 # define dlclose(handle) shl_unload((shl_t)(handle))
@@ -309,6 +313,7 @@ hpux_dlsym(void *handle, char *name)
 # ifndef HAVE_DLCLOSE
 #  define dlclose(X) ((X), 0)
 # endif
+/**/
 #endif
 
 #ifdef DLSYM_NEEDS_UNDERSCORE
@@ -331,6 +336,7 @@ hpux_dlsym(void *handle, char *name)
 # define STR_FINISH_S  "finish_%s"
 #endif /* !DLSYM_NEEDS_UNDERSCORE */
 
+/**/
 #endif /* !AIXDYNAMIC */
 
 #ifndef RTLD_LAZY
@@ -408,6 +414,7 @@ find_module(const char *name)
     return NULL;
 }
 
+/**/
 #ifdef AIXDYNAMIC
 
 /**/
@@ -438,6 +445,7 @@ finish_module(Module m)
     return ((int (*)_((int,Module))) m->handle)(3, m);
 }
 
+/**/
 #else
 
 static Module_func
@@ -523,6 +531,7 @@ finish_module(Module m)
     return r;
 }
 
+/**/
 #endif /* !AIXDYNAMIC */
 
 /**/
@@ -664,7 +673,7 @@ autoloadscan(HashNode hn, int printflags)
     if(bn->flags & BINF_ADDED)
 	return;
     if(printflags & PRINT_LIST) {
-	fputs("zmodload -a ", stdout);
+	fputs("zmodload -ab ", stdout);
 	if(bn->optstr[0] == '-')
 	    fputs("-- ", stdout);
 	quotedzputs(bn->optstr, stdout);
@@ -687,7 +696,12 @@ autoloadscan(HashNode hn, int printflags)
 int
 bin_zmodload(char *nam, char **args, char *ops, int func)
 {
-    if(ops['d'] && ops['a']) {
+    if ((ops['b'] || ops['c'] || ops['p']) && !(ops['a'] || ops['u'])) {
+	zwarnnam(nam, "-b, -c, and -p must be combined with -a or -u",
+		 NULL, 0);
+	return 1;
+    }
+    if (ops['d'] && ops['a']) {
 	zwarnnam(nam, "-d cannot be combined with -a", NULL, 0);
 	return 1;
     }
@@ -695,16 +709,20 @@ bin_zmodload(char *nam, char **args, char *ops, int func)
 	zwarnnam(nam, "what do you want to unload?", NULL, 0);
 	return 1;
     }
-    if(ops['d'])
+    if (ops['d'])
 	return bin_zmodload_dep(nam, args, ops);
-    else if(ops['a'])
+    else if ((ops['a'] || ops['b']) && !(ops['c'] || ops['p']))
 	return bin_zmodload_auto(nam, args, ops);
-    else if (ops['c'] || ops['C'])
+    else if (ops['c'] && !(ops['b'] || ops['p']))
 	return bin_zmodload_cond(nam, args, ops);
-    else if (ops['p'])
+    else if (ops['p'] && !(ops['b'] || ops['c']))
 	return bin_zmodload_param(nam, args, ops);
-    else
+    else if (!(ops['a'] || ops['b'] || ops['c'] || ops['p']))
 	return bin_zmodload_load(nam, args, ops);
+    else
+	zwarnnam(nam, "use only one of -b, -c, or -p", NULL, 0);
+
+    return 1;
 }
 
 /**/
@@ -865,7 +883,7 @@ bin_zmodload_cond(char *nam, char **args, char *ops)
 	for (p = condtab; p; p = p->next) {
 	    if (p->module) {
 		if (ops['L']) {
-		    fputs("zmodload -c", stdout);
+		    fputs("zmodload -ac", stdout);
 		    if (p->flags & CONDF_INFIX)
 			putchar('I');
 		    printf(" %s %s\n", p->module, p->name);
@@ -908,7 +926,7 @@ printautoparams(HashNode hn, int lon)
 
     if (pm->flags & PM_AUTOLOAD) {
 	if (lon)
-	    printf("zmodload -p %s %s\n", pm->u.str, pm->nam);
+	    printf("zmodload -ap %s %s\n", pm->u.str, pm->nam);
 	else
 	    printf("%s (%s)\n", pm->nam, pm->u.str);
     }
@@ -1097,6 +1115,7 @@ bin_zmodload_load(char *nam, char **args, char *ops)
     }
 }
 
+/**/
 #endif /* DYNAMIC */
 
 /* The list of module-defined conditions. */
@@ -1252,6 +1271,7 @@ deleteparamdefs(char const *nam, Paramdef d, int size)
     return 1;
 }
 
+/**/
 #ifdef DYNAMIC
 
 /* This adds a definition for autoloading a module for a condition. */
@@ -1342,4 +1362,5 @@ add_autoparam(char *nam, char *module)
     pm->flags |= PM_AUTOLOAD;
 }
 
+/**/
 #endif
diff --git a/Src/options.c b/Src/options.c
index 9010831c0..c0042a662 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -124,15 +124,20 @@ static struct optname optns[] = {
 {NULL, "hashlistall",	      OPT_ALL,			 HASHLISTALL},
 {NULL, "histallowclobber",    0,			 HISTALLOWCLOBBER},
 {NULL, "histbeep",	      OPT_ALL,			 HISTBEEP},
+{NULL, "histexpiredupsfirst", 0,			 HISTEXPIREDUPSFIRST},
+{NULL, "histfindnodups",      0,			 HISTFINDNODUPS},
+{NULL, "histignorealldups",   0,			 HISTIGNOREALLDUPS},
 {NULL, "histignoredups",      0,			 HISTIGNOREDUPS},
 {NULL, "histignorespace",     0,			 HISTIGNORESPACE},
 {NULL, "histnofunctions",     0,			 HISTNOFUNCTIONS},
 {NULL, "histnostore",	      0,			 HISTNOSTORE},
 {NULL, "histreduceblanks",    0,			 HISTREDUCEBLANKS},
+{NULL, "histsavenodups",      0,			 HISTSAVENODUPS},
 {NULL, "histverify",	      0,			 HISTVERIFY},
 {NULL, "hup",		      OPT_EMULATE|OPT_ZSH,	 HUP},
 {NULL, "ignorebraces",	      OPT_EMULATE|OPT_SH,	 IGNOREBRACES},
 {NULL, "ignoreeof",	      0,			 IGNOREEOF},
+{NULL, "incrementalappendhistory",0,			 INCREMENTALAPPENDHISTORY},
 {NULL, "interactive",	      OPT_SPECIAL,		 INTERACTIVE},
 {NULL, "interactivecomments", OPT_EMULATE|OPT_BOURNE,	 INTERACTIVECOMMENTS},
 {NULL, "ksharrays",	      OPT_EMULATE|OPT_BOURNE,	 KSHARRAYS},
@@ -176,6 +181,7 @@ static struct optname optns[] = {
 {NULL, "restricted",	      OPT_SPECIAL,		 RESTRICTED},
 {NULL, "rmstarsilent",	      OPT_BOURNE,		 RMSTARSILENT},
 {NULL, "rmstarwait",	      0,			 RMSTARWAIT},
+{NULL, "sharehistory",	      OPT_KSH,			 SHAREHISTORY},
 {NULL, "shfileexpansion",     OPT_EMULATE|OPT_BOURNE,	 SHFILEEXPANSION},
 {NULL, "shglob",	      OPT_EMULATE|OPT_BOURNE,	 SHGLOB},
 {NULL, "shinstdin",	      OPT_SPECIAL,		 SHINSTDIN},
@@ -378,9 +384,9 @@ printoptionnode(HashNode hn, int set)
 	optno = -optno;
     if (isset(KSHOPTIONPRINT)) {
 	if (defset(on))
-	    printf("no%-20s%s\n", on->nam, isset(optno) ? "off" : "on");
+	    printf("no%-19s %s\n", on->nam, isset(optno) ? "off" : "on");
 	else
-	    printf("%-22s%s\n", on->nam, isset(optno) ? "on" : "off");
+	    printf("%-21s %s\n", on->nam, isset(optno) ? "on" : "off");
     } else if (set == (isset(optno) ^ defset(on))) {
 	if (set ^ isset(optno))
 	    fputs("no", stdout);
@@ -399,6 +405,7 @@ createoptiontable(void)
     optiontab->hash        = hasher;
     optiontab->emptytable  = NULL;
     optiontab->filltable   = NULL;
+    optiontab->cmpnodes    = strcmp;
     optiontab->addnode     = addhashnode;
     optiontab->getnode     = gethashnode;
     optiontab->getnode2    = gethashnode2;
@@ -635,6 +642,8 @@ dosetopt(int optno, int value, int force)
 	}
     } else if(!force && (optno == INTERACTIVE || optno == SHINSTDIN ||
 	    optno == SINGLECOMMAND)) {
+	if (opts[optno] == value)
+	    return 0;
 	/* it is not permitted to change the value of these options */
 	return -1;
     } else if(!force && optno == USEZLE && value) {
diff --git a/Src/params.c b/Src/params.c
index d60f91990..a8b027988 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -270,6 +270,7 @@ newparamtable(int size, char const *name)
     ht->hash        = hasher;
     ht->emptytable  = emptyhashtable;
     ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
     ht->addnode     = addhashnode;
     ht->getnode     = getparamnode;
     ht->getnode2    = getparamnode;
@@ -282,6 +283,7 @@ newparamtable(int size, char const *name)
     return ht;
 }
 
+/**/
 #ifdef DYNAMIC
 /**/
 static HashNode
@@ -299,6 +301,7 @@ getparamnode(HashTable ht, char *nam)
     }
     return hn;
 }
+/**/
 #endif /* DYNAMIC */
 
 /* Copy a parameter hash table */
@@ -1753,7 +1756,7 @@ unsetparam_pm(Param pm, int altflag, int exp)
      * Some specials, such as those used in zle, still need removing
      * from the parameter table; they have the PM_REMOVABLE flag.
      */
-    if ((locallevel && locallevel >= pm->level) ||
+    if ((pm->level && locallevel >= pm->level) ||
 	(pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
 	return;
 
@@ -1963,23 +1966,11 @@ intvarsetfn(Param pm, long x)
 void
 zlevarsetfn(Param pm, long x)
 {
-    if ((long *)pm->u.data == & columns) {
-	if(x <= 0)
-	    x = tccolumns > 0 ? tccolumns : 80;
-	if (x > 2)
-	    termflags &= ~TERM_NARROW;
-	else
-	    termflags |= TERM_NARROW;
-    } else if ((long *)pm->u.data == & lines) {
-	if(x <= 0)
-	    x = tclines > 0 ? tclines : 24;
-	if (x > 2)
-	    termflags &= ~TERM_SHORT;
-	else
-	    termflags |= TERM_SHORT;
-    }
+    long *p = (long *)pm->u.data;
 
-    *((long *)pm->u.data) = x;
+    *p = x;
+    if (p == &lines || p == &columns)
+	adjustwinsize(2 + (p == &columns));
 }
 
 /* Function to set value of generic special scalar    *
diff --git a/Src/signals.c b/Src/signals.c
index 65bac0f52..3e655cbfd 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -129,10 +129,9 @@ intr(void)
         install_handler(SIGINT);
 }
 
-#if 0
 /* disable ^C interrupts */
  
-/**/
+#if 0 /**/
 void
 nointr(void)
 {
@@ -505,7 +504,7 @@ handler(int sig)
 
 #ifdef SIGWINCH
     case SIGWINCH:
-        adjustwinsize();  /* check window size and adjust */
+        adjustwinsize(1);  /* check window size and adjust */
 	if (sigtrapped[SIGWINCH])
 	    dotrap(SIGWINCH);
         break;
diff --git a/Src/subst.c b/Src/subst.c
index e8e22f943..4b60de120 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -245,35 +245,43 @@ singsub(char **s)
  * the result is stored in *a. If `a' is zero a multiple word result is *
  * joined using sep or the IFS parameter if sep is zero and the result  *
  * is returned in *s.  The return value is true iff the expansion       *
- * resulted in an empty list                                            */
+ * resulted in an empty list.                                           *
+ * The mult_isarr variable is used by paramsubst() to tell if it yields *
+ * an array.                                                            */
+
+static int mult_isarr;
 
 /**/
 static int
 multsub(char **s, char ***a, int *isarr, char *sep)
 {
     LinkList foo;
-    int l;
+    int l, omi = mult_isarr;
     char **r, **p;
 
+    mult_isarr = 0;
     foo = newlinklist();
     addlinknode(foo, *s);
     prefork(foo, 0);
     if (errflag) {
 	if (isarr)
 	    *isarr = 0;
+	mult_isarr = omi;
 	return 0;
     }
-    if ((l = countlinknodes(foo)) > 1 || a) {
+    if ((l = countlinknodes(foo))) {
 	p = r = ncalloc((l + 1) * sizeof(char*));
 	while (nonempty(foo))
 	    *p++ = (char *)ugetnode(foo);
 	*p = NULL;
-	if (a) {
+	if (a && mult_isarr) {
 	    *a = r;
 	    *isarr = 1;
+	    mult_isarr = omi;
 	    return 0;
 	}
 	*s = sepjoin(r, NULL);
+	mult_isarr = omi;
 	return 0;
     }
     if (l)
@@ -282,6 +290,7 @@ multsub(char **s, char ***a, int *isarr, char *sep)
 	*s = dupstring("");
     if (isarr)
 	*isarr = 0;
+    mult_isarr = omi;
     return !l;
 }
 
@@ -977,16 +986,12 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
 	sav = *s;
 	*s = 0;
-	if (multsub(&val, ((!aspar && (!quoted || nojoin)) ? &aval : NULL),
-		    &isarr, NULL) &&
-	    quoted) {
+	if (multsub(&val, (aspar ? NULL : &aval), &isarr, NULL) && quoted) {
 	    isarr = -1;
 	    aval = alloc(sizeof(char *));
 	    aspar = 0;
 	} else if (aspar)
 	    idbeg = val;
-	if (isarr)
-	    isarr = -1;
 	copied = 1;
 	*s = sav;
 	v = (Value) NULL;
@@ -1465,6 +1470,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	val = dupstring(buf);
 	isarr = 0;
     }
+    mult_isarr = isarr;
     if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
 	val = dupstring("");
 	isarr = 0;
@@ -1485,6 +1491,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 		val = aval[0];
 	    else
 		isarr = 2;
+	    mult_isarr = isarr;
 	}
     }
     if (casmod) {
diff --git a/Src/text.c b/Src/text.c
index 88a27495d..d3eafaf9f 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -72,10 +72,9 @@ taddstr(char *s)
     tptr += sl;
 }
 
-#if 0
 /* add an integer to the text buffer */
 
-/**/
+#if 0 /**/
 void
 taddint(int x)
 {
diff --git a/Src/utils.c b/Src/utils.c
index 86679e90f..faeeb0c58 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -209,10 +209,9 @@ nicechar(int c)
     return buf;
 }
 
-#if 0
 /* Output a string's visible representation. */
 
-/**/
+#if 0 /**/
 void
 nicefputs(char *s, FILE *f)
 {
@@ -853,27 +852,64 @@ int resetneeded;
 /**/
 int winchanged;
 #endif
- 
-/* check the size of the window and adjust if necessary */
+
+/* check the size of the window and adjust if necessary. *
+ * The value of from:					 *
+ *   0: called from update_job or setupvals		 *
+ *   1: called from the SIGWINCH handler		 *
+ *   2: the user have just changed LINES manually	 *
+ *   3: the user have just changed COLUMNS manually      */
 
 /**/
 void
-adjustwinsize(void)
+adjustwinsize(int from)
 {
-#ifdef TIOCGWINSZ
     int oldcols = columns, oldrows = lines;
 
+#ifdef TIOCGWINSZ
+    static int userlines, usercols;
+
     if (SHTTY == -1)
 	return;
 
-    ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize);
-    setiparam("COLUMNS", shttyinfo.winsize.ws_col);
-    setiparam("LINES", shttyinfo.winsize.ws_row);
-    if (zleactive && (oldcols != columns || oldrows != lines)) {
+    if (from == 2)
+	userlines = lines > 0;
+    if (from == 3)
+	usercols = columns > 0;
+
+    if (!ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize)) {
+	if (!userlines || from == 1)
+	    lines = shttyinfo.winsize.ws_row;
+	if (!usercols || from == 1)
+	    columns = shttyinfo.winsize.ws_col;
+    }
+#endif   /* TIOCGWINSZ */
+
+    if (lines <= 0)
+	lines = tclines > 0 ? tclines : 24;
+    if (columns <= 0)
+	columns = tccolumns > 0 ? tccolumns : 80;
+    if (lines > 2)
+	termflags &= ~TERM_SHORT;
+    else
+	termflags |= TERM_SHORT;
+    if (columns > 2)
+	termflags &= ~TERM_NARROW;
+    else
+	termflags |= TERM_NARROW;
+
+#ifdef TIOCGWINSZ
+    if (interact && from >= 2) {
+	shttyinfo.winsize.ws_row = lines;
+	shttyinfo.winsize.ws_col = columns;
+	ioctl(SHTTY, TIOCSWINSZ, (char *)&shttyinfo.winsize);
+    }
+#endif
+
+    if (zleactive && (from >= 2 || oldcols != columns || oldrows != lines)) {
 	resetneeded = winchanged = 1;
 	zrefresh();
     }
-#endif   /* TIOCGWINSZ */
 }
 
 /* Move a fd to a place >= 10 and mark the new fd in fdtable.  If the fd *
@@ -3343,10 +3379,9 @@ dquotedztrdup(char const *s)
     return ret;
 }
 
-#if 0
 /* Unmetafy and output a string, double quoting it in its entirety. */
 
-/**/
+#if 0 /**/
 int
 dquotedzputs(char const *s, FILE *stream)
 {
diff --git a/Src/zsh.export b/Src/zsh.export
index 7f994bf29..b62621e0f 100644
--- a/Src/zsh.export
+++ b/Src/zsh.export
@@ -4,6 +4,7 @@ addbuiltins
 addconddefs
 addedx
 addhashnode
+addhistnum
 addparamdefs
 addwrapper
 arrvargetfn
@@ -45,6 +46,7 @@ deleteparamdefs
 deletewrapper
 domatch
 doshfunc
+down_histent
 dputs
 dquotedztrdup
 dummy_list
@@ -97,6 +99,8 @@ hgetc
 hgetline
 histentarr
 histentct
+hist_ring
+hist_skip_flags
 holdintr
 hptr
 hrealloc
@@ -132,6 +136,7 @@ metadiffer
 metafy
 metalen
 mode_to_octal
+movehistent
 mypgrp
 mypid
 nameddirtab
@@ -240,6 +245,7 @@ unmetafy
 unsetparam
 unsetparam_pm
 untokenize
+up_histent
 uremnode
 useheap
 winchanged
diff --git a/Src/zsh.h b/Src/zsh.h
index 1eefc51c1..1d635afdc 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -294,7 +294,7 @@ struct linklist {
 #define peekfirst(X) ((X)->first->dat)
 #define pushnode(X,Y) insertlinknode(X,(LinkNode) X,Y)
 #define incnode(X) (X = nextnode(X))
-#define gethistent(X) (histentarr+((X)%histentct))
+#define firsthist() (hist_ring? hist_ring->down->histnum : curhist)
 
 
 /********************************/
@@ -611,6 +611,7 @@ struct job {
 #define STAT_CURSH	(1<<9)	/* last command is in current shell     */
 #define STAT_NOSTTY	(1<<10)	/* the tty settings are not inherited   */
 				/* from this job when it exits.         */
+#define STAT_ATTACH	(1<<11)	/* delay reattaching shell to tty       */
 
 #define SP_RUNNING -1		/* fake status for jobs currently running */
 
@@ -679,6 +680,7 @@ typedef void     (*AddNodeFunc)    _((HashTable, char *, void *));
 typedef HashNode (*GetNodeFunc)    _((HashTable, char *));
 typedef HashNode (*RemoveNodeFunc) _((HashTable, char *));
 typedef void     (*FreeNodeFunc)   _((HashNode));
+typedef int      (*CompareFunc)    _((const char *, const char *));
 
 /* type of function that is passed to *
  * scanhashtable or scanmatchtable    */
@@ -698,6 +700,7 @@ struct hashtable {
     HashFunc hash;		/* pointer to hash function for this table    */
     TableFunc emptytable;	/* pointer to function to empty table         */
     TableFunc filltable;	/* pointer to function to fill table          */
+    CompareFunc cmpnodes;	/* pointer to function to compare two nodes     */
     AddNodeFunc addnode;	/* pointer to function to add new node        */
     GetNodeFunc getnode;	/* pointer to function to get an enabled node */
     GetNodeFunc getnode2;	/* pointer to function to get node            */
@@ -1015,18 +1018,30 @@ struct nameddir {
 /* history entry */
 
 struct histent {
+    HashNode hash_next;		/* next in hash chain               */
     char *text;			/* the history line itself          */
+    int flags;			/* Misc flags                       */
+
+    Histent up;			/* previous line (moving upward)    */
+    Histent down;		/* next line (moving downward)      */
     char *zle_text;		/* the edited history line          */
     time_t stim;		/* command started time (datestamp) */
     time_t ftim;		/* command finished time            */
     short *words;		/* Position of words in history     */
 				/*   line:  as pairs of start, end  */
     int nwords;			/* Number of words in history line  */
-    int flags;			/* Misc flags                       */
+    int histnum;		/* A sequential history number      */
 };
 
-#define HIST_OLD	0x00000001	/* Command is already written to disk*/
-#define HIST_READ	0x00000002	/* Command was read back from disk*/
+#define HIST_MAKEUNIQUE	0x00000001	/* Kill this new entry if not unique */
+#define HIST_OLD	0x00000002	/* Command is already written to disk*/
+#define HIST_READ	0x00000004	/* Command was read back from disk*/
+#define HIST_DUP	0x00000008	/* Command duplicates a later line */
+#define HIST_FOREIGN	0x00000010	/* Command came from another shell */
+
+#define GETHIST_UPWARD  (-1)
+#define GETHIST_DOWNWARD  1
+#define GETHIST_EXACT     0
 
 /* Parts of the code where history expansion is disabled *
  * should be within a pair of STOPHIST ... ALLOWHIST     */
@@ -1039,6 +1054,13 @@ struct histent {
 #define HISTFLAG_RECALL 4
 #define HISTFLAG_SETTY  8
 
+#define HFILE_APPEND		0x0001
+#define HFILE_SKIPOLD		0x0002
+#define HFILE_SKIPDUPS		0x0004
+#define HFILE_SKIPFOREIGN	0x0008
+#define HFILE_FAST		0x0010
+#define HFILE_USE_OPTIONS	0x8000
+
 /******************************************/
 /* Definitions for programable completion */
 /******************************************/
@@ -1120,15 +1142,20 @@ enum {
     HASHLISTALL,
     HISTALLOWCLOBBER,
     HISTBEEP,
+    HISTEXPIREDUPSFIRST,
+    HISTFINDNODUPS,
+    HISTIGNOREALLDUPS,
     HISTIGNOREDUPS,
     HISTIGNORESPACE,
     HISTNOFUNCTIONS,
     HISTNOSTORE,
     HISTREDUCEBLANKS,
+    HISTSAVENODUPS,
     HISTVERIFY,
     HUP,
     IGNOREBRACES,
     IGNOREEOF,
+    INCREMENTALAPPENDHISTORY,
     INTERACTIVE,
     INTERACTIVECOMMENTS,
     KSHARRAYS,
@@ -1172,6 +1199,7 @@ enum {
     RESTRICTED,
     RMSTARSILENT,
     RMSTARWAIT,
+    SHAREHISTORY,
     SHFILEEXPANSION,
     SHGLOB,
     SHINSTDIN,