about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Modules/zftp.c6
-rw-r--r--Src/Zle/comp.h26
-rw-r--r--Src/Zle/comp1.c1
-rw-r--r--Src/Zle/comp1.export1
-rw-r--r--Src/Zle/compctl.c1
-rw-r--r--Src/Zle/complist.c42
-rw-r--r--Src/Zle/zle.export1
-rw-r--r--Src/Zle/zle_params.c25
-rw-r--r--Src/Zle/zle_refresh.c8
-rw-r--r--Src/Zle/zle_tricky.c162
-rw-r--r--Src/builtin.c114
-rw-r--r--Src/exec.c17
-rw-r--r--Src/glob.c10
-rw-r--r--Src/lex.c1
-rw-r--r--Src/options.c1
-rw-r--r--Src/parse.c26
-rw-r--r--Src/utils.c99
-rw-r--r--Src/zsh.h1
18 files changed, 342 insertions, 200 deletions
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index 63bca854c..738d596fa 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -2261,7 +2261,7 @@ zftp_type(char *name, char **args, int flags)
 	fflush(stdout);
 	return 0;
     } else {
-	nt = toupper(*str);
+	nt = toupper(STOUC(*str));
 	/*
 	 * RFC959 specifies other types, but these are the only
 	 * ones we know what to do with.
@@ -2294,7 +2294,7 @@ zftp_mode(char *name, char **args, int flags)
 	fflush(stdout);
 	return 0;
     }
-    nt = str[0] = toupper(*str);
+    nt = str[0] = toupper(STOUC(*str));
     if (str[1] || (nt != 'S' && nt != 'B')) {
 	zwarnnam(name, "transfer mode %s not recognised", str, 0);
 	return 1;
@@ -2651,7 +2651,7 @@ bin_zftp(char *name, char **args, char *ops, int func)
     if ((prefs = getsparam("ZFTP_PREFS"))) {
 	zfprefs = 0;
 	for (ptr = prefs; *ptr; ptr++) {
-	    switch (toupper(*ptr)) {
+	    switch (toupper(STOUC(*ptr))) {
 	    case 'S':
 		/* sendport */
 		zfprefs |= ZFPF_SNDP;
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 8264890df..b0fbf3ac6 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -218,6 +218,8 @@ struct cmatch {
     int brsl;			/* ...and the suffix */
     char *rems;			/* when to remove the suffix */
     char *remf;			/* shell function to call for suffix-removal */
+    int qipl;			/* length of quote-prefix */
+    int qisl;			/* length of quote-suffix */
     int rnum;			/* group relative number */
     int gnum;			/* global number */
 };
@@ -349,25 +351,25 @@ struct chdata {
 
 
 #define CPN_NMATCHES   0
-#define CP_NMATCHES    (1 <<  CPN_NMATCHES)
+#define CP_NMATCHES    (1 << CPN_NMATCHES)
 #define CPN_MATCHER    1
-#define CP_MATCHER     (1 <<  CPN_MATCHER)
+#define CP_MATCHER     (1 << CPN_MATCHER)
 #define CPN_MATCHERSTR 2
-#define CP_MATCHERSTR  (1 <<  CPN_MATCHERSTR)
+#define CP_MATCHERSTR  (1 << CPN_MATCHERSTR)
 #define CPN_MATCHERTOT 3
-#define CP_MATCHERTOT  (1 <<  CPN_MATCHERTOT)
+#define CP_MATCHERTOT  (1 << CPN_MATCHERTOT)
 #define CPN_CONTEXT    4
-#define CP_CONTEXT     (1 <<  CPN_CONTEXT)
+#define CP_CONTEXT     (1 << CPN_CONTEXT)
 #define CPN_PARAMETER  5
-#define CP_PARAMETER   (1 <<  CPN_PARAMETER)
+#define CP_PARAMETER   (1 << CPN_PARAMETER)
 #define CPN_REDIRECT   6
-#define CP_REDIRECT    (1 <<  CPN_REDIRECT)
+#define CP_REDIRECT    (1 << CPN_REDIRECT)
 #define CPN_QUOTE      7
-#define CP_QUOTE       (1 <<  CPN_QUOTE)
+#define CP_QUOTE       (1 << CPN_QUOTE)
 #define CPN_QUOTING    8
-#define CP_QUOTING     (1 <<  CPN_QUOTING)
+#define CP_QUOTING     (1 << CPN_QUOTING)
 #define CPN_RESTORE    9
-#define CP_RESTORE     (1 <<  CPN_RESTORE)
+#define CP_RESTORE     (1 << CPN_RESTORE)
 #define CPN_LIST       10
 #define CP_LIST        (1 << CPN_LIST)
 #define CPN_FORCELIST  11
@@ -398,6 +400,8 @@ struct chdata {
 #define CP_OLDINS      (1 << CPN_OLDINS)
 #define CPN_VARED      24
 #define CP_VARED       (1 << CPN_VARED)
+#define CPN_NNMATCHES  25
+#define CP_NNMATCHES   (1 << CPN_NNMATCHES)
 
-#define CP_KEYPARAMS   25
+#define CP_KEYPARAMS   26
 #define CP_ALLKEYS     ((unsigned int) 0xffffff)
diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c
index ba8bcc868..c1e2bfb57 100644
--- a/Src/Zle/comp1.c
+++ b/Src/Zle/comp1.c
@@ -105,6 +105,7 @@ int incompfunc;
 /**/
 zlong compcurrent,
       compnmatches,
+      compnnmatches,
       compmatcher,
       compmatchertot,
       complistmax;
diff --git a/Src/Zle/comp1.export b/Src/Zle/comp1.export
index 9b738cc78..4b6dd92fd 100644
--- a/Src/Zle/comp1.export
+++ b/Src/Zle/comp1.export
@@ -27,6 +27,7 @@ compmatcher
 compmatcherstr
 compmatchertot
 compnmatches
+compnnmatches
 compoldlist
 compoldins
 compparameter
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 694af8429..e9ff83387 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -2210,6 +2210,7 @@ static struct compparam compkparams[] = {
     { "old_list", PM_SCALAR, VAL(compoldlist), NULL, NULL },
     { "old_insert", PM_SCALAR, VAL(compoldins), NULL, NULL },
     { "vared", PM_SCALAR, VAL(compvared), NULL, NULL },
+    { "normal_nmatches", PM_INTEGER, VAL(compnnmatches), NULL, NULL },
     { NULL, 0, NULL, NULL, NULL }
 };
 
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 0e7152866..4bf3fec0e 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -308,8 +308,8 @@ complistmatches(Hookdef dummy, Chdata dat)
     Cmatch *p, m;
     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, cl;
+    int of = isset(LISTTYPES), cf;
+    int mc, ml = 0, cc, hasm = 0, cl = -1;
     struct listcols col;
 
     if (minfo.asked == 2) {
@@ -318,15 +318,6 @@ complistmatches(Hookdef dummy, Chdata dat)
     }
     getcols(&col);
 
-    /* Set the cursor below the prompt. */
-    if (inselect)
-	clearflag = 0;
-    trashzle();
-    showinglist = listshown = 0;
-
-    clearflag = (isset(USEZLE) && !termflags &&
-		 complastprompt && *complastprompt);
-
     for (g = amatches; g; g = g->next) {
 	char **pp = g->ylist;
 	int nl = 0, l;
@@ -403,6 +394,19 @@ complistmatches(Hookdef dummy, Chdata dat)
 			nlines += 1 + ((1 + niceztrlen(m->str)) / columns);
 	}
     }
+    cf = (isset(USEZLE) && !termflags && complastprompt && *complastprompt);
+    if (!nlines || (mselect >= 0 && (!cf || (nlines + nlnct - 1) >= lines))) {
+	showinglist = listshown = 0;
+	noselect = 1;
+	return 1;
+    }
+    /* Set the cursor below the prompt. */
+    if (inselect)
+	clearflag = 0;
+    trashzle();
+    showinglist = listshown = 0;
+
+    clearflag = cf;
 
     /* Maybe we have to ask if the user wants to see the list. */
     if ((!minfo.cur || !minfo.asked) &&
@@ -654,20 +658,20 @@ complistmatches(Hookdef dummy, Chdata dat)
 	    pnl = 1;
 	g = g->next;
     }
-
     if (clearflag) {
 	/* Move the cursor up to the prompt, if always_last_prompt *
 	 * is set and all that...                                  */
 	if ((nlines += nlnct - 1) < lines) {
 	    tcmultout(TCUP, TCMULTUP, nlines);
 	    showinglist = -1;
-	    listshown = 1;
 	} else
 	    clearflag = 0, putc('\n', shout);
     } else
 	putc('\n', shout);
+    listshown = (clearflag ? 1 : -1);
     if (!hasm || nlines >= lines)
 	noselect = 1;
+
     return noselect;
 }
 
@@ -676,7 +680,7 @@ typedef struct menustack *Menustack;
 struct menustack {
     Menustack prev;
     char *line;
-    int cs;
+    int cs, acc;
     struct menuinfo info;
     Cmgroup amatches, pmatches, lmatches;
 };
@@ -742,19 +746,19 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->amatches = amatches;
 	    s->pmatches = pmatches;
 	    s->lmatches = lmatches;
-	    menucmp = 0;
+	    s->acc = menuacc;
+	    menucmp = menuacc = 0;
 	    fixsuffix();
 	    validlist = 0;
 	    pmatches = NULL;
 	    invalidatelist();
 	    menucomplete(zlenoargs);
 	    if (dat->num < 2 || !minfo.cur || !*(minfo.cur)) {
-		noselect = 1;
-		clearlist = 1;
+		noselect = clearlist = listshown = 1;
 		zrefresh();
 		break;
 	    }
-	    clearlist = 1;
+	    clearlist = listshown = 1;
 	    mselect = (*(minfo.cur))->gnum;
 	    continue;
 	} else if (cmd == Th(z_acceptandhold) ||
@@ -767,6 +771,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->cs = cs;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
 	    s->amatches = s->pmatches = s->lmatches = NULL;
+	    s->acc = menuacc;
 	    acceptlast();
 	    do_menucmp(0);
 	    mselect = (*(minfo.cur))->gnum;
@@ -782,6 +787,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    spaceinline(l = strlen(u->line));
 	    strncpy((char *) line, u->line, l);
 	    cs = u->cs;
+	    menuacc = u->acc;
 	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
 	    p = &(minfo.cur);
 	    if (u->pmatches && pmatches != u->pmatches) {
diff --git a/Src/Zle/zle.export b/Src/Zle/zle.export
index 8bc049e16..f63f45eb8 100644
--- a/Src/Zle/zle.export
+++ b/Src/Zle/zle.export
@@ -24,6 +24,7 @@ lastambig
 linkkeymap
 listshown
 lmatches
+menuacc
 menucmp
 menucomplete
 menucur
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
index dc4e27685..5ed846cd6 100644
--- a/Src/Zle/zle_params.c
+++ b/Src/Zle/zle_params.c
@@ -67,7 +67,7 @@ static struct zleparam {
         zleunsetfn, NULL },
     { "LASTWIDGET", PM_SCALAR | PM_READONLY, NULL, FN(get_lwidget),
         zleunsetfn, NULL },
-    { "keys", PM_ARRAY | PM_READONLY, NULL, FN(get_keys),
+    { "KEYS", PM_SCALAR | PM_READONLY, NULL, FN(get_keys),
         zleunsetfn, NULL },
     { "NUMERIC", PM_INTEGER | PM_UNSET, FN(set_numeric), FN(get_numeric),
         unset_numeric, NULL },
@@ -247,29 +247,10 @@ get_lwidget(Param pm)
 }
 
 /**/
-static char **
+static char *
 get_keys(Param pm)
 {
-    char **r, **q, *p, *k, c;
-
-    r = (char **) zhalloc((strlen(keybuf) + 1) * sizeof(char *));
-    for (q = r, p = keybuf; (c = *p); q++, p++) {
-	k = *q = (char *) zhalloc(5);
-	if (c & 0x80) {
-	    *k++ = 'M';
-	    *k++ = '-';
-	    c &= 0x7f;
-	}
-	if (c < 32 || c == 0x7f) {
-	    *k++ = '^';
-	    c ^= 64;
-	}
-	*k++ = c;
-	*k = '\0';
-    }
-    *q = NULL;
-
-    return r;
+    return keybuf;
 }
 
 /**/
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index 48e1071b8..1dbffc21c 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -53,7 +53,8 @@ int nlnct;
 /**/
 int showinglist;
 
-/* Non-zero if a completion list was displayed. */
+/* > 0 if a completion list is displayed below the prompt,
+ * < 0 if a list is displayed above the prompt. */
 
 /**/
 int listshown;
@@ -265,7 +266,7 @@ zrefresh(void)
     if (inlist)
 	return;
 
-    if (clearlist && listshown) {
+    if (clearlist && listshown > 0) {
 	if (tccan(TCCLEAREOD)) {
 	    int ovln = vln, ovcs = vcs;
 	    char *nb = nbuf[vln];
@@ -331,7 +332,8 @@ zrefresh(void)
                 tcout(TCCLEAREOD);
             else
                 cleareol = 1;   /* request: clear to end of line */
-	    listshown = 0;
+	    if (listshown > 0)
+		listshown = 0;
 	}
         if (t0 > -1)
             olnct = t0;
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 5461079cb..b678a5cad 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -106,10 +106,11 @@ static int insmnum, insgnum, insgroup, insspace;
 
 static int movetoend;
 
-/* != 0 if we are in the middle of a menu completion */
+/* != 0 if we are in the middle of a menu completion and number of matches
+* accepted with accept-and-menu-complete */
 
 /**/
-int menucmp;
+int menucmp, menuacc;
 
 /* Information about menucompletion. */
 
@@ -192,6 +193,10 @@ static char *qipre, *qisuf, autoq;
 static int lpl, lsl, rpl, rsl, fpl, fsl, lppl, lpsl;
 static int noreal;
 
+/* A parameter expansion prefix (like ${). */
+
+static char *parpre;
+
 /* This is either zero or equal to the special character the word we are *
  * trying to complete starts with (e.g. Tilde or Equals).                */
 
@@ -209,9 +214,9 @@ static char *qword;
 
 static Cmgroup mgroup;
 
-/* A match counter. */
+/* Match counters: all matches, normal matches (not alternate set). */
 
-static int mnum;
+static int mnum, nmnum;
 
 /* The match counter when unambig_data() was called. */
 
@@ -540,6 +545,8 @@ reversemenucomplete(char **args)
 void
 acceptlast(void)
 {
+    menuacc++;
+
     if (brbeg && *brbeg) {
 	int l;
 
@@ -553,10 +560,16 @@ acceptlast(void)
 	brbeg[l] = ',';
 	brbeg[l + 1] = '\0';
     } else {
+	int l;
+
 	cs = minfo.pos + minfo.len + minfo.insc;
 	iremovesuffix(' ', 1);
-
+	l = cs;
+	cs = minfo.pos + minfo.len - (*(minfo.cur))->qisl;
+	foredel(l - cs);
 	inststrlen(" ", 1, 1);
+	if (parpre)
+	    inststr(parpre);
 	minfo.insc = minfo.len = 0;
 	minfo.pos = cs;
 	minfo.we = 1;
@@ -694,6 +707,9 @@ check_param(char *s, int set, int test)
 {
     char *p;
 
+    zsfree(parpre);
+    parpre = NULL;
+
     if (!test)
 	ispar = parq = eparq = 0;
     /* Try to find a `$'. */
@@ -755,6 +771,8 @@ check_param(char *s, int set, int test)
 
 	/* Now make sure that the cursor is inside the name. */
 	if (offs <= e - s && offs >= b - s && n <= 0) {
+	    char sav;
+
 	    if (br) {
 		p = e;
 		while (*p == (test ? Dnull : '"'))
@@ -782,6 +800,12 @@ check_param(char *s, int set, int test)
 	    else
 		parq = eparq = 0;
 
+	    /* Save the prefix. */
+	    sav = *b;
+	    *b = '\0';
+	    untokenize(parpre = ztrdup(s));
+	    *b = sav;
+
 	    /* And adjust wb, we, and offs again. */
 	    offs -= b - s;
 	    wb = cs - offs;
@@ -1017,7 +1041,7 @@ docomplete(int lst)
 	    int ocs = cs, ne = noerrs;
 
 	    noerrs = 1;
-	    doexpansion(s, lst, olst, lincmd);
+	    ret = doexpansion(s, lst, olst, lincmd);
 	    lastambig = 0;
 	    noerrs = ne;
 
@@ -1043,8 +1067,8 @@ docomplete(int lst)
 			}
 		}
 		ret = docompletion(s, lst, lincmd);
-	    } else
-		ret = !strcmp(ol, (char *) line);
+	    } else if (ret)
+		clearlist = 1;
 	} else
 	    /* Just do completion. */
 	    ret = docompletion(s, lst, lincmd);
@@ -1064,7 +1088,7 @@ docomplete(int lst)
 	dat.num = nmatches;
 	dat.cur = NULL;
 	if (runhookdef(MENUSTARTHOOK, (void *) &dat))
-	    menucmp = 0;
+	    menucmp = menuacc = 0;
     }
     return ret;
 }
@@ -1736,9 +1760,10 @@ get_comp_string(void)
 /* Expand the current word. */
 
 /**/
-static void
+static int
 doexpansion(char *s, int lst, int olst, int explincmd)
 {
+    int ret = 1;
     LinkList vl;
     char *ss;
 
@@ -1775,7 +1800,7 @@ doexpansion(char *s, int lst, int olst, int explincmd)
 	}
 	if (lst == COMP_LIST_EXPAND) {
 	    /* Only the list of expansions was requested. */
-	    listlist(vl);
+	    ret = listlist(vl);
 	    showinglist = 0;
 	    goto end;
 	}
@@ -1783,6 +1808,7 @@ doexpansion(char *s, int lst, int olst, int explincmd)
 	cs = wb;
 	foredel(we - wb);
 	while ((ss = (char *)ugetnode(vl))) {
+	    ret = 0;
 	    untokenize(ss);
 	    ss = quotename(ss, NULL);
 	    inststr(ss);
@@ -1801,6 +1827,8 @@ doexpansion(char *s, int lst, int olst, int explincmd)
       end:
 	popheap();
     } LASTALLOC;
+
+    return ret;
 }
 
 /* This is called from the lexer to give us word positions. */
@@ -3547,6 +3575,8 @@ add_match_data(int alt, char *str, Cline line,
     ai->line = join_clines(ai->line, line);
 
     mnum++;
+    if (!alt)
+	nmnum++;
     ai->count++;
     
     /* Allocate and fill the match structure. */
@@ -3569,6 +3599,8 @@ add_match_data(int alt, char *str, Cline line,
     cm->flags = flags;
     cm->brpl = bpl;
     cm->brsl = bsl;
+    cm->qipl = qipl;
+    cm->qisl = qisl;
     cm->autoq = (autoq ? autoq : (inbackt ? '`' : '\0'));
     cm->rems = cm->remf = NULL;
     addlinknode((alt ? fmatches : matches), cm);
@@ -3896,6 +3928,7 @@ addmatches(Cadata dat, char **argv)
 		}
 	    }
 	    compnmatches = mnum;
+	    compnnmatches = nmnum;
 	    if (dat->exp)
 		addexpl();
 	    if (dat->apar)
@@ -4380,10 +4413,14 @@ docompletion(char *s, int lst, int incmd)
 	}
 	if (comppatmatch && *comppatmatch && comppatmatch != opm)
 	    haspattern = 1;
-	if (!useline && uselist)
+	if (!useline && uselist) {
 	    /* All this and the guy only wants to see the list, sigh. */
+	    cs = 0;
+	    foredel(ll);
+	    inststr(origline);
+	    cs = origcs;
 	    showinglist = -2;
-	else if (useline) {
+	} else if (useline) {
 	    /* We have matches. */
 	    if (nmatches > 1) {
 		/* There is more than one match. */
@@ -4399,9 +4436,13 @@ docompletion(char *s, int lst, int incmd)
 		do_single(m->matches[0]);
 		invalidatelist();
 	    }
-	} else
+	} else {
 	    invalidatelist();
-
+	    cs = 0;
+	    foredel(ll);
+	    inststr(origline);
+	    cs = origcs;
+	}
 	/* Print the explanation strings if needed. */
 	if (!showinglist && validlist && usemenu != 2 && nmatches != 1) {
 	    Cmgroup g = amatches;
@@ -4582,22 +4623,22 @@ callcompfunc(char *s, char *fn)
 	zsfree(compprefix);
 	zsfree(compsuffix);
 	if (unset(COMPLETEINWORD)) {
-	    /* Maybe we'll have to do quoting here some time. */
-	    tmp = dupstring(s);
+	    tmp = quotename(s, NULL);
 	    untokenize(tmp);
 	    compprefix = ztrdup(tmp);
 	    compsuffix = ztrdup("");
 	} else {
 	    char *ss, sav;
 	    
-	    tmp = dupstring(s);
-	    ss = tmp + offs;
+	    ss = s + offs;
 
 	    sav = *ss;
 	    *ss = '\0';
+	    tmp = quotename(s, NULL);
 	    untokenize(tmp);
 	    compprefix = ztrdup(tmp);
 	    *ss = sav;
+	    ss = quotename(ss, NULL);
 	    untokenize(ss);
 	    compsuffix = ztrdup(ss);
 	}
@@ -4611,6 +4652,7 @@ callcompfunc(char *s, char *fn)
 	compqisuffix = ztrdup(qisuf ? qisuf : "");
 	compcurrent = (usea ? (clwpos + 1 - aadd) : 0);
 	compnmatches = mnum;
+	compnnmatches = nmnum;
 
 	zsfree(complist);
 	switch (uselist) {
@@ -4825,13 +4867,13 @@ makecomplist(char *s, int incmd, int lst)
 	if (!validlist)
 	    lastambig = 0;
 	amatches = NULL;
-	mnum = 0;
+	mnum = nmnum = 0;
 	unambig_mnum = -1;
 	isuf = NULL;
 	insmnum = insgnum = 1;
 	insgroup = oldlist = oldins = 0;
 	begcmgroup("default", 0);
-	menucmp = 0;
+	menucmp = menuacc = 0;
 
 	ccused = newlinklist();
 	ccstack = newlinklist();
@@ -4984,6 +5026,8 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
     memcpy(tmp + sl + 1, s, noffs);
     tmp[(scs = cs = sl + 1 + noffs)] = 'x';
     strcpy(tmp + sl + 2 + noffs, s + noffs);
+    if (incompfunc)
+	tmp = rembslash(tmp);
     inpush(dupstrspace(tmp), 0, NULL);
     line = (unsigned char *) tmp;
     ll = tl - 1;
@@ -5013,6 +5057,7 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
 	    p = NULL;
 	if (!got && !zleparse) {
 	    DPUTS(!p, "no current word in substr");
+	    got = 1;
 	    cur = i;
 	    swb = wb - 1;
 	    swe = we - 1;
@@ -5077,7 +5122,7 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
     }
     sav = s[(i = swb - sl - 1)];
     s[i] = '\0';
-    qp = tricat(qipre, s, "");
+    qp = tricat(qipre, (incompfunc ? rembslash(s) : s), "");
     s[i] = sav;
     if (swe < swb)
 	swe = swb;
@@ -5085,7 +5130,7 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
     sl = strlen(s);
     if (swe > sl)
 	swe = sl, ns[swe - swb + 1] = '\0';
-    qs = tricat(s + swe, qisuf, "");
+    qs = tricat((incompfunc ? rembslash(s + swe) : s + swe), qisuf, "");
     sl = strlen(ns);
     if (soffs > sl)
 	soffs = sl;
@@ -5172,7 +5217,7 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
 	compisuffix = ztrdup("");
 	zsfree(compqiprefix);
 	zsfree(compqisuffix);
-	if (instring) {
+	if (ois) {
 	    compqiprefix = qp;
 	    compqisuffix = qs;
 	} else {
@@ -5188,6 +5233,7 @@ sep_comp_string(char *ss, char *s, int noffs, int rec)
 	    p = compwords[i] = (char *) getdata(n);
 	    untokenize(p);
 	}
+	compcurrent = cur + 1;
 	compwords[i] = NULL;
     }
     autoq = oaq;
@@ -5242,6 +5288,7 @@ makecomplistcall(Compctl cc)
 	    inbackt = oib;
 	    autoq = oaq;
 	    compnmatches = mnum;
+	    compnnmatches = nmnum;
 	} LASTALLOC;
     } SWITCHBACKHEAPS;
 
@@ -5317,6 +5364,7 @@ makecomplistctl(int flags)
 	    autoq = oaq;
 	    offs = ooffs;
 	    compnmatches = mnum;
+	    compnnmatches = nmnum;
 	    zsfree(cmdstr);
 	    freearray(clwords);
 	    cmdstr = os;
@@ -5979,7 +6027,10 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	    char save = line[cs];
 
 	    line[cs] = 0;
-	    lppre = dupstring((char *) (line + wb));
+	    lppre = dupstring((char *) line + wb +
+			      (qipre && *qipre ?
+			       (strlen(qipre) -
+				(*qipre == '\'' || *qipre == '\"')) : 0));
 	    line[cs] = save;
 	    if (brbeg && *brbeg)
 		strcpy(lppre + qbrpl, lppre + qbrpl + strlen(brbeg));
@@ -5998,11 +6049,17 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	    lppl = 0;
 	}
 	if (cs != we) {
-	    char save = line[we];
+	    int end = we;
+	    char save = line[end];
+
+	    if (qisuf && *qisuf) {
+		int ql = strlen(qisuf);
 
-	    line[we] = 0;
+		end -= ql - (qisuf[ql-1] == '\'' || qisuf[ql-1] == '"');
+	    }
+	    line[end] = 0;
 	    lpsuf = dupstring((char *) (line + cs));
-	    line[we] = save;
+	    line[end] = save;
 	    if (brend && *brend) {
 		char *p = lpsuf + qbrsl - (cs - wb);
 
@@ -6640,7 +6697,8 @@ invalidatelist(void)
 	listmatches();
     if (validlist)
 	freematches();
-    lastambig = menucmp = validlist = showinglist = fromcomp = 0;
+    lastambig = menucmp = menuacc = validlist = showinglist =
+	fromcomp = listshown = 0;
     minfo.cur = NULL;
     minfo.asked = 0;
     compwidget = NULL;
@@ -6917,6 +6975,8 @@ dupmatch(Cmatch m)
     r->rems = ztrdup(m->rems);
     r->remf = ztrdup(m->remf);
     r->autoq = m->autoq;
+    r->qipl = m->qipl;
+    r->qisl = m->qisl;
 
     return r;
 }
@@ -7346,7 +7406,9 @@ instmatch(Cmatch m, int *scs)
 
     /* Ignored prefix. */
     if (m->ipre) {
-	inststrlen(m->ipre, 1, (l = strlen(m->ipre)));
+	char *p = m->ipre + (menuacc ? m->qipl : 0);
+
+	inststrlen(p, 1, (l = strlen(p)));
 	r += l;
     }
     /* -P prefix. */
@@ -7413,7 +7475,8 @@ static int
 do_ambiguous(void)
 {
     int ret = 0;
-    menucmp = 0;
+
+    menucmp = menuacc = 0;
 
     /* If we have to insert the first match, call do_single().  This is *
      * how REC_EXACT takes effect.  We effectively turn the ambiguous   *
@@ -7501,11 +7564,13 @@ do_ambiguous(void)
      * if it is needed.                                                     */
     if (isset(LISTBEEP))
 	ret = 1;
-    if (uselist && (usemenu != 2 || (!showinglist && !oldlist)) &&
+
+    if (uselist && (usemenu != 2 || (!listshown && !oldlist)) &&
 	((!showinglist && (!listshown || !oldlist)) ||
 	 (usemenu == 3 && !oldlist)) &&
 	(smatches >= 2 || (compforcelist && *compforcelist)))
 	showinglist = -2;
+
     return ret;
 }
 
@@ -7649,7 +7714,7 @@ do_single(Cmatch m)
 	    }
 	}
 	if (!minfo.insc)
-	    cs = minfo.pos + minfo.len;
+	    cs = minfo.pos + minfo.len - m->qisl;
     }
     /* If completing in a brace expansion... */
     if (brbeg) {
@@ -7688,8 +7753,11 @@ do_single(Cmatch m)
     if (minfo.we && m->ripre && isset(AUTOPARAMKEYS))
 	makeparamsuffix(((m->flags & CMF_PARBR) ? 1 : 0), minfo.insc - parq);
 
-    if ((menucmp && !minfo.we) || !movetoend)
+    if ((menucmp && !minfo.we) || !movetoend) {
 	cs = minfo.end;
+	if (cs + m->qisl == lastend)
+	    cs += minfo.insc;
+    }
     {
 	Cmatch *om = minfo.cur;
 	struct chdata dat;
@@ -7732,6 +7800,7 @@ do_ambig_menu(void)
 
     if (usemenu != 3) {
 	menucmp = 1;
+	menuacc = 0;
 	minfo.cur = NULL;
     } else {
 	if (oldlist) {
@@ -7931,13 +8000,6 @@ ilistmatches(Hookdef dummy, Chdata dat)
     int nlines = 0, ncols, nlist = 0, longest = 1, pnl = 0;
     int of = isset(LISTTYPES), opl = 0;
 
-    /* Set the cursor below the prompt. */
-    trashzle();
-    showinglist = listshown = 0;
-
-    clearflag = (isset(USEZLE) && !termflags &&
-		 complastprompt && *complastprompt);
-
     for (g = amatches; g; g = g->next) {
 	char **pp = g->ylist;
 	int nl = 0, l;
@@ -8013,6 +8075,16 @@ ilistmatches(Hookdef dummy, Chdata dat)
 			nlines += 1 + ((1 + niceztrlen(m->str)) / columns);
 	}
     }
+    if (!nlines) {
+	showinglist = listshown = 0;
+	return 1;
+    }
+    /* Set the cursor below the prompt. */
+    trashzle();
+    showinglist = listshown = 0;
+
+    clearflag = (isset(USEZLE) && !termflags &&
+		 complastprompt && *complastprompt);
 
     /* Maybe we have to ask if the user wants to see the list. */
     if ((!minfo.cur || !minfo.asked) &&
@@ -8152,25 +8224,25 @@ ilistmatches(Hookdef dummy, Chdata dat)
 	    pnl = 1;
 	g = g->next;
     }
-
     if (clearflag) {
 	/* Move the cursor up to the prompt, if always_last_prompt *
 	 * is set and all that...                                  */
 	if ((nlines += nlnct - 1) < lines) {
 	    tcmultout(TCUP, TCMULTUP, nlines);
 	    showinglist = -1;
-	    listshown = 1;
 	} else
 	    clearflag = 0, putc('\n', shout);
     } else
 	putc('\n', shout);
+    listshown = (clearflag ? 1 : -1);
+
     return 0;
 }
 
 /* This is used to print expansions. */
 
 /**/
-void
+int
 listlist(LinkList l)
 {
     struct cmgroup dg;
@@ -8193,6 +8265,8 @@ listlist(LinkList l)
     validlist = vl;
     smatches = sm;
     complastprompt = oclp;
+
+    return !dg.lcount;
 }
 
 /* Expand the history references. */
diff --git a/Src/builtin.c b/Src/builtin.c
index 0e9baf9be..5c6b24601 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -658,6 +658,9 @@ set_pwd_env(void)
     }
 }
 
+/* set if we are resolving links to their true paths */
+static int chasinglinks;
+
 /* The main pwd changing function.  The real work is done by other     *
  * functions.  cd_get_dest() does the initial argument processing;     *
  * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
@@ -670,7 +673,6 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 {
     LinkNode dir;
     struct stat st1, st2;
-    int chaselinks;
 
     if (isset(RESTRICTED)) {
 	zwarnnam(nam, "restricted", NULL, 0);
@@ -694,7 +696,7 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 	for (s = *argv; *++s; ops[STOUC(*s)] = 1);
     }
   brk:
-    chaselinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
+    chasinglinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
     PERMALLOC {
 	pushnode(dirstack, ztrdup(pwd));
 	if (!(dir = cd_get_dest(nam, argv, ops, func))) {
@@ -702,7 +704,7 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 	    LASTALLOC_RETURN 1;
 	}
     } LASTALLOC;
-    cd_new_pwd(func, dir, chaselinks);
+    cd_new_pwd(func, dir);
 
     if (stat(unmeta(pwd), &st1) < 0) {
 	zsfree(pwd);
@@ -710,7 +712,7 @@ bin_cd(char *nam, char **argv, char *ops, int func)
     } else if (stat(".", &st2) < 0)
 	chdir(unmeta(pwd));
     else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) {
-	if (chaselinks) {
+	if (chasinglinks) {
 	    zsfree(pwd);
 	    pwd = metafy(zgetcwd(), -1, META_DUP);
 	} else {
@@ -915,40 +917,49 @@ static char *
 cd_try_chdir(char *pfix, char *dest, int hard)
 {
     char *buf;
+    int dlen, dochaselinks = 0;
 
     /* handle directory prefix */
     if (pfix && *pfix) {
 	if (*pfix == '/')
 	    buf = tricat(pfix, "/", dest);
 	else {
-	    int pwl = strlen(pwd);
 	    int pfl = strlen(pfix);
+	    dlen = strlen(pwd);
 
-	    buf = zalloc(pwl + pfl + strlen(dest) + 3);
+	    buf = zalloc(dlen + pfl + strlen(dest) + 3);
 	    strcpy(buf, pwd);
-	    buf[pwl] = '/';
-	    strcpy(buf + pwl + 1, pfix);
-	    buf[pwl + 1 + pfl] = '/';
-	    strcpy(buf + pwl + pfl + 2, dest);
+	    buf[dlen] = '/';
+	    strcpy(buf + dlen + 1, pfix);
+	    buf[dlen + 1 + pfl] = '/';
+	    strcpy(buf + dlen + pfl + 2, dest);
 	}
     } else if (*dest == '/')
 	buf = ztrdup(dest);
     else {
-	int pwl = strlen(pwd);
+	dlen = strlen(pwd);
 
-	buf = zalloc(pwl + strlen(dest) + 2);
+	buf = zalloc(dlen + strlen(dest) + 2);
 	strcpy(buf, pwd);
-	buf[pwl] = '/';
-	strcpy(buf + pwl + 1, dest);
+	buf[dlen] = '/';
+	strcpy(buf + dlen + 1, dest);
     }
 
-    /* Normalise path.  See the definition of fixdir() for what this means. */
-    fixdir(buf);
+    /* Normalise path.  See the definition of fixdir() for what this means.
+     * We do not do this if we are chasing links.
+     */
+    if (!chasinglinks)
+	dochaselinks = fixdir(buf);
+    else
+	unmetafy(buf, &dlen);
 
     if (lchdir(buf, NULL, hard)) {
-	zsfree(buf);
+	free(buf);
 	return NULL;
     }
+    /* the chdir succeeded, so decide if we should force links to be chased */
+    if (dochaselinks)
+	chasinglinks = 1;
     return metafy(buf, -1, META_NOALLOC);
 }
 
@@ -956,7 +967,7 @@ cd_try_chdir(char *pfix, char *dest, int hard)
 
 /**/
 static void
-cd_new_pwd(int func, LinkNode dir, int chaselinks)
+cd_new_pwd(int func, LinkNode dir)
 {
     List l;
     char *new_pwd, *s;
@@ -972,7 +983,7 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks)
     } else if (func == BIN_CD && unset(AUTOPUSHD))
 	zsfree(getlinknode(dirstack));
 
-    if (chaselinks) {
+    if (chasinglinks) {
 	s = new_pwd;
 	new_pwd = findpwd(s);
 	zsfree(s);
@@ -1039,17 +1050,20 @@ printdirstack(void)
 }
 
 /* Normalise a path.  Segments consisting of ., and foo/.. *
- * combinations, are removed and the path is unmetafied.   */
+ * combinations, are removed and the path is unmetafied.
+ * Returns 1 if we found a ../ path which should force links to
+ * be chased, 0 otherwise.
+ */
 
 /**/
-static void
+int
 fixdir(char *src)
 {
-    char *dest = src;
-    char *d0 = dest;
-#ifdef __CYGWIN__
+    char *dest = src, *d0 = dest;
+#ifdef __CYGWIN
     char *s0 = src;
 #endif
+    int ret = 0;
 
 /*** if have RFS superroot directory ***/
 #ifdef HAVE_SUPERROOT
@@ -1081,19 +1095,40 @@ fixdir(char *src)
 	    while (dest > d0 + 1 && dest[-1] == '/')
 		dest--;
 	    *dest = '\0';
-	    return;
+	    return ret;
 	}
 	if (src[0] == '.' && src[1] == '.' &&
-	  (src[2] == '\0' || src[2] == '/')) {
-	    if (dest > d0 + 1) {
-		/* remove a foo/.. combination */
-		for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
-		if (dest[-1] != '/')
-		    dest--;
-	    }
-	    src++;
-	    while (*++src == '/');
-	} else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
+	    (src[2] == '\0' || src[2] == '/')) {
+	    if (isset(CHASEDOTS)) {
+		ret = 1;
+		/* and treat as normal path segment */
+	    } else {
+		if (dest > d0 + 1) {
+		    /*
+		     * remove a foo/.. combination:
+		     * first check foo exists, else return.
+		     */
+		    struct stat st;
+		    *dest = '\0';
+		    if (stat(d0, &st) < 0 || !S_ISDIR(st.st_mode)) {
+			char *ptrd, *ptrs;
+			if (dest == src)
+			    *dest = '.';
+			for (ptrs = src, ptrd = dest; *ptrs; ptrs++, ptrd++)
+			    *ptrd = (*ptrs == Meta) ? (*++ptrs ^ 32) : *ptrs;
+			*ptrd = '\0';
+			return 1;
+		    }
+		    for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
+		    if (dest[-1] != '/')
+			dest--;
+		}
+		src++;
+		while (*++src == '/');
+		continue;
+	    }
+	}
+	if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
 	    /* skip a . section */
 	    while (*++src == '/');
 	} else {
@@ -3249,12 +3284,11 @@ bin_read(char *name, char **args, char *ops, int func)
 	    nchars = 1;
 	args++;
     }
-
-    firstarg = *args;
-    if (*args && **args == '?')
-	args++;
-    /* default result parameter */
+    /* This `*args++ : *args' looks a bit weird, but it works around a bug
+     * in gcc-2.8.1 under DU 4.0. */
+    firstarg = (*args && **args == '?' ? *args++ : *args);
     reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
+
     if (ops['A'] && *args) {
 	zwarnnam(name, "only one array argument allowed", NULL, 0);
 	return 1;
diff --git a/Src/exec.c b/Src/exec.c
index 9c7a1ceb5..dc281675f 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -3144,9 +3144,24 @@ static int
 cancd2(char *s)
 {
     struct stat buf;
-    char *us = unmeta(s);
+    char *us, *us2 = NULL;
 
+    /*
+     * If CHASEDOTS and CHASELINKS are not set, we want to rationalize the
+     * path by removing foo/.. combinations in the logical rather than
+     * the physical path.  If either is set, we test the physical path.
+     */
+    if (!isset(CHASEDOTS) && !isset(CHASELINKS)) {
+	if (*s != '/')
+	    us = tricat(pwd[1] ? pwd : "", "/", s);
+	else
+	    us = ztrdup(s);
+	fixdir(us2 = us);
+    } else
+	us = unmeta(s);
     return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+    if (us2)
+	free(us2);
 }
 
 /**/
diff --git a/Src/glob.c b/Src/glob.c
index ea4980b8b..cf22ef923 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -2724,6 +2724,9 @@ charmatch(Comp c, char *x, char *y)
      * Here we bypass tulower() and tuupper() for speed.
      */
     int xi = (STOUC(UNMETA(x)) & 0xff), yi = (STOUC(UNMETA(y)) & 0xff);
+    /* A NULL is a real null, since a \000 would be metafied. */
+    if (!*x || !*y)
+	return 0;
     return xi == yi ||
 	(((c->stat & C_IGNCASE) ?
 	  ((isupper(xi) ? tolower(xi) : xi) ==
@@ -2926,7 +2929,10 @@ rangematch(char **patptr, int ch, int rchar)
      * and optional ^ have already been skipped.          */
 
     char *pat = *patptr;
-#ifdef HAVE_STRCOLL
+    /* We don't use strcoll() for ranges, since it can have side
+     * effects.  It's less necessary now we have [:posix:] ranges.
+     */
+#if 0
     char l_buf[2], r_buf[2], ch_buf[2];
 
     ch_buf[0] = ch;
@@ -2944,7 +2950,7 @@ rangematch(char **patptr, int ch, int rchar)
 		break;
 	} else if (*pat == '-' && pat[-1] != rchar &&
 		   pat[1] != Outbrack) {
-#ifdef HAVE_STRCOLL
+#if 0
 	    l_buf[0] = PPAT(-1);
 	    r_buf[0] = PAT(1);
 	    if (strcoll(l_buf, ch_buf) <= 0 &&
diff --git a/Src/lex.c b/Src/lex.c
index 33b6598b9..069f9b39b 100644
--- a/Src/lex.c
+++ b/Src/lex.c
@@ -241,6 +241,7 @@ lexsave(void)
     cmdsp = 0;
     inredir = 0;
     hdocs = NULL;
+    histactive = 0;
 
     ls->next = lstack;
     lstack = ls;
diff --git a/Src/options.c b/Src/options.c
index 2eb73690e..0207cd232 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -91,6 +91,7 @@ static struct optname optns[] = {
 {NULL, "braceccl",	      0,			 BRACECCL},
 {NULL, "bsdecho",	      OPT_EMULATE|OPT_SH,	 BSDECHO},
 {NULL, "cdablevars",	      0,			 CDABLEVARS},
+{NULL, "chasedots",	      0,			 CHASEDOTS},
 {NULL, "chaselinks",	      0,			 CHASELINKS},
 {NULL, "clobber",	      OPT_ALL,			 CLOBBER},
 {NULL, "completealiases",     0,			 COMPLETEALIASES},
diff --git a/Src/parse.c b/Src/parse.c
index 626ffc982..658a66660 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -72,7 +72,13 @@ struct list dummy_list;
 
 #define YYERROR  { tok = LEXERR; return NULL; }
 #define YYERRORV { tok = LEXERR; return; }
-#define COND_ERROR(X,Y) do{herrflush();zerr(X,Y,0);YYERROR}while(0)
+#define COND_ERROR(X,Y) do { \
+  zwarn(X,Y,0); \
+  herrflush(); \
+  if (noerrs != 2) \
+    errflag = 1; \
+  YYERROR \
+} while(0)
 
 #define make_list()     allocnode(N_LIST)
 #define make_sublist()  allocnode(N_SUBLIST)
@@ -140,11 +146,13 @@ par_event(void)
     }
     if (!l) {
 	if (errflag) {
-	    yyerror();
+	    yyerror(0);
 	    return NULL;
 	}
+	yyerror(1);
 	herrflush();
-	yyerror();
+	if (noerrs != 2)
+	    errflag = 1;
 	return NULL;
     } else {
 	l->right = par_event();
@@ -163,7 +171,7 @@ parse_list(void)
     yylex();
     ret = par_list();
     if (tok == LEXERR) {
-	yyerror();
+	yyerror(0);
 	return NULL;
     }
     return ret;
@@ -1480,7 +1488,7 @@ par_cond_multi(char *a, LinkList l)
 
 /**/
 static void
-yyerror(void)
+yyerror(int noerr)
 {
     int t0;
 
@@ -1488,9 +1496,11 @@ yyerror(void)
 	if (!yytext || !yytext[t0] || yytext[t0] == '\n')
 	    break;
     if (t0 == 20)
-	zerr("parse error near `%l...'", yytext, 20);
+	zwarn("parse error near `%l...'", yytext, 20);
     else if (t0)
-	zerr("parse error near `%l'", yytext, t0);
+	zwarn("parse error near `%l'", yytext, t0);
     else
-	zerr("parse error", NULL, 0);
+	zwarn("parse error", NULL, 0);
+    if (!noerr && noerrs != 2)
+	errflag = 1;
 }
diff --git a/Src/utils.c b/Src/utils.c
index d82f62694..f86c18b16 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -30,23 +30,12 @@
 #include "zsh.mdh"
 #include "utils.pro"
 
-/* Print an error */
-
-/**/
-void
-zwarnnam(const char *cmd, const char *fmt, const char *str, int num)
-{
-    int waserr;
-
-    waserr = errflag;
-    zerrnam(cmd, fmt, str, num);
-    errflag = waserr;
-}
-
 /* name of script being sourced */
 
 /**/
 char *scriptname;
+
+/* Print an error */
  
 /**/
 void
@@ -57,7 +46,27 @@ zerr(const char *fmt, const char *str, int num)
 	    errflag = 1;
 	return;
     }
+    zwarn(fmt, str, num);
     errflag = 1;
+}
+
+/**/
+void
+zerrnam(const char *cmd, const char *fmt, const char *str, int num)
+{
+    if (errflag || noerrs)
+	return;
+
+    zwarnnam(cmd, fmt, str, num);
+    errflag = 1;
+}
+
+/**/
+void
+zwarn(const char *fmt, const char *str, int num)
+{
+    if (errflag || noerrs)
+	return;
     trashzle();
     /*
      * scriptname is set when sourcing scripts, so that we get the
@@ -68,25 +77,29 @@ zerr(const char *fmt, const char *str, int num)
     nicezputs((isset(SHINSTDIN) && !locallevel) ? "zsh" :
 	      scriptname ? scriptname : argzero, stderr);
     fputs(": ", stderr);
-    zerrnam(NULL, fmt, str, num);
+    zerrmsg(fmt, str, num);
 }
 
 /**/
 void
-zerrnam(const char *cmd, const char *fmt, const char *str, int num)
+zwarnnam(const char *cmd, const char *fmt, const char *str, int num)
 {
-    if (cmd) {
-	if (errflag || noerrs)
-	    return;
-	errflag = 1;
-	trashzle();
-	if (unset(SHINSTDIN) || locallevel) {
-	    nicezputs(scriptname ? scriptname : argzero, stderr);
-	    fputs(": ", stderr);
-	}
-	nicezputs(cmd, stderr);
+    if (errflag || noerrs)
+	return;
+    trashzle();
+    if (unset(SHINSTDIN) || locallevel) {
+	nicezputs(scriptname ? scriptname : argzero, stderr);
 	fputs(": ", stderr);
     }
+    nicezputs(cmd, stderr);
+    fputs(": ", stderr);
+    zerrmsg(fmt, str, num);
+}
+
+/**/
+void
+zerrmsg(const char *fmt, const char *str, int num)
+{
     while (*fmt)
 	if (*fmt == '%') {
 	    fmt++;
@@ -302,7 +315,7 @@ slashsplit(char *s)
 
 /**/
 static int
-xsymlinks(char *s, int flag)
+xsymlinks(char *s)
 {
     char **pp, **opp;
     char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2];
@@ -325,15 +338,9 @@ xsymlinks(char *s, int flag)
 	    *p = '\0';
 	    continue;
 	}
-	if (unset(CHASELINKS)) {
-	    strcat(xbuf, "/");
-	    strcat(xbuf, *pp);
-	    zsfree(*pp);
-	    continue;
-	}
 	sprintf(xbuf2, "%s/%s", xbuf, *pp);
 	t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
-	if (t0 == -1 || !flag) {
+	if (t0 == -1) {
 	    strcat(xbuf, "/");
 	    strcat(xbuf, *pp);
 	    zsfree(*pp);
@@ -342,9 +349,9 @@ xsymlinks(char *s, int flag)
 	    metafy(xbuf3, t0, META_NOALLOC);
 	    if (*xbuf3 == '/') {
 		strcpy(xbuf, "");
-		xsymlinks(xbuf3 + 1, flag);
+		xsymlinks(xbuf3 + 1);
 	    } else
-		xsymlinks(xbuf3, flag);
+		xsymlinks(xbuf3);
 	    zsfree(*pp);
 	}
     }
@@ -352,19 +359,19 @@ xsymlinks(char *s, int flag)
     return ret;
 }
 
-/* expand symlinks in s, and remove other weird things */
+/*
+ * expand symlinks in s, and remove other weird things:
+ * note that this always expands symlinks.
+ */
 
 /**/
 char *
 xsymlink(char *s)
 {
-    if (unset(CHASELINKS))
-	return ztrdup(s);
     if (*s != '/')
 	return NULL;
     *xbuf = '\0';
-    if (!xsymlinks(s + 1, 1))
-	return ztrdup(s);
+    xsymlinks(s + 1);
     if (!*xbuf)
 	return ztrdup("/");
     return ztrdup(xbuf);
@@ -374,15 +381,10 @@ xsymlink(char *s)
 void
 print_if_link(char *s)
 {
-    int chase;
-
     if (*s == '/') {
-	chase = opts[CHASELINKS];
-	opts[CHASELINKS] = 1;
 	*xbuf = '\0';
-	if (xsymlinks(s + 1, 1))
+	if (xsymlinks(s + 1))
 	    printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout);
-	opts[CHASELINKS] = chase;
     }
 }
 
@@ -573,7 +575,8 @@ getnameddir(char *name)
 	/* Retrieve an entry from the password table/database for this user. */
 	struct passwd *pw;
 	if ((pw = getpwnam(name))) {
-	    char *dir = xsymlink(pw->pw_dir);
+	    char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir)
+		: ztrdup(pw->pw_dir);
 	    adduserdir(name, dir, ND_USERNAME, 1);
 	    str = dupstring(dir);
 	    zsfree(dir);
@@ -3202,7 +3205,7 @@ getkeystring(char *s, int *len, int fromwhere, int *misc)
     int meta = 0, control = 0;
 
     if (fromwhere == 6)
-	t = tmp;
+	t = buf = tmp;
     else if (fromwhere != 4)
 	t = buf = zhalloc(strlen(s) + 1);
     else {
diff --git a/Src/zsh.h b/Src/zsh.h
index 2070e9b3f..d2b64b9bb 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1171,6 +1171,7 @@ enum {
     BRACECCL,
     BSDECHO,
     CDABLEVARS,
+    CHASEDOTS,
     CHASELINKS,
     CLOBBER,
     COMPLETEALIASES,