about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Zle/compctl.c4
-rw-r--r--Src/Zle/complist.c106
-rw-r--r--Src/Zle/zle.export10
-rw-r--r--Src/Zle/zle_bindings.c8
-rw-r--r--Src/Zle/zle_main.c8
-rw-r--r--Src/Zle/zle_misc.c2
-rw-r--r--Src/Zle/zle_params.c8
-rw-r--r--Src/Zle/zle_refresh.c4
-rw-r--r--Src/Zle/zle_thingy.c44
-rw-r--r--Src/Zle/zle_tricky.c57
-rw-r--r--Src/builtin.c170
-rw-r--r--Src/exec.c41
-rw-r--r--Src/hist.c2
-rw-r--r--Src/jobs.c160
-rw-r--r--Src/loop.c33
-rw-r--r--Src/math.c6
-rw-r--r--Src/params.c52
-rw-r--r--Src/prompt.c81
-rw-r--r--Src/utils.c26
-rw-r--r--Src/zsh.h49
20 files changed, 636 insertions, 235 deletions
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 2b426492e..694af8429 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -2096,9 +2096,7 @@ bin_compset(char *name, char **argv, char *ops, int func)
     case 'P': test = CVT_PREPAT; break;
     case 's': test = CVT_SUFNUM; break;
     case 'S': test = CVT_SUFPAT; break;
-    case 'q': return !(compquote && *compquote &&
-		       (*compquote == '\'' || *compquote == '"') &&
-		       !set_comp_sepptr());
+    case 'q': return set_comp_sepptr();
     default:
 	zerrnam(name, "bad option -%c", NULL, argv[0][1]);
 	return 1;
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index aac8b410d..0e7152866 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -309,7 +309,7 @@ complistmatches(Hookdef dummy, Chdata dat)
     Cexpl *e;
     int nlines = 0, ncols, nlist = 0, longest = 1, pnl = 0, opl = 0;
     int of = isset(LISTTYPES);
-    int mc, ml = 0, cc, hasm = 0;
+    int mc, ml = 0, cc, hasm = 0, cl;
     struct listcols col;
 
     if (minfo.asked == 2) {
@@ -448,7 +448,12 @@ complistmatches(Hookdef dummy, Chdata dat)
 	mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup));
 	memset(mgtab, 0, i * sizeof(Cmgroup));
 	mcols = ncols;
-	mlines = nlines;
+	mlines = cl = nlines;
+	if (cl < 2) {
+	    cl = -1;
+	    if (tccan(TCCLEAREOD))
+		tcout(TCCLEAREOD);
+	}
     }
     /* Now print the matches. */
     g = amatches;
@@ -456,14 +461,27 @@ complistmatches(Hookdef dummy, Chdata dat)
 	char **pp = g->ylist;
 
 	if ((e = g->expls)) {
+	    int l;
+
 	    while (*e) {
 		if ((*e)->count) {
 		    if (pnl) {
 			putc('\n', shout);
 			pnl = 0;
 			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
+		    }
+		    l = printfmt((*e)->str, (*e)->count, 1);
+		    ml += l;
+		    if (cl >= 0 && (cl -= l) <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
 		    }
-		    ml += printfmt((*e)->str, (*e)->count, 1);
 		    pnl = 1;
 		}
 		e++;
@@ -474,6 +492,11 @@ complistmatches(Hookdef dummy, Chdata dat)
 		putc('\n', shout);
 		pnl = 0;
 		ml++;
+		if (cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
 	    }
 	    if (g->flags & CGF_LINES) {
 		while (*pp) {
@@ -504,6 +527,11 @@ complistmatches(Hookdef dummy, Chdata dat)
 		    if (n) {
 			putc('\n', shout);
 			ml++;
+			if (cl >= 0 && --cl <= 1) {
+			    cl = -1;
+			    if (tccan(TCCLEAREOD))
+				tcout(TCCLEAREOD);
+			}
 		    }
 		    pp++;
 		}
@@ -517,6 +545,11 @@ complistmatches(Hookdef dummy, Chdata dat)
 		putc('\n', shout);
 		pnl = 0;
 		ml++;
+		if (cl >= 0 && --cl <= 1) {
+		    cl = -1;
+		    if (tccan(TCCLEAREOD))
+			tcout(TCCLEAREOD);
+		}
 	    }
 	    for (p = skipnolist(g->matches); n && nl--;) {
 		i = ncols;
@@ -607,6 +640,11 @@ complistmatches(Hookdef dummy, Chdata dat)
 		if (n) {
 		    putc('\n', shout);
 		    ml++;
+		    if (cl >= 0 && --cl <= 1) {
+			cl = -1;
+			if (tccan(TCCLEAREOD))
+			    tcout(TCCLEAREOD);
+		    }
 		    if (n && nl)
 			p = skipnolist(p + 1);
 		}
@@ -640,11 +678,13 @@ struct menustack {
     char *line;
     int cs;
     struct menuinfo info;
+    Cmgroup amatches, pmatches, lmatches;
 };
 
 static int
 domenuselect(Hookdef dummy, Chdata dat)
 {
+    static Chdata fdat = NULL;
     Cmatch **p;
     Cmgroup *pg;
     Thingy cmd;
@@ -652,10 +692,15 @@ domenuselect(Hookdef dummy, Chdata dat)
     int i = 0, acc = 0;
     char *s;
 
-    if (dummy && (!(s = getsparam("SELECTMIN")) ||
-		  (dat && dat->num < atoi(s))))
+    if (fdat || (dummy && (!(s = getsparam("SELECTMIN")) ||
+			   (dat && dat->num < atoi(s))))) {
+	if (fdat) {
+	    fdat->matches = dat->matches;
+	    fdat->num = dat->num;
+	}
 	return 0;
-
+    }
+    fdat = dat;
     selectlocalmap(mskeymap);
     noselect = 0;
     mselect = (*(minfo.cur))->gnum;
@@ -686,6 +731,32 @@ domenuselect(Hookdef dummy, Chdata dat)
 	else if (cmd == Th(z_acceptline)) {
 	    acc = 1;
 	    break;
+	} else if (cmd == Th(z_acceptandinfernexthistory)) {
+	    Menustack s = (Menustack) zhalloc(sizeof(*s));
+
+	    s->prev = u;
+	    u = s;
+	    s->line = dupstring((char *) line);
+	    s->cs = cs;
+	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
+	    s->amatches = amatches;
+	    s->pmatches = pmatches;
+	    s->lmatches = lmatches;
+	    menucmp = 0;
+	    fixsuffix();
+	    validlist = 0;
+	    pmatches = NULL;
+	    invalidatelist();
+	    menucomplete(zlenoargs);
+	    if (dat->num < 2 || !minfo.cur || !*(minfo.cur)) {
+		noselect = 1;
+		clearlist = 1;
+		zrefresh();
+		break;
+	    }
+	    clearlist = 1;
+	    mselect = (*(minfo.cur))->gnum;
+	    continue;
 	} else if (cmd == Th(z_acceptandhold) ||
 		 cmd == Th(z_acceptandmenucomplete)) {
 	    Menustack s = (Menustack) zhalloc(sizeof(*s));
@@ -695,6 +766,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->line = dupstring((char *) line);
 	    s->cs = cs;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
+	    s->amatches = s->pmatches = s->lmatches = NULL;
 	    acceptlast();
 	    do_menucmp(0);
 	    mselect = (*(minfo.cur))->gnum;
@@ -712,7 +784,15 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    cs = u->cs;
 	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
 	    p = &(minfo.cur);
+	    if (u->pmatches && pmatches != u->pmatches) {
+		freematches();
+		amatches = u->amatches;
+		pmatches = u->pmatches;
+		lmatches = u->lmatches;
+		hasperm = 1;
+	    }
 	    u = u->prev;
+	    clearlist = 1;
 	} else if (cmd == Th(z_redisplay)) {
 	    redisplay(zlenoargs);
 	    continue;
@@ -846,6 +926,19 @@ domenuselect(Hookdef dummy, Chdata dat)
 	do_single(**p);
 	mselect = (**p)->gnum;
     }
+    if (u) {
+	int hp = hasperm;
+	Cmgroup m = pmatches;
+
+	for (; u; u = u->prev) {
+	    if (u->pmatches != m) {
+		pmatches = u->pmatches;
+		freematches();
+	    }
+	}
+	pmatches = m;
+	hasperm = hp;
+    }
     selectlocalmap(NULL);
     mselect = -1;
     inselect = 0;
@@ -858,6 +951,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	showinglist = -2;
 	zrefresh();
     }
+    fdat = NULL;
     return (!noselect ^ acc);
 }
 
diff --git a/Src/Zle/zle.export b/Src/Zle/zle.export
index e6f469ad0..8bc049e16 100644
--- a/Src/Zle/zle.export
+++ b/Src/Zle/zle.export
@@ -1,23 +1,29 @@
 #!
 acceptlast
 addzlefunction
+amatches
 backdel
 backkill
 bindkey
 clearflag
+clearlist
 clearscreen
 deletezlefunction
 do_menucmp
 do_single
-feep
+fixsuffix
 foredel
 forekill
+freematches
 getkey
 getkeycmd
 getzlequery
+hasperm
+invalidatelist
 lastambig
 linkkeymap
 listshown
+lmatches
 menucmp
 menucomplete
 menucur
@@ -25,6 +31,7 @@ menugrp
 minfo
 newkeymap
 nlnct
+pmatches
 printfmt
 redisplay
 refthingy
@@ -39,6 +46,7 @@ thingies
 trashzle
 ungetkeycmd
 unlinkkeymap
+validlist
 zlenoargs
 zmod
 zrefresh
diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c
index 40e555ad1..940d578d9 100644
--- a/Src/Zle/zle_bindings.c
+++ b/Src/Zle/zle_bindings.c
@@ -200,9 +200,9 @@ int metabind[128] = {
     /* M-K */ z_undefinedkey,
     /* M-L */ z_downcaseword,
     /* M-M */ z_undefinedkey,
-    /* M-N */ z_historybeginningsearchforward,
+    /* M-N */ z_historysearchforward,
     /* M-O */ z_undefinedkey,
-    /* M-P */ z_historybeginningsearchbackward,
+    /* M-P */ z_historysearchbackward,
     /* M-Q */ z_pushline,
     /* M-R */ z_undefinedkey,
     /* M-S */ z_spellword,
@@ -232,9 +232,9 @@ int metabind[128] = {
     /* M-k */ z_undefinedkey,
     /* M-l */ z_downcaseword,
     /* M-m */ z_undefinedkey,
-    /* M-n */ z_historybeginningsearchforward,
+    /* M-n */ z_historysearchforward,
     /* M-o */ z_undefinedkey,
-    /* M-p */ z_historybeginningsearchbackward,
+    /* M-p */ z_historysearchbackward,
     /* M-q */ z_pushline,
     /* M-r */ z_undefinedkey,
     /* M-s */ z_spellword,
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 8f6dfdf75..beef708fb 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -307,7 +307,7 @@ getkey(int keytmout)
     unsigned int ret;
     long exp100ths;
     int die = 0, r, icnt = 0;
-    int old_errno = errno;
+    int old_errno = errno, obreaks = breaks;
 
 #ifdef HAVE_SELECT
     fd_set foofd;
@@ -397,6 +397,7 @@ getkey(int keytmout)
 		if (!errflag && !retflag && !breaks)
 		    continue;
 		errflag = 0;
+		breaks = obreaks;
 		errno = old_errno;
 		return EOF;
 	    } else if (errno == EWOULDBLOCK) {
@@ -717,7 +718,7 @@ bin_vared(char *name, char **args, char *ops, int func)
     Value v;
     Param pm = 0;
     int create = 0;
-    int type = PM_SCALAR;
+    int type = PM_SCALAR, obreaks = breaks;
     char *p1 = NULL, *p2 = NULL;
 
     if (zleactive) {
@@ -809,6 +810,7 @@ bin_vared(char *name, char **args, char *ops, int func)
     if (!t || errflag) {
 	/* error in editing */
 	errflag = 0;
+	breaks = obreaks;
 	return 1;
     }
     /* strip off trailing newline, if any */
@@ -948,7 +950,7 @@ trashzle(void)
 static struct builtin bintab[] = {
     BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL),
     BUILTIN("vared",   0, bin_vared,   1,  7, 0, NULL,             NULL),
-    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANCLmMgGcRa",  NULL),
+    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANCLmMgGcRaU", NULL),
 };
 
 /* The order of the entries in this table has to match the *HOOK
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
index a51cdf92e..fedb9d5cf 100644
--- a/Src/Zle/zle_misc.c
+++ b/Src/Zle/zle_misc.c
@@ -736,6 +736,7 @@ executenamedcommand(char *prmt)
 		    statusll = l + len + 1;
 		    zmult = 1;
 		    listlist(cmdll);
+		    showinglist = 0;
 		    zmult = zmultsav;
 		} else if (!nextnode(firstnode(cmdll))) {
 		    strcpy(ptr = cmdbuf, peekfirst(cmdll));
@@ -754,6 +755,7 @@ executenamedcommand(char *prmt)
 			statusll = l + cmdambig + 1;
 			zmult = 1;
 			listlist(cmdll);
+			showinglist = 0;
 			zmult = zmultsav;
 		    }
 		    len = cmdambig;
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
index bac399e7d..dc4e27685 100644
--- a/Src/Zle/zle_params.c
+++ b/Src/Zle/zle_params.c
@@ -137,6 +137,8 @@ set_buffer(Param pm, char *x)
 	    cs = ll;
     } else
 	cs = ll = 0;
+    fixsuffix();
+    menucmp = 0;
 }
 
 /**/
@@ -156,6 +158,8 @@ set_cursor(Param pm, zlong x)
 	cs = ll;
     else
 	cs = x;
+    fixsuffix();
+    menucmp = 0;
 }
 
 /**/
@@ -182,6 +186,8 @@ set_lbuffer(Param pm, char *x)
     ll = ll - cs + len;
     cs = len;
     zsfree(x);
+    fixsuffix();
+    menucmp = 0;
 }
 
 /**/
@@ -205,6 +211,8 @@ set_rbuffer(Param pm, char *x)
     sizeline(ll = cs + len);
     memcpy(line + cs, y, len);
     zsfree(x);
+    fixsuffix();
+    menucmp = 0;
 }
 
 /**/
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index dd4310078..48e1071b8 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -281,7 +281,9 @@ zrefresh(void)
 	    clearflag = 0;
 	    resetneeded = 1;
 	}
-	listshown = showinglist = 0;
+	listshown = 0;
+	if (showinglist != -2)
+	    showinglist = 0;
     }
     clearlist = 0;
 
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index cf01f2fc1..f753b4769 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -340,7 +340,8 @@ bin_zle(char *name, char **args, char *ops, int func)
 	{ 'A', bin_zle_link, 2,  2 },
 	{ 'N', bin_zle_new,  1,  2 },
 	{ 'C', bin_zle_complete, 3, 3 },
-	{ 'R', bin_zle_refresh, 0, 1 },
+	{ 'R', bin_zle_refresh, 0, -1 },
+	{ 'U', bin_zle_unget, 1, 1 },
 	{ 0,   bin_zle_call, 0, -1 },
     };
     struct opn const *op, *opp;
@@ -396,23 +397,50 @@ static int
 bin_zle_refresh(char *name, char **args, char *ops, char func)
 {
     char *s = statusline;
-    int sl = statusll;
+    int sl = statusll, ocl = clearlist;
 
+    statusline = NULL;
+    statusll = 0;
     if (*args) {
-	statusline = *args;
-	statusll = strlen(statusline);
-    } else {
-	statusline = NULL;
-	statusll = 0;
-    }
+	if (**args) {
+	    statusline = *args;
+	    statusll = strlen(statusline);
+	}
+	if (*++args) {
+	    LinkList l = newlinklist();
+	    int zmultsav = zmult;
+
+	    for (; *args; args++)
+		addlinknode(l, *args);
+
+	    zmult = 1;
+	    listlist(l);
+	    showinglist = clearlist = 0;
+	    zmult = zmultsav;
+	} else if (ops['c'])
+	    clearlist = 1;
+    } else if (ops['c'])
+	clearlist = 1;
     zrefresh();
 
+    clearlist = ocl;
     statusline = s;
     statusll = sl;
     return 0;
 }
 
 /**/
+static int
+bin_zle_unget(char *name, char **args, char *ops, char func)
+{
+    char *p = *args;
+
+    while (*p)
+	ungetkey((int) *p++);
+    return 0;
+}
+
+/**/
 static void
 scanlistwidgets(HashNode hn, int list)
 {
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index f7b243d31..5461079cb 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -133,7 +133,8 @@ static LinkList matches, fmatches;
 /* This holds the list of matches-groups. lmatches is a pointer to the  *
  * last element in this list. */
 
-static Cmgroup pmatches, amatches, lmatches;
+/**/
+Cmgroup pmatches, amatches, lmatches;
 
 /* Non-zero if we have permanently allocated matches. */
 
@@ -150,7 +151,8 @@ static int nmatches, smatches;
 
 /* !=0 if we have a valid completion list. */
 
-static int validlist;
+/**/
+int validlist;
 
 /* This flag is non-zero if we are completing a pattern (with globcomplete) */
 
@@ -818,7 +820,8 @@ docomplete(int lst)
      * string inserted by the last completion. */
 
     if (fromcomp & FC_INWORD)
-	cs = lastend;
+	if ((cs = lastend) > ll)
+	    cs = ll;
 
     /* Check if we have to start a menu-completion (via automenu). */
 
@@ -1773,6 +1776,7 @@ doexpansion(char *s, int lst, int olst, int explincmd)
 	if (lst == COMP_LIST_EXPAND) {
 	    /* Only the list of expansions was requested. */
 	    listlist(vl);
+	    showinglist = 0;
 	    goto end;
 	}
 	/* Remove the current word and put the expansions there. */
@@ -2577,7 +2581,8 @@ comp_match(char *pfx, char *sfx, char *w, Comp cp,
 	    return NULL;
     
 	r = (qu ? quotename(r, NULL) : dupstring(r));
-
+	if (qu == 2 && r[0] == '\\' && r[1] == '~')
+	    chuck(r);
 	/* We still break it into parts here, trying to build a sensible
 	 * cline list for these matches, too. */
 	wl = strlen(w);
@@ -2590,6 +2595,8 @@ comp_match(char *pfx, char *sfx, char *w, Comp cp,
 	int mpl, rpl, wl;
 
 	w = (qu ? quotename(w, NULL) : dupstring(w));
+	if (qu == 2 && w[0] == '\\' && w[1] == '~')
+	    chuck(w);
 
 	wl = strlen(w);
 
@@ -3814,15 +3821,20 @@ addmatches(Cadata dat, char **argv)
 		    dat->rems = dupstring(dat->rems);
 
 		/* Probably quote the prefix and suffix for testing. */
-		if (!cp && (dat->aflags & CAF_MATCH) &&
-		    !(dat->aflags & CAF_QUOTE)) {
-		    lpre = quotename(lpre, NULL);
-		    lsuf = quotename(lsuf, NULL);
+		if (!(dat->aflags & CAF_QUOTE)) {
+		    if (!cp && (dat->aflags & CAF_MATCH)) {
+			lpre = quotename(lpre, NULL);
+			lsuf = quotename(lsuf, NULL);
+		    }
+		    if (dat->ppre) {
+			dat->ppre = quotename(dat->ppre, NULL);
+			if ((dat->flags & CMF_FILE) &&
+			    dat->ppre[0] == '\\' && dat->ppre[1] == '~')
+			    chuck(dat->ppre);
+		    }
+		    if (dat->psuf)
+			dat->psuf = quotename(dat->psuf, NULL);
 		}
-		if (dat->ppre)
-		    dat->ppre = quotename(dat->ppre, NULL);
-		if (dat->psuf)
-		    dat->psuf = quotename(dat->psuf, NULL);
 	    }
 	    /* Walk through the matches given. */
 	    for (; (s = *argv); argv++) {
@@ -3855,7 +3867,9 @@ addmatches(Cadata dat, char **argv)
 		    lc = bld_parts(ms, sl, -1, NULL);
 		    isexact = 0;
 		} else if (!(ms = comp_match(lpre, lsuf, s, cp, &lc,
-					     !(dat->aflags & CAF_QUOTE),
+					     (!(dat->aflags & CAF_QUOTE) ?
+					      ((dat->ppre && dat->ppre) ||
+					       !(dat->flags & CMF_FILE) ? 1 : 2) : 0),
 					     &bpl, &bsl, &isexact))) {
 		    if (dparr && !*++dparr)
 			dparr = NULL;
@@ -3962,7 +3976,7 @@ addmatch(char *s, char *t)
 	}
 	ms = ((addwhat == CC_FILES || addwhat == -6 ||
 	       addwhat == -5 || addwhat == -8) ? 
-	      comp_match(qfpre, qfsuf, s, filecomp, &lc, 1,
+	      comp_match(qfpre, qfsuf, s, filecomp, &lc, (ppre && *ppre ? 1 : 2),
 			 &bpl, &bsl, &isexact) :
 	      comp_match(fpre, fsuf, s, filecomp, &lc, 0,
 			 &bpl, &bsl, &isexact));
@@ -4361,6 +4375,7 @@ docompletion(char *s, int lst, int incmd)
 	    cs = origcs;
 	    clearlist = 1;
 	    ret = 1;
+	    minfo.cur = NULL;
 	    goto compend;
 	}
 	if (comppatmatch && *comppatmatch && comppatmatch != opm)
@@ -5156,10 +5171,16 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
 	zsfree(compisuffix);
 	compisuffix = ztrdup("");
 	zsfree(compqiprefix);
-	compqiprefix = qp;
 	zsfree(compqisuffix);
-	compqisuffix = qs;
-
+	if (instring) {
+	    compqiprefix = qp;
+	    compqisuffix = qs;
+	} else {
+	    compqiprefix = ztrdup(quotename(qp, NULL));
+	    zsfree(qp);
+	    compqisuffix = ztrdup(quotename(qs, NULL));
+	    zsfree(qs);
+	}
 	freearray(compwords);
 	i = countlinknodes(foo);
 	compwords = (char **) zalloc((i + 1) * sizeof(char *));
@@ -7028,7 +7049,7 @@ freematch(Cmatch m)
 /* This frees the groups of matches. */
 
 /**/
-static void
+void
 freematches(void)
 {
     Cmgroup g = pmatches, n;
diff --git a/Src/builtin.c b/Src/builtin.c
index 8fe112cb5..0e9baf9be 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -42,7 +42,7 @@ static struct builtin builtins[] =
     BUILTIN("[", 0, bin_test, 0, -1, BIN_BRACKET, NULL, NULL),
     BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
     BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
-    BUILTIN("alias", BINF_MAGICEQUALS, bin_alias, 0, -1, 0, "Lgmr", NULL),
+    BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmr", NULL),
     BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "tU", "u"),
     BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
     BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
@@ -87,7 +87,7 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPnrslzNu0123456789pioOcm-", NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrslzNu0123456789pioOcm-", NULL),
     BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
     BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
@@ -645,12 +645,14 @@ set_pwd_env(void)
     setsparam("OLDPWD", ztrdup(oldpwd));
 
     pm = (Param) paramtab->getnode(paramtab, "PWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
 	pm->env = addenv("PWD", pwd);
     }
     pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
 	pm->env = addenv("OLDPWD", oldpwd);
     }
@@ -1492,7 +1494,7 @@ Param
 typeset_single(char *cname, char *pname, Param pm, int func,
 	       int on, int off, int roff, char *value, Param altpm)
 {
-    int usepm, tc, keeplocal = 0;
+    int usepm, tc, keeplocal = 0, newspecial = 0;
 
     /*
      * Do we use the existing pm?  Note that this isn't the end of the
@@ -1503,22 +1505,31 @@ typeset_single(char *cname, char *pname, Param pm, int func,
      */
     usepm = pm && !(pm->flags & PM_UNSET);
 
-    /* Always use an existing pm if special at current locallevel */
-    if (pm && (pm->flags & PM_SPECIAL) && pm->level == locallevel)
+    /*
+     * We need to compare types with an existing pm if special,
+     * even if that's unset
+     */
+    if (pm && (pm->flags & PM_SPECIAL))
 	usepm = 1;
 
     /*
-     * Don't use a non-special existing param if
+     * Don't use an existing param if
      *   - the local level has changed, and
      *   - we are really locallizing the parameter
      */
-    if (usepm && !(pm->flags & PM_SPECIAL) &&
-	locallevel != pm->level && (on & PM_LOCAL))
+    if (usepm && locallevel != pm->level && (on & PM_LOCAL)) {
+	/*
+	 * If the original parameter was special and we're creating
+	 * a new one, we need to keep it special.
+	 */
+	newspecial = (pm->flags & PM_SPECIAL);
 	usepm = 0;
+    }
 
     /* attempting a type conversion, or making a tied colonarray? */
-    if ((tc = usepm && (((off & pm->flags) | (on & ~pm->flags)) &
-			(PM_INTEGER|PM_HASHED|PM_ARRAY|PM_TIED|PM_AUTOLOAD))))
+    if ((tc = (usepm || newspecial)
+	 && (((off & pm->flags) | (on & ~pm->flags)) &
+	     (PM_INTEGER|PM_HASHED|PM_ARRAY|PM_TIED|PM_AUTOLOAD))))
 	usepm = 0;
     if (tc && (pm->flags & PM_SPECIAL)) {
 	zerrnam(cname, "%s: can't change type of a special parameter",
@@ -1526,13 +1537,27 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	return NULL;
     }
 
+    /*
+     * According to the manual, local parameters don't get exported.
+     * A parameter will be local if
+     * 1. we are re-using an existing local parameter
+     *    or
+     * 2. we are not using an existing parameter, but
+     *   i. there is already a parameter, which will be hidden
+     *     or
+     *   ii. we are creating a new local parameter
+     */
+    if ((usepm && pm->level) ||
+	(!usepm && (pm || (locallevel && (on & PM_LOCAL)))))
+	on &= ~PM_EXPORTED;
+
     if (usepm) {
 	on &= ~PM_LOCAL;
 	if (!on && !roff && !value) {
 	    paramtab->printnode((HashNode)pm, 0);
 	    return pm;
 	}
-	if ((pm->flags & PM_RESTRICTED && isset(RESTRICTED))) {
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
 	    zerrnam(cname, "%s: restricted", pname, 0);
 	    return pm;
 	}
@@ -1553,7 +1578,8 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	    if (pm->flags & PM_EXPORTED) {
 		if (!(pm->flags & PM_UNSET) && !pm->env && !value)
 		    pm->env = addenv(pname, getsparam(pname));
-	    } else if (pm->env) {
+	    } else if (pm->env &&
+		       (!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 		delenv(pm->env);
 		zsfree(pm->env);
 		pm->env = NULL;
@@ -1593,13 +1619,68 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	pname = dupstring(pname);
 	unsetparam_pm(pm, 0, 1);
     }
-    /*
-     * Create a new node for a parameter with the flags in `on' minus the
-     * readonly flag
-     */
-    pm = createparam(pname, on & ~PM_READONLY);
-    DPUTS(!pm, "BUG: parameter not created");
-    pm->ct = auxlen;
+
+    if (newspecial) {
+	Param tpm, pm2;
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	    zerrnam(cname, "%s: restricted", pname, 0);
+	    return pm;
+	}
+	/*
+	 * For specials, we keep the same struct but zero everything.
+	 * Maybe it would be easier to create a new struct but copy
+	 * the get/set methods.
+	 */
+	tpm = (Param) zalloc(sizeof *tpm);
+
+	tpm->nam = pm->nam;
+	if (pm->ename &&
+	    (pm2 = (Param) paramtab->getnode(paramtab, pm->ename)) &&
+	    pm2->level == locallevel) {
+	    /* This is getting silly, but anyway:  if one of a path/PATH
+	     * pair has already been made local at the current level, we
+	     * have to make sure that the other one does not have its value
+	     * saved:  since that comes from an internal variable it will
+	     * already reflect the local value, so restoring it on exit
+	     * would be wrong.
+	     *
+	     * This problem is also why we make sure we have a copy
+	     * of the environment entry in tpm->env, rather than relying
+	     * on the restored value to provide it.
+	     */
+	    tpm->flags = pm->flags | PM_NORESTORE;
+	} else {
+	    copyparam(tpm, pm, 1);
+	}
+	tpm->old = pm->old;
+	tpm->level = pm->level;
+	tpm->ct = pm->ct;
+	tpm->env = pm->env;
+
+	pm->old = tpm;
+	/*
+	 * The remaining on/off flags should be harmless to use,
+	 * because we've checked for unpleasant surprises above.
+	 */
+	pm->flags = (PM_TYPE(pm->flags) | on | PM_SPECIAL) & ~off;
+	/*
+	 * Final tweak: if we've turned on one of the flags with
+	 * numbers, we should use the appropriate integer.
+	 */
+	if (on & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z|PM_INTEGER))
+	    pm->ct = auxlen;
+	else
+	    pm->ct = 0;
+	pm->env = NULL;
+    } else {
+	/*
+	 * Create a new node for a parameter with the flags in `on' minus the
+	 * readonly flag
+	 */
+	pm = createparam(pname, on & ~PM_READONLY);
+	DPUTS(!pm, "BUG: parameter not created");
+	pm->ct = auxlen;
+    }
 
     if (altpm && PM_TYPE(pm->flags) == PM_SCALAR) {
 	/*
@@ -1618,6 +1699,28 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	pm->level = locallevel;
     if (value && !(pm->flags & (PM_ARRAY|PM_HASHED)))
 	setsparam(pname, ztrdup(value));
+    else if (newspecial && !(pm->old->flags & PM_NORESTORE)) {
+	/*
+	 * We need to use the special setting function to re-initialise
+	 * the special parameter to empty.
+	 */
+	HEAPALLOC {
+	    switch (PM_TYPE(pm->flags)) {
+	    case PM_SCALAR:
+		pm->sets.cfn(pm, ztrdup(""));
+		break;
+	    case PM_INTEGER:
+		pm->sets.ifn(pm, 0);
+		break;
+	    case PM_ARRAY:
+		pm->sets.afn(pm, mkarray(NULL));
+		break;
+	    case PM_HASHED:
+		pm->sets.hfn(pm, newparamtable(17, pm->nam));
+		break;
+	    }
+	} LASTALLOC;
+    }
     pm->flags |= (on & PM_READONLY);
     if (value && (pm->flags & (PM_ARRAY|PM_HASHED))) {
 	zerrnam(cname, "%s: can't assign initial value for array", pname, 0);
@@ -1839,7 +1942,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
     Comp com;
     Shfunc shf;
     int i, returnval = 0;
-    int on = 0, off = 0;
+    int on = 0, off = 0, pflags = 0;
 
     /* Do we have any flags defined? */
     if (ops['u'] == 1)
@@ -1860,13 +1963,17 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	return 1;
     }
 
+    if (ops['f'] == 2 || ops['+'])
+	pflags |= PRINT_NAMEONLY;
+
     /* If no arguments given, we will print functions.  If flags *
      * are given, we will print only functions containing these  *
      * flags, else we'll print them all.                         */
     if (!*argv) {
 	if (ops['U'] && !ops['u'])
 	    on &= ~PM_UNDEFINED;
-	scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode, 0);
+	scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode,
+		      pflags);
 	return 0;
     }
 
@@ -1879,7 +1986,8 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	    if ((com = parsereg(*argv))) {
 		/* with no options, just print all functions matching the glob pattern */
 		if (!(on|off)) {
-		    scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0);
+		    scanmatchtable(shfunctab, com, 0, DISABLED,
+				   shfunctab->printnode, pflags);
 		} else {
 		/* apply the options to all functions matching the glob pattern */
 		    for (i = 0; i < shfunctab->hsize; i++) {
@@ -1906,7 +2014,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
 		shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off;
 	    else
 		/* no flags, so just print */
-		shfunctab->printnode((HashNode) shf, 0);
+		shfunctab->printnode((HashNode) shf, pflags);
 	} else if (on & PM_UNDEFINED) {
 	    /* Add a new undefined (autoloaded) function to the *
 	     * hash table with the corresponding flags set.     */
@@ -2407,6 +2515,8 @@ bin_alias(char *name, char **argv, char *ops, int func)
 
     if (ops['L'])
 	printflags |= PRINT_LIST;
+    else if (ops['r'] == 2 || ops['g'] == 2 || ops['m'] == 2 || ops['+'])
+	printflags |= PRINT_NAMEONLY;
 
     /* In the absence of arguments, list all aliases.  If a command *
      * line flag is specified, list only those of that type.        */
@@ -2512,8 +2622,8 @@ bin_print(char *name, char **args, char *ops, int func)
 	if (!ops['e'] && (ops['R'] || ops['r'] || ops['E']))
 	    unmetafy(args[n], &len[n]);
 	else
-	    args[n] = getkeystring(args[n], &len[n],
-				    func != BIN_ECHO && !ops['e'], &nnl);
+	    args[n] = getkeystring(args[n], &len[n], ops['b'] ? 2 :
+				    (func != BIN_ECHO && !ops['e']), &nnl);
 	/* -P option -- interpret as a prompt sequence */
 	if(ops['P']) {
 	    /*
@@ -3098,11 +3208,7 @@ bin_eval(char *nam, char **argv, char *ops, int func)
 {
     List list;
 
-    inpush(zjoin(argv, ' '), 0, NULL);
-    strinbeg(0);
-    list = parse_list();
-    strinend();
-    inpop();
+    list = parse_string(zjoin(argv, ' '), 0);
     if (!list) {
 	errflag = 0;
 	return 1;
diff --git a/Src/exec.c b/Src/exec.c
index bc5548f1e..9c7a1ceb5 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -916,7 +916,7 @@ execpline(Sublist l, int how, int last1)
 		    }
 
 		    jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
-		    jn->stat |= STAT_STOPPED | STAT_CHANGED;
+		    jn->stat |= STAT_STOPPED | STAT_CHANGED | STAT_LOCKED;
 		    printjob(jn, !!isset(LONGLISTJOBS), 1);
 		}
 		else if (newjob != list_pipe_job)
@@ -1012,7 +1012,9 @@ execpline(Sublist l, int how, int last1)
 		jn = jobtab + pj;
 		killjb(jn, lastval & ~0200);
 	    }
-	    if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
+	    if (list_pipe_child ||
+		((jn->stat & STAT_DONE) &&
+		 (list_pipe || (pline_level && !(jn->stat & STAT_SUBJOB)))))
 		deletejob(jn);
 	    thisjob = pj;
 
@@ -1530,6 +1532,9 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 			/* Was this "exec < foobar"? */
 			nullexec = 1;
 			break;
+		    } else if (vars && nonempty(vars)) {
+			nullexec = 2;
+			break;
 		    } else if (!nullcmd || !*nullcmd ||
 			       (cflags & BINF_PREFIX)) {
 			zerr("redirection with no command", NULL, 0);
@@ -1869,7 +1874,8 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 		addfd(forked, save, mfds, fn->fd1, fil, 0);
 		/* If this is 'exec < file', read from stdin, *
 		 * not terminal, unless `file' is a terminal. */
-		if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact)
+		if (nullexec == 1 && fn->fd1 == 0 &&
+		    isset(SHINSTDIN) && interact)
 		    init_io();
 		break;
 	    case CLOSE:
@@ -1929,17 +1935,28 @@ execcmd(Cmd cmd, int input, int output, int how, int last1)
 	closemn(mfds, i);
 
     if (nullexec) {
-	for (i = 0; i < 10; i++)
-	    if (save[i] != -2)
-		zclose(save[i]);
+	if (nullexec == 1) {
+	    /*
+	     * If nullexec is 1 we specifically *don't* restore the original
+	     * fd's before returning.
+	     */
+	    for (i = 0; i < 10; i++)
+		if (save[i] != -2)
+		    zclose(save[i]);
+	    return;
+	}
 	/*
-	 * Here we specifically *don't* restore the original fd's
-	 * before returning.
+	 * If nullexec is 2, we have variables to add with the redirections
+	 * in place.
 	 */
-	return;
-    }
-
-    if (isset(EXECOPT) && !errflag) {
+	if (vars)
+	    addvars(vars, 0);
+	lastval = errflag ? errflag : cmdoutval;
+	if (isset(XTRACE)) {
+	    fputc('\n', stderr);
+	    fflush(stderr);
+	}
+    } else if (isset(EXECOPT) && !errflag) {
 	/*
 	 * We delay the entersubsh() to here when we are exec'ing
 	 * the current shell (including a fake exec to run a builtin then
diff --git a/Src/hist.c b/Src/hist.c
index edf74009e..167ffe171 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -291,7 +291,7 @@ safeinungetc(int c)
 void
 herrflush(void)
 {
-    while (!lexstop && inbufct)
+    while (!lexstop && inbufct && !strin)
 	hwaddc(ingetc());
 }
 
diff --git a/Src/jobs.c b/Src/jobs.c
index b2d7e9af1..fdf69a960 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -125,6 +125,86 @@ findproc(pid_t pid, Job *jptr, Process *pptr)
     return 0;
 }
 
+/* Find the super-job of a sub-job. */
+
+/**/
+static int
+super_job(int sub)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if ((jobtab[i].stat & STAT_SUPERJOB) &&
+	    jobtab[i].other == sub &&
+	    jobtab[i].gleader)
+	    return i;
+    return 0;
+}
+
+/**/
+static int
+handle_sub(int job, int fg)
+{
+    Job jn = jobtab + job, sj = jobtab + jn->other;
+
+    if ((sj->stat & STAT_DONE) || !sj->procs) {
+	struct process *p;
+		    
+	for (p = sj->procs; p; p = p->next)
+	    if (WIFSIGNALED(p->status)) {
+		if (jn->gleader != mypgrp && jn->procs->next)
+		    killpg(jn->gleader, WTERMSIG(p->status));
+		else
+		    kill(jn->procs->pid, WTERMSIG(p->status));
+		kill(sj->other, SIGCONT);
+		kill(sj->other, WTERMSIG(p->status));
+		break;
+	    }
+	if (!p) {
+	    int cp;
+
+	    jn->stat &= ~STAT_SUPERJOB;
+	    jn->stat |= STAT_WASSUPER;
+
+	    if ((cp = ((WIFEXITED(jn->procs->status) ||
+			WIFSIGNALED(jn->procs->status)) &&
+		       killpg(jn->gleader, 0) == -1))) {
+		Process p;
+		for (p = jn->procs; p->next; p = p->next);
+		jn->gleader = p->pid;
+	    }
+	    /* This deleted the job too early if the parent
+	       shell waited for a command in a list that will
+	       be executed by the sub-shell (e.g.: if we have
+	       `ls|if true;then sleep 20;cat;fi' and ^Z the
+	       sleep, the rest will be executed by a sub-shell,
+	       but the parent shell gets notified for the
+	       sleep.
+	       deletejob(sj); */
+	    /* If this super-job contains only the sub-shell,
+	       we have to attach the tty to its process group
+	       now. */
+	    if ((fg || thisjob == job) &&
+		(!jn->procs->next || cp || jn->procs->pid != jn->gleader))
+		attachtty(jn->gleader);
+	    kill(sj->other, SIGCONT);
+	}
+	curjob = jn - jobtab;
+    } else if (sj->stat & STAT_STOPPED) {
+	struct process *p;
+
+	jn->stat |= STAT_STOPPED;
+	for (p = jn->procs; p; p = p->next)
+	    if (p->status == SP_RUNNING ||
+		(!WIFEXITED(p->status) && !WIFSIGNALED(p->status)))
+		p->status = sj->procs->status;
+	curjob = jn - jobtab;
+	printjob(jn, !!isset(LONGLISTJOBS), 1);
+	return 1;
+    }
+    return 0;
+}
+
 /* Update status of process that we have just WAIT'ed for */
 
 /**/
@@ -183,13 +263,8 @@ update_job(Job jn)
 		 * or to exit. So we have to send it a SIGTSTP. */
 		int i;
 
-		for (i = 1; i < MAXJOB; i++)
-		    if ((jobtab[i].stat & STAT_SUPERJOB) &&
-			jobtab[i].other == job &&
-			jobtab[i].gleader) {
-			killpg(jobtab[i].gleader, SIGTSTP);
-			break;
-		    }
+		if ((i = super_job(job)))
+		    killpg(jobtab[i].gleader, SIGTSTP);
 	    }
 	    return;
 	}
@@ -254,6 +329,13 @@ update_job(Job jn)
 	return;
     jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
 	STAT_CHANGED | STAT_DONE;
+    if (!inforeground &&
+	(jn->stat & (STAT_SUBJOB | STAT_DONE)) == (STAT_SUBJOB | STAT_DONE)) {
+	int su;
+
+	if ((su = super_job(jn - jobtab)))
+	    handle_sub(su, 0);
+    }
     if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
 	prevjob = curjob;
 	curjob = job;
@@ -303,13 +385,14 @@ setprevjob(void)
 
     for (i = MAXJOB - 1; i; i--)
 	if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
-	    i != curjob && i != thisjob) {
+	    !(jobtab[i].stat & STAT_SUBJOB) && i != curjob && i != thisjob) {
 	    prevjob = i;
 	    return;
 	}
 
     for (i = MAXJOB - 1; i; i--)
-	if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
+	if ((jobtab[i].stat & STAT_INUSE) && !(jobtab[i].stat & STAT_SUBJOB) &&
+	    i != curjob && i != thisjob) {
 	    prevjob = i;
 	    return;
 	}
@@ -791,64 +874,9 @@ waitjob(int job, int sig)
 		killjb(jn, SIGCONT);
 		jn->stat &= ~STAT_STOPPED;
 	    }
-	    if (jn->stat & STAT_SUPERJOB) {
-		Job sj = jobtab + jn->other;
-		if ((sj->stat & STAT_DONE) || !sj->procs) {
-		    struct process *p;
-		    
-		    for (p = sj->procs; p; p = p->next)
-			if (WIFSIGNALED(p->status)) {
-			    if (jn->gleader != mypgrp && jn->procs->next)
-				killpg(jn->gleader, WTERMSIG(p->status));
-			    else
-				kill(jn->procs->pid, WTERMSIG(p->status));
-			    kill(sj->other, SIGCONT);
-			    kill(sj->other, WTERMSIG(p->status));
-			    break;
-			}
-		    if (!p) {
-			int cp;
-
-			jn->stat &= ~STAT_SUPERJOB;
-			jn->stat |= STAT_WASSUPER;
-
-			if ((cp = ((WIFEXITED(jn->procs->status) ||
-				    WIFSIGNALED(jn->procs->status)) &&
-				   killpg(jn->gleader, 0) == -1))) {
-			    Process p;
-			    for (p = jn->procs; p->next; p = p->next);
-			    jn->gleader = p->pid;
-			}
-			/* This deleted the job too early if the parent
-			   shell waited for a command in a list that will
-			   be executed by the sub-shell (e.g.: if we have
-			   `ls|if true;then sleep 20;cat;fi' and ^Z the
-			   sleep, the rest will be executed by a sub-shell,
-			   but the parent shell gets notified for the
-			   sleep.
-			   deletejob(sj); */
-			/* If this super-job contains only the sub-shell,
-			   we have to attach the tty to our process group
-			   (which is shared by the sub-shell) now. */
-			if (!jn->procs->next || cp || jn->procs->pid != jn->gleader)
-			    attachtty(jn->gleader);
-			kill(sj->other, SIGCONT);
-		    }
-		    curjob = jn - jobtab;
-		}
-		else if (sj->stat & STAT_STOPPED) {
-		    struct process *p;
-
-		    jn->stat |= STAT_STOPPED;
-		    for (p = jn->procs; p; p = p->next)
-			if (p->status == SP_RUNNING ||
-			    (!WIFEXITED(p->status) && !WIFSIGNALED(p->status)))
-			    p->status = sj->procs->status;
-		    curjob = jn - jobtab;
-		    printjob(jn, !!isset(LONGLISTJOBS), 1);
+	    if (jn->stat & STAT_SUPERJOB)
+		if (handle_sub(jn - jobtab, 1))
 		    break;
-		}
-	    }
 	    child_block();
 	}
     } else
diff --git a/Src/loop.c b/Src/loop.c
index 16e4ff314..fd63edb5a 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -57,6 +57,13 @@ execfor(Cmd cmd, LinkList args, int flags)
     if (node->condition) {
 	str = dupstring(node->name);
 	singsub(&str);
+	if (isset(XTRACE)) {
+	    char *str2 = dupstring(str);
+	    untokenize(str2);
+	    printprompt4();
+	    fprintf(stderr, "%s\n", str2);
+	    fflush(stderr);
+	}
 	if (!errflag)
 	    matheval(str);
 	if (errflag)
@@ -79,9 +86,14 @@ execfor(Cmd cmd, LinkList args, int flags)
 	    if (!errflag) {
 		while (iblank(*str))
 		    str++;
-		if (*str)
+		if (*str) {
+		    if (isset(XTRACE)) {
+			printprompt4();
+			fprintf(stderr, "%s\n", str);
+			fflush(stderr);
+		    }
 		    val = matheval(str);
-		else
+		} else
 		    val = 1;
 	    }
 	    if (errflag) {
@@ -95,6 +107,11 @@ execfor(Cmd cmd, LinkList args, int flags)
 	} else {
 	    if (!args || !(str = (char *) ugetnode(args)))
 		break;
+	    if (isset(XTRACE)) {
+		printprompt4();
+		fprintf(stderr, "%s=%s\n", node->name, str);
+		fflush(stderr);
+	    }
 	    setsparam(node->name, ztrdup(str));
 	}
 	execlist(node->list, 1,
@@ -107,6 +124,11 @@ execfor(Cmd cmd, LinkList args, int flags)
 	}
 	if (node->condition && !errflag) {
 	    str = dupstring(node->advance);
+	    if (isset(XTRACE)) {
+		printprompt4();
+		fprintf(stderr, "%s\n", str);
+		fflush(stderr);
+	    }
 	    singsub(&str);
 	    if (!errflag)
 		matheval(str);
@@ -410,6 +432,13 @@ execcase(Cmd cmd, LinkList args, int flags)
 	while (*p) {
 	    char *pat = dupstring(*p + 1);
 	    singsub(&pat);
+	    if (isset(XTRACE)) {
+		char *pat2 = dupstring(pat);
+		untokenize(pat2);
+		printprompt4();
+		fprintf(stderr, "case %s (%s)\n", word, pat2);
+		fflush(stderr);
+	    }
 	    if (matchpat(word, pat)) {
 		do {
 		    execlist(*l++, 1, **p == ';' && (flags & CFLAG_EXEC));
diff --git a/Src/math.c b/Src/math.c
index 7e7e557ed..10821b284 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -357,9 +357,11 @@ zzlex(void)
 	    }
 	    if (*ptr == '#') {
 		if (*++ptr == '\\') {
+		    int v;
+
 		    ptr++;
-		    yyval = *ptr == Meta ? *++ptr ^ 32 : *ptr;
-		    ptr++;
+		    ptr = getkeystring(ptr, NULL, 6, &v);
+		    yyval = v;
 		    unary = 0;
 		    return NUM;
 		}
diff --git a/Src/params.c b/Src/params.c
index d71cfb46e..094d7a166 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -1905,7 +1905,7 @@ arrhashsetfn(Param pm, char **val)
 
     if (alen % 2) {
 	freearray(val);
-	zerr("bad set of key/value pairs for associative array\n",
+	zerr("bad set of key/value pairs for associative array",
 	     NULL, 0);
 	return;
     }
@@ -2500,11 +2500,17 @@ arrfixenv(char *s, char **t)
     Param pm;
 
     MUSTUSEHEAP("arrfixenv");
+    pm = (Param) paramtab->getnode(paramtab, s);
+    /*
+     * Only one level of a parameter can be exported.  Unless
+     * ALLEXPORT is set, this must be global.
+     */
     if (t == path)
 	cmdnamtab->emptytable(cmdnamtab);
+    if (isset(ALLEXPORT) ? !!pm->old : pm->level)
+	return;
     u = t ? zjoin(t, ':') : "";
     len_s = strlen(s);
-    pm = (Param) paramtab->getnode(paramtab, s);
     for (ep = environ; *ep; ep++)
 	if (!strncmp(*ep, s, len_s) && (*ep)[len_s] == '=') {
 	    pm->env = replenv(*ep, u);
@@ -2685,8 +2691,46 @@ static void
 scanendscope(HashNode hn, int flags)
 {
     Param pm = (Param)hn;
-    if(pm->level > locallevel)
-	unsetparam_pm(pm, 0, 0);
+    if (pm->level > locallevel) {
+	if ((pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+	    /*
+	     * Removable specials are normal in that they can be removed
+	     * to reveal an ordinary parameter beneath.  Here we handle
+	     * non-removable specials, which were made local by stealth
+	     * (see newspecial code in typeset_single()).  In fact the
+	     * visible pm is always the same struct; the pm->old is
+	     * just a place holder for old data and flags.
+	     */
+	    Param tpm = pm->old;
+
+	    DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+		  !(tpm->flags & PM_SPECIAL),
+		  "BUG: in restoring scope of special parameter");
+	    pm->old = tpm->old;
+	    pm->flags = (tpm->flags & ~PM_NORESTORE);
+	    pm->level = tpm->level;
+	    pm->ct = tpm->ct;
+	    pm->env = tpm->env;
+
+	    if (!(tpm->flags & PM_NORESTORE))
+		switch (PM_TYPE(pm->flags)) {
+		case PM_SCALAR:
+		    pm->sets.cfn(pm, tpm->u.str);
+		    break;
+		case PM_INTEGER:
+		    pm->sets.ifn(pm, tpm->u.val);
+		    break;
+		case PM_ARRAY:
+		    pm->sets.afn(pm, tpm->u.arr);
+		    break;
+		case PM_HASHED:
+		    pm->sets.hfn(pm, tpm->u.hash);
+		    break;
+		}
+	    zfree(tpm, sizeof(*tpm));
+	} else
+	    unsetparam_pm(pm, 0, 0);
+    }
 }
 
 
diff --git a/Src/prompt.c b/Src/prompt.c
index 89407694e..dcba16d56 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -97,6 +97,38 @@ static int dontcount;
 
 static char *rstring, *Rstring;
 
+/*
+ * Expand path p; maximum is npath segments where 0 means the whole path.
+ * If tilde is 1, try and find a named directory to use.
+ */
+
+static void
+promptpath(char *p, int npath, int tilde)
+{
+    char *modp = p;
+    Nameddir nd;
+
+    if (tilde && ((nd = finddir(p))))
+	modp = tricat("~", nd->nam, p + strlen(nd->dir));
+
+    if (npath) {
+	char *sptr;
+	for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
+	    if (*sptr == '/' && !--npath) {
+		sptr++;
+		break;
+	    }
+	}
+	if (*sptr == '/' && sptr[1] && sptr != modp)
+	    sptr++;
+	stradd(sptr);
+    } else
+	stradd(modp);
+
+    if (p != modp)
+	zsfree(modp);
+}
+
 /* Perform prompt expansion on a string, putting the result in a *
  * permanently-allocated string.  If ns is non-zero, this string *
  * may have embedded Inpar and Outpar, which indicate a toggling *
@@ -293,49 +325,21 @@ putpromptchar(int doprint, int endchar)
 		}
 	    switch (*fm) {
 	    case '~':
-		if ((nd = finddir(pwd))) {
-		    char *t = tricat("~", nd->nam, pwd + strlen(nd->dir));
-		    stradd(t);
-		    zsfree(t);
-		    break;
-		}
+		promptpath(pwd, arg, 1);
+		break;
 	    case 'd':
 	    case '/':
-		stradd(pwd);
+		promptpath(pwd, arg, 0);
 		break;
 	    case 'c':
 	    case '.':
-	        {
-		    char *t;
-
-		    if ((nd = finddir(pwd)))
-			t = tricat("~", nd->nam, pwd + strlen(nd->dir));
-		    else
-			t = ztrdup(pwd);
-		    if (!arg)
-			arg++;
-		    for (ss = t + strlen(t); ss > t; ss--)
-			if (*ss == '/' && !--arg) {
-			    ss++;
-			    break;
-			}
-		    if(*ss == '/' && ss[1] && ss != t)
-			ss++;
-		    stradd(ss);
-		    zsfree(t);
-		    break;
-		}
+		promptpath(pwd, arg ? arg : 1, 1);
+		break;
 	    case 'C':
-		if (!arg)
-		    arg++;
-		for (ss = pwd + strlen(pwd); ss > pwd; ss--)
-		    if (*ss == '/' && !--arg) {
-			ss++;
-			break;
-		    }
-		if (*ss == '/' && ss[1] && (ss != pwd))
-		    ss++;
-		stradd(ss);
+		promptpath(pwd, arg ? arg : 1, 0);
+		break;
+	    case 'N':
+		promptpath(scriptname ? scriptname : argzero, arg, 0);
 		break;
 	    case 'h':
 	    case '!':
@@ -536,9 +540,6 @@ putpromptchar(int doprint, int endchar)
 		sprintf(bp, "%ld", (long)lineno);
 		bp += strlen(bp);
 		break;
-	    case 'N':
-		stradd(scriptname ? scriptname : argzero);
-		break;
 	    case '\0':
 		return 0;
 	    case Meta:
diff --git a/Src/utils.c b/Src/utils.c
index a409ab03c..d82f62694 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1735,10 +1735,11 @@ findsep(char **s, char *sep)
 	if (!*t)
 	    return i;
 	if (*(*s)++ == Meta) {
-	    (*s)++;
 #ifdef DEBUG
-	    if (! **s)
+	    if (! *(*s)++)
 		fprintf(stderr, "BUG: unexpected end of string in findsep()\n");
+#else
+	    (*s)++;
 #endif
 	}
     }
@@ -3187,24 +3188,28 @@ dquotedzputs(char const *s, FILE *stream)
  *   4:  Do $'...' quoting.  Overwrites the existing string instead of
  *       zhalloc'ing 
  *   5:  As 2, but \- is special.  Expects misc to be defined.
+ *   6:  As 2, but parses only one character and returns end-pointer
+ *       and parsed character in *misc
  */
 
 /**/
 char *
 getkeystring(char *s, int *len, int fromwhere, int *misc)
 {
-    char *buf;
+    char *buf, tmp[1];
     char *t, *u = NULL;
     char svchar = '\0';
     int meta = 0, control = 0;
 
-    if (fromwhere != 4)
-	buf = zhalloc(strlen(s) + 1);
+    if (fromwhere == 6)
+	t = tmp;
+    else if (fromwhere != 4)
+	t = buf = zhalloc(strlen(s) + 1);
     else {
-	buf = s;
+	t = buf = s;
 	s += 2;
     }
-    for (t = buf; *s; s++) {
+    for (; *s; s++) {
 	if (*s == '\\' && s[1]) {
 	    switch (*++s) {
 	    case 'a':
@@ -3303,7 +3308,8 @@ getkeystring(char *s, int *len, int fromwhere, int *misc)
 	} else if (fromwhere == 4 && *s == Snull) {
 	    for (u = t; (*u++ = *s++););
 	    return t + 1;
-	} else if (*s == '^' && (fromwhere == 2 || fromwhere == 5)) {
+	} else if (*s == '^' &&
+		   (fromwhere == 2 || fromwhere == 5 || fromwhere == 6)) {
 	    control = 1;
 	    continue;
 	} else if (*s == Meta)
@@ -3330,6 +3336,10 @@ getkeystring(char *s, int *len, int fromwhere, int *misc)
 	    t[-1] = Meta;
 	    t++;
 	}
+	if (fromwhere == 6 && t != tmp) {
+	    *misc = (int) tmp[0];
+	    return s + 1;
+	}
     }
     DPUTS(fromwhere == 4, "BUG: unterminated $' substitution");
     *t = '\0';
diff --git a/Src/zsh.h b/Src/zsh.h
index 88175eea6..2070e9b3f 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -951,40 +951,41 @@ struct param {
 /* flags for parameters */
 
 /* parameter types */
-#define PM_SCALAR	0	/* scalar                                     */
-#define PM_ARRAY	(1<<0)	/* array                                      */
-#define PM_INTEGER	(1<<1)	/* integer                                    */
-#define PM_HASHED	(1<<2)	/* association                                */
+#define PM_SCALAR	0	/* scalar                                   */
+#define PM_ARRAY	(1<<0)	/* array                                    */
+#define PM_INTEGER	(1<<1)	/* integer                                  */
+#define PM_HASHED	(1<<2)	/* association                              */
 
 #define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY|PM_HASHED))
 
-#define PM_LEFT		(1<<3)	/* left justify and remove leading blanks     */
-#define PM_RIGHT_B	(1<<4)	/* right justify and fill with leading blanks */
-#define PM_RIGHT_Z	(1<<5)	/* right justify and fill with leading zeros  */
-#define PM_LOWER	(1<<6)	/* all lower case                             */
+#define PM_LEFT		(1<<3)	/* left justify, remove leading blanks      */
+#define PM_RIGHT_B	(1<<4)	/* right justify, fill with leading blanks  */
+#define PM_RIGHT_Z	(1<<5)	/* right justify, fill with leading zeros   */
+#define PM_LOWER	(1<<6)	/* all lower case                           */
 
 /* The following are the same since they *
  * both represent -u option to typeset   */
-#define PM_UPPER	(1<<7)	/* all upper case                             */
-#define PM_UNDEFINED	(1<<7)	/* undefined (autoloaded) shell function      */
+#define PM_UPPER	(1<<7)	/* all upper case                           */
+#define PM_UNDEFINED	(1<<7)	/* undefined (autoloaded) shell function    */
 
-#define PM_READONLY	(1<<8)	/* readonly                                   */
-#define PM_TAGGED	(1<<9)	/* tagged                                     */
-#define PM_EXPORTED	(1<<10)	/* exported                                   */
+#define PM_READONLY	(1<<8)	/* readonly                                 */
+#define PM_TAGGED	(1<<9)	/* tagged                                   */
+#define PM_EXPORTED	(1<<10)	/* exported                                 */
 
 /* The following are the same since they *
  * both represent -U option to typeset   */
-#define PM_UNIQUE	(1<<11)	/* remove duplicates                          */
-#define PM_UNALIASED	(1<<11)	/* do not expand aliases when autoloading     */
-
-#define PM_TIED 	(1<<12)	/* array tied to colon-path or v.v. */
-#define PM_LOCAL	(1<<13) /* this parameter will be made local */
-#define PM_SPECIAL	(1<<14) /* special builtin parameter                  */
-#define PM_DONTIMPORT	(1<<15)	/* do not import this variable                */
-#define PM_RESTRICTED	(1<<16) /* cannot be changed in restricted mode       */
-#define PM_UNSET	(1<<17)	/* has null value                             */
-#define PM_REMOVABLE	(1<<18)	/* special can be removed from paramtab */
-#define PM_AUTOLOAD     (1<<19) /* autoloaded from module */
+#define PM_UNIQUE	(1<<11)	/* remove duplicates                        */
+#define PM_UNALIASED	(1<<11)	/* do not expand aliases when autoloading   */
+
+#define PM_TIED 	(1<<12)	/* array tied to colon-path or v.v.         */
+#define PM_LOCAL	(1<<13) /* this parameter will be made local        */
+#define PM_SPECIAL	(1<<14) /* special builtin parameter                */
+#define PM_DONTIMPORT	(1<<15)	/* do not import this variable              */
+#define PM_RESTRICTED	(1<<16) /* cannot be changed in restricted mode     */
+#define PM_UNSET	(1<<17)	/* has null value                           */
+#define PM_REMOVABLE	(1<<18)	/* special can be removed from paramtab     */
+#define PM_AUTOLOAD	(1<<19) /* autoloaded from module                   */
+#define PM_NORESTORE	(1<<20)	/* do not restore value of local special    */
 
 /* Flags for extracting elements of arrays and associative arrays */
 #define SCANPM_WANTVALS   (1<<0)