about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Builtins/sched.c6
-rw-r--r--Src/Modules/curses.c104
-rw-r--r--Src/Modules/datetime.c8
-rw-r--r--Src/Modules/db_gdbm.c537
-rw-r--r--Src/Modules/db_gdbm.mdd2
-rw-r--r--Src/Modules/example.c3
-rw-r--r--Src/Modules/mathfunc.c4
-rw-r--r--Src/Modules/parameter.c137
-rw-r--r--Src/Modules/regex.c8
-rw-r--r--Src/Modules/system.c36
-rw-r--r--Src/Modules/system.mdd7
-rw-r--r--Src/Modules/tcp.c3
-rw-r--r--Src/Modules/terminfo.c2
-rw-r--r--Src/Modules/zpty.c16
-rw-r--r--Src/Modules/zutil.c24
-rw-r--r--Src/Zle/comp.h33
-rw-r--r--Src/Zle/compcore.c56
-rw-r--r--Src/Zle/compctl.c61
-rw-r--r--Src/Zle/complete.c81
-rw-r--r--Src/Zle/complist.c24
-rw-r--r--Src/Zle/compmatch.c215
-rw-r--r--Src/Zle/compresult.c5
-rw-r--r--Src/Zle/computil.c499
-rw-r--r--Src/Zle/iwidgets.list2
-rw-r--r--Src/Zle/textobjects.c18
-rw-r--r--Src/Zle/zle.h14
-rw-r--r--Src/Zle/zle_hist.c17
-rw-r--r--Src/Zle/zle_keymap.c38
-rw-r--r--Src/Zle/zle_main.c106
-rw-r--r--Src/Zle/zle_misc.c10
-rw-r--r--Src/Zle/zle_params.c128
-rw-r--r--Src/Zle/zle_refresh.c17
-rw-r--r--Src/Zle/zle_thingy.c42
-rw-r--r--Src/Zle/zle_tricky.c53
-rw-r--r--Src/Zle/zle_utils.c14
-rw-r--r--Src/Zle/zle_vi.c193
-rw-r--r--Src/Zle/zle_word.c90
-rw-r--r--Src/builtin.c484
-rw-r--r--Src/compat.c362
-rw-r--r--Src/cond.c30
-rw-r--r--Src/exec.c789
-rw-r--r--Src/glob.c96
-rw-r--r--Src/hashtable.c187
-rw-r--r--Src/hist.c50
-rw-r--r--Src/init.c35
-rw-r--r--Src/input.c27
-rw-r--r--Src/jobs.c80
-rw-r--r--Src/lex.c42
-rw-r--r--Src/linklist.c29
-rw-r--r--Src/loop.c35
-rw-r--r--Src/math.c39
-rw-r--r--Src/mem.c34
-rw-r--r--Src/module.c3
-rw-r--r--Src/options.c4
-rw-r--r--Src/params.c635
-rw-r--r--Src/parse.c119
-rw-r--r--Src/pattern.c12
-rw-r--r--Src/prompt.c23
-rw-r--r--Src/signals.c43
-rw-r--r--Src/signals.h31
-rw-r--r--Src/string.c31
-rw-r--r--Src/subst.c201
-rw-r--r--Src/text.c7
-rw-r--r--Src/utils.c321
-rw-r--r--Src/watch.c107
-rw-r--r--Src/wcwidth9.h1325
-rw-r--r--Src/zsh.h120
-rw-r--r--Src/zsh_system.h8
-rw-r--r--Src/ztype.h6
69 files changed, 6002 insertions, 1926 deletions
diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c
index 70625edca..835e72cb7 100644
--- a/Src/Builtins/sched.c
+++ b/Src/Builtins/sched.c
@@ -353,7 +353,11 @@ schedgetfn(UNUSED(Param pm))
 	time_t t;
 
 	t = sch->time;
-	sprintf(tbuf, "%ld", t);
+#if defined(PRINTF_HAS_LLD)
+	sprintf(tbuf, "%lld", (long long)t);
+#else
+	sprintf(tbuf, "%ld", (long)t);
+#endif
 	if (sch->flags & SCHEDFLAG_TRASH_ZLE)
 	    flagstr = "-o";
 	else
diff --git a/Src/Modules/curses.c b/Src/Modules/curses.c
index 63c6748f5..a60dfcbf8 100644
--- a/Src/Modules/curses.c
+++ b/Src/Modules/curses.c
@@ -1082,15 +1082,7 @@ zccmd_input(const char *nam, char **args)
 #endif
 
     /*
-     * Some documentation for wgetch() says:
-
-       The behavior of getch and friends in the presence of  handled  signals
-       is  unspecified  in the SVr4 and XSI Curses documentation.  Under his-
-       torical curses implementations, it varied  depending  on  whether  the
-       operating system's implementation of handled signal receipt interrupts
-       a read(2) call in progress or not, and also (in some  implementations)
-       depending  on  whether  an input timeout or non-blocking mode has been
-       set.
+     * Linux, OS X, FreeBSD documentation for wgetch() mentions:
 
        Programmers concerned about portability should be prepared for  either
        of  two cases: (a) signal receipt does not interrupt getch; (b) signal
@@ -1098,21 +1090,16 @@ zccmd_input(const char *nam, char **args)
        EINTR.  Under the ncurses implementation, handled signals never inter-
        rupt getch.
 
-     * The observed behavior, however, is different:  wgetch() consistently
-     * returns ERR with EINTR when a signal is handled by the shell "trap"
-     * command mechanism.  Further, it consistently returns ERR twice, the
-     * second time without even attempting to repeat the interrupted read,
-     * which has the side-effect of NOT updating errno.  A third call will
-     * then begin reading again.
-     *
-     * Therefore, to properly implement signal trapping, we must (1) call
-     * wgetch() in a loop as long as errno remains EINTR, and (2) clear
-     * errno only before beginning the loop, not on every pass.
+     * Some observed behavior: wgetch() returns ERR with EINTR when a signal is
+     * handled by the shell "trap" command mechanism. Observed that it returns
+     * ERR twice, the second time without even attempting to repeat the
+     * interrupted read. Third call will then begin reading again.
      *
-     * There remains a potential bug here in that, if the caller has set
-     * a timeout for the read [see zccmd_timeout()] the countdown is very
-     * likely restarted on every call to wgetch(), so an interrupted call
-     * might wait much longer than desired.
+     * Because of widespread of previous implementation that called wget*ch
+     * possibly indefinitely many times after ERR/EINTR, and because of the
+     * above observation, wget_wch call is repeated after each ERR/EINTR, but
+     * errno is being reset (it wasn't) and the loop to all means should break.
+     * Problem: the timeout may be waited twice.
      */
     errno = 0;
 
@@ -1120,6 +1107,7 @@ zccmd_input(const char *nam, char **args)
     while ((ret = wget_wch(w->win, &wi)) == ERR) {
 	if (errno != EINTR || errflag || retflag || breaks || exit_pending)
 	    break;
+        errno = 0;
     }
     switch (ret) {
     case OK:
@@ -1146,6 +1134,7 @@ zccmd_input(const char *nam, char **args)
     while ((ci = wgetch(w->win)) == ERR) {
 	if (errno != EINTR || errflag || retflag || breaks || exit_pending)
 	    return 1;
+        errno = 0;
     }
     if (ci >= 256) {
 	keypadnum = ci;
@@ -1501,6 +1490,74 @@ zccmd_touch(const char *nam, char **args)
     return ret;
 }
 
+static int
+zccmd_resize(const char *nam, char **args)
+{
+#ifdef HAVE_RESIZE_TERM
+    int y, x, do_endwin=0, do_save=1;
+    LinkNode stdscr_win = zcurses_getwindowbyname("stdscr");
+
+    if (stdscr_win) {
+        y = atoi(args[0]);
+        x = atoi(args[1]);
+        if (args[2]) {
+            if (0 == strcmp(args[2], "endwin")) {
+                do_endwin=1;
+            } else if (0 == strcmp(args[2], "endwin_nosave")) {
+                do_endwin=1;
+                do_save=0;
+            } else if (0 == strcmp(args[2], "nosave")) {
+                do_save=0;
+            } else {
+                zwarnnam(nam, "`resize' expects `endwin', `nosave' or `endwin_nosave' for third argument, if given");
+            }
+        }
+
+        if (y == 0 && x == 0 && args[2] == NULL) {
+            // Special case to just test that curses has resize_term. #ifdef
+            // HAVE_RESIZE_TERM will result in return value 2 if resize_term
+            // is not available.
+            return 0;
+        } else {
+            // Without this call some window moves are innacurate. Tested on
+            // OS X ncurses 5.4, Homebrew ncursesw 6.0-2, Arch Linux ncursesw
+            // 6.0, Ubuntu 14.04 ncurses 5.9, FreeBSD ncursesw.so.8
+            //
+            // On the other hand, the whole resize goal can be (from tests)
+            // accomplished by calling endwin and refresh. But to secure any
+            // future problems, resize_term is provided, and it is featured
+            // with endwin, so that users have multiple options.
+            if (do_endwin) {
+                endwin();
+            }
+
+            if( resize_term( y, x ) == OK ) {
+                // Things work without this, but we need to get out from
+                // endwin (i.e. call refresh), and in theory store new
+                // curses state (the resize might have changed it), which
+                // should be presented to terminal only after refresh.
+                if (do_endwin || do_save) {
+                    ZCWin w;
+                    w = (ZCWin)getdata(stdscr_win);
+                    wnoutrefresh(w->win);
+                    doupdate();
+                }
+
+                if (do_save) {
+                    gettyinfo(&curses_tty_state);
+                }
+                return 0;
+            } else {
+                return 1;
+            }
+        }
+    } else {
+        return 1;
+    }
+#else
+    return 2;
+#endif
+}
 
 /*********************
   Main builtin handler
@@ -1534,6 +1591,7 @@ bin_zcurses(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	{"mouse", zccmd_mouse, 0, -1},
 	{"querychar", zccmd_querychar, 1, 2},
 	{"touch", zccmd_touch, 1, -1},
+	{"resize", zccmd_resize, 2, 3},
 	{NULL, (zccmd_t)0, 0, 0}
     };
 
diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c
index bb82c542f..6e9047bc5 100644
--- a/Src/Modules/datetime.c
+++ b/Src/Modules/datetime.c
@@ -133,11 +133,15 @@ output_strftime(char *nam, char **argv, Options ops, UNUSED(int func))
 
     len = 0;
     for (x=0; x < 4; x++) {
-        if ((len = ztrftime(buffer, bufsize, argv[0], t, 0L)) >= 0)
+        if ((len = ztrftime(buffer, bufsize, argv[0], t, 0L)) >= 0 || x==3)
 	    break;
 	buffer = zrealloc(buffer, bufsize *= 2);
     }
-    DPUTS(len < 0, "bad output from ztrftime");
+    if (len < 0) {
+	zwarnnam(nam, "bad/unsupported format: '%s'", argv[0]);
+	zfree(buffer, bufsize);
+	return 1;
+    }
 
     if (scalar) {
 	setsparam(scalar, metafy(buffer, len, META_DUP));
diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c
index 8dd60fc0d..35254b68c 100644
--- a/Src/Modules/db_gdbm.c
+++ b/Src/Modules/db_gdbm.c
@@ -6,6 +6,9 @@
  * Copyright (c) 2008 Clint Adams
  * All rights reserved.
  *
+ * Modifications copyright (c) 2017 Sebastian Gniazdowski
+ * All rights reserved.
+ *
  * Permission is hereby granted, without written agreement and without
  * license or royalty fees, to use, copy, modify, and distribute this
  * software and to distribute modified versions of this software for any
@@ -31,6 +34,16 @@
 #include "db_gdbm.mdh"
 #include "db_gdbm.pro"
 
+#ifndef PM_UPTODATE
+#define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
+#endif
+
+static Param createhash( char *name, int flags );
+static int append_tied_name( const char *name );
+static int remove_tied_name( const char *name );
+static char *unmetafy_zalloc(const char *to_copy, int *new_len);
+static void set_length(char *buf, int size);
+
 /*
  * Make sure we have all the bits I'm using for memory mapping, otherwise
  * I don't know what I'm doing.
@@ -41,8 +54,34 @@
 
 static char *backtype = "db/gdbm";
 
-static const struct gsu_scalar gdbm_gsu =
-{ gdbmgetfn, gdbmsetfn, gdbmunsetfn };
+/*
+ * Longer GSU structure, to carry GDBM_FILE of owning
+ * database. Every parameter (hash value) receives GSU
+ * pointer and thus also receives GDBM_FILE - this way
+ * parameters can access proper database.
+ *
+ * Main HashTable parameter has the same instance of
+ * the custom GSU struct in u.hash->tmpdata field.
+ * When database is closed, `dbf` field is set to NULL
+ * and hash values know to not access database when
+ * being unset (total purge at zuntie).
+ *
+ * When database closing is ended, custom GSU struct
+ * is freed. Only new ztie creates new custom GSU
+ * struct instance.
+ */
+
+struct gsu_scalar_ext {
+    struct gsu_scalar std;
+    GDBM_FILE dbf;
+    char *dbfile_path;
+};
+
+/* Source structure - will be copied to allocated one,
+ * with `dbf` filled. `dbf` allocation <-> gsu allocation. */
+static const struct gsu_scalar_ext gdbm_gsu_ext =
+{ { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
+
 /**/
 static const struct gsu_hash gdbm_hash_gsu =
 { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
@@ -50,6 +89,17 @@ static const struct gsu_hash gdbm_hash_gsu =
 static struct builtin bintab[] = {
     BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL),
     BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL),
+    BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL),
+};
+
+#define ROARRPARAMDEF(name, var) \
+    { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
+
+/* Holds names of all tied parameters */
+char **zgdbm_tied;
+
+static struct paramdef patab[] = {
+    ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ),
 };
 
 /**/
@@ -77,8 +127,7 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
     }
 
     /* Here should be a lookup of the backend type against
-     * a registry.
-     */
+     * a registry, if generam DB mechanism is to be added */
     if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
         zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd'));
 	return 1;
@@ -92,7 +141,8 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
 	/*
 	 * Unset any existing parameter.  Note there's no implicit
 	 * "local" here, but if the existing parameter is local
-	 * that will be reflected in the new one.
+	 * then new parameter will be also local without following
+         * unset.
 	 *
 	 * We need to do this before attempting to open the DB
 	 * in case this variable is already tied to a DB.
@@ -105,16 +155,17 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
 	    return 1;
     }
 
+    gdbm_errno=0;
     dbf = gdbm_open(resource_name, 0, read_write, 0666, 0);
-    if(dbf)
-	addmodulefd(gdbm_fdesc(dbf), FDT_INTERNAL);
-    else {
-	zwarnnam(nam, "error opening database file %s", resource_name);
+    if(dbf) {
+	addmodulefd(gdbm_fdesc(dbf), FDT_MODULE);
+        append_tied_name(pmname);
+    } else {
+	zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno));
 	return 1;
     }
 
-    if (!(tied_param = createspecialhash(pmname, &getgdbmnode, &scangdbmkeys,
-					 pmflags))) {
+    if (!(tied_param = createhash(pmname, pmflags))) {
         zwarnnam(nam, "cannot create the requested parameter %s", pmname);
 	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
 	gdbm_close(dbf);
@@ -122,8 +173,23 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
     }
 
     tied_param->gsu.h = &gdbm_hash_gsu;
-    tied_param->u.hash->tmpdata = (void *)dbf;
 
+    /* Allocate parameter sub-gsu, fill dbf field. 
+     * dbf allocation is 1 to 1 accompanied by
+     * gsu_scalar_ext allocation. */
+
+    struct gsu_scalar_ext *dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+    dbf_carrier->std = gdbm_gsu_ext.std;
+    dbf_carrier->dbf = dbf;
+    tied_param->u.hash->tmpdata = (void *)dbf_carrier;
+
+    /* Fill also file path field */
+    if (*resource_name != '/') {
+        /* Code copied from check_autoload() */
+        resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name);
+        resource_name = xsymlink(resource_name, 1);
+    }
+    dbf_carrier->dbfile_path = ztrdup(resource_name);
     return 0;
 }
 
@@ -162,6 +228,53 @@ bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
 }
 
 /**/
+static int
+bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    Param pm;
+    char *pmname;
+
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam(nam, "no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h != &gdbm_hash_gsu) {
+        zwarnnam(nam, "not a tied gdbm parameter: %s", pmname);
+        return 1;
+    }
+
+    /* Paranoia, it *will* be always set */
+    if (((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path) {
+        setsparam("REPLY", ztrdup(((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path));
+    } else {
+        setsparam("REPLY", ztrdup(""));
+    }
+
+    return 0;
+}
+
+/*
+ * The param is actual param in hash – always, because
+ * getgdbmnode creates every new key seen. However, it
+ * might be not PM_UPTODATE - which means that database
+ * wasn't yet queried.
+ *
+ * It will be left in this state if database doesn't
+ * contain such key. That might be a drawback, maybe
+ * setting to empty value has sense, as no other writer
+ * can exist. This would remove subtle hcalloc(1) leak.
+ */
+
+/**/
 static char *
 gdbmgetfn(Param pm)
 {
@@ -169,18 +282,58 @@ gdbmgetfn(Param pm)
     int ret;
     GDBM_FILE dbf;
 
-    key.dptr = pm->node.nam;
-    key.dsize = strlen(key.dptr) + 1;
+    /* Key already retrieved? There is no sense of asking the
+     * database again, because:
+     * - there can be only multiple readers
+     * - so, no writer + reader use is allowed
+     *
+     * Thus:
+     * - if we are writers, we for sure have newest copy of data
+     * - if we are readers, we for sure have newest copy of data
+     */
+    if ( pm->node.flags & PM_UPTODATE ) {
+        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    }
+
+    /* Unmetafy key. GDBM fits nice into this
+     * process, as it uses length of data */
+    int umlen = 0;
+    char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+
+    key.dptr = umkey;
+    key.dsize = umlen;
+
+    dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
+
+    if((ret = gdbm_exists(dbf, key))) {
+        /* We have data – store it, return it */
+        pm->node.flags |= PM_UPTODATE;
 
-    dbf = (GDBM_FILE)(pm->u.hash->tmpdata);
-    ret = gdbm_exists(dbf, key);
-    if(ret) {
         content = gdbm_fetch(dbf, key);
-    } else {
-        content.dptr = dupstring("");
+
+        /* Ensure there's no leak */
+        if (pm->u.str) {
+            zsfree(pm->u.str);
+        }
+
+        /* Metafy returned data. All fits - metafy
+         * can obtain data length to avoid using \0 */
+        pm->u.str = metafy(content.dptr, content.dsize, META_DUP);
+
+        /* Free key, restoring its original length */
+        set_length(umkey, umlen);
+        zsfree(umkey);
+
+        /* Can return pointer, correctly saved inside hash */
+        return pm->u.str;
     }
 
-    return content.dptr;
+    /* Free key, restoring its original length */
+    set_length(umkey, umlen);
+    zsfree(umkey);
+
+    /* Can this be "" ? */
+    return (char *) hcalloc(1);
 }
 
 /**/
@@ -190,78 +343,128 @@ gdbmsetfn(Param pm, char *val)
     datum key, content;
     GDBM_FILE dbf;
 
-    key.dptr = pm->node.nam;
-    key.dsize = strlen(key.dptr) + 1;
-    content.dptr = val;
-    content.dsize = strlen(content.dptr) + 1;
+    /* Set is done on parameter and on database.
+     * See the allowed workers / readers comment
+     * at gdbmgetfn() */
 
-    dbf = (GDBM_FILE)(pm->u.hash->tmpdata);
-    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
+    /* Parameter */
+    if (pm->u.str) {
+        zsfree(pm->u.str);
+        pm->u.str = NULL;
+        pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    if (val) {
+        pm->u.str = ztrdup(val);
+        pm->node.flags |= PM_UPTODATE;
+    }
+
+    /* Database */
+    dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
+    if (dbf) {
+        int umlen = 0;
+        char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+
+        key.dptr = umkey;
+        key.dsize = umlen;
+
+        if (val) {
+            /* Unmetafy with exact zalloc size */
+            char *umval = unmetafy_zalloc(val,&umlen);
+
+            /* Store */
+            content.dptr = umval;
+            content.dsize = umlen;
+            (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
+
+            /* Free */
+            set_length(umval, umlen);
+            zsfree(umval);
+        } else {
+            (void)gdbm_delete(dbf, key);
+        }
+
+        /* Free key */
+        set_length(umkey, key.dsize);
+        zsfree(umkey);
+    }
 }
 
 /**/
 static void
 gdbmunsetfn(Param pm, UNUSED(int um))
 {
-    datum key;
-    GDBM_FILE dbf;
-
-    key.dptr = pm->node.nam;
-    key.dsize = strlen(key.dptr) + 1;
-
-    dbf = (GDBM_FILE)(pm->u.hash->tmpdata);
-    (void)gdbm_delete(dbf, key);
+    /* Set with NULL */
+    gdbmsetfn(pm, NULL);
 }
 
 /**/
 static HashNode
 getgdbmnode(HashTable ht, const char *name)
 {
-    int len;
-    char *nameu;
-    Param pm = NULL;
-
-    nameu = dupstring(name);
-    unmetafy(nameu, &len);
-
-    pm = (Param) hcalloc(sizeof(struct param));
-    pm->node.nam = nameu;
-    pm->node.flags = PM_SCALAR;
-    pm->gsu.s = &gdbm_gsu;
-    pm->u.hash = ht;
+    HashNode hn = gethashnode2( ht, name );
+    Param val_pm = (Param) hn;
+
+    /* Entry for key doesn't exist? Create it now,
+     * it will be interfacing between the database
+     * and Zsh - through special gdbm_gsu. So, any
+     * seen key results in new interfacing parameter.
+     *
+     * Previous code was returning heap arena Param
+     * that wasn't actually added to the hash. It was
+     * plainly name / database-key holder. Here we
+     * add the Param to its hash, it is not PM_UPTODATE.
+     * It will be loaded from database *and filled*
+     * or left in that state if the database doesn't
+     * contain it.
+     *
+     * No heap arena memory is used, memory usage is
+     * now limited - by number of distinct keys seen,
+     * not by number of key *uses*.
+     * */
+
+    if ( ! val_pm ) {
+        val_pm = (Param) zshcalloc( sizeof (*val_pm) );
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
+        val_pm->gsu.s = (GsuScalar) ht->tmpdata;
+        ht->addnode( ht, ztrdup( name ), val_pm ); // sets pm->node.nam
+    }
 
-    return &pm->node;
+    return (HashNode) val_pm;
 }
 
 /**/
 static void
 scangdbmkeys(HashTable ht, ScanFunc func, int flags)
 {
-    Param pm = NULL;
-    datum key, content;
-    GDBM_FILE dbf = (GDBM_FILE)(ht->tmpdata);
-
-    pm = (Param) hcalloc(sizeof(struct param));
-
-    pm->node.flags = PM_SCALAR;
-    pm->gsu.s = &nullsetscalar_gsu;
+    datum key;
+    GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf;
 
+    /* Iterate keys adding them to hash, so
+     * we have Param to use in `func` */
     key = gdbm_firstkey(dbf);
 
     while(key.dptr) {
-	content = gdbm_fetch(dbf, key);
+        /* This returns database-interfacing Param,
+         * it will return u.str or first fetch data
+         * if not PM_UPTODATE (newly created) */
+        char *zkey = metafy(key.dptr, key.dsize, META_DUP);
+        HashNode hn = getgdbmnode(ht, zkey);
+        zsfree( zkey );
 
-	pm->node.nam = key.dptr;
-	pm->u.str = content.dptr;
-	pm->gsu.s = &nullsetscalar_gsu;
-
-	func(&pm->node, flags);
+	func(hn, flags);
 
+        /* Iterate - no problem as interfacing Param
+         * will do at most only fetches, not stores */
         key = gdbm_nextkey(dbf, key);
     }
 
 }
 
+/*
+ * Replace database with new hash
+ */
+
 /**/
 static void
 gdbmhashsetfn(Param pm, HashTable ht)
@@ -274,7 +477,7 @@ gdbmhashsetfn(Param pm, HashTable ht)
     if (!pm->u.hash || pm->u.hash == ht)
 	return;
 
-    if (!(dbf = (GDBM_FILE)(pm->u.hash->tmpdata)))
+    if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf))
 	return;
 
     key = gdbm_firstkey(dbf);
@@ -292,6 +495,9 @@ gdbmhashsetfn(Param pm, HashTable ht)
     if (!ht)
 	return;
 
+     /* Put new strings into database, waiting
+      * for their interfacing-Params to be created */
+
     for (i = 0; i < ht->hsize; i++)
 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
 	    struct value v;
@@ -301,16 +507,31 @@ gdbmhashsetfn(Param pm, HashTable ht)
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
 
-	    key.dptr = v.pm->node.nam;
-	    key.dsize = strlen(key.dptr) + 1;
+            /* Unmetafy key */
+            int umlen = 0;
+            char *umkey = unmetafy_zalloc(v.pm->node.nam,&umlen);
+
+	    key.dptr = umkey;
+	    key.dsize = umlen;
 
 	    queue_signals();
 
-	    content.dptr = getstrvalue(&v);
-	    content.dsize = strlen(content.dptr) + 1;
+            /* Unmetafy */
+            char *umval = unmetafy_zalloc(getstrvalue(&v),&umlen);
 
+            /* Store */
+	    content.dptr = umval;
+	    content.dsize = umlen;
 	    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);	
 
+            /* Free - unmetafy_zalloc allocates exact required
+             * space, however unmetafied string can have zeros
+             * in content, so we must first fill with non-0 bytes */
+            set_length(umval, content.dsize);
+            zsfree(umval);
+            set_length(umkey, key.dsize);
+            zsfree(umkey);
+
 	    unqueue_signals();
 	}
 }
@@ -319,15 +540,19 @@ gdbmhashsetfn(Param pm, HashTable ht)
 static void
 gdbmuntie(Param pm)
 {
-    GDBM_FILE dbf = (GDBM_FILE)(pm->u.hash->tmpdata);
+    GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf;
     HashTable ht = pm->u.hash;
 
     if (dbf) { /* paranoia */
 	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
-	gdbm_close(dbf);
-    }
+        gdbm_close(dbf);
 
-    ht->tmpdata = NULL;
+        /* Let hash fields know there's no backend */
+        ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL;
+
+        /* Remove from list of tied parameters */
+        remove_tied_name(pm->node.nam);
+    }
 
     /* for completeness ... createspecialhash() should have an inverse */
     ht->getnode = ht->getnode2 = gethashnode2;
@@ -342,20 +567,28 @@ static void
 gdbmhashunsetfn(Param pm, UNUSED(int exp))
 {
     gdbmuntie(pm);
-    /* hash table is now normal, so proceed normally... */
+
+    /* Remember custom GSU structure assigned to
+     * u.hash->tmpdata before hash gets deleted */
+    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+
+    /* Uses normal unsetter. Will delete all owned
+     * parameters and also hashtable. */
     pm->gsu.h->setfn(pm, NULL);
+
+    /* Don't need custom GSU structure with its
+     * GDBM_FILE pointer anymore */
+    zsfree( gsu_ext->dbfile_path );
+    zfree( gsu_ext, sizeof(struct gsu_scalar_ext));
+
     pm->node.flags |= PM_UNSET;
 }
 
-#else
-# error no gdbm
-#endif /* have gdbm */
-
 static struct features module_features = {
     bintab, sizeof(bintab)/sizeof(*bintab),
     NULL, 0,
     NULL, 0,
-    NULL, 0,
+    patab, sizeof(patab)/sizeof(*patab),
     0
 };
 
@@ -385,6 +618,7 @@ enables_(Module m, int **enables)
 int
 boot_(UNUSED(Module m))
 {
+    zgdbm_tied = zshcalloc((1) * sizeof(char *));
     return 0;
 }
 
@@ -392,6 +626,7 @@ boot_(UNUSED(Module m))
 int
 cleanup_(Module m)
 {
+    /* This frees `zgdbm_tied` */
     return setfeatureenables(m, &module_features, NULL);
 }
 
@@ -401,3 +636,153 @@ finish_(UNUSED(Module m))
 {
     return 0;
 }
+
+/*********************
+ * Utility functions *
+ *********************/
+
+static Param createhash( char *name, int flags ) {
+    Param pm;
+    HashTable ht;
+
+    pm = createparam(name, PM_SPECIAL | PM_HASHED);
+    if (!pm) {
+        return NULL;
+    }
+
+    if (pm->old)
+	pm->level = locallevel;
+
+    /* This creates standard hash. */
+    ht = pm->u.hash = newparamtable(32, name);
+    if (!pm->u.hash) {
+        paramtab->removenode(paramtab, name);
+        paramtab->freenode(&pm->node);
+        zwarnnam(name, "Out of memory when allocating hash");
+    }
+
+    /* These provide special features */
+    ht->getnode = ht->getnode2 = getgdbmnode;
+    ht->scantab = scangdbmkeys;
+
+    return pm;
+}
+
+/*
+ * Adds parameter name to `zgdbm_tied`
+ */
+
+static int append_tied_name( const char *name ) {
+    int old_len = arrlen(zgdbm_tied);
+    char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *));
+
+    /* Copy */
+    char **p = zgdbm_tied;
+    char **dst = new_zgdbm_tied;
+    while (*p) {
+        *dst++ = *p++;
+    }
+
+    /* Append new one */
+    *dst = ztrdup(name);
+
+    /* Substitute, free old one */
+    zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
+    zgdbm_tied = new_zgdbm_tied;
+
+    return 0;
+}
+
+/*
+ * Removes parameter name from `zgdbm_tied`
+ */
+
+static int remove_tied_name( const char *name ) {
+    int old_len = arrlen(zgdbm_tied);
+
+    /* Two stage, to always have arrlen() == zfree-size - 1.
+     * Could do allocation and revert when `not found`, but
+     * what would be better about that. */
+
+    /* Find one to remove */
+    char **p = zgdbm_tied;
+    while (*p) {
+        if (0==strcmp(name,*p)) {
+            break;
+        }
+        p++;
+    }
+
+    /* Copy x+1 to x */
+    while (*p) {
+        *p=*(p+1);
+        p++;
+    }
+
+    /* Second stage. Size changed? Only old_size-1
+     * change is possible, but.. paranoia way */
+    int new_len = arrlen(zgdbm_tied);
+    if (new_len != old_len) {
+        char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *));
+
+        /* Copy */
+        p = zgdbm_tied;
+        char **dst = new_zgdbm_tied;
+        while (*p) {
+            *dst++ = *p++;
+        }
+        *dst = NULL;
+
+        /* Substitute, free old one */
+        zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
+        zgdbm_tied = new_zgdbm_tied;
+    }
+
+    return 0;
+}
+
+/*
+ * Unmetafy that:
+ * - duplicates bufer to work on it,
+ * - does zalloc of exact size for the new string,
+ * - restores work buffer to original content, to restore strlen
+ *
+ * No zsfree()-confusing string will be produced.
+ */
+static char *unmetafy_zalloc(const char *to_copy, int *new_len) {
+    char *work, *to_return;
+    int my_new_len = 0;
+
+    work = ztrdup(to_copy);
+    work = unmetafy(work,&my_new_len);
+
+    if (new_len)
+        *new_len = my_new_len;
+
+    /* This string can be correctly zsfree()-d */
+    to_return = (char *) zalloc((my_new_len+1)*sizeof(char));
+    memcpy(to_return, work, sizeof(char)*my_new_len); // memcpy handles $'\0'
+    to_return[my_new_len]='\0';
+
+    /* Restore original strlen and correctly free */
+    strcpy(work, to_copy);
+    zsfree(work);
+
+    return to_return;
+}
+
+/*
+ * For zsh-allocator, rest of Zsh seems to use
+ * free() instead of zsfree(), and such length
+ * restoration causes slowdown, but all is this
+ * way strict - correct */
+static void set_length(char *buf, int size) {
+    buf[size]='\0';
+    while ( -- size >= 0 ) {
+        buf[size]=' ';
+    }
+}
+
+#else
+# error no gdbm
+#endif /* have gdbm */
diff --git a/Src/Modules/db_gdbm.mdd b/Src/Modules/db_gdbm.mdd
index ce7926bd9..210c22177 100644
--- a/Src/Modules/db_gdbm.mdd
+++ b/Src/Modules/db_gdbm.mdd
@@ -7,6 +7,6 @@ fi
 '
 load=no
 
-autofeatures="b:ztie b:zuntie"
+autofeatures="b:ztie b:zuntie b:zgdbmpath p:zgdbm_tied"
 
 objects="db_gdbm.o"
diff --git a/Src/Modules/example.c b/Src/Modules/example.c
index 45ca2cffa..c80c9e7b2 100644
--- a/Src/Modules/example.c
+++ b/Src/Modules/example.c
@@ -69,7 +69,8 @@ bin_example(char *nam, char **args, Options ops, UNUSED(int func))
     intparam = i;
     zsfree(strparam);
     strparam = ztrdup(*oargs ? *oargs : "");
-    freearray(arrparam);
+    if (arrparam)
+	freearray(arrparam);
     arrparam = zarrdup(oargs);
     return 0;
 }
diff --git a/Src/Modules/mathfunc.c b/Src/Modules/mathfunc.c
index 451b3cfeb..a7e8b294c 100644
--- a/Src/Modules/mathfunc.c
+++ b/Src/Modules/mathfunc.c
@@ -411,7 +411,11 @@ math_func(char *name, int argc, mnumber *argv, int id)
       break;
 
   case MF_SCALB:
+#ifdef HAVE_SCALBN
+      retd = scalbn(argd, argi);
+#else
       retd = scalb(argd, argi);
+#endif
       break;
 
 #ifdef HAVE_SIGNGAM
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index edb051785..10c47d214 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -167,7 +167,7 @@ unsetpmcommand(Param pm, UNUSED(int exp))
 
 /**/
 static void
-setpmcommands(UNUSED(Param pm), HashTable ht)
+setpmcommands(Param pm, HashTable ht)
 {
     int i;
     HashNode hn;
@@ -190,7 +190,15 @@ setpmcommands(UNUSED(Param pm), HashTable ht)
 
 	    cmdnamtab->addnode(cmdnamtab, ztrdup(hn->nam), &cn->node);
 	}
-    deleteparamtable(ht);
+    /*
+     * On full-array assignment ht is a temporary hash with the default
+     * get/set functions, whereas pm->u.hash has the special $commands
+     * get/set functions.  Do not assign ht to pm, just delete it.
+     *
+     * On append, ht and pm->u.hash are the same table, don't delete.
+     */
+    if (ht != pm->u.hash)
+	deleteparamtable(ht);
 }
 
 static const struct gsu_scalar pmcommand_gsu =
@@ -330,7 +338,7 @@ unsetpmfunction(Param pm, UNUSED(int exp))
 
 /**/
 static void
-setfunctions(UNUSED(Param pm), HashTable ht, int dis)
+setfunctions(Param pm, HashTable ht, int dis)
 {
     int i;
     HashNode hn;
@@ -349,7 +357,9 @@ setfunctions(UNUSED(Param pm), HashTable ht, int dis)
 
 	    setfunction(hn->nam, ztrdup(getstrvalue(&v)), dis);
 	}
-    deleteparamtable(ht);
+    /* See setpmcommands() above */
+    if (ht != pm->u.hash)
+	deleteparamtable(ht);
 }
 
 /**/
@@ -487,11 +497,6 @@ scanfunctions(UNUSED(HashTable ht), ScanFunc func, int flags, int dis)
 			    strcat(pm.u.str, " \"$@\"");
 			} else
 			    pm.u.str = dyncat(start, t);
-			/*
-			 * TBD: Is this unmetafy correct?  Surely as this
-			 * is a parameter value it stays metafied?
-			 */
-			unmetafy(pm.u.str, NULL);
 			zsfree(t);
 
 			if (shf->redir) {
@@ -520,6 +525,98 @@ scanpmdisfunctions(HashTable ht, ScanFunc func, int flags)
     scanfunctions(ht, func, flags, DISABLED);
 }
 
+/* Functions for the functions_source special parameter. */
+
+/* Retrieve the source file for a function by explicit name */
+
+/**/
+static HashNode
+getfunction_source(UNUSED(HashTable ht), const char *name, int dis)
+{
+    Shfunc shf;
+    Param pm = NULL;
+
+    pm = (Param) hcalloc(sizeof(struct param));
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR|PM_READONLY;
+    pm->gsu.s = dis ? &pmdisfunction_gsu :  &pmfunction_gsu;
+
+    if ((shf = (Shfunc) shfunctab->getnode2(shfunctab, name)) &&
+	(dis ? (shf->node.flags & DISABLED) : !(shf->node.flags & DISABLED))) {
+	pm->u.str = getshfuncfile(shf);
+	if (!pm->u.str)
+	    pm->u.str = dupstring("");
+    }
+    return &pm->node;
+}
+
+/* Retrieve the source file for functions by scanning the table */
+
+/**/
+static void
+scanfunctions_source(UNUSED(HashTable ht), ScanFunc func, int flags, int dis)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    memset((void *)&pm, 0, sizeof(struct param));
+    pm.node.flags = PM_SCALAR|PM_READONLY;
+    pm.gsu.s = dis ? &pmdisfunction_gsu : &pmfunction_gsu;
+
+    for (i = 0; i < shfunctab->hsize; i++) {
+	for (hn = shfunctab->nodes[i]; hn; hn = hn->next) {
+	    if (dis ? (hn->flags & DISABLED) : !(hn->flags & DISABLED)) {
+		pm.node.nam = hn->nam;
+		if (func != scancountparams &&
+		    ((flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)) ||
+		     !(flags & SCANPM_WANTKEYS))) {
+		    pm.u.str = getshfuncfile((Shfunc)hn);
+		    if (!pm.u.str)
+			pm.u.str = dupstring("");
+		}
+		func(&pm.node, flags);
+	    }
+	}
+    }
+}
+
+/* Param table entry for retrieving functions_source element */
+
+/**/
+static HashNode
+getpmfunction_source(HashTable ht, const char *name)
+{
+    return getfunction_source(ht, name, 0);
+}
+
+/* Param table entry for retrieving ds_functions_source element */
+
+/**/
+static HashNode
+getpmdisfunction_source(HashTable ht, const char *name)
+{
+    return getfunction_source(ht, name, 1);
+}
+
+/* Param table entry for scanning functions_source table */
+
+/**/
+static void
+scanpmfunction_source(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions_source(ht, func, flags, 0);
+}
+
+/* Param table entry for scanning dis_functions_source table */
+
+/**/
+static void
+scanpmdisfunction_source(HashTable ht, ScanFunc func, int flags)
+{
+    scanfunctions_source(ht, func, flags, 1);
+}
+
 /* Functions for the funcstack special parameter. */
 
 /**/
@@ -850,7 +947,7 @@ unsetpmoption(Param pm, UNUSED(int exp))
 
 /**/
 static void
-setpmoptions(UNUSED(Param pm), HashTable ht)
+setpmoptions(Param pm, HashTable ht)
 {
     int i;
     HashNode hn;
@@ -875,7 +972,9 @@ setpmoptions(UNUSED(Param pm), HashTable ht)
 			      (val && strcmp(val, "off")), 0, opts))
 		zwarn("can't change option: %s", hn->nam);
 	}
-    deleteparamtable(ht);
+    /* See setpmcommands() above */
+    if (ht != pm->u.hash)
+	deleteparamtable(ht);
 }
 
 static const struct gsu_scalar pmoption_gsu =
@@ -1414,7 +1513,7 @@ unsetpmnameddir(Param pm, UNUSED(int exp))
 
 /**/
 static void
-setpmnameddirs(UNUSED(Param pm), HashTable ht)
+setpmnameddirs(Param pm, HashTable ht)
 {
     int i;
     HashNode hn, next, hd;
@@ -1456,7 +1555,9 @@ setpmnameddirs(UNUSED(Param pm), HashTable ht)
 
     i = opts[INTERACTIVE];
     opts[INTERACTIVE] = 0;
-    deleteparamtable(ht);
+    /* See setpmcommands() above */
+    if (ht != pm->u.hash)
+	deleteparamtable(ht);
     opts[INTERACTIVE] = i;
 }
 
@@ -1637,7 +1738,7 @@ unsetpmsalias(Param pm, UNUSED(int exp))
 
 /**/
 static void
-setaliases(HashTable alht, UNUSED(Param pm), HashTable ht, int flags)
+setaliases(HashTable alht, Param pm, HashTable ht, int flags)
 {
     int i;
     HashNode hn, next, hd;
@@ -1673,7 +1774,9 @@ setaliases(HashTable alht, UNUSED(Param pm), HashTable ht, int flags)
 		alht->addnode(alht, ztrdup(hn->nam),
 			      createaliasnode(ztrdup(val), flags));
 	}
-    deleteparamtable(ht);
+    /* See setpmcommands() above */
+    if (ht != pm->u.hash)
+	deleteparamtable(ht);
 }
 
 /**/
@@ -2100,6 +2203,8 @@ static struct paramdef partab[] = {
 	    NULL, getpmdisbuiltin, scanpmdisbuiltins),
     SPECIALPMDEF("dis_functions", 0, 
 	    &pmdisfunctions_gsu, getpmdisfunction, scanpmdisfunctions),
+    SPECIALPMDEF("dis_functions_source", PM_READONLY, NULL,
+		 getpmdisfunction_source, scanpmdisfunction_source),
     SPECIALPMDEF("dis_galiases", 0,
 	    &pmdisgaliases_gsu, getpmdisgalias, scanpmdisgaliases),
     SPECIALPMDEF("dis_patchars", PM_ARRAY|PM_READONLY,
@@ -2116,6 +2221,8 @@ static struct paramdef partab[] = {
 	    &funcstack_gsu, NULL, NULL),
     SPECIALPMDEF("functions", 0, &pmfunctions_gsu, getpmfunction,
 		 scanpmfunctions),
+    SPECIALPMDEF("functions_source", PM_READONLY, NULL,
+		 getpmfunction_source, scanpmfunction_source),
     SPECIALPMDEF("functrace", PM_ARRAY|PM_READONLY,
 	    &functrace_gsu, NULL, NULL),
     SPECIALPMDEF("galiases", 0,
diff --git a/Src/Modules/regex.c b/Src/Modules/regex.c
index edb7234d4..d02769ef0 100644
--- a/Src/Modules/regex.c
+++ b/Src/Modules/regex.c
@@ -111,7 +111,7 @@ zcond_regex_match(char **a, int id)
 		*x = NULL;
 	    }
 	    if (isset(BASHREMATCH)) {
-		setaparam("BASH_REMATCH", arr);
+		assignaparam("BASH_REMATCH", arr, 0);
 	    } else {
 		zlong offs;
 		char *ptr;
@@ -119,7 +119,7 @@ zcond_regex_match(char **a, int id)
 
 		m = matches;
 		s = metafy(lhstr + m->rm_so, m->rm_eo - m->rm_so, META_DUP);
-		setsparam("MATCH", s);
+		assignsparam("MATCH", s, 0);
 		/*
 		 * Count the characters before the match.
 		 */
@@ -133,7 +133,7 @@ zcond_regex_match(char **a, int id)
 		    ptr += clen;
 		    leftlen -= clen;
 		}
-		setiparam("MBEGIN", offs + !isset(KSHARRAYS));
+		assigniparam("MBEGIN", offs + !isset(KSHARRAYS), 0);
 		/*
 		 * Add on the characters in the match.
 		 */
@@ -144,7 +144,7 @@ zcond_regex_match(char **a, int id)
 		    ptr += clen;
 		    leftlen -= clen;
 		}
-		setiparam("MEND", offs + !isset(KSHARRAYS) - 1);
+		assigniparam("MEND", offs + !isset(KSHARRAYS) - 1, 0);
 		if (nelem) {
 		    char **mbegin, **mend, **bptr, **eptr;
 		    bptr = mbegin = (char **)zalloc(sizeof(char *)*(nelem+1));
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index 1ee61c00b..3eecd7e95 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -313,7 +313,7 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
     int flags = O_NOCTTY | append | ((append || write) ?
 	(read ? O_RDWR : O_WRONLY) : O_RDONLY);
     char *opt, *ptr, *nextopt, *fdvar;
-    int o, fd, explicit = -1;
+    int o, fd, moved_fd, explicit = -1;
     mode_t perms = 0666;
 #if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
     int fdflags;
@@ -376,22 +376,32 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
 	zwarnnam(nam, "can't open file %s: %e", *args, errno);
 	return 1;
     }
-    fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd);
-    if (fd == -1) {
+    moved_fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd);
+    if (moved_fd == -1) {
 	zwarnnam(nam, "can't open file %s", *args);
 	return 1;
     }
 
-#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
+#ifdef FD_CLOEXEC
+#ifdef O_CLOEXEC
+    /*
+     * the O_CLOEXEC is a flag attached to the *file descriptor*, not the
+     * *open file description* so it doesn't survive a dup(). If that flag was
+     * requested and the fd was moved, we need to reapply it to the moved fd
+     * even if the original one was open with O_CLOEXEC
+     */
+    if ((flags & O_CLOEXEC) && fd != moved_fd)
+#else
     if (fdflags)
-	fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
+#endif /* O_CLOEXEC */
+	fcntl(moved_fd, F_SETFD, FD_CLOEXEC);
+#endif /* FD_CLOEXEC */
     if (explicit == -1) {
-	fdtable[fd] = FDT_EXTERNAL;
-	setiparam(fdvar, fd);
-	/* if setting the variable failed, close fd to avoid leak */
+	fdtable[moved_fd] = FDT_EXTERNAL;
+	setiparam(fdvar, moved_fd);
+	/* if setting the variable failed, close moved_fd to avoid leak */
 	if (errflag)
-	    zclose(fd);
+	    zclose(moved_fd);
     }
 
     return 0;
@@ -521,7 +531,7 @@ static int
 bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     int cloexec = 1, unlock = 0, readlock = 0;
-    time_t timeout = 0;
+    zlong timeout = -1;
     char *fdvar = NULL;
 #ifdef HAVE_FCNTL_H
     struct flock lck;
@@ -573,7 +583,7 @@ bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		} else {
 		    optarg = *args++;
 		}
-		timeout = (time_t)mathevali(optarg);
+		timeout = mathevali(optarg);
 		break;
 
 	    case 'u':
@@ -650,7 +660,7 @@ bin_zsystem_flock(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    sleep(1);
 	}
     } else {
-	while (fcntl(flock_fd, F_SETLKW, &lck) < 0) {
+	while (fcntl(flock_fd, timeout == 0 ? F_SETLK : F_SETLKW, &lck) < 0) {
 	    if (errflag)
 		return 1;
 	    if (errno == EINTR)
diff --git a/Src/Modules/system.mdd b/Src/Modules/system.mdd
index eed0c1b9d..00a3e7896 100644
--- a/Src/Modules/system.mdd
+++ b/Src/Modules/system.mdd
@@ -15,7 +15,12 @@ errnames.c: errnames1.awk errnames2.awk $(dir_top)/config.h @ERRNO_H@
 		touch errtmp.out; \
 	   else \
 		$(AWK) -f $(sdir)/errnames1.awk @ERRNO_H@ >errtmp.c; \
-		$(CPP) errtmp.c >errtmp.out; \
+		case "`$(CPP) --version </dev/null 2>&1`" in \
+		*"Free Software Foundation"*) \
+		$(CPP) -P errtmp.c >errtmp.out;; \
+		*) \
+		$(CPP) errtmp.c >errtmp.out;; \
+		esac; \
 	   fi
 	   $(AWK) -f $(sdir)/errnames2.awk errtmp.out > $@
 	   rm -f errtmp.c errtmp.out
diff --git a/Src/Modules/tcp.c b/Src/Modules/tcp.c
index dec12142b..0bbce5d49 100644
--- a/Src/Modules/tcp.c
+++ b/Src/Modules/tcp.c
@@ -343,7 +343,8 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
 {
     int herrno, err=1, destport, force=0, verbose=0, test=0, targetfd=0;
     ZSOCKLEN_T  len;
-    char **addrp, *desthost, *localname, *remotename;
+    char **addrp, *desthost;
+    const char *localname, *remotename;
     struct hostent *zthost = NULL, *ztpeer = NULL;
     struct servent *srv;
     Tcp_session sess = NULL;
diff --git a/Src/Modules/terminfo.c b/Src/Modules/terminfo.c
index e0439afca..bbd325899 100644
--- a/Src/Modules/terminfo.c
+++ b/Src/Modules/terminfo.c
@@ -99,7 +99,7 @@ bin_echoti(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	return 1;
     }
     /* check that the number of arguments provided is not too high */
-    if (arrlen(argv) > 9) {
+    if (arrlen_gt(argv, 9)) {
         zwarnnam(name, "too many arguments");
         return 1;
     }
diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c
index 0ef753915..3c1bef58f 100644
--- a/Src/Modules/zpty.c
+++ b/Src/Modules/zpty.c
@@ -331,6 +331,7 @@ newptycmd(char *nam, char *pname, char **args, int echo, int nblock)
 	/* This code copied from the clone module, except for getting *
 	 * the descriptor from get_pty() and duplicating it to 0/1/2. */
 
+	deletehookfunc("exit", ptyhook);
 	clearjobtab(0);
 	ppid = getppid();
 	mypid = getpid();
@@ -544,7 +545,8 @@ ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch)
 	p = dupstring(args[1]);
 	tokenize(p);
 	remnulargs(p);
-	if (!(prog = patcompile(p, PAT_STATIC, NULL))) {
+	/* Signals handlers might stomp PAT_STATIC */
+	if (!(prog = patcompile(p, PAT_ZDUP, NULL))) {
 	    zwarnnam(nam, "bad pattern: %s", args[1]);
 	    return 1;
 	}
@@ -682,9 +684,14 @@ ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch)
 	write_loop(1, buf, used);
     }
 
-    if (seen && (!prog || matchok || !mustmatch))
-	return 0;
-    return cmd->fin + 1;
+    {
+	int ret = cmd->fin + 1;
+	if (seen && (!prog || matchok || !mustmatch))
+	    ret = 0;
+	if (prog)
+	    freepatprog(prog);
+	return ret;
+    }
 }
 
 static int
@@ -846,6 +853,7 @@ bin_zpty(char *nam, char **args, Options ops, UNUSED(int func))
     }
 }
 
+/**/
 static int
 ptyhook(UNUSED(Hookdef d), UNUSED(void *dummy))
 {
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index 90d8faf2e..82542cf4f 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -472,7 +472,7 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	Patprog prog;
 	char *pat;
 
-	if (arrlen(args) < 2) {
+	if (arrlen_lt(args, 2)) {
 	    zwarnnam(nam, "not enough arguments");
 	    return 1;
 	}
@@ -491,7 +491,7 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	Style s;
 	char *context, *stylename;
 
-	switch (arrlen(args)) {
+	switch (arrlen_ge(args, 3) ? 3 : arrlen(args)) {
 	case 2:
 	    context = args[0];
 	    stylename = args[1];
@@ -510,25 +510,33 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    zwarnnam(nam, "too many arguments");
 	    return 1;
 	}
+
+	queue_signals();	/* Protect PAT_STATIC */
+
 	if (context) {
 	    tokenize(context);
 	    zstyle_contprog = patcompile(context, PAT_STATIC, NULL);
 
-	    if (!zstyle_contprog)
+	    if (!zstyle_contprog) {
+		unqueue_signals();
 		return 1;
+	    }
 	} else
 	    zstyle_contprog = NULL;
 
 	if (stylename) {
 	    s = (Style)zstyletab->getnode2(zstyletab, stylename);
-	    if (!s)
+	    if (!s) {
+		unqueue_signals();
 		return 1;
+	    }
 	    zstyletab->printnode(&s->node, list);
 	} else {
 	    scanhashtable(zstyletab, 1, 0, 0,
 			  zstyletab->printnode, list);
 	}
 
+	unqueue_signals();
 	return 0;
     }
     switch (args[0][1]) {
@@ -675,14 +683,20 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    char **vals;
 	    Patprog prog;
 
+	    queue_signals();	/* Protect PAT_STATIC */
+
 	    tokenize(args[3]);
 
 	    if ((vals = lookupstyle(args[1], args[2])) &&
 		(prog = patcompile(args[3], PAT_STATIC, NULL))) {
 		while (*vals)
-		    if (pattry(prog, *vals++))
+		    if (pattry(prog, *vals++)) {
+			unqueue_signals();
 			return 0;
+		    }
 	    }
+
+	    unqueue_signals();
 	    return 1;
 	}
 	break;
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index 023c41814..3e9834560 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -125,7 +125,7 @@ struct cmatch {
 #define CMF_REMOVE   (1<< 1)	/* remove the suffix */
 #define CMF_ISPAR    (1<< 2)	/* is paramter expansion */
 #define CMF_PARBR    (1<< 3)	/* paramter expansion with a brace */
-#define CMF_PARNEST  (1<< 4)	/* nested paramter expansion */
+#define CMF_PARNEST  (1<< 4)	/* nested parameter expansion */
 #define CMF_NOLIST   (1<< 5)	/* should not be listed */
 #define CMF_DISPLINE (1<< 6)	/* display strings one per line */
 #define CMF_HIDE     (1<< 7)	/* temporarily hide this one */
@@ -153,16 +153,24 @@ struct cmatcher {
     Cpattern line;		/* what matches on the line */
     int llen;			/* length of line pattern */
     Cpattern word;		/* what matches in the word */
-    int wlen;			/* length of word pattern */
+    int wlen;			/* length of word pattern, or:
+				    -1: word pattern is one asterisk
+				    -2: word pattern is two asterisks */
     Cpattern left;		/* left anchor */
     int lalen;			/* length of left anchor */
     Cpattern right;		/* right anchor */
     int ralen;			/* length of right anchor */
 };
 
+/* Flags for cmatcher::flags */
+/* Upon match, insert the string from the line rather than the string
+ * from the trial completion ("word"). */
 #define CMF_LINE  1
+/* Match with an anchor on the left. */
 #define CMF_LEFT  2
+/* Match with an anchor on the right. */
 #define CMF_RIGHT 4
+/* ... */
 #define CMF_INTER 8
 
 /*
@@ -229,7 +237,6 @@ struct cpattern {
  * the anchor. */
 
 typedef struct cline *Cline;
-typedef struct clsub Clsub;
 
 struct cline {
     Cline next;
@@ -285,14 +292,14 @@ struct menuinfo {
 
 /* Flags for compadd and addmatches(). */
 
-#define CAF_QUOTE    1
-#define CAF_NOSORT   2
-#define CAF_MATCH    4
-#define CAF_UNIQCON  8
-#define CAF_UNIQALL 16
-#define CAF_ARRAYS  32
-#define CAF_KEYS    64
-#define CAF_ALL    128
+#define CAF_QUOTE    1    /* compadd -Q: positional arguments already quoted */
+#define CAF_NOSORT   2    /* compadd -V: don't sort */
+#define CAF_MATCH    4    /* compadd without -U: do matching */
+#define CAF_UNIQCON  8    /* compadd -2: don't deduplicate */
+#define CAF_UNIQALL 16    /* compadd -1: deduplicate */
+#define CAF_ARRAYS  32    /* compadd -a or -k: array/assoc parameter names */
+#define CAF_KEYS    64    /* compadd -k: assoc parameter names */
+#define CAF_ALL    128    /* compadd -C: _all_matches */
 
 /* Data for compadd and addmatches() */
 
@@ -367,7 +374,7 @@ typedef void (*CLPrintFunc)(Cmgroup, Cmatch *, int, int, int, int);
 #define CP_QISUFFIX    (1 <<  CPN_QISUFFIX)
 #define CPN_COMPSTATE  9
 #define CP_COMPSTATE   (1 <<  CPN_COMPSTATE)
-
+/* See comprpms */
 #define CP_REALPARAMS  10
 #define CP_ALLREALS    ((unsigned int) 0x3ff)
 
@@ -424,7 +431,7 @@ typedef void (*CLPrintFunc)(Cmgroup, Cmatch *, int, int, int, int);
 #define CP_QUOTES      (1 << CPN_QUOTES)
 #define CPN_IGNORED    25
 #define CP_IGNORED     (1 << CPN_IGNORED)
-
+/* See compkpms */
 #define CP_KEYPARAMS   26
 #define CP_ALLKEYS     ((unsigned int) 0x3ffffff)
 
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 0c32be14b..5fc15b0d3 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -425,6 +425,7 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
 	}
     } else {
 	invalidatelist();
+	lastambig = isset(BASHAUTOLIST);
 	if (forcelist)
 	    clearlist = 1;
 	zlemetacs = 0;
@@ -835,6 +836,7 @@ callcompfunc(char *s, char *fn)
 	endparamscope();
 	lastcmd = 0;
 	incompfunc = icf;
+	startauto = 0;
 
 	if (!complist)
 	    uselist = 0;
@@ -882,8 +884,13 @@ callcompfunc(char *s, char *fn)
 		useline = 1, usemenu = 1;
 	    else if (strpfx("auto", compinsert))
 		useline = 1, usemenu = 2;
-	    else
+	    else {
 		useline = usemenu = 0;
+		/* if compstate[insert] was emptied, no unambiguous prefix
+		 * ever gets inserted so allow the next tab to already start
+		 * menu completion */
+		startauto = lastambig = isset(AUTOMENU);
+	    }
 
 	    if (useline && (p = strchr(compinsert, ':'))) {
 		insmnum = atoi(++p);
@@ -896,7 +903,7 @@ callcompfunc(char *s, char *fn)
 #endif
 	    }
 	}
-	startauto = ((compinsert &&
+	startauto = startauto || ((compinsert &&
 		      !strcmp(compinsert, "automenu-unambiguous")) ||
 		     (bashlistfirst && isset(AUTOMENU) &&
                       (!compinsert || !*compinsert)));
@@ -1043,6 +1050,13 @@ makecomplist(char *s, int incmd, int lst)
     }
 }
 
+/*
+ * Quote 's' according to compqstack, aka $compstate[all_quotes].
+ *
+ * If 'ign' is 1, skip the innermost quoting level.  Otherwise 'ign'
+ * must be 0.
+ */
+
 /**/
 mod_export char *
 multiquote(char *s, int ign)
@@ -1050,12 +1064,11 @@ multiquote(char *s, int ign)
     if (s) {
 	char *os = s, *p = compqstack;
 
-	if (p && *p && (ign < 1 || p[ign])) {
-	    if (ign > 0)
-		p += ign;
+	if (p && *p && (ign == 0 || p[1])) {
+	    if (ign)
+		p++;
 	    while (*p) {
-		if (ign >= 0 || p[1])
-		    s = quotestring(s, NULL, *p);
+		s = quotestring(s, *p);
 		p++;
 	    }
 	}
@@ -1065,6 +1078,12 @@ multiquote(char *s, int ign)
     return NULL;
 }
 
+/*
+ * tildequote(s, ign): Equivalent to multiquote(s, ign), except that if
+ * compqstack[0] == QT_BACKSLASH and s[0] == '~', then that tilde is not
+ * quoted.
+ */
+
 /**/
 mod_export char *
 tildequote(char *s, int ign)
@@ -1964,6 +1983,11 @@ get_user_var(char *nam)
     }
 }
 
+/*
+ * If KEYS, then NAME is an associative array; return its keys.
+ * Else, NAME is a plain array; return its elements.
+ */
+
 static char **
 get_data_arr(char *name, int keys)
 {
@@ -2025,16 +2049,17 @@ addmatch(char *str, int flags, char ***dispp, int line)
 int
 addmatches(Cadata dat, char **argv)
 {
+    /* ms: "match string" - string to use as completion.
+     * Overloaded at one place as a temporary. */
     char *s, *ms, *lipre = NULL, *lisuf = NULL, *lpre = NULL, *lsuf = NULL;
     char **aign = NULL, **dparr = NULL, *oaq = autoq, *oppre = dat->ppre;
     char *oqp = qipre, *oqs = qisuf, qc, **disp = NULL, *ibuf = NULL;
     char **arrays = NULL;
-    int lpl, lsl, pl, sl, bcp = 0, bcs = 0, bpadd = 0, bsadd = 0;
+    int lpl, lsl, bcp = 0, bcs = 0, bpadd = 0, bsadd = 0;
     int ppl = 0, psl = 0, ilen = 0;
     int llpl = 0, llsl = 0, nm = mnum, gflags = 0, ohp = haspattern;
     int isexact, doadd, ois = instring, oib = inbackt;
     Cline lc = NULL, pline = NULL, sline = NULL;
-    Cmatch cm;
     struct cmlist mst;
     Cmlist oms = mstack;
     Patprog cp = NULL, *pign = NULL;
@@ -2193,9 +2218,14 @@ addmatches(Cadata dat, char **argv)
 
 	    /* Test if there is an existing -P prefix. */
 	    if (dat->pre && *dat->pre) {
-		pl = pfxlen(dat->pre, lpre);
-		llpl -= pl;
-		lpre += pl;
+		int prefix_length = pfxlen(dat->pre, lpre);
+		if (dat->pre[prefix_length] == '\0' ||
+		    lpre[prefix_length] == '\0') {
+		    /* $compadd_args[-P] is a prefix of ${PREFIX}, or
+		     * vice-versa. */
+		    llpl -= prefix_length;
+		    lpre += prefix_length;
+		}
 	    }
 	}
 	/* Now duplicate the strings we have from the command line. */
@@ -2407,6 +2437,7 @@ addmatches(Cadata dat, char **argv)
 	if (dat->psuf)
 	    psl = strlen(dat->psuf);
 	for (; (s = *argv); argv++) {
+	    int sl;
 	    bpl = obpl;
 	    bsl = obsl;
 	    if (disp) {
@@ -2467,6 +2498,7 @@ addmatches(Cadata dat, char **argv)
 		goto next_array;
 	    }
 	    if (doadd) {
+		Cmatch cm;
 		Brinfo bp;
 
 		for (bp = obpl; bp; bp = bp->next)
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index ce4576297..9e6ccb404 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -99,7 +99,7 @@ freecompctlp(HashNode hn)
 }
 
 /**/
-void
+static void
 freecompctl(Compctl cc)
 {
     if (cc == &cc_default ||
@@ -142,7 +142,7 @@ freecompctl(Compctl cc)
 }
 
 /**/
-void
+static void
 freecompcond(void *a)
 {
     Compcond cc = (Compcond) a;
@@ -186,7 +186,7 @@ freecompcond(void *a)
 }
 
 /**/
-int
+static int
 compctlread(char *name, char **args, Options ops, char *reply)
 {
     char *buf, *bptr;
@@ -1400,7 +1400,7 @@ printcompctl(char *s, Compctl cc, int printflags, int ispat)
 	    untokenize(p);
 	    quotedzputs(p, stdout);
 	} else
-	    quotedzputs(quotestring(s, NULL, QT_BACKSLASH), stdout);
+	    quotedzputs(quotestring(s, QT_BACKSLASH), stdout);
     }
 
     /* loop through flags w/o args that are set, printing them if so */
@@ -1536,7 +1536,7 @@ printcompctl(char *s, Compctl cc, int printflags, int ispat)
 		char *p = dupstring(s);
 
 		untokenize(p);
-		quotedzputs(quotestring(p, NULL, QT_BACKSLASH), stdout);
+		quotedzputs(quotestring(p, QT_BACKSLASH), stdout);
 	    }
 	}
 	putchar('\n');
@@ -1564,6 +1564,8 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
     Compctl cc = NULL;
     int ret = 0;
 
+    queue_signals();
+
     /* clear static flags */
     cclist = 0;
     showmask = 0;
@@ -1571,12 +1573,15 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
     /* Parse all the arguments */
     if (*argv) {
 	/* Let's see if this is a global matcher definition. */
-	if ((ret = get_gmatcher(name, argv)))
+	if ((ret = get_gmatcher(name, argv))) {
+	    unqueue_signals();
 	    return ret - 1;
+	}
 
 	cc = (Compctl) zshcalloc(sizeof(*cc));
 	if (get_compctl(name, &argv, cc, 1, 0, 0)) {
 	    freecompctl(cc);
+	    unqueue_signals();
 	    return 1;
 	}
 
@@ -1604,6 +1609,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0, 0);
  	printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0, 0);
 	print_gmatcher((cclist & COMP_LIST));
+	unqueue_signals();
 	return ret;
     }
 
@@ -1642,6 +1648,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	    printcompctl("", &cc_first, 0, 0);
 	if (cclist & COMP_LISTMATCH)
 	    print_gmatcher(COMP_LIST);
+	unqueue_signals();
 	return ret;
     }
 
@@ -1656,6 +1663,7 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	    compctl_process_cc(argv, cc);
     }
 
+    unqueue_signals();
     return ret;
 }
 
@@ -1667,12 +1675,18 @@ bin_compctl(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 static int
 bin_compcall(char *name, UNUSED(char **argv), Options ops, UNUSED(int func))
 {
+    int ret;
+
     if (incompfunc != 1) {
 	zwarnnam(name, "can only be called from completion function");
 	return 1;
     }
-    return makecomplistctl((OPT_ISSET(ops,'T') ? 0 : CFN_FIRST) |
-			   (OPT_ISSET(ops,'D') ? 0 : CFN_DEFAULT));
+
+    queue_signals();
+    ret = makecomplistctl((OPT_ISSET(ops,'T') ? 0 : CFN_FIRST) |
+			  (OPT_ISSET(ops,'D') ? 0 : CFN_DEFAULT));
+    unqueue_signals();
+    return ret;
 }
 
 /*
@@ -1740,8 +1754,8 @@ static int addwhat;
  * This uses the instring variable exported from zle_tricky.c.
  */
 
-#define quotename(s, e) \
-quotestring(s, e, instring == QT_NONE ? QT_BACKSLASH : instring)
+#define quotename(s) \
+quotestring(s, instring == QT_NONE ? QT_BACKSLASH : instring)
 
 /* Hook functions */
 
@@ -1756,6 +1770,8 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat)
     int onm = nmatches, odm = diffmatches, osi = movefd(0);
     LinkNode n;
 
+    queue_signals();
+
     /* We build a copy of the list of matchers to use to make sure that this
      * works even if a shell function called from the completion code changes
      * the global matchers. */
@@ -1883,6 +1899,8 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat)
     }
     redup(osi, 0);
     dat->lst = 1;
+
+    unqueue_signals();
     return 0;
 }
 
@@ -1965,7 +1983,7 @@ addmatch(char *s, char *t)
 	if (!ms)
 	    return;
 
-	if (addwhat == -7 && !findcmd(s, 0))
+	if (addwhat == -7 && !findcmd(s, 0, 0))
 	    return;
 	isfile = CMF_FILE;
     } else if (addwhat == CC_QUOTEFLAG || addwhat == -2  ||
@@ -2044,7 +2062,7 @@ maketildelist(void)
 /* This does the check for compctl -x `n' and `N' patterns. */
 
 /**/
-int
+static int
 getcpat(char *str, int cpatindex, char *cpat, int class)
 {
     char *s, *t, *p;
@@ -2135,7 +2153,7 @@ gen_matches_files(int dirs, int execs, int all)
 {
     DIR *d;
     struct stat buf;
-    char *n, p[PATH_MAX], *q = NULL, *e, *pathpref;
+    char *n, p[PATH_MAX+1], *q = NULL, *e, *pathpref;
     LinkList l = NULL;
     int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat, pathpreflen;
 
@@ -2469,7 +2487,7 @@ makecomplistcmd(char *os, int incmd, int flags)
     /* If the command string starts with `=', try the path name of the *
      * command. */
     if (cmdstr && cmdstr[0] == Equals) {
-	char *c = findcmd(cmdstr + 1, 1);
+	char *c = findcmd(cmdstr + 1, 1, 0);
 
 	if (c) {
 	    zsfree(cmdstr);
@@ -2509,7 +2527,8 @@ makecomplistpc(char *os, int incmd)
     int ret = 0;
 
     s = ((shfunctab->getnode(shfunctab, cmdstr) ||
-	  builtintab->getnode(builtintab, cmdstr)) ? NULL : findcmd(cmdstr, 1));
+	  builtintab->getnode(builtintab, cmdstr)) ? NULL :
+	 findcmd(cmdstr, 1, 0));
 
     for (pc = patcomps; pc; pc = pc->next) {
 	if ((pat = patcompile(pc->pat, PAT_STATIC, NULL)) &&
@@ -3153,10 +3172,10 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
     lpre = zhalloc(lpl + 1);
     memcpy(lpre, s, lpl);
     lpre[lpl] = '\0';
-    qlpre = quotename(lpre, NULL);
+    qlpre = quotename(lpre);
     lsuf = dupstring(s + offs);
     lsl = strlen(lsuf);
-    qlsuf = quotename(lsuf, NULL);
+    qlsuf = quotename(lsuf);
 
     /* First check for ~.../... */
     if (ic == Tilde) {
@@ -3175,11 +3194,11 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
     rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
 	(noreal = 0, getreal(tt)) :
 	dupstring(tt);
-    qrpre = quotename(rpre, NULL);
+    qrpre = quotename(rpre);
 
     for (p = lsuf; *p && *p != String && *p != Tick; p++);
     rsuf = *p ? (noreal = 0, getreal(lsuf)) : dupstring(lsuf);
-    qrsuf = quotename(rsuf, NULL);
+    qrsuf = quotename(rsuf);
 
     /* Check if word is a pattern. */
 
@@ -3315,10 +3334,10 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	/* And get the file prefix. */
 	fpre = dupstring(((s1 == s || s1 == rpre || ic) &&
 			  (*s != '/' || zlemetacs == wb)) ? s1 : s1 + 1);
-	qfpre = quotename(fpre, NULL);
+	qfpre = quotename(fpre);
 	/* And the suffix. */
 	fsuf = dupstrpfx(rsuf, s2 - rsuf);
-	qfsuf = quotename(fsuf, NULL);
+	qfsuf = quotename(fsuf);
 
 	if (comppatmatch && *comppatmatch && (ispattern & 2)) {
 	    int t2;
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 30fab541a..68bdf2332 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -52,7 +52,7 @@ char **compwords,
      *compqiprefix,
      *compqisuffix,
      *compquote,
-     *compqstack,
+     *compqstack,      /* compstate[all_quotes] */
      *comppatmatch,
      *complastprompt;
 /**/
@@ -67,13 +67,31 @@ char *compiprefix,
      *compexact,
      *compexactstr,
      *comppatinsert,
-     *comptoend,
+     *comptoend,      /* compstate[to_end]; populates 'movetoend' */
      *compoldlist,
      *compoldins,
      *compvared;
 
+/*
+ * An array of Param structures for compsys special parameters;
+ * see 'comprparams' below.  An entry for $compstate is added
+ * by makecompparams().
+ *
+ * See CP_REALPARAMS.
+ */
+
+/**/
+Param *comprpms;
+
+/* 
+ * An array of Param structures for elemens of $compstate; see
+ * 'compkparams' below.
+ *
+ * See CP_KEYPARAMS.
+ */
+
 /**/
-Param *comprpms, *compkpms;
+Param *compkpms;
 
 /**/
 mod_export void
@@ -209,7 +227,15 @@ cpcpattern(Cpattern o)
     return r;
 }
 
-/* Parse a string for matcher control, containing multiple matchers. */
+/* 
+ * Parse a string for matcher control, containing multiple matchers.
+ *
+ * 's' is the string to be parsed.
+ *
+ * 'name' is the name of the builtin from which this is called, for errors.
+ *
+ * Return 'pcm_err' on error; a NULL return value means ...
+ */
 
 /**/
 mod_export Cmatcher
@@ -231,16 +257,17 @@ parse_cmatcher(char *name, char *s)
 	if (!*s) break;
 
 	switch (*s) {
-	case 'b': fl2 = CMF_INTER;
+	case 'b': fl2 = CMF_INTER; /* FALLTHROUGH */
 	case 'l': fl = CMF_LEFT; break;
-	case 'e': fl2 = CMF_INTER;
+	case 'e': fl2 = CMF_INTER; /* FALLTHROUGH */
 	case 'r': fl = CMF_RIGHT; break;
 	case 'm': fl = 0; break;
-	case 'B': fl2 = CMF_INTER;
+	case 'B': fl2 = CMF_INTER; /* FALLTHROUGH */
 	case 'L': fl = CMF_LEFT | CMF_LINE; break;
-	case 'E': fl2 = CMF_INTER;
+	case 'E': fl2 = CMF_INTER; /* FALLTHROUGH */
 	case 'R': fl = CMF_RIGHT | CMF_LINE; break;
 	case 'M': fl = CMF_LINE; break;
+	case 'x': break;
 	default:
 	    if (name)
 		zwarnnam(name, "unknown match specification character `%c'",
@@ -252,6 +279,15 @@ parse_cmatcher(char *name, char *s)
 		zwarnnam(name, "missing `:'");
 	    return pcm_err;
 	}
+	if (*s == 'x') {
+	    if (s[2] && !inblank(s[2])) {
+		if (name)
+		    zwarnnam(name,
+			"unexpected pattern following x: specification");
+		return pcm_err;
+	    }
+	    return ret;
+	}
 	s += 2;
 	if (!*s) {
 	    if (name)
@@ -527,8 +563,8 @@ static int
 bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 {
     struct cadata dat;
-    char *p, **sp, *e, *m = NULL, *mstr = NULL;
-    int dm;
+    char *mstr = NULL; /* argument of -M options, accumulated */
+    int added; /* return value */
     Cmatcher match = NULL;
 
     if (incompfunc != 1) {
@@ -544,14 +580,16 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
     dat.dummies = -1;
 
     for (; *argv && **argv ==  '-'; argv++) {
+	char *p; /* loop variable, points into argv */
 	if (!(*argv)[1]) {
 	    argv++;
 	    break;
 	}
 	for (p = *argv + 1; *p; p++) {
-	    sp = NULL;
-	    e = NULL;
-	    dm = 0;
+	    char *m = NULL; /* argument of -M option (this one only) */
+	    char **sp = NULL; /* the argument to an option should be copied
+				 to *sp. */
+	    const char *e; /* error message */
 	    switch (*p) {
 	    case 'q':
 		dat.flags |= CMF_REMOVE;
@@ -633,7 +671,6 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	    case 'M':
 		sp = &m;
 		e = "matching specification expected after -%c";
-		dm = 1;
 		break;
 	    case 'X':
 		sp = &(dat.exp);
@@ -720,14 +757,13 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 		    zsfree(mstr);
 		    return 1;
 		}
-		if (dm) {
+		if (m) {
 		    if (mstr) {
 			char *tmp = tricat(mstr, " ", m);
 			zsfree(mstr);
 			mstr = tmp;
 		    } else
 			mstr = ztrdup(m);
-		    m = NULL;
 		}
 	    }
 	}
@@ -746,10 +782,10 @@ bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
 	return 1;
 
     dat.match = match = cpcmatcher(match);
-    dm = addmatches(&dat, argv);
+    added = addmatches(&dat, argv);
     freecmatcher(match);
 
-    return dm;
+    return added;
 }
 
 #define CVT_RANGENUM 0
@@ -865,7 +901,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
 		return 0;
 
 	    singsub(&sa);
-	    pp = patcompile(sa, PAT_STATIC, NULL);
+	    pp = patcompile(sa, PAT_HEAPDUP, NULL);
 
 	    for (i--, p = compwords + i; i >= 0; p--, i--) {
 		if (pattry(pp, *p)) {
@@ -919,7 +955,7 @@ do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
 	    if (!na)
 		return 0;
 
-	    if (!(pp = patcompile(sa, PAT_STATIC, 0)))
+	    if (!(pp = patcompile(sa, PAT_HEAPDUP, 0)))
 		return 0;
 
 	    if (test == CVT_PREPAT) {
@@ -1210,8 +1246,9 @@ makecompparams(void)
 
     addcompparams(comprparams, comprpms);
 
-    if (!(cpm = createparam(COMPSTATENAME,
-			    PM_SPECIAL|PM_REMOVABLE|PM_LOCAL|PM_HASHED)))
+    if (!(cpm = createparam(
+	      COMPSTATENAME,
+	      PM_SPECIAL|PM_REMOVABLE|PM_SINGLE|PM_LOCAL|PM_HASHED)))
 	cpm = (Param) paramtab->getnode(paramtab, COMPSTATENAME);
     DPUTS(!cpm, "param not set in makecompparams");
 
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 8aeb6c3b8..035038815 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -347,9 +347,10 @@ getcoldef(char *s)
 	    char sav = p[1];
 
 	    p[1] = '\0';
+	    s = metafy(s, -1, META_USEHEAP);
 	    tokenize(s);
 	    gprog = patcompile(s, 0, NULL);
-	    p[1]  =sav;
+	    p[1] = sav;
 
 	    s = p + 1;
 	}
@@ -415,6 +416,7 @@ getcoldef(char *s)
 		break;
 	    *s++ = '\0';
 	}
+	p = metafy(p, -1, META_USEHEAP);
 	tokenize(p);
 	if ((prog = patcompile(p, 0, NULL))) {
 	    Patcol pc, po;
@@ -662,7 +664,9 @@ clprintfmt(char *p, int ml)
 
     initiscol();
 
-    for (; *p; p++) {
+    while (*p) {
+	convchar_t chr;
+	int chrlen = MB_METACHARLENCONV(p, &chr);
 	doiscol(i++);
 	cc++;
 	if (*p == '\n') {
@@ -673,11 +677,16 @@ clprintfmt(char *p, int ml)
 	if (ml == mlend - 1 && (cc % zterm_columns) == zterm_columns - 1)
 	    return 0;
 
-	if (*p == Meta) {
+	while (chrlen) {
+	    if (*p == Meta) {
+		p++;
+		chrlen--;
+		putc(*p ^ 32, shout);
+	    } else
+		putc(*p, shout);
+	    chrlen--;
 	    p++;
-	    putc(*p ^ 32, shout);
-	} else
-	    putc(*p, shout);
+	}
 	if ((beg = !(cc % zterm_columns)))
 	    ml++;
 	if (mscroll && !(cc % zterm_columns) &&
@@ -1984,7 +1993,8 @@ complistmatches(UNUSED(Hookdef dummy), Chdata dat)
     if (noselect > 0)
 	noselect = 0;
 
-    if ((minfo.asked == 2 && mselect < 0) || nlnct >= zterm_lines) {
+    if ((minfo.asked == 2 && mselect < 0) || nlnct >= zterm_lines ||
+	errflag) {
 	showinglist = 0;
 	amatches = oamatches;
 	return (noselect = 1);
diff --git a/Src/Zle/compmatch.c b/Src/Zle/compmatch.c
index 0e41ac3a5..1cdbb8a48 100644
--- a/Src/Zle/compmatch.c
+++ b/Src/Zle/compmatch.c
@@ -498,14 +498,27 @@ add_match_sub(Cmatcher m, char *l, int ll, char *w, int wl)
 /**/
 int
 match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
-	  int sfx, int test, int part)
+	  const int sfx, int test, int part)
 {
-    int ll = strlen(l), lw = strlen(w), oll = ll, olw = lw, exact = 0, wexact = 0;
-    int il = 0, iw = 0, t, ind, add, he = 0, bpc, obc = bc, bslash;
+    /* How many characters from the line string and from the word string are
+     * yet to be matched. */
+    int ll = strlen(l), lw = strlen(w);
+    /* Number of characters from the line string and word string matched. */
+    int il = 0, iw = 0;
+    /* How many characters were matched exactly in the line and in the word. */
+    int exact = 0, wexact = 0;
+    int he = 0;
+    int bslash;
     char *ow;
-    Cmlist ms;
+    Cmlist ms; /* loop variable */
     Cmatcher mp, lm = NULL;
     Brinfo bp = NULL;
+    const int obc = bc;
+    const int ind = (sfx ? -1 : 0);
+    const int add = (sfx ? -1 : 1);
+    const int original_ll = ll, original_lw = lw;
+
+    /* INVARIANT: il+ll == original_ll; iw+lw == original_lw */
 
     if (!test) {
 	start_match();
@@ -516,9 +529,6 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 
     if (sfx) {
 	l += ll; w += lw;
-	ind = -1; add = -1;
-    } else {
-	ind = 0; add = 1;
     }
     /* ow will always point to the beginning (or end) of that sub-string
      * in w that wasn't put in the match-variables yet. */
@@ -559,8 +569,7 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 	bslash = 0;
 	if (!sfx && lw && (!part || test) &&
 	    (l[ind] == w[ind] ||
-	     (bslash = (lw > 1 && w[ind] == '\\' &&
-			(ind ? (w[0] == l[0]) : (w[1] == l[0])))))) {
+	     (bslash = (lw > 1 && w[ind] == '\\' && w[ind+1] == l[0])))) {
 	    /* No matcher could be used, but the strings have the same
 	     * character here, skip over it. */
 	    l += add; w += (bslash ? (add + add) : add);
@@ -583,9 +592,8 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 	/* First try the matchers. Err... see above. */
 	for (mp = NULL, ms = mstack; !mp && ms; ms = ms->next) {
 	    for (mp = ms->matcher; mp; mp = mp->next) {
-		t = 1;
 		if ((lm && lm == mp) ||
-		    ((oll == ll || olw == lw) &&
+		    ((original_ll == ll || original_lw == lw) &&
 		     (test == 1 || (test && !mp->left && !mp->right)) &&
 		     mp->wlen < 0))
 		    /* If we were called recursively, don't use `*' patterns
@@ -593,9 +601,88 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 		    continue;
 
 		if (mp->wlen < 0) {
-		    int both, loff, aoff, llen, alen, zoff, moff, ct, ict, aol;
-		    char *tp, savl = '\0', savw;
-		    Cpattern ap, aop;
+		    /* `*'-pattern. */
+		    /*
+		     * Similar to the identically-named variable in the 'else'
+		     * block.
+		     */
+		    int t;
+		    /* 
+		     * 1 iff the anchor and the word are on the same side of
+		     * the line pattern; that is: if either
+		     * - the anchor is on the left and we are matching
+		     *   a prefix; or
+		     * - the anchor is on the right and we are matching
+		     *   a suffix.
+		     */
+		    int both;
+		    /*
+		     * Offset from the line pattern pointer ('l') to the start
+		     * of the line pattern.
+		     */
+		    int loff;
+		    /*
+		     * Offset from the line pattern pointer ('l') to the start
+		     * of the anchor.
+		     */
+		    int aoff;
+		    /*
+		     * The length of the line pattern.
+		     */
+		    int llen;
+		    /*
+		     * The length of the anchor.
+		     *
+		     * SEE: ap; aol, aop
+		     */
+		    int alen;
+		    /*
+		     * ### Related to 'zoff', which was removed in 2016.
+		     */
+		    int moff;
+		    /*
+		     * ### These two are related.
+		     *
+		     * ### They may have a relation similar to that of lw/iw
+		     * ### (q.v.), at least during the 'for' loop.  They may be
+		     * ### overloaded/repurposed after it.
+		     */
+		    int ct, ict;
+		    /*
+		     * The length of the OTHER anchor: the left anchor when
+		     * we're anchored on the right, and of the right anchor
+		     * when we're anchored on the left.
+		     */
+		    int aol;
+		    /*
+		     * LOST: Documentation comment.  Last seen 10 years ago in
+		     * the temporal lobe.  Reward promised for its safe return.
+		     * Contact zsh-workers@zsh.org.
+		     */
+		    char *tp;
+		    /* 
+		     * Temporary variable.  Used as temporary storage for a
+		     *
+		     *     {
+		     *         () {
+		     *           local foo="$foo"
+		     *           foo[1]=bar
+		     *           ...
+		     *         }
+		     *         (use original $foo here)
+		     *     }
+		     *
+		     * operation.  Similar to savw.
+		     */
+		    char savl = 0;
+		    /*
+		     * The anchor on this end.
+		     */
+		    Cpattern ap;
+		    /*
+		     * The anchor on the other end.
+		     */
+		    Cpattern aop;
 
 		    /* This is for `*' patterns, first initialise some
 		     * local variables. */
@@ -611,14 +698,14 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 			continue;
 
 		    if (mp->flags & CMF_LEFT) {
-			ap = mp->left; zoff = 0; moff = alen; aop = mp->right;
+			ap = mp->left; moff = alen; aop = mp->right;
 			if (sfx) {
 			    both = 0; loff = -llen; aoff = -(llen + alen);
 			} else {
 			    both = 1; loff = alen; aoff = 0;
 			}
 		    } else {
-			ap = mp->right; zoff = alen; moff = 0; aop = mp->left;
+			ap = mp->right; moff = 0; aop = mp->left;
 			if (sfx) {
 			    both = 1; loff = -(llen + alen); aoff = -alen;
 			} else {
@@ -644,8 +731,8 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 
 		    /* Fine, now we call ourselves recursively to find the
 		     * string matched by the `*'. */
-		    if (sfx && (savl = l[-(llen + zoff)]))
-			l[-(llen + zoff)] = '\0';
+		    if (sfx && (savl = l[-(llen + alen)]))
+			l[-(llen + alen)] = '\0';
 		    for (t = 0, tp = w, ct = 0, ict = lw - alen + 1;
 			 ict;
 			 tp += add, ct++, ict--) {
@@ -667,22 +754,25 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 				!match_parts(l + aoff , tp - moff, alen, part))
 				break;
 			    if (sfx) {
-				if ((savw = tp[-zoff]))
-				    tp[-zoff] = '\0';
+				/* Call ourselves recursively with the
+				 * anchor removed. */
+				char savw;
+				if ((savw = tp[-alen]))
+				    tp[-alen] = '\0';
 				t = match_str(l - ll, w - lw,
-					      NULL, 0, NULL, 1, 2, part);
+					      NULL, 0, NULL, sfx, 2, part);
 				if (savw)
-				    tp[-zoff] = savw;
+				    tp[-alen] = savw;
 			    } else
 				t = match_str(l + llen + moff, tp + moff,
-					      NULL, 0, NULL, 0, 1, part);
+					      NULL, 0, NULL, sfx, 1, part);
 			    if (t || (mp->wlen == -1 && !both))
 				break;
 			}
 		    }
 		    ict = ct;
 		    if (sfx && savl)
-			l[-(llen + zoff)] = savl;
+			l[-(llen + alen)] = savl;
 
 		    /* Have we found a position in w where the rest of l
 		     * matches? */
@@ -752,18 +842,22 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 		    bc += llen;
 		    exact = 0;
 
-		    if (!test)
+		    if (!test) {
+			int bpc;
 			while (bp &&
 			       bc >= (bpc = (useqbr ? bp->qpos : bp->pos))) {
 			    bp->curpos = matchbufadded + bpc - bc + obc;
 			    bp = bp->next;
 			}
+		    }
 		    ow = w;
 
 		    if (!llen && !alen) {
 			lm = mp;
-			if (he)
+			if (he) {
+			    /* Signal the outer for loop to continue. */
 			    mp = NULL;
+			}
 			else
 			    he = 1;
 		    } else {
@@ -772,6 +866,11 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 		    break;
 		} else if (ll >= mp->llen && lw >= mp->wlen) {
 		    /* Non-`*'-pattern. */
+		    /*
+		     * Similar to the identically-named variable in the 'if'
+		     * block.
+		     */
+		    int t = 1;
 		    char *tl, *tw;
 		    int tll, tlw, til, tiw;
 
@@ -875,12 +974,14 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 		    bc += mp->llen;
 		    exact = 0;
 
-		    if (!test)
+		    if (!test) {
+			int bpc;
 			while (bp &&
 			       bc >= (bpc = (useqbr ? bp->qpos : bp->pos))) {
 			    bp->curpos = matchbufadded + bpc - bc + obc;
 			    bp = bp->next;
 			}
+		    }
 		    ow = w;
 		    lm = NULL;
 		    he = 0;
@@ -896,8 +997,7 @@ match_str(char *l, char *w, Brinfo *bpp, int bc, int *rwlp,
 	bslash = 0;
 	if ((!test || sfx) && lw &&
 	    (l[ind] == w[ind] ||
-	     (bslash = (lw > 1 && w[ind] == '\\' &&
-			(ind ? (w[0] == l[0]) : (w[1] == l[0])))))) {
+	     (bslash = (lw > 1 && w[ind] == '\\' && w[ind+1] == l[0])))) {
 	    /* No matcher could be used, but the strings have the same
 	     * character here, skip over it. */
 	    l += add; w += (bslash ? (add + add ) : add);
@@ -1448,27 +1548,11 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws)
 {
     convchar_t c, wc;
     convchar_t ind, wind;
-    int len = 0, wlen, mt, wmt;
-#ifdef MULTIBYTE_SUPPORT
-    mbstate_t lstate, wstate;
-
-    memset(&lstate, 0, sizeof(lstate));
-    memset(&wstate, 0, sizeof(wstate));
-#endif
+    int len = 0, wlen = 0, mt, wmt;
 
     while (p && wp && *s && *ws) {
 	/* First test the word character */
-#ifdef MULTIBYTE_SUPPORT
-	wlen = mb_metacharlenconv_r(ws, &wc, &wstate);
-#else
-	if (*ws == Meta) {
-	    wc = STOUC(ws[1]) ^ 32;
-	    wlen = 2;
-	} else {
-	    wc = STOUC(*ws);
-	    wlen = 1;
-	}
-#endif
+	wc = unmeta_one(ws, &wlen);
 	wind = pattern_match1(wp, wc, &wmt);
 	if (!wind)
 	    return 0;
@@ -1476,18 +1560,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws)
 	/*
 	 * Now the line character.
 	 */
-#ifdef MULTIBYTE_SUPPORT
-	len = mb_metacharlenconv_r(s, &c, &lstate);
-#else
-	/* We have the character itself. */
-	if (*s == Meta) {
-	    c = STOUC(s[1]) ^ 32;
-	    len = 2;
-	} else {
-	    c = STOUC(*s);
-	    len = 1;
-	}
-#endif
+	c = unmeta_one(s, &len);
 	/*
 	 * If either is "?", they match each other; no further tests.
 	 * Apply this even if the character wasn't convertable;
@@ -1527,17 +1600,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws)
     }
 
     while (p && *s) {
-#ifdef MULTIBYTE_SUPPORT
-	len = mb_metacharlenconv_r(s, &c, &lstate);
-#else
-	if (*s == Meta) {
-	    c = STOUC(s[1]) ^ 32;
-	    len = 2;
-	} else {
-	    c = STOUC(*s);
-	    len = 1;
-	}
-#endif
+	c = unmeta_one(s, &len);
 	if (!pattern_match1(p, c, &mt))
 	    return 0;
 	p = p->next;
@@ -1545,17 +1608,7 @@ pattern_match(Cpattern p, char *s, Cpattern wp, char *ws)
     }
 
     while (wp && *ws) {
-#ifdef MULTIBYTE_SUPPORT
-	wlen = mb_metacharlenconv_r(ws, &wc, &wstate);
-#else
-	if (*ws == Meta) {
-	    wc = STOUC(ws[1]) ^ 32;
-	    wlen = 2;
-	} else {
-	    wc = STOUC(*ws);
-	    wlen = 1;
-	}
-#endif
+	wc = unmeta_one(ws, &wlen);
 	if (!pattern_match1(wp, wc, &wmt))
 	    return 0;
 	wp = wp->next;
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index 7fec7c804..05799399d 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -1174,6 +1174,10 @@ do_single(Cmatch m)
 	zlemetacs = minfo.end;
 	if (zlemetacs + m->qisl == lastend)
 	    zlemetacs += minfo.insc;
+
+	/* Advance CURSOR past compadd -s/-S suffixes. */
+	zlemetacs += strlen(psuf);
+	zlemetacs += m->suf ? strlen(m->suf) : 0;
     }
     {
 	Cmatch *om = minfo.cur;
@@ -1191,6 +1195,7 @@ do_single(Cmatch m)
 	if (menucmp)
 	    minfo.cur = &m;
 	runhookdef(INSERTMATCHHOOK, (void *) &dat);
+	redrawhook();
 	minfo.cur = om;
     }
 }
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index 184b263ee..7ffe00df4 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -199,11 +199,11 @@ cd_calc(void)
             set->count++;
             if ((l = strlen(str->str)) > cd_state.pre)
                 cd_state.pre = l;
-            if ((l = MB_METASTRWIDTH(str->str)) > cd_state.premaxw)
+            if ((l = ZMB_nicewidth(str->str)) > cd_state.premaxw)
                 cd_state.premaxw = l;
             if (str->desc) {
                 set->desc++;
-                if ((l = strlen(str->desc)) > cd_state.suf)
+                if ((l = strlen(str->desc)) > cd_state.suf) /* ### strlen() assumes no \n */
                     cd_state.suf = l;
             }
         }
@@ -490,7 +490,7 @@ cd_init(char *nam, char *hide, char *mlen, char *sep,
     setp = &(cd_state.sets);
     cd_state.sep = ztrdup(sep);
     cd_state.slen = strlen(sep);
-    cd_state.swidth = MB_METASTRWIDTH(sep);
+    cd_state.swidth = ZMB_nicewidth(sep);
     cd_state.sets = NULL;
     cd_state.showd = disp;
     cd_state.maxg = cd_state.groups = cd_state.descs = 0;
@@ -526,7 +526,8 @@ cd_init(char *nam, char *hide, char *mlen, char *sep,
             str->other = NULL;
             str->set = set;
 
-            for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
+	    /* Advance tmp to the first unescaped colon. */
+	    for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
                 if (*tmp == '\\' && tmp[1])
                     tmp++;
 
@@ -537,7 +538,7 @@ cd_init(char *nam, char *hide, char *mlen, char *sep,
             *tmp = '\0';
             str->str = str->match = ztrdup(rembslash(*ap));
             str->len = strlen(str->str);
-            str->width = MB_METASTRWIDTH(str->str);
+            str->width = ZMB_nicewidth(str->str);
 	    str->sortstr = NULL;
         }
         if (str)
@@ -692,7 +693,7 @@ cd_get(char **params)
 			 * end of screen as safety margin
 			 */
 			d = str->desc;
-			w = MB_METASTRWIDTH(d);
+			w = ZMB_nicewidth(d);
 			if (w <= remw)
 			    strcpy(p, d);
 			else {
@@ -701,7 +702,7 @@ cd_get(char **params)
 				l = MB_METACHARLEN(d);
 				memcpy(pp, d, l);
 				pp[l] = '\0';
-				w = MB_METASTRWIDTH(pp);
+				w = ZMB_nicewidth(pp);
 				if (w > remw) {
 				    *pp = '\0';
 				    break;
@@ -792,7 +793,7 @@ cd_get(char **params)
 			cd_state.swidth - CM_SPACE;
 		    p = pp = dbuf + cd_state.slen;
 		    d = str->desc;
-		    w = MB_METASTRWIDTH(d);
+		    w = ZMB_nicewidth(d);
 		    if (w <= remw) {
 			strcpy(p, d);
 			remw -= w;
@@ -802,7 +803,7 @@ cd_get(char **params)
 			    l = MB_METACHARLEN(d);
 			    memcpy(pp, d, l);
 			    pp[l] = '\0';
-			    w = MB_METASTRWIDTH(pp);
+			    w = ZMB_nicewidth(pp);
 			    if (w > remw) {
 				*pp = '\0';
 				break;
@@ -913,15 +914,14 @@ struct cadef {
     int lastt;			/* last time this was used */
     Caopt *single;		/* array of single-letter options */
     char *match;		/* -M spec to use */
-    int argsactive;		/* if arguments are still allowed */
+    int argsactive;		/* if normal arguments are still allowed */
 				/* used while parsing a command line */
     char *set;			/* set name prefix (<name>-), shared */
-    char *sname;		/* set name */
     int flags;			/* see CDF_* below */
     char *nonarg;		/* pattern for non-args (-A argument) */
 };
 
-#define CDF_SEP 1
+#define CDF_SEP 1		/* -S was specified: -- terminates options */
 
 /* Description for an option. */
 
@@ -934,15 +934,15 @@ struct caopt {
     Caarg args;			/* option arguments */
     int active;			/* still allowed on command line */
     int num;			/* it's the num'th option */
-    char *set;			/* set name, shared */
+    char *gsname;		/* group or set name, shared */
     int not;			/* don't complete this option (`!...') */
 };
 
-#define CAO_NEXT    1
-#define CAO_DIRECT  2
-#define CAO_ODIRECT 3
-#define CAO_EQUAL   4
-#define CAO_OEQUAL  5
+#define CAO_NEXT    1		/* argument follows in next argument (`-opt:...') */
+#define CAO_DIRECT  2		/* argument follows option directly (`-opt-:...') */
+#define CAO_ODIRECT 3		/* argument may follow option directly (`-opt+:...') */
+#define CAO_EQUAL   4		/* argument follows mandatory equals (`-opt=-:...') */
+#define CAO_OEQUAL  5		/* argument follows optional equals (`-opt=:...') */
 
 /* Description for an argument */
 
@@ -955,10 +955,10 @@ struct caarg {
     char *end;			/* end-pattern for ::<pat>:... */
     char *opt;			/* option name if for an option */
     int num;			/* it's the num'th argument */
-    int min;			/* it's also this argument, using opt. args */
-    int direct;			/* number was given directly */
+    int min;			/* earliest possible arg pos, given optional args */
+    int direct;			/* true if argument number was given explicitly */
     int active;			/* still allowed on command line */
-    char *set;			/* set name, shared */
+    char *gsname;		/* group or set name, shared */
 };
 
 #define CAA_NORMAL 1
@@ -1019,7 +1019,6 @@ freecadef(Cadef d)
 	s = d->snext;
 	zsfree(d->match);
 	zsfree(d->set);
-	zsfree(d->sname);
 	if (d->defs)
 	    freearray(d->defs);
 
@@ -1097,7 +1096,7 @@ parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
     ret->type = type;
     ret->opt = ztrdup(oname);
     ret->direct = 0;
-    ret->set = set;
+    ret->gsname = set;
 
     /* Get the description. */
 
@@ -1146,8 +1145,11 @@ alloc_cadef(char **args, int single, char *match, char *nonarg, int flags)
 	ret->defs = NULL;
 	ret->ndefs = 0;
     }
+    ret->nopts = 0;
+    ret->ndopts = 0;
+    ret->nodopts = 0;
     ret->lastt = time(0);
-    ret->set = ret->sname = NULL;
+    ret->set = NULL;
     if (single) {
 	ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
 	memset(ret->single, 0, 256 * sizeof(Caopt));
@@ -1181,12 +1183,10 @@ parse_cadef(char *nam, char **args)
     Cadef all, ret;
     Caopt *optp;
     char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
-    char *adpre, *adsuf, *axor = NULL, *doset = NULL, **setp = NULL;
+    char *adpre, *adsuf, *axor = NULL, *doset = NULL, **pendset = NULL, **curset = NULL;
     char *nonarg = NULL;
-    int single = 0, anum = 1, xnum, nopts, ndopts, nodopts, flags = 0;
-    int state = 0, not = 0;
-
-    nopts = ndopts = nodopts = 0;
+    int single = 0, anum = 1, xnum, flags = 0;
+    int foreignset = 0, not = 0;
 
     /* First string is the auto-description definition. */
 
@@ -1249,51 +1249,72 @@ parse_cadef(char *nam, char **args)
 
     if (nonarg)
 	tokenize(nonarg = dupstring(nonarg));
-
     /* Looks good. Optimistically allocate the cadef structure. */
 
     all = ret = alloc_cadef(orig_args, single, match, nonarg, flags);
     optp = &(ret->opts);
-    anum = 1;
-
     sargs = args;
 
     /* Get the definitions. */
 
-    for (; *args; args++) {
+    for (; *args || pendset; args++) {
+	if (!*args) {
+	    /* start new set */
+	    args = sargs; /* go back and repeat parse of common options */
+	    doset = NULL;
+	    set_cadef_opts(ret);
+	    ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags);
+	    optp = &(ret->opts);
+	    anum = 1;
+	    foreignset = 0;
+	    curset = pendset;
+	    pendset = 0;
+        }
         if (args[0][0] == '-' && !args[0][1] && args[1]) {
-	    if (!state) {
-		char *p;
-		int l;
-
-		if (setp)
-		    args = setp;
-		p = *++args;
-		l = strlen(p) - 1;
+	    if ((foreignset = curset && args != curset)) {
+		if (!pendset && args > curset)
+		    pendset = args; /* mark pointer to next pending set */
+		++args;
+	    } else {
+		/* Carrying on: this is the current set */
+		char *p = *++args;
+		int l = strlen(p) - 1;
+
 		if (*p == '(' && p[l] == ')') {
 		    axor = p = dupstring(p + 1);
 		    p[l - 1] = '\0';
 		} else
 		    axor = NULL;
+		if (!*p) {
+		    freecadef(all);
+		    zwarnnam(nam, "empty set name");
+		    return NULL;
+		}
 		ret->set = doset = tricat(p, "-", "");
-		ret->sname = ztrdup(p);
-		state = 1;
-	    } else {
-		setp = args;
-		state = 0;
-		args = sargs - 1;
-		doset = NULL;
-		ret->nopts = nopts;
-		ret->ndopts = ndopts;
-		ret->nodopts = nodopts;
-		set_cadef_opts(ret);
-		ret = ret->snext = alloc_cadef(NULL, single, NULL, nonarg, flags);
-		optp = &(ret->opts);
-		nopts = ndopts = nodopts = 0;
-		anum = 1;
+		curset = args; /* needed for the first set */
 	    }
 	    continue;
-	}
+	} else if (args[0][0] == '+' && !args[0][1] && args[1]) {
+	    char *p;
+	    int l;
+
+	    foreignset = 0; /* group not in any set, don't want to skip it */
+	    p = *++args;
+	    l = strlen(p) - 1;
+	    if (*p == '(' && p[l] == ')') {
+		axor = p = dupstring(p + 1);
+		p[l - 1] = '\0';
+	    } else
+		axor = NULL;
+	    if (!*p) {
+		freecadef(all);
+		zwarnnam(nam, "empty group name");
+		return NULL;
+	    }
+	    doset = tricat(p, "-", "");
+	    continue;
+	} else if (foreignset) /* skipping over a different set */
+	    continue;
 	p = dupstring(*args);
 	xnum = 0;
 	if ((not = (*p == '!')))
@@ -1505,7 +1526,7 @@ parse_cadef(char *nam, char **args)
 	    optp = &((*optp)->next);
 
 	    opt->next = NULL;
-	    opt->set = doset;
+	    opt->gsname = doset;
 	    opt->name = ztrdup(rembslashcolon(name));
 	    if (descr)
 		opt->descr = ztrdup(descr);
@@ -1525,13 +1546,13 @@ parse_cadef(char *nam, char **args)
 	    opt->xor = (again == 1 && xor ? zarrdup(xor) : xor);
 	    opt->type = otype;
 	    opt->args = oargs;
-	    opt->num = nopts++;
+	    opt->num = ret->nopts++;
 	    opt->not = not;
 
 	    if (otype == CAO_DIRECT || otype == CAO_EQUAL)
-		ndopts++;
+		ret->ndopts++;
 	    else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL)
-		nodopts++;
+		ret->nodopts++;
 
 	    /* If this is for single-letter option we also store a
 	     * pointer for the definition in the array for fast lookup.
@@ -1583,7 +1604,7 @@ parse_cadef(char *nam, char **args)
 		continue;
 
 	    if ((direct = idigit(*p))) {
-		/* Argment number is given. */
+		/* Argument number is given. */
 		int num = 0;
 
 		while (*p && idigit(*p))
@@ -1629,9 +1650,6 @@ parse_cadef(char *nam, char **args)
 		ret->args = arg;
 	}
     }
-    ret->nopts = nopts;
-    ret->ndopts = ndopts;
-    ret->nodopts = nodopts;
     set_cadef_opts(ret);
 
     return all;
@@ -1693,10 +1711,10 @@ ca_get_opt(Cadef d, char *line, int full, char **end)
 	for (p = d->opts; p; p = p->next)
 	    if (p->active && ((!p->args || p->type == CAO_NEXT) ?
 			      !strcmp(p->name, line) : strpfx(p->name, line))) {
-	    int l = strlen(p->name);
-	    if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
-		line[l] && line[l] != '=')
-		continue;
+		int l = strlen(p->name);
+		if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
+		    line[l] && line[l] != '=')
+		    continue;
 
 		if (end) {
 		    /* Return a pointer to the end of the option. */
@@ -1750,6 +1768,27 @@ ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp)
     return pp;
 }
 
+/* Search for an option in all sets except the current one.
+ * Return true if found */
+
+static int
+ca_foreign_opt(Cadef curset, Cadef all, char *option)
+{
+    Cadef d;
+    Caopt p;
+
+    for (d = all; d; d = d->snext) {
+	if (d == curset)
+	    continue;
+
+	for (p = d->opts; p; p = p->next) {
+	    if (!strcmp(p->name, option))
+		return 1;
+	}
+    }
+    return 0;
+}
+
 /* Return the n'th argument definition. */
 
 static Caarg
@@ -1771,98 +1810,135 @@ ca_get_arg(Cadef d, int n)
     return NULL;
 }
 
-/* Use a xor list, marking options as inactive. */
+/* Mark options as inactive.
+ *   d: option definitions for a set
+ *   pass either:
+ *     xor: a list if exclusions
+ *     opts: if set, all options excluded leaving only nornal/rest arguments */
 
-static LinkList ca_xor;
-
-static int
-ca_inactive(Cadef d, char **xor, int cur, int opts, char *optname)
+static void
+ca_inactive(Cadef d, char **xor, int cur, int opts)
 {
     if ((xor || opts) && cur <= compcurrent) {
 	Caopt opt;
 	char *x;
-	int sl = (d->set ? (int)strlen(d->set) : -1), set = 0;
+        /* current word could be a prefix of a longer one so only do
+	 * exclusions for single-letter options (for option clumping) */
+	int single = (cur == compcurrent);
 
 	for (; (x = (opts ? "-" : *xor)); xor++) {
-            if (optname && optname[0] == x[0] && strcmp(optname, x))
-                continue;
-	    if (ca_xor)
-		addlinknode(ca_xor, x);
-	    set = 0;
-	    if (sl > 0) {
-		if (strpfx(d->set, x)) {
-		    x += sl;
-		    set = 1;
-		} else if (!strncmp(d->set, x, sl - 1)) {
-		    Caopt p;
-
-		    for (p = d->opts; p; p = p->next)
-			if (p->set)
-			    p->active = 0;
-			
-		    x = ":";
-		    set = 1;
+	    int excludeall = 0;
+	    char *grp = NULL;
+	    size_t grplen;
+	    char *next, *sep = x;
+
+	    while (*sep != '+' && *sep != '-' && *sep != ':' && *sep != '*' && !idigit(*sep)) {
+		if (!(next = strchr(sep, '-')) || !*++next) {
+		    /* exclusion is just the name of a set or group */
+		    excludeall = 1; /* excluding options and args */
+		    sep += strlen(sep);
+		    /* A trailing '-' is included in the various gsname fields but is not
+		     * there for this branch. This is why we add excludeall to grplen
+		     * when checking for the null in a few places below */
+		    break;
 		}
+		sep = next;
+	    }
+	    if (sep > x) { /* exclusion included a set or group name */
+		grp = x;
+		grplen = sep - grp;
+		x = sep;
 	    }
-	    if (x[0] == ':' && !x[1]) {
-		if (set) {
+
+	    if (excludeall || (x[0] == ':' && !x[1])) {
+		if (grp) {
 		    Caarg a;
 
 		    for (a = d->args; a; a = a->next)
-			if (a->set)
+			if (a->gsname && !strncmp(a->gsname, grp, grplen) &&
+				!a->gsname[grplen + excludeall])
 			    a->active = 0;
-		    if (d->rest && (!set || d->rest->set))
+		    if (d->rest && d->rest->gsname &&
+			    !strncmp(d->rest->gsname, grp, grplen) &&
+			    !d->rest->gsname[grplen + excludeall])
 			d->rest->active = 0;
 		} else
 		    d->argsactive = 0;
-	    } else if (x[0] == '-' && !x[1]) {
+	    }
+
+	    if (excludeall || (x[0] == '-' && !x[1])) {
 		Caopt p;
 
 		for (p = d->opts; p; p = p->next)
-		    if (!set || p->set)
+		    if ((!grp || (p->gsname && !strncmp(p->gsname, grp, grplen) &&
+			    !p->gsname[grplen + excludeall])) &&
+			    !(single && *p->name && p->name[1] && p->name[2]))
 			p->active = 0;
-	    } else if (x[0] == '*' && !x[1]) {
-		if (d->rest && (!set || d->rest->set))
-		    d->rest->active = 0;
-	    } else if (idigit(x[0])) {
-		int n = atoi(x);
-		Caarg a = d->args;
-
-		while (a && a->num < n)
-		    a = a->next;
+	    }
 
-		if (a && a->num == n && (!set || a->set))
-		    a->active = 0;
-	    } else if ((opt = ca_get_opt(d, x, 1, NULL)) && (!set || opt->set))
-		opt->active = 0;
+	    if (excludeall || (x[0] == '*' && !x[1])) {
+		if (d->rest && (!grp || (d->rest->gsname &&
+			!strncmp(d->rest->gsname, grp, grplen) &&
+			!d->rest->gsname[grplen + excludeall])))
+		    d->rest->active = 0;
+            }
 
-	    if (opts)
-		break;
+	    if (!excludeall) {
+		if (idigit(x[0])) {
+		    int n = atoi(x);
+		    Caarg a = d->args;
+
+		    while (a && a->num < n)
+			a = a->next;
+
+		    if (a && a->num == n && (!grp || (a->gsname &&
+			    !strncmp(a->gsname, grp, grplen))))
+			a->active = 0;
+		} else if ((opt = ca_get_opt(d, x, 1, NULL)) &&
+			(!grp || (opt->gsname && !strncmp(opt->gsname, grp, grplen))) &&
+			!(single && *opt->name && opt->name[1] && opt->name[2]))
+		    opt->active = 0;
+		if (opts)
+		    break;
+	    }
 	}
     }
-    return 0;
 }
 
 /* State when parsing a command line. */
 
 typedef struct castate *Castate;
 
-/*
- *           **** DOCUMENT ME ****
- *
- * This structure and its use are a nightmare.
- */
+/* Encapsulates details from parsing the current line against a particular set,
+ * Covers positions of options and normal arguments. Used as a linked list
+ * with one state for each set. */
 
 struct castate {
-    Castate snext;
-    Cadef d;
-    int nopts;
-    Caarg def, ddef;
-    Caopt curopt, dopt;
-    int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos, argend;
-    int inopt, inrest, inarg, nth, doff, singles, oopt, actopts;
-    LinkList args;
-    LinkList *oargs;
+    Castate snext;	/* state for next set */
+    Cadef d;		/* parsed _arguments specs for the set */
+    int nopts;		/* number of specified options (size of oargs) */
+    Caarg def;		/* definition for the current set */
+    Caarg ddef;
+    Caopt curopt;	/* option description corresponding to option found on the command-line */
+    Caopt dopt;
+    int opt;		/* the length of the option up to a maximum of 2 */
+    int arg;		/* completing arguments to an option or rest args */
+    int argbeg;         /* position of first rest argument (+1) */
+    int optbeg;		/* first word after the last option to the left of the cursor:
+			 * in effect the start of any arguments to the current option */
+    int nargbeg;	/* same as optbeg but used during parse */
+    int restbeg;	/* same as argbeg but used during parse */
+    int curpos;		/* current word position */
+    int argend;         /* total number of words */
+    int inopt;		/* set to current word pos if word is a recognised option */
+    int inarg;          /* in a normal argument */
+    int nth;		/* number of current normal arg */
+    int doff;		/* length of current option */
+    int singles;	/* argument consists of clumped options */
+    int oopt;
+    int actopts;	/* count of active options */
+    LinkList args;	/* list of non-option args used for populating $line */
+    LinkList *oargs;	/* list of lists used for populating $opt_args */
 };
 
 static struct castate ca_laststate;
@@ -1908,10 +1984,12 @@ ca_opt_arg(Caopt opt, char *line)
     return ztrdup(line);
 }
 
-/* Parse a command line. */
+/* Parse the command line for a particular argument set (d).
+ * Returns 1 if the set should be skipped because it doesn't match
+ * existing options on the line. */
 
 static int
-ca_parse_line(Cadef d, int multi, int first)
+ca_parse_line(Cadef d, Cadef all, int multi, int first)
 {
     Caarg adef, ddef;
     Caopt ptr, wasopt = NULL, dopt;
@@ -1955,7 +2033,7 @@ ca_parse_line(Cadef d, int multi, int first)
     state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts =
 	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
     state.argend = argend = arrlen(compwords) - 1;
-    state.inrest = state.doff = state.singles = state.oopt = 0;
+    state.doff = state.singles = state.oopt = 0;
     state.curpos = compcurrent;
     state.args = znewlinklist();
     state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
@@ -1970,7 +2048,7 @@ ca_parse_line(Cadef d, int multi, int first)
 
 	goto end;
     }
-    if (d->nonarg)
+    if (d->nonarg) /* argument to -A */
 	napat = patcompile(d->nonarg, 0, NULL);
 
     /* Loop over the words from the line. */
@@ -2002,14 +2080,14 @@ ca_parse_line(Cadef d, int multi, int first)
         remnulargs(line);
         untokenize(line);
 
-	if (ca_inactive(d, argxor, cur, 0, NULL) ||
-	    ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--"))) {
-	    if (ca_inactive(d, NULL, cur, 1, NULL))
-		return 1;
+	ca_inactive(d, argxor, cur, 0);
+	if ((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--")) {
+	    ca_inactive(d, NULL, cur, 1);
 	    continue;
 	}
-	/* We've got a definition for an argument, skip to the next. */
 
+	/* We've got a definition for an option/rest argument. For an option,
+	 * this means that we're completing arguments to that option. */
 	if (state.def) {
 	    state.arg = 0;
 	    if (state.curopt)
@@ -2080,9 +2158,7 @@ ca_parse_line(Cadef d, int multi, int first)
 	    if (!state.oargs[state.curopt->num])
 		state.oargs[state.curopt->num] = znewlinklist();
 
-	    if (ca_inactive(d, state.curopt->xor, cur, 0,
-                            (cur == compcurrent ? state.curopt->name : NULL)))
-		return 1;
+	    ca_inactive(d, state.curopt->xor, cur, 0);
 
 	    /* Collect the argument strings. Maybe. */
 
@@ -2135,9 +2211,7 @@ ca_parse_line(Cadef d, int multi, int first)
 		    if (!state.oargs[tmpopt->num])
 			state.oargs[tmpopt->num] = znewlinklist();
 
-		    if (ca_inactive(d, tmpopt->xor, cur, 0,
-                                    (cur == compcurrent ? tmpopt->name : NULL)))
-			return 1;
+		    ca_inactive(d, tmpopt->xor, cur, 0);
 		}
 	    }
 	    if (state.def &&
@@ -2159,20 +2233,17 @@ ca_parse_line(Cadef d, int multi, int first)
 	    else
 		state.curopt = NULL;
 	} else if (multi && (*line == '-' || *line == '+') && cur != compcurrent
-#if 0
-		   /**** Ouch. Using this will disable the mutual exclusion
-			 of different sets. Not using it will make the -A
-			 pattern be effectively ignored with multiple sets. */
-		   && (!napat || !pattry(napat, line))
-#endif
-		   )
+		&& (ca_foreign_opt(d, all, line)))
 	    return 1;
-	else if (state.arg && (!napat || !pattry(napat, line))) {
+	else if (state.arg &&
+		 (!napat || cur <= compcurrent || !pattry(napat, line))) {
 	    /* Otherwise it's a normal argument. */
-	    if (napat && ca_inactive(d, NULL, cur + 1, 1, NULL))
-		return 1;
+	    if (napat && cur <= compcurrent)
+		ca_inactive(d, NULL, cur + 1, 1);
 
 	    arglast = 1;
+	    /* if this is the first normal arg after an option, may have been
+	     * earlier normal arguments if they're intermixed with options */
 	    if (state.inopt) {
 		state.inopt = 0;
 		state.nargbeg = cur - 1;
@@ -2203,7 +2274,6 @@ ca_parse_line(Cadef d, int multi, int first)
 		if (ca_laststate.def)
 		    break;
 
-		state.inrest = 0;
 		state.opt = (cur == state.nargbeg + 1 &&
 			     (!multi || !*line || 
 			      *line == '-' || *line == '+'));
@@ -2308,7 +2378,10 @@ ca_parse_line(Cadef d, int multi, int first)
     return 0;
 }
 
-/* Build a colon-list from a list. */
+/* Build a colon-list from a list.
+ *
+ * This is only used to populate values of $opt_args.
+ */
 
 static char *
 ca_colonlist(LinkList l)
@@ -2318,16 +2391,19 @@ ca_colonlist(LinkList l)
 	int len = 0;
 	char *p, *ret, *q;
 
+	/* Compute the length to be allocated. */
 	for (n = firstnode(l); n; incnode(n)) {
 	    len++;
 	    for (p = (char *) getdata(n); *p; p++)
-		len += (*p == ':' ? 2 : 1);
+		len += (*p == ':' || *p == '\\') ? 2 : 1;
 	}
 	ret = q = (char *) zalloc(len);
 
+	/* Join L into RET, joining with colons and escaping colons and
+	 * backslashes. */
 	for (n = firstnode(l); n;) {
 	    for (p = (char *) getdata(n); *p; p++) {
-		if (*p == ':')
+		if (*p == ':' || *p == '\\')
 		    *q++ = '\\';
 		*q++ = *p;
 	    }
@@ -2387,19 +2463,19 @@ ca_set_data(LinkList descr, LinkList act, LinkList subc,
 		    restrict_range(ca_laststate.argbeg, ca_laststate.argend);
 	    }
 	    if (arg->opt) {
-		buf = (char *) zhalloc((arg->set ? strlen(arg->set) : 0) +
+		buf = (char *) zhalloc((arg->gsname ? strlen(arg->gsname) : 0) +
 				       strlen(arg->opt) + 40);
 		if (arg->num > 0 && arg->type < CAA_REST)
 		    sprintf(buf, "%soption%s-%d",
-			    (arg->set ? arg->set : ""), arg->opt, arg->num);
+			    (arg->gsname ? arg->gsname : ""), arg->opt, arg->num);
 		else
 		    sprintf(buf, "%soption%s-rest",
-			    (arg->set ? arg->set : ""), arg->opt);
+			    (arg->gsname ? arg->gsname : ""), arg->opt);
 	    } else if (arg->num > 0) {
 		sprintf(nbuf, "argument-%d", arg->num);
-		buf = (arg->set ? dyncat(arg->set, nbuf) : dupstring(nbuf));
+		buf = (arg->gsname ? dyncat(arg->gsname, nbuf) : dupstring(nbuf));
 	    } else
-		buf = (arg->set ? dyncat(arg->set, "argument-rest") :
+		buf = (arg->gsname ? dyncat(arg->gsname, "argument-rest") :
 		       dupstring("argument-rest"));
 
 	    addlinknode(subc, buf);
@@ -2503,46 +2579,29 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
          * auto-description string, the optional -s, -S, -A and -M options
          * given to _arguments and the specs. */
 	if (compcurrent > 1 && compwords[0]) {
-	    Cadef def;
+	    Cadef def, all;
 	    int cap = ca_parsed, multi, first = 1, use, ret = 0;
-	    LinkList cax = ca_xor, nx;
-	    LinkNode node;
 	    Castate states = NULL, sp;
-	    char *xor[2];
 
 	    ca_parsed = 0;
-	    xor[1] = NULL;
 
-	    if (!(def = get_cadef(nam, args + 1)))
+	    if (!(def = all = get_cadef(nam, args + 1)))
 		return 1;
 
-	    multi = !!def->snext;
+	    multi = !!def->snext; /* if we have sets */
 	    ca_parsed = cap;
-	    ca_xor = (multi ? newlinklist() : NULL);
-
-	    while (def) {
-		use = !ca_parse_line(def, multi, first);
-		nx = ca_xor;
-		ca_xor = NULL;
-		while ((def = def->snext)) {
-		    if (nx) {
-			for (node = firstnode(nx); node; incnode(node)) {
-			    xor[0] = (char *) getdata(node);
-			    if (!strcmp(xor[0], def->sname) ||
-				ca_inactive(def, xor, compcurrent, 0, NULL))
-				break;
-			}
-			if (!node)
-			    break;
-		    }
-		}
-		ca_xor = nx;
+
+	    while (def) { /* for each set */
+		use = !ca_parse_line(def, all, multi, first);
+		def = def->snext;
 		if (use && def) {
+		    /* entry needed so save it into list */
 		    sp = (Castate) zalloc(sizeof(*sp));
 		    memcpy(sp, &ca_laststate, sizeof(*sp));
 		    sp->snext = states;
 		    states = sp;
 		} else if (!use && !def) {
+		    /* final entry not needed */
 		    if (states) {
 			freecastate(&ca_laststate);
 			memcpy(&ca_laststate, states, sizeof(*sp));
@@ -2554,7 +2613,6 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		}
 		first = 0;
 	    }
-	    ca_xor = cax;
 	    ca_parsed = 1;
 	    ca_laststate.snext = states;
 
@@ -2567,7 +2625,7 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
          * things _arguments has to execute at this place on the line (the
          * sub-contexts are used as tags).
          * The return value is particularly important here, it says if 
-         * there are arguments to completely at all. */
+         * there are arguments to complete at all. */
 	{
 	    LinkList descr, act, subc;
 	    Caarg arg;
@@ -2770,7 +2828,7 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    for (s = lstate; s; s = s->snext)
 		for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
 		    if (*a) {
-			*p++ = (o->set ? tricat(o->set, o->name, "") :
+			*p++ = (o->gsname ? tricat(o->gsname, o->name, "") :
 				ztrdup(o->name));
 			*p++ = ca_colonlist(*a);
 		    }
@@ -3511,8 +3569,8 @@ bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 	    Cvval val = cv_get_val(cv_laststate.d, args[1]);
 
 	    if (val && val->arg) {
-		setsparam(args[2], val->arg->descr);
-		setsparam(args[3], val->arg->action);
+		setsparam(args[2], ztrdup(val->arg->descr));
+		setsparam(args[3], ztrdup(val->arg->action));
 
 		if (args[4])
 		    setsparam(args[4], ztrdup(val->name));
@@ -3546,7 +3604,7 @@ comp_quote(char *str, int prefix)
     if ((x = (prefix && *str == '=')))
 	*str = 'x';
 
-    ret = quotestring(str, NULL, *compqstack);
+    ret = quotestring(str, *compqstack);
 
     if (x)
 	*str = *ret = '=';
@@ -3870,6 +3928,8 @@ bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		    if (*q) {
 			char *qq, *qqq;
 
+			queue_signals();
+
 			if (c)
 			    *c = '\0';
 
@@ -3941,6 +4001,8 @@ bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 			}
 			if (c)
 			    *c = ':';
+
+			unqueue_signals();
 		    }
 		}
 		if (num) {
@@ -4403,17 +4465,24 @@ cfp_matcher_pats(char *matcher, char *add)
     if (m && m != pcm_err) {
 	char *tmp;
 	int al = strlen(add), zl = ztrlen(add), tl, cl;
-	VARARR(Cmatcher, ms, zl);
+	VARARR(Cmatcher, ms, zl);	/* One Cmatcher per character */
 	Cmatcher *mp;
 	Cpattern stopp;
 	int stopl = 0;
 
+	/* zl >= (number of wide characters) is guaranteed */
 	memset(ms, 0, zl * sizeof(Cmatcher));
 
 	for (; m && *add; m = m->next) {
 	    stopp = NULL;
 	    if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) {
 		if (m->llen == 1 && m->wlen == 1) {
+		    /*
+		     * In this loop and similar loops below we step
+		     * through tmp one (possibly wide) character at a
+		     * time.  pattern_match() compares only the first
+		     * character using unmeta_one() so keep in step.
+		     */
 		    for (tmp = add, tl = al, mp = ms; tl; ) {
 			if (pattern_match(m->line, tmp, NULL, NULL)) {
 			    if (*mp) {
@@ -4423,10 +4492,10 @@ cfp_matcher_pats(char *matcher, char *add)
 			    } else
 				*mp = m;
 			}
-			cl = (*tmp == Meta) ? 2 : 1;
+			(void) unmeta_one(tmp, &cl);
 			tl -= cl;
 			tmp += cl;
-			mp += cl;
+			mp++;
 		    }
 		} else {
 		    stopp = m->line;
@@ -4443,10 +4512,10 @@ cfp_matcher_pats(char *matcher, char *add)
 			    } else
 				*mp = m;
 			}
-			cl = (*tmp == Meta) ? 2 : 1;
+			(void) unmeta_one(tmp, &cl);
 			tl -= cl;
 			tmp += cl;
-			mp += cl;
+			mp++;
 		    }
 		} else if (m->llen) {
 		    stopp = m->line;
@@ -4469,7 +4538,7 @@ cfp_matcher_pats(char *matcher, char *add)
 			al = tmp - add;
 			break;
 		    }
-		    cl = (*tmp == Meta) ? 2 : 1;
+		    (void) unmeta_one(tmp, &cl);
 		    tl -= cl;
 		    tmp += cl;
 		}
@@ -4480,6 +4549,10 @@ cfp_matcher_pats(char *matcher, char *add)
     return add;
 }
 
+/*
+ * ### This function call is skipped by _approximate, so "opt" probably means "optimize".
+ */
+
 static void
 cfp_opt_pats(char **pats, char *matcher)
 {
@@ -4646,6 +4719,8 @@ cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
 		if (!*p)
 		    continue;
 
+		queue_signals();	/* Protect PAT_STATIC */
+
 		tokenize(f);
 		pprog = patcompile(f, PAT_STATIC, NULL);
 		untokenize(f);
@@ -4678,6 +4753,8 @@ cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
 			}
 		    }
 		}
+
+		unqueue_signals();
 	    }
 	}
     }
@@ -4732,7 +4809,7 @@ cf_ignore(char **names, LinkList ign, char *style, char *path)
     for (; (n = *names); names++) {
 	if (!ztat(n, &nst, 1) && S_ISDIR(nst.st_mode)) {
 	    if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
-		addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
+		addlinknode(ign, quotestring(n, QT_BACKSLASH));
 		continue;
 	    }
 	    if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
@@ -4748,7 +4825,7 @@ cf_ignore(char **names, LinkList ign, char *style, char *path)
 		if (found || ((e = strrchr(c, '/')) && e > c + pl &&
 			      !ztat(c, &st, 1) && st.st_dev == nst.st_dev &&
 			      st.st_ino == nst.st_ino))
-		    addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
+		    addlinknode(ign, quotestring(n, QT_BACKSLASH));
 	    }
 	}
     }
@@ -4811,6 +4888,20 @@ cf_remove_other(char **names, char *pre, int *amb)
     return NULL;
 }
 
+/*
+ * SYNOPSIS:
+ *     1. compfiles -p  parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
+ *     2. compfiles -p- parnam1 parnam2 skipped matcher sdirs parnam3 varargs [..varargs]
+ *     3. compfiles -P  parnam1 parnam2 skipped matcher sdirs parnam3 
+ *
+ *     1. Set parnam1 to an array of patterns....
+ *        ${(P)parnam1} is an in/out parameter.
+ *     2. Like #1 but without calling cfp_opt_pats().  (This is only used by _approximate.)
+ *     3. Like #1 but varargs is implicitly set to  char *varargs[2] = { "*(-/)", NULL };.
+ *
+ *     parnam2 has to do with the accept-exact style (see cfp_test_exact()).
+ */
+
 static int
 bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 {
@@ -4843,7 +4934,7 @@ bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		return 0;
 	    }
 	    for (l = newlinklist(); *tmp; tmp++)
-		addlinknode(l, *tmp);
+		addlinknode(l, quotestring(*tmp, QT_BACKSLASH_PATTERN));
 	    set_list_array(args[1], cf_pats((args[0][1] == 'P'), !!args[0][2],
 					    l, getaparam(args[2], NULL), args[3],
 					    args[4], args[5],
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
index 2b2654c5d..58310cd74 100644
--- a/Src/Zle/iwidgets.list
+++ b/Src/Zle/iwidgets.list
@@ -143,6 +143,7 @@
 "vi-delete", videlete, ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_VIOPER
 "vi-delete-char", videletechar, ZLE_KEEPSUFFIX
 "vi-digit-or-beginning-of-line", vidigitorbeginningofline, 0
+"vi-down-case", vidowncase, ZLE_LASTCOL | ZLE_VIOPER
 "vi-down-line-or-history", vidownlineorhistory, ZLE_LINEMOVE
 "vi-end-of-line", viendofline, ZLE_LASTCOL
 "vi-fetch-history", vifetchhistory, ZLE_LINEMOVE
@@ -188,6 +189,7 @@
 "vi-swap-case", viswapcase, ZLE_LASTCOL
 "vi-undo-change", viundochange, ZLE_KEEPSUFFIX
 "vi-unindent", viunindent, ZLE_LASTCOL | ZLE_VIOPER
+"vi-up-case", viupcase, ZLE_LASTCOL | ZLE_VIOPER
 "vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE
 "vi-yank", viyank, ZLE_LASTCOL | ZLE_VIOPER
 "vi-yank-eol", viyankeol, 0
diff --git a/Src/Zle/textobjects.c b/Src/Zle/textobjects.c
index 9b3277a97..3db0781ff 100644
--- a/Src/Zle/textobjects.c
+++ b/Src/Zle/textobjects.c
@@ -1,5 +1,5 @@
 /*
- * textobjects.c - ZLE module implementing Vim style text objects
+ * textobjects.c - ZLE widgets implementing Vim style text objects
  *
  * This file is part of zsh, the Z shell.
  *
@@ -54,11 +54,7 @@ selectword(UNUSED(char **args))
     int sclass = viclass(zleline[zlecs]);
     int doblanks = all && sclass;
 
-    if (!invicmdmode()) {
-	region_active = 1;
-	mark = zlecs;
-    }
-    if (!region_active || zlecs == mark) {
+    if (!region_active || zlecs == mark || mark == -1) {
 	/* search back to first character of same class as the start position
 	 * also stop at the beginning of the line */
 	mark = zlecs;
@@ -207,8 +203,12 @@ selectword(UNUSED(char **args))
     /* Adjustment: vi operators don't include the cursor position, in insert
      * or emacs mode the region also doesn't but for vi visual mode it is
      * included. */
-    if (zlecs && zlecs > mark && !virangeflag)
-	DECCS();
+    if (!virangeflag) {
+	if (!invicmdmode())
+	    region_active = 1;
+	else if (zlecs && zlecs > mark)
+	    DECCS();
+    }
 
     return 0;
 }
@@ -315,7 +315,7 @@ selectargument(UNUSED(char **args))
     }
 
     /* Adjustment: vi operators don't include the cursor position */
-    if (!virangeflag)
+    if (!virangeflag && invicmdmode())
        DECCS();
 
     return 0;
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
index e9b14281d..8f92e5611 100644
--- a/Src/Zle/zle.h
+++ b/Src/Zle/zle.h
@@ -284,6 +284,20 @@ struct change {
 #define CH_NEXT (1<<0)   /* next structure is also part of this change */
 #define CH_PREV (1<<1)   /* previous structure is also part of this change */
 
+/* vi change handling for vi-repeat-change */
+
+/*
+ * Examination of the code suggests vichgbuf is consistently tied
+ * to raw byte input, so it is left as a character array rather
+ * than turned into wide characters.  In particular, when we replay
+ * it we use ungetbytes().
+ */
+struct vichange {
+    struct modifier mod; /* value of zmod associated with vi change */
+    char *buf;           /* bytes for keys that make up the vi command */
+    int bufsz, bufptr;   /* allocated and in use sizes of buf */
+};
+
 /* known thingies */
 
 #define Th(X) (&thingies[X])
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
index abd6e1749..581ca4979 100644
--- a/Src/Zle/zle_hist.c
+++ b/Src/Zle/zle_hist.c
@@ -1220,13 +1220,14 @@ doisearch(char **args, int dir, int pattern)
 		char *patbuf = ztrdup(sbuf);
 		char *patstring;
 		/*
-		 * Use static pattern buffer since we don't need
-		 * to maintain it and won't call other pattern functions
-		 * meanwhile.
-		 * Use PAT_NOANCH because we don't need the match
-		 * anchored to the end, even if it is at the start.
+		 * Do not use static pattern buffer (PAT_STATIC) since we
+		 * call zle hooks, which might call other pattern
+		 * functions.  Use PAT_ZDUP because we re-use the pattern
+		 * in subsequent loops, so we can't pushheap/popheap.
+		 * Use PAT_NOANCH because we don't need the match anchored
+		 * to the end, even if it is at the start.
 		 */
-		int patflags = PAT_STATIC|PAT_NOANCH;
+		int patflags = PAT_ZDUP|PAT_NOANCH;
 		if (sbuf[0] == '^') {
 		    /*
 		     * We'll handle the anchor later when
@@ -1521,6 +1522,7 @@ doisearch(char **args, int dir, int pattern)
 		    if (only_one || !top_spot || old_sbptr != sbptr)
 			break;
 		}
+		freepatprog(patprog);
 		patprog = NULL;
 		nosearch = 1;
 		skip_pos = 0;
@@ -1632,6 +1634,7 @@ doisearch(char **args, int dir, int pattern)
 	    }
 	    strcpy(sbuf + sbptr, paste);
 	    sbptr += pastelen;
+	    freepatprog(patprog);
 	    patprog = NULL;
 	    free(paste);
 	} else if (cmd == Th(z_acceptsearch)) {
@@ -1682,6 +1685,7 @@ doisearch(char **args, int dir, int pattern)
 	     * always valid at this point.
 	     */
 	    sbptr += zlecharasstring(LASTFULLCHAR, sbuf + sbptr);
+	    freepatprog(patprog);
 	    patprog = NULL;
 	}
 	if (feep)
@@ -1702,6 +1706,7 @@ doisearch(char **args, int dir, int pattern)
     zsfree(okeymap);
     if (matchlist)
 	freematchlist(matchlist);
+    freepatprog(patprog);
     isearch_active = 0;
     /*
      * Don't allow unused characters provided as a string to the
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
index 13fd13844..04eb70675 100644
--- a/Src/Zle/zle_keymap.c
+++ b/Src/Zle/zle_keymap.c
@@ -135,7 +135,10 @@ mod_export HashTable keymapnamtab;
 /**/
 char *keybuf;
 
-static int keybuflen, keybufsz = 20;
+/**/
+int keybuflen;
+
+static int keybufsz = 20;
 
 /* last command executed with execute-named-command */
 
@@ -1322,15 +1325,15 @@ default_bindings(void)
 	amap->first[i] = refthingy(t_undefinedkey);
 
     /* safe fallback keymap:
-     *   0-255  self-insert, except: *
-     *    '\n'  accept-line          *
-     *    '\r'  accept-line          */
+     *   0-255  .self-insert, except: *
+     *    '\n'  .accept-line          *
+     *    '\r'  .accept-line          */
     for (i = 0; i < 256; i++)
-	smap->first[i] = refthingy(t_selfinsert);
-    unrefthingy(t_selfinsert);
-    unrefthingy(t_selfinsert);
-    smap->first['\n'] = refthingy(t_acceptline);
-    smap->first['\r'] = refthingy(t_acceptline);
+	smap->first[i] = refthingy(t_Dselfinsert);
+    unrefthingy(t_Dselfinsert);
+    unrefthingy(t_Dselfinsert);
+    smap->first['\n'] = refthingy(t_Dacceptline);
+    smap->first['\r'] = refthingy(t_Dacceptline);
 
     /* vt100 arrow keys are bound by default, for historical reasons. *
      * Both standard and keypad modes are supported.                  */
@@ -1366,6 +1369,8 @@ default_bindings(void)
     bindkey(vismap, "\33", refthingy(t_deactivateregion), NULL);
     bindkey(vismap, "o", refthingy(t_exchangepointandmark), NULL);
     bindkey(vismap, "p", refthingy(t_putreplaceselection), NULL);
+    bindkey(vismap, "u", refthingy(t_vidowncase), NULL);
+    bindkey(vismap, "U", refthingy(t_viupcase), NULL);
     bindkey(vismap, "x", refthingy(t_videlete), NULL);
     bindkey(vismap, "~", refthingy(t_vioperswapcase), NULL);
 
@@ -1374,8 +1379,12 @@ default_bindings(void)
     bindkey(amap, "ge", refthingy(t_vibackwardwordend), NULL);
     bindkey(amap, "gE", refthingy(t_vibackwardblankwordend), NULL);
     bindkey(amap, "gg", refthingy(t_beginningofbufferorhistory), NULL);
+    bindkey(amap, "gu", refthingy(t_vidowncase), NULL);
+    bindkey(amap, "gU", refthingy(t_viupcase), NULL);
     bindkey(amap, "g~", refthingy(t_vioperswapcase), NULL);
     bindkey(amap, "g~~", NULL, "g~g~");
+    bindkey(amap, "guu", NULL, "gugu");
+    bindkey(amap, "gUU", NULL, "gUgU");
 
     /* emacs mode: arrow keys */ 
     add_cursor_key(emap, TCUPCURSOR, t_uplineorhistory, 'A');
@@ -1615,11 +1624,18 @@ getkeymapcmd(Keymap km, Thingy *funcp, char **strp)
     else
 	lastchar = lastc;
     if(lastlen != keybuflen) {
+	/*
+	 * We want to keep only the first lastlen bytes of the key
+	 * buffer in the key buffer that were marked as used by the key
+	 * binding above, and make the rest available for input again.
+	 * That rest (but not what we are keeping) needs to be
+	 * unmetafied.
+	 */
 	unmetafy(keybuf + lastlen, &keybuflen);
 	ungetbytes(keybuf+lastlen, keybuflen);
 	if(vichgflag)
-	    vichgbufptr -= keybuflen;
-	keybuf[lastlen] = 0;
+	    curvichg.bufptr -= keybuflen;
+	keybuf[keybuflen = lastlen] = 0;
     }
     *funcp = func;
     *strp = str;
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 9bea76e9b..dda2f56e6 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -471,7 +471,7 @@ calc_timeout(struct ztmout *tmoutp, long do_keytmout)
 
 	    tfdat = (Timedfn)getdata(tfnode);
 	    diff = tfdat->when - time(NULL);
-	    if (diff < 0) {
+	    if (diff <= 0) {
 		/* Already due; call it and rescan. */
 		tfdat->func();
 		continue;
@@ -924,13 +924,13 @@ getbyte(long do_keytmout, int *timeout)
 	ret = STOUC(cc);
     }
     /*
-     * vichgbuf is raw bytes, not wide characters, so is dealt
+     * curvichg.buf is raw bytes, not wide characters, so is dealt
      * with here.
      */
     if (vichgflag) {
-	if (vichgbufptr == vichgbufsz)
-	    vichgbuf = realloc(vichgbuf, vichgbufsz *= 2);
-	vichgbuf[vichgbufptr++] = ret;
+	if (curvichg.bufptr == curvichg.bufsz)
+	    curvichg.buf = realloc(curvichg.buf, curvichg.bufsz *= 2);
+	curvichg.buf[curvichg.bufptr++] = ret;
     }
     errno = old_errno;
     return lastchar = ret;
@@ -1041,28 +1041,43 @@ getrestchar(int inchar, char *outstr, int *outcount)
 #endif
 
 /**/
-void redrawhook(void)
+void
+redrawhook(void)
 {
     Thingy initthingy;
     if ((initthingy = rthingy_nocreate("zle-line-pre-redraw"))) {
+	/* Duplicating most of zlecallhook() to save additional state */
+	int saverrflag = errflag, savretflag = retflag;
 	int lastcmd_prev = lastcmd;
 	int old_incompfunc = incompfunc;
 	char *args[2];
 	Thingy lbindk_save = lbindk, bindk_save = bindk;
+
 	refthingy(lbindk_save);
 	refthingy(bindk_save);
 	args[0] = initthingy->nam;
 	args[1] = NULL;
+
+	/* The generic redraw hook cannot be a completion function, so
+	 * temporarily reset state for special variable handling etc.
+	 */
 	incompfunc = 0;
-	execzlefunc(initthingy, args, 0);
+	execzlefunc(initthingy, args, 1);
 	incompfunc = old_incompfunc;
+
+	/* Restore errflag and retflag as zlecallhook() does */
+	errflag = saverrflag | (errflag & ERRFLAG_INT);
+	retflag = savretflag;
+
 	unrefthingy(initthingy);
 	unrefthingy(lbindk);
 	unrefthingy(bindk);
 	lbindk = lbindk_save;
 	bindk = bindk_save;
+
 	/* we can't set ZLE_NOTCOMMAND since it's not a legit widget, so
-	 * restore lastcmd manually so that we don't mess up the global state */
+	 * restore lastcmd manually so that we don't mess up the global state
+	 */
 	lastcmd = lastcmd_prev;
     }
 }
@@ -1159,11 +1174,19 @@ zlecore(void)
 
     }
 
-    region_active = 0;
     popheap();
 }
 
-/* Read a line.  It is returned metafied. */
+/* Read a line.  It is returned metafied.
+ *
+ * Parameters:
+ * - lp: left prompt, e.g., $PS1
+ * - rp: right prompt, e.g., $RPS1
+ * - flags: ZLRF_* flags (I think), see zlereadflags
+ * - context: ZLCON_* flags (I think), see zlecontext
+ * - init: "zle-line-init"
+ * - finish: "zle-line-finish"
+ */
 
 /**/
 char *
@@ -1223,6 +1246,7 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
     resetneeded = 0;
     fetchttyinfo = 0;
     trashedzle = 0;
+    clearflag = 0;
     raw_lp = lp;
     lpromptbuf = promptexpand(lp ? *lp : NULL, 1, NULL, NULL, &pmpt_attr);
     raw_rp = rp;
@@ -1238,6 +1262,7 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
     *zleline = ZWC('\0');
     virangeflag = lastcmd = done = zlecs = zlell = mark = yankb = yanke = 0;
     vichgflag = 0;
+    viinrepeat = 0;
     viinsbegin = 0;
     statusline = NULL;
     selectkeymap("main", 1);
@@ -1293,6 +1318,7 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
     lastcol = -1;
     initmodifier(&zmod);
     prefixflag = 0;
+    region_active = 0;
 
     zrefresh();
 
@@ -1300,7 +1326,11 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
 
     zlecallhook(init, NULL);
 
-    if ((bracket = getaparam("zle_bracketed_paste", &bracket_len)) && bracket_len == 2)
+    if (zleline && *zleline)
+	redrawhook();
+
+    if ((bracket = getaparam("zle_bracketed_paste", &bracket_len)) &&
+	bracket_len == 2)
 	fputs(*bracket, shout);
 
     zrefresh();
@@ -1342,6 +1372,16 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
     return s;
 }
 
+/**/
+static int
+execimmortal(Thingy func, char **args)
+{
+    Thingy immortal = rthingy_nocreate(dyncat(".", func->nam));
+    if (immortal)
+	return execzlefunc(immortal, args, 0);
+    return 1;
+}
+
 /*
  * Execute a widget.  The third argument indicates that the global
  * variable bindk should be set temporarily so that WIDGET etc.
@@ -1353,6 +1393,8 @@ int
 execzlefunc(Thingy func, char **args, int set_bindk)
 {
     int r = 0, ret = 0, remetafy = 0;
+    int nestedvichg = vichgflag;
+    int isrepeat = (viinrepeat == 3);
     Widget w;
     Thingy save_bindk = bindk;
 
@@ -1362,8 +1404,10 @@ execzlefunc(Thingy func, char **args, int set_bindk)
 	unmetafy_line();
 	remetafy = 1;
     }
+    if (isrepeat)
+	viinrepeat = 2;
 
-    if(func->flags & DISABLED) {
+    if (func->flags & DISABLED) {
 	/* this thingy is not the name of a widget */
 	char *nm = nicedup(func->nam, 0);
 	char *msg = tricat("No such widget `", nm, "'");
@@ -1371,7 +1415,7 @@ execzlefunc(Thingy func, char **args, int set_bindk)
 	zsfree(nm);
 	showmsg(msg);
 	zsfree(msg);
-	ret = 1;
+	ret = execimmortal(func, args);
     } else if((w = func->widget)->flags & (WIDGET_INT|WIDGET_NCOMP)) {
 	int wflags = w->flags;
 
@@ -1435,7 +1479,7 @@ execzlefunc(Thingy func, char **args, int set_bindk)
 	    zsfree(nm);
 	    showmsg(msg);
 	    zsfree(msg);
-	    ret = 1;
+	    ret = execimmortal(func, args);
 	} else {
 	    int osc = sfcontext, osi = movefd(0);
 	    int oxt = isset(XTRACE);
@@ -1457,6 +1501,12 @@ execzlefunc(Thingy func, char **args, int set_bindk)
 	    opts[XTRACE] = oxt;
 	    sfcontext = osc;
 	    endparamscope();
+	    if (errflag == ERRFLAG_ERROR) {
+		int saverr = errflag;
+		errflag &= ~ERRFLAG_ERROR;
+		if ((ret = execimmortal(func, args)) != 0)
+		    errflag |= saverr;
+	    }
 	    lastcmd = w->flags & ~(WIDGET_INUSE|WIDGET_FREE);
 	    if (inuse) {
 		w->flags &= WIDGET_INUSE|WIDGET_FREE;
@@ -1485,6 +1535,25 @@ execzlefunc(Thingy func, char **args, int set_bindk)
     CCRIGHT();
     if (remetafy)
 	metafy_line();
+
+    /* if this widget constituted the vi change, end it */
+    if (vichgflag == 2 && !nestedvichg) {
+	if (invicmdmode()) {
+	    if (ret) {
+		free(curvichg.buf);
+	    } else {
+		if (lastvichg.buf)
+		    free(lastvichg.buf);
+		lastvichg = curvichg;
+	    }
+	    vichgflag = 0;
+	    curvichg.buf = NULL;
+	} else
+	    vichgflag = 1; /* vi change continues while in insert mode */
+    }
+    if (isrepeat)
+        viinrepeat = !invicmdmode();
+
     return ret;
 }
 
@@ -1620,6 +1689,7 @@ bin_vared(char *name, char **args, Options ops, UNUSED(int func))
 	return 1;
     } else if (v) {
 	if (*s) {
+	    unqueue_signals();
 	    zwarnnam(name, "not an identifier: `%s'", args[0]);
 	    return 1;
 	}
@@ -1856,11 +1926,17 @@ int
 recursiveedit(UNUSED(char **args))
 {
     int locerror;
+    int q = queue_signal_level();
+
+    /* zlecore() expects to be entered with signal queue disabled */
+    dont_queue_signals();
 
     redrawhook();
     zrefresh();
     zlecore();
 
+    restore_queue_signals(q);
+
     locerror = errflag ? 1 : 0;
     errflag = done = eofsent = 0;
 
@@ -2185,7 +2261,7 @@ finish_(UNUSED(Module m))
     cleanup_keymaps();
     deletehashtable(thingytab);
 
-    zfree(vichgbuf, vichgbufsz);
+    zfree(lastvichg.buf, lastvichg.bufsz);
     zfree(kungetbuf, kungetsz);
     free_isrch_spots();
     if (rdstrs)
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
index a040ca0df..898b552de 100644
--- a/Src/Zle/zle_misc.c
+++ b/Src/Zle/zle_misc.c
@@ -609,8 +609,10 @@ viputbefore(UNUSED(char **args))
     int n = zmult;
 
     startvichange(-1);
-    if (n < 0 || zmod.flags & MOD_NULL)
+    if (n < 0)
 	return 1;
+    if (zmod.flags & MOD_NULL)
+	return 0;
     if (zmod.flags & MOD_VIBUF)
 	kctbuf = &vibuf[zmod.vibuf];
     else
@@ -630,8 +632,10 @@ viputafter(UNUSED(char **args))
     int n = zmult;
 
     startvichange(-1);
-    if (n < 0 || zmod.flags & MOD_NULL)
+    if (n < 0)
 	return 1;
+    if (zmod.flags & MOD_NULL)
+	return 0;
     if (zmod.flags & MOD_VIBUF)
 	kctbuf = &vibuf[zmod.vibuf];
     else
@@ -780,7 +784,7 @@ bracketedpaste(char **args)
 	int n;
 	ZLE_STRING_T wpaste;
 	wpaste = stringaszleline((zmult == 1) ? pbuf :
-	    quotestring(pbuf, NULL, QT_SINGLE_OPTIONAL), 0, &n, NULL, NULL);
+	    quotestring(pbuf, QT_SINGLE_OPTIONAL), 0, &n, NULL, NULL);
 	cuttext(wpaste, n, CUT_REPLACE);
 	if (!(zmod.flags & MOD_VIBUF)) {
 	    kct = -1;
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
index c6387bfc7..0a922d2d6 100644
--- a/Src/Zle/zle_params.c
+++ b/Src/Zle/zle_params.c
@@ -85,6 +85,8 @@ static const struct gsu_integer cursor_gsu =
 { get_cursor, set_cursor, zleunsetfn };
 static const struct gsu_integer histno_gsu =
 { get_histno, set_histno, zleunsetfn };
+static const struct gsu_integer keys_queued_count_gsu =
+{ get_keys_queued_count, NULL, zleunsetfn };
 static const struct gsu_integer mark_gsu =
 { get_mark, set_mark, zleunsetfn };
 static const struct gsu_integer numeric_gsu =
@@ -118,6 +120,12 @@ static const struct gsu_integer suffixactive_gsu =
 
 static const struct gsu_array killring_gsu =
 { get_killring, set_killring, unset_killring };
+
+static const struct gsu_scalar register_gsu =
+{ strgetfn, set_register, unset_register };
+static const struct gsu_hash registers_gsu =
+{ hashgetfn, set_registers, zleunsetfn };
+
 /* implementation is in zle_refresh.c */
 static const struct gsu_array region_highlight_gsu =
 { get_region_highlight, set_region_highlight, unset_region_highlight };
@@ -140,6 +148,8 @@ static struct zleparam {
     { "HISTNO", PM_INTEGER, GSU(histno_gsu), NULL },
     { "KEYMAP", PM_SCALAR | PM_READONLY, GSU(keymap_gsu), NULL },
     { "KEYS", PM_SCALAR | PM_READONLY, GSU(keys_gsu), NULL },
+    { "KEYS_QUEUED_COUNT", PM_INTEGER | PM_READONLY, GSU(keys_queued_count_gsu),
+      NULL},
     { "killring", PM_ARRAY, GSU(killring_gsu), NULL },
     { "LASTABORTEDSEARCH", PM_SCALAR | PM_READONLY, GSU(lastabortedsearch_gsu),
       NULL },
@@ -181,6 +191,7 @@ mod_export void
 makezleparams(int ro)
 {
     struct zleparam *zp;
+    Param reg_param;
 
     for(zp = zleparams; zp->name; zp++) {
 	Param pm = createparam(zp->name, (zp->type |PM_SPECIAL|PM_REMOVABLE|
@@ -206,6 +217,11 @@ makezleparams(int ro)
 	if ((zp->type & PM_UNSET) && (zmod.flags & (MOD_MULT|MOD_TMULT)))
 	    pm->node.flags &= ~PM_UNSET;
     }
+
+    reg_param = createspecialhash("registers", get_registers, &scan_registers,
+	    PM_LOCAL|PM_REMOVABLE);
+    reg_param->gsu.h = &registers_gsu;
+    reg_param->level = locallevel + 1;
 }
 
 /* Special unset function for ZLE special parameters: act like the standard *
@@ -446,6 +462,13 @@ get_keys(UNUSED(Param pm))
 }
 
 /**/
+static zlong
+get_keys_queued_count(UNUSED(Param pm))
+{
+    return kungetct;
+}
+
+/**/
 static void
 set_numeric(UNUSED(Param pm), zlong x)
 {
@@ -712,6 +735,111 @@ unset_killring(Param pm, int exp)
     }
 }
 
+/**/
+static void
+set_register(Param pm, char *value)
+{
+    int n = 0;
+    int offset = -1;
+    Cutbuffer vbuf;
+
+    if (!pm->node.nam || pm->node.nam[1])
+	;
+    else if (*pm->node.nam >= '0' && *pm->node.nam <= '9')
+	offset = '0' - 26;
+    else if (*pm->node.nam >= 'a' && *pm->node.nam <= 'z')
+	offset = 'a';
+
+    if (offset == -1) {
+	zerr("invalid zle register: %s", pm->node.nam);
+	return;
+    }
+
+    vbuf = &vibuf[*pm->node.nam - offset];
+    if (*value)
+	vbuf->buf = stringaszleline(value, 0, &n, NULL, NULL);
+    vbuf->len = n;
+}
+
+/**/
+static void
+unset_register(Param pm, UNUSED(int exp))
+{
+    set_register(pm, "");
+}
+
+/**/
+static void
+scan_registers(UNUSED(HashTable ht), ScanFunc func, int flags)
+{
+    int i;
+    char ch;
+    struct param pm;
+
+    memset((void *)&pm, 0, sizeof(struct param));
+    pm.node.flags = PM_SCALAR | PM_READONLY;
+    pm.gsu.s = &nullsetscalar_gsu;
+
+    for (i = 0, ch = 'a'; i < 36; i++) {
+	pm.node.nam = zhalloc(2);
+	*pm.node.nam = ch;
+	pm.node.nam[1] = '\0';
+	pm.u.str = zlelineasstring(vibuf[i].buf, vibuf[i].len, 0, NULL, NULL, 1);
+	func(&pm.node, flags);
+	if (ch++ == 'z')
+	    ch = '0';
+    }
+}
+
+/**/
+static HashNode
+get_registers(UNUSED(HashTable ht), const char *name)
+{
+    Param pm = (Param) hcalloc(sizeof(struct param));
+    int vbuf = -1;
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR;
+    pm->gsu.s = &register_gsu;
+
+    if (name[1])
+       ;
+    else if (*name >= '0' && *name <= '9')
+	vbuf = *name - '0' + 26;
+    else if (*name >= 'a' && *name <= 'z')
+	vbuf = *name - 'a';
+
+    if (vbuf == -1) {
+	pm->u.str = dupstring("");
+	pm->node.flags |= (PM_UNSET|PM_SPECIAL);
+    } else
+	pm->u.str = zlelineasstring(vibuf[vbuf].buf, vibuf[vbuf].len, 0, NULL, NULL, 1);
+
+    return &pm->node;
+}
+
+/**/
+static void
+set_registers(UNUSED(Param pm), HashTable ht)
+{
+    int i;
+    HashNode hn;
+
+    if (!ht)
+        return;
+
+    for (i = 0; i < ht->hsize; i++)
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+            v.isarr = v.flags = v.start = 0;
+            v.end = -1;
+            v.arr = NULL;
+            v.pm = (Param) hn;
+
+	    set_register(v.pm, getstrvalue(&v));
+        }
+    deleteparamtable(ht);
+}
+
 static void
 set_prepost(ZLE_STRING_T *textvar, int *lenvar, char *x)
 {
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index 13ab144ed..a64e2f29e 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -946,7 +946,7 @@ addmultiword(REFRESH_ELEMENT *base, ZLE_STRING_T tptr, int ichars)
 
 /*
  * Swap the old and new video buffers, plus any associated multiword
- * buffers.  The new buffer becomes the old one; the new new buffer
+ * buffers.  The new buffer becomes the old one; the new buffer
  * will be filled with the command line next time.
  */
 static void
@@ -1143,8 +1143,7 @@ zrefresh(void)
 	tsetcap(TCALLATTRSOFF, 0);
 	tsetcap(TCSTANDOUTEND, 0);
 	tsetcap(TCUNDERLINEEND, 0);
-	/* cheat on attribute unset */
-	txtunset(TXTBOLDFACE|TXTSTANDOUT|TXTUNDERLINE);
+	txtattrmask = 0;
 
 	if (trashedzle && !clearflag)
 	    reexpandprompt(); 
@@ -1279,7 +1278,7 @@ zrefresh(void)
 #ifdef __STDC_ISO_10646__
 		 !ZSH_INVALID_WCHAR_TEST(*t) &&
 #endif
-		 iswprint(*t) && (width = WCWIDTH(*t)) > 0) {
+		 WC_ISPRINT(*t) && (width = WCWIDTH(*t)) > 0) {
 	    int ichars;
 	    if (width > rpms.sen - rpms.s) {
 		int started = 0;
@@ -1461,7 +1460,7 @@ zrefresh(void)
 	u = outputline;
 	for (; u < outputline + outll; u++) {
 #ifdef MULTIBYTE_SUPPORT
-	    if (iswprint(*u)) {
+	    if (WC_ISPRINT(*u)) {
 		int width = WCWIDTH(*u);
 		/* Handle wide characters as above */
 		if (width > rpms.sen - rpms.s) {
@@ -2435,8 +2434,8 @@ redisplay(UNUSED(char **args))
     moveto(0, 0);
     zputc(&zr_cr);		/* extra care */
     tc_upcurs(lprompth - 1);
-    resetneeded = !showinglist;
-    clearflag = showinglist;
+    resetneeded = 1;
+    clearflag = 0;
     return 0;
 }
 
@@ -2469,7 +2468,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	if (tmpline[t0] == ZWC('\t'))
 	    vsiz = (vsiz | 7) + 2;
 #ifdef MULTIBYTE_SUPPORT
-	else if (iswprint(tmpline[t0]) && ((width = WCWIDTH(tmpline[t0])) > 0)) {
+	else if (WC_ISPRINT(tmpline[t0]) && ((width = WCWIDTH(tmpline[t0])) > 0)) {
 	    vsiz += width;
 	    if (isset(COMBININGCHARS) && IS_BASECHAR(tmpline[t0])) {
 		while (t0 < tmpll-1 && IS_COMBINING(tmpline[t0+1]))
@@ -2557,7 +2556,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	    vp->atr = all_atr_on | all_atr_off;
 	    vp++;
 #ifdef MULTIBYTE_SUPPORT
-	} else if (iswprint(tmpline[t0]) &&
+	} else if (WC_ISPRINT(tmpline[t0]) &&
 		   (width = WCWIDTH(tmpline[t0])) > 0) {
 	    int ichars;
 	    if (isset(COMBININGCHARS) && IS_BASECHAR(tmpline[t0])) {
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index 21495b6f2..c003148f8 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -269,7 +269,7 @@ freewidget(Widget w)
     zfree(w, sizeof(*w));
 }
 
-/* Add am internal widget provided by a module.  The name given is the  *
+/* Add an internal widget provided by a module.  The name given is the  *
  * canonical one, which must not begin with a dot.  The widget is first *
  * bound to the dotted canonical name; if that name is already taken by *
  * an internal widget, failure is indicated.  The same widget is then   *
@@ -678,7 +678,16 @@ bin_zle_flags(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
 		else if (!strcmp(*flag, "keepsuffix"))
 		    w->flags |= ZLE_KEEPSUFFIX;
 		*/
-		else {
+	        else if (!strcmp(*flag, "vichange")) {
+		    if (invicmdmode()) {
+			startvichange(-1);
+			if (zmod.flags & (MOD_MULT|MOD_TMULT)) {
+			    Param pm = (Param) paramtab->getnode(paramtab, "NUMERIC");
+			    if (pm && pm->node.flags & PM_SPECIAL)
+				pm->node.flags &= ~PM_UNSET;
+			}
+		    }
+		} else {
 		    zwarnnam(name, "invalid flag `%s' given to zle -f", *flag);
 		    ret = 1;
 		}
@@ -694,7 +703,7 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
 {
     Thingy t;
     struct modifier modsave = zmod;
-    int ret, saveflag = 0, setbindk = 0;
+    int ret, saveflag = 0, setbindk = 0, remetafy;
     char *wname = *args++, *keymap_restore = NULL, *keymap_tmp;
 
     if (!wname)
@@ -705,7 +714,15 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
 	return 1;
     }
 
-    UNMETACHECK();
+    /*
+     * zle is callable in traps, so we can't be sure the line is
+     * in its normal state.
+     */
+    if (zlemetaline) {
+	unmetafy_line();
+	remetafy = 1;
+    } else
+	remetafy = 0;
 
     while (*args && **args == '-') {
 	char *num;
@@ -719,6 +736,8 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
 		num = args[0][1] ? args[0]+1 : args[1];
 		if (!num) {
 		    zwarnnam(name, "number expected after -%c", **args);
+		    if (remetafy)
+			metafy_line();
 		    return 1;
 		}
 		if (!args[0][1])
@@ -736,19 +755,26 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
 		keymap_tmp = args[0][1] ? args[0]+1 : args[1];
 		if (!keymap_tmp) {
 		    zwarnnam(name, "keymap expected after -%c", **args);
+		    if (remetafy)
+			metafy_line();
 		    return 1;
 		}
 		if (!args[0][1])
 		    *++args = "" - 1;
 		keymap_restore = dupstring(curkeymapname);
-		if (selectkeymap(keymap_tmp, 0))
+		if (selectkeymap(keymap_tmp, 0)) {
+		    if (remetafy)
+			metafy_line();
 		    return 1;
+		}
 		break;
 	    case 'w':
 		setbindk = 1;
 		break;
 	    default:
 		zwarnnam(name, "unknown option: %s", *args);
+		if (remetafy)
+		    metafy_line();
 		return 1;
 	    }
 	}
@@ -756,12 +782,18 @@ bin_zle_call(char *name, char **args, UNUSED(Options ops), UNUSED(char func))
     }
 
     t = rthingy(wname);
+    /* for internal widgets we set bindk except for when getting
+     * a vi range to detect a repeated key */
+    setbindk = setbindk ||
+	(t->widget && (t->widget->flags & (WIDGET_INT | ZLE_VIOPER)) == WIDGET_INT);
     ret = execzlefunc(t, args, setbindk);
     unrefthingy(t);
     if (saveflag)
 	zmod = modsave;
     if (keymap_restore)
 	selectkeymap(keymap_restore, 0);
+    if (remetafy)
+	metafy_line();
     return ret;
 }
 
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 1d4e1d284..3d8679119 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -424,8 +424,8 @@ mod_export int instring, inbackt;
  * This uses the instring variable above.
  */
 
-#define quotename(s, e) \
-quotestring(s, e, instring == QT_NONE ? QT_BACKSLASH : instring)
+#define quotename(s) \
+quotestring(s, instring == QT_NONE ? QT_BACKSLASH : instring)
 
 /* Check if the given string is the name of a parameter and if this *
  * parameter is one worth expanding.                                */
@@ -709,7 +709,8 @@ docomplete(int lst)
 			for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
 			    for (hn = cmdnamtab->nodes[t0]; hn;
 				 hn = hn->next) {
-				if (strpfx(q, hn->nam) && findcmd(hn->nam, 0))
+				if (strpfx(q, hn->nam) &&
+				    findcmd(hn->nam, 0, 0))
 				    n++;
 				if (n == 2)
 				    break;
@@ -1304,8 +1305,12 @@ get_comp_string(void)
 	    zsfree(cmdstr);
 	    cmdstr = ztrdup(tokstr);
 	    cmdtok = tok;
-	    /* If everything before is a redirection, don't reset the index */
-	    if (wordpos != redirpos)
+	    /*
+	     * If everything before is a redirection, or anything
+	     * complicated enough that we've seen the word the
+	     * cursor is on, don't reset the index.
+	     */
+	    if (wordpos != redirpos && clwpos == -1)
 		wordpos = redirpos = 0;
 	} else if (tok == SEPER) {
 	    /*
@@ -1413,9 +1418,17 @@ get_comp_string(void)
 	/* If this is the word the cursor is in and we added a `x', *
 	 * remove it.                                               */
 	if (clwpos == wordpos++ && addedx) {
+	    int chuck_at, word_diff;
 	    zlemetacs_qsub = zlemetacs - qsub;
-	    chuck(&clwords[wordpos - 1][((zlemetacs_qsub - wb) >= sl) ?
-				 (sl - 1) : (zlemetacs_qsub - wb)]);
+	    word_diff = zlemetacs_qsub - wb;
+	    /* Ensure we chuck within the word... */
+	    if (word_diff >= sl)
+		chuck_at = sl -1;
+	    else if (word_diff < 0)
+		chuck_at = 0;
+	    else
+		chuck_at = word_diff;
+	    chuck(&clwords[wordpos - 1][chuck_at]);
 	}
     } while (tok != LEXERR && tok != ENDINPUT &&
 	     (tok != SEPER || (lexflags && tt0 == NULLTOK)));
@@ -1463,7 +1476,9 @@ get_comp_string(void)
 	t0 = STRING;
     } else if (t0 == STRING || t0 == TYPESET) {
 	/* We found a simple string. */
-	s = ztrdup(clwords[clwpos]);
+	s = clwords[clwpos];
+	DPUTS(!s, "Completion word has disappeared!");
+	s = ztrdup(s ? s : "");
     } else if (t0 == ENVSTRING) {
 	char sav;
 	/* The cursor was inside a parameter assignment. */
@@ -2004,11 +2019,11 @@ get_comp_string(void)
 
 			new->next = NULL;
 			new->str = dupstrpfx(bbeg, len);
-			new->str = ztrdup(quotename(new->str, NULL));
+			new->str = ztrdup(quotename(new->str));
 			untokenize(new->str);
 			new->pos = begi;
 			*dbeg = '\0';
-			new->qpos = strlen(quotename(predup, NULL));
+			new->qpos = strlen(quotename(predup));
 			*dbeg = '{';
 			i -= len;
 			boffs -= len;
@@ -2067,11 +2082,11 @@ get_comp_string(void)
 			lastbrbeg = new;
 
 			new->str = dupstrpfx(bbeg, len);
-			new->str = ztrdup(quotename(new->str, NULL));
+			new->str = ztrdup(quotename(new->str));
 			untokenize(new->str);
 			new->pos = begi;
 			*dbeg = '\0';
-			new->qpos = strlen(quotename(predup, NULL));
+			new->qpos = strlen(quotename(predup));
 			*dbeg = '{';
 			i -= len;
 			boffs -= len;
@@ -2117,7 +2132,7 @@ get_comp_string(void)
 		    brend = new;
 
 		    new->str = dupstrpfx(bbeg, len);
-		    new->str = ztrdup(quotename(new->str, NULL));
+		    new->str = ztrdup(quotename(new->str));
 		    untokenize(new->str);
 		    new->pos = dp - predup - len + 1;
 		    new->qpos = len;
@@ -2146,11 +2161,11 @@ get_comp_string(void)
 		lastbrbeg = new;
 
 		new->str = dupstrpfx(bbeg, len);
-		new->str = ztrdup(quotename(new->str, NULL));
+		new->str = ztrdup(quotename(new->str));
 		untokenize(new->str);
 		new->pos = begi;
 		*dbeg = '\0';
-		new->qpos = strlen(quotename(predup, NULL));
+		new->qpos = strlen(quotename(predup));
 		*dbeg = '{';
 		boffs -= len;
 		memmove(dbeg, dbeg + len, 1+strlen(dbeg+len));
@@ -2165,7 +2180,7 @@ get_comp_string(void)
 		    p = bp->pos;
 		    l = bp->qpos;
 		    bp->pos = strlen(predup + p + l);
-		    bp->qpos = strlen(quotename(predup + p + l, NULL));
+		    bp->qpos = strlen(quotename(predup + p + l));
 		    memmove(predup + p, predup + p + l, 1+bp->pos);
 		}
 	    }
@@ -2288,7 +2303,7 @@ doexpansion(char *s, int lst, int olst, int explincmd)
     foredel(we - wb, CUT_RAW);
     while ((ss = (char *)ugetnode(vl))) {
 	ret = 0;
-	ss = quotename(ss, NULL);
+	ss = quotename(ss);
 	untokenize(ss);
 	inststr(ss);
 	if (nonempty(vl) || !first) {
@@ -2997,7 +3012,7 @@ processcmd(UNUSED(char **args))
     inststr(" ");
     untokenize(s);
 
-    inststr(quotename(s, NULL));
+    inststr(quotename(s));
 
     zsfree(s);
     done = 1;
@@ -3027,7 +3042,7 @@ expandcmdpath(UNUSED(char **args))
 	return 1;
     }
 
-    str = findcmd(s, 1);
+    str = findcmd(s, 1, 0);
     zsfree(s);
     if (!str)
 	return 1;
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 68794c66a..c6df3d89c 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -1589,9 +1589,14 @@ undo(char **args)
 	    break;
 	if (prev->changeno <= undo_limitno && !*args)
 	    return 1;
-	if (!unapplychange(prev) && last_change >= 0)
-	    unapplychange(prev);
-	curchange = prev;
+	if (!unapplychange(prev)) {
+	    if (last_change >= 0) {
+		unapplychange(prev);
+		curchange = prev;
+	    }
+	} else {
+	    curchange = prev;
+	}
     } while (last_change >= (zlong)0 || (curchange->flags & CH_PREV));
     setlastline();
     return 0;
@@ -1699,6 +1704,7 @@ mergeundo(void)
 	current->flags |= CH_PREV;
 	current->prev->flags |= CH_NEXT;
     }
+    vistartchange = -1;
 }
 
 /*
@@ -1720,6 +1726,8 @@ zlecallhook(char *name, char *arg)
     if (!thingy)
 	return;
 
+    /* If anything here needs changing, see also redrawhook() */
+
     saverrflag = errflag;
     savretflag = retflag;
 
diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c
index 953af2401..e0923db3e 100644
--- a/Src/Zle/zle_vi.c
+++ b/Src/Zle/zle_vi.c
@@ -45,61 +45,71 @@ int wordflag;
 /**/
 int vilinerange;
 
-/* last vi change buffer, for vi change repetition */
+/*
+ * lastvichg: last vi change buffer, for vi change repetition
+ * curvichg: current incomplete vi change
+ */
+
+/**/
+struct vichange lastvichg, curvichg;
+
+/*
+ * true whilst a vi change is active causing keys to be
+ * accumulated in curvichg.buf
+ * first set to 2 and when the initial widget finishes, reduced to 1 if
+ * in insert mode implying that the change continues until returning to
+ * normal mode
+ */
 
 /**/
-int vichgbufsz, vichgbufptr, vichgflag;
+int vichgflag;
 
 /*
- * Examination of the code suggests vichgbuf is consistently tied
- * to raw byte input, so it is left as a character array rather
- * than turned into wide characters.  In particular, when we replay
- * it we use ungetbytes().
+ * analogous to vichgflag for a repeated change with the value following
+ * a similar pattern (is 3 until first repeated widget starts)
  */
+
 /**/
-char *vichgbuf;
+int viinrepeat;
 
 /* point where vi insert mode was last entered */
 
 /**/
 int viinsbegin;
 
-static struct modifier lastmod;
-static int inrepeat, vichgrepeat;
-
 /**
  * im: >= 0: is an insertmode
- *    -1: skip setting insert mode
+ *    -1: skip setting insert/overwrite mode
  *    -2: entering viins at start of editing from clean --- don't use
- *        inrepeat or lastchar, synthesise an i to enter insert mode.
+ *        inrepeat or keybuf, synthesise an entry to insert mode.
+ * Note that zmult is updated so this should be called before zmult is used.
  */
 
 /**/
 void
 startvichange(int im)
 {
-    if (im != -1) {
-	vichgflag = 1;
-	if (im > -1)
-	    insmode = im;
-    }
-    if (inrepeat && im != -2) {
-	zmod = lastmod;
-	inrepeat = vichgflag = 0;
-	vichgrepeat = 1;
-    } else {
-	lastmod = zmod;
-	if (vichgbuf)
-	    free(vichgbuf);
-	vichgbuf = (char *)zalloc(vichgbufsz = 16);
+    if (im > -1)
+	insmode = im;
+    if (viinrepeat && im != -2) {
+	zmod = lastvichg.mod;
+	vichgflag = 0;
+    } else if (!vichgflag) {
+	curvichg.mod = zmod;
+	if (curvichg.buf)
+	    free(curvichg.buf);
+	curvichg.buf = (char *)zalloc(curvichg.bufsz = 16 + keybuflen);
 	if (im == -2) {
-	    vichgbuf[0] =
+	    vichgflag = 1;
+	    curvichg.buf[0] =
 		zlell ? (insmode ? (zlecs < zlell ? 'i' : 'a') : 'R') : 'o';
+	    curvichg.buf[1] = '\0';
+	    curvichg.bufptr = 1;
 	} else {
-	    vichgbuf[0] = lastchar;
+	    vichgflag = 2;
+	    strcpy(curvichg.buf, keybuf);
+	    unmetafy(curvichg.buf, &curvichg.bufptr);
 	}
-	vichgbufptr = 1;
-	vichgrepeat = 0;
     }
 }
 
@@ -208,10 +218,13 @@ getvirange(int wf)
 	     */
 	    if ((k2 == bindk) ? dovilinerange() : execzlefunc(k2, zlenoargs, 1))
 		ret = -1;
-	    if(vichgrepeat)
+	    if (viinrepeat)
 		zmult = mult1;
-	    else
+	    else {
 		zmult = mult1 * zmod.tmult;
+		if (vichgflag == 2)
+		    curvichg.mod.mult = zmult;
+            }
 	} while(prefixflag && !ret);
 	wordflag = 0;
 	selectlocalmap(NULL);
@@ -383,7 +396,6 @@ videlete(UNUSED(char **args))
 	    vifirstnonblank(zlenoargs);
 	}
     }
-    vichgflag = 0;
     return ret;
 }
 
@@ -391,9 +403,10 @@ videlete(UNUSED(char **args))
 int
 videletechar(char **args)
 {
-    int n = zmult;
+    int n;
 
     startvichange(-1);
+    n = zmult;
 
     /* handle negative argument */
     if (n < 0) {
@@ -440,9 +453,10 @@ vichange(UNUSED(char **args))
 int
 visubstitute(UNUSED(char **args))
 {
-    int n = zmult;
+    int n;
 
     startvichange(1);
+    n = zmult;
     if (n < 0)
 	return 1;
     /* it is an error to be on the end of line */
@@ -498,7 +512,6 @@ viyank(UNUSED(char **args))
 	cut(zlecs, c2 - zlecs, CUT_YANK);
 	ret = 0;
     }
-    vichgflag = 0;
     /* cursor now at the start of the range yanked. For line mode
      * restore the column position */
     if (vilinerange && lastcol != -1) {
@@ -536,9 +549,10 @@ int
 viyankwholeline(UNUSED(char **args))
 {
     int bol = findbol(), oldcs = zlecs;
-    int n = zmult;
+    int n;
 
     startvichange(-1);
+    n = zmult;
     if (n < 1)
 	return 1;
     while(n--) {
@@ -572,16 +586,17 @@ vireplace(UNUSED(char **args))
  * a change, we always read the argument normally, even if the count    *
  * was bad.  When recording a change for repeating, and a bad count is  *
  * given, we squash the repeat buffer to avoid repeating the partial    *
- * command; we've lost the previous change, but that can't be avoided   *
- * without a rewrite of the repeat code.                                */
+ * command.                                                             */
 
 /**/
 int
 vireplacechars(UNUSED(char **args))
 {
     ZLE_INT_T ch;
-    int n = zmult, fail = 0, newchars = 0;
+    int n, fail = 0, newchars = 0;
 
+    startvichange(1);
+    n = zmult;
     if (n > 0) {
 	if (region_active) {
 	    int a, b;
@@ -618,21 +633,15 @@ vireplacechars(UNUSED(char **args))
 	    n = pos - zlecs;
 	}
     }
-    startvichange(1);
+
     /* check argument range */
     if (n < 1 || fail) {
-	if(vichgrepeat)
+	if (viinrepeat)
 	    vigetkey();
-	if(vichgflag) {
-	    free(vichgbuf);
-	    vichgbuf = NULL;
-	    vichgflag = 0;
-	}
 	return 1;
     }
     /* get key */
     if((ch = vigetkey()) == ZLEEOF) {
-	vichgflag = 0;
 	return 1;
     }
     /* do change */
@@ -659,7 +668,6 @@ vireplacechars(UNUSED(char **args))
 	    zleline[zlecs++] = ch;
 	zlecs--;
     }
-    vichgflag = 0;
     return 0;
 }
 
@@ -670,7 +678,16 @@ vicmdmode(UNUSED(char **args))
     if (invicmdmode() || selectkeymap("vicmd", 0))
 	return 1;
     mergeundo();
-    vichgflag = 0;
+    insmode = unset(OVERSTRIKE);
+    if (vichgflag == 1) {
+	vichgflag = 0;
+	if (lastvichg.buf)
+	    free(lastvichg.buf);
+	lastvichg = curvichg;
+	curvichg.buf = NULL;
+    }
+    if (viinrepeat == 1)
+        viinrepeat = 0;
     if (zlecs != findbol())
 	DECCS();
     return 0;
@@ -725,7 +742,50 @@ vioperswapcase(UNUSED(char **args))
 	vifirstnonblank();
 #endif
     }
-    vichgflag = 0;
+    return ret;
+}
+
+/**/
+int
+viupcase(UNUSED(char **args))
+{
+    int oldcs, c2, ret = 1;
+
+    /* get the range */
+    startvichange(1);
+    if ((c2 = getvirange(0)) != -1) {
+	oldcs = zlecs;
+	/* covert the case of all letters within range */
+	while (zlecs < c2) {
+	    zleline[zlecs] = ZC_toupper(zleline[zlecs]);
+	    INCCS();
+	}
+	/* go back to the first line of the range */
+	zlecs = oldcs;
+	ret = 0;
+    }
+    return ret;
+}
+
+/**/
+int
+vidowncase(UNUSED(char **args))
+{
+    int oldcs, c2, ret = 1;
+
+    /* get the range */
+    startvichange(1);
+    if ((c2 = getvirange(0)) != -1) {
+	oldcs = zlecs;
+	/* convert the case of all letters within range */
+	while (zlecs < c2) {
+	    zleline[zlecs] = ZC_tolower(zleline[zlecs]);
+	    INCCS();
+	}
+	/* go back to the first line of the range */
+	zlecs = oldcs;
+	ret = 0;
+    }
     return ret;
 }
 
@@ -734,21 +794,23 @@ int
 virepeatchange(UNUSED(char **args))
 {
     /* make sure we have a change to repeat */
-    if (!vichgbuf || vichgflag)
+    if (!lastvichg.buf || vichgflag || virangeflag)
 	return 1;
     /* restore or update the saved count and buffer */
     if (zmod.flags & MOD_MULT) {
-	lastmod.mult = zmod.mult;
-	lastmod.flags |= MOD_MULT;
+	lastvichg.mod.mult = zmod.mult;
+	lastvichg.mod.flags |= MOD_MULT;
     }
     if (zmod.flags & MOD_VIBUF) {
-	lastmod.vibuf = zmod.vibuf;
-	lastmod.flags = (lastmod.flags & ~MOD_VIAPP) |
+	lastvichg.mod.vibuf = zmod.vibuf;
+	lastvichg.mod.flags = (lastvichg.mod.flags & ~MOD_VIAPP) |
 	    MOD_VIBUF | (zmod.flags & MOD_VIAPP);
-    }
+    } else if (lastvichg.mod.flags & MOD_VIBUF &&
+	    lastvichg.mod.vibuf >= 27 && lastvichg.mod.vibuf <= 34)
+	lastvichg.mod.vibuf++; /* for "1 to "8 advance to next buffer */
     /* repeat the command */
-    inrepeat = 1;
-    ungetbytes(vichgbuf, vichgbufptr);
+    viinrepeat = 3;
+    ungetbytes(lastvichg.buf, lastvichg.bufptr);
     return 0;
 }
 
@@ -764,10 +826,8 @@ viindent(UNUSED(char **args))
 	region_active = 2;
     /* get the range */
     if ((c2 = getvirange(0)) == -1) {
-	vichgflag = 0;
 	return 1;
     }
-    vichgflag = 0;
     /* must be a line range */
     if (!vilinerange) {
 	zlecs = oldcs;
@@ -802,10 +862,8 @@ viunindent(UNUSED(char **args))
 	region_active = 2;
     /* get the range */
     if ((c2 = getvirange(0)) == -1) {
-	vichgflag = 0;
 	return 1;
     }
-    vichgflag = 0;
     /* must be a line range */
     if (!vilinerange) {
 	zlecs = oldcs;
@@ -828,12 +886,13 @@ viunindent(UNUSED(char **args))
 int
 vibackwarddeletechar(char **args)
 {
-    int n = zmult;
+    int n;
 
     if (invicmdmode())
 	startvichange(-1);
 
     /* handle negative argument */
+    n = zmult;
     if (n < 0) {
 	int ret;
 	zmult = -n;
@@ -873,10 +932,11 @@ int
 vijoin(UNUSED(char **args))
 {
     int x, pos;
-    int n = zmult;
+    int n;
     int visual = region_active;
 
     startvichange(-1);
+    n = zmult;
     if (n < 1)
 	return 1;
     if (visual && zlecs > mark) {
@@ -915,9 +975,10 @@ vijoin(UNUSED(char **args))
 int
 viswapcase(UNUSED(char **args))
 {
-    int eol, n = zmult;
+    int eol, n;
 
     startvichange(-1);
+    n = zmult;
     if (n < 1)
 	return 1;
     eol = findeol();
diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c
index 3c1f26c40..e4a878eab 100644
--- a/Src/Zle/zle_word.c
+++ b/Src/Zle/zle_word.c
@@ -678,46 +678,54 @@ killword(char **args)
 int
 transposewords(UNUSED(char **args))
 {
-    int p1, p2, p3, p4, len, x = zlecs, pos;
+    int p1, p2, p3, p4, pt, len, x = zlecs, pos;
     ZLE_STRING_T temp, pp;
     int n = zmult;
     int neg = n < 0, ocs = zlecs;
 
     if (neg)
 	n = -n;
-    while (n--) {
-	while (x != zlell && zleline[x] != ZWC('\n') && !ZC_iword(zleline[x]))
-	    INCPOS(x);
-	if (x == zlell || zleline[x] == ZWC('\n')) {
-	    x = zlecs;
-	    while (x) {
-		if (ZC_iword(zleline[x]))
-		    break;
-		pos = x;
-		DECPOS(pos);
-		if (zleline[pos] == ZWC('\n'))
-		    break;
-		x = pos;
-	    }
-	    if (!x)
-		return 1;
+
+    while (x != zlell && zleline[x] != ZWC('\n') && !ZC_iword(zleline[x]))
+	INCPOS(x);
+
+    if (x == zlell || zleline[x] == ZWC('\n')) {
+	x = zlecs;
+	while (x) {
+	    if (ZC_iword(zleline[x]))
+		break;
 	    pos = x;
 	    DECPOS(pos);
 	    if (zleline[pos] == ZWC('\n'))
-		return 1;
-	}
-	for (p4 = x; p4 != zlell && ZC_iword(zleline[p4]); INCPOS(p4))
-	    ;
-	for (p3 = p4; p3; ) {
-	    pos = p3;
-	    DECPOS(pos);
-	    if (!ZC_iword(zleline[pos]))
 		break;
-	    p3 = pos;
+	    x = pos;
 	}
-	if (!p3)
+	if (!x)
 	    return 1;
-	for (p2 = p3; p2; ) {
+	pos = x;
+	DECPOS(pos);
+	if (zleline[pos] == ZWC('\n'))
+	    return 1;
+    }
+
+    for (p4 = x; p4 != zlell && ZC_iword(zleline[p4]); INCPOS(p4))
+	;
+
+    for (p3 = p4; p3; ) {
+	pos = p3;
+	DECPOS(pos);
+	if (!ZC_iword(zleline[pos]))
+	    break;
+	p3 = pos;
+    }
+
+    if (!p3)
+	return 1;
+
+    p1 = p2 = pt = p3;
+
+    while (n--) {
+	for (p2 = pt; p2; ) {
 	    pos = p2;
 	    DECPOS(pos);
 	    if (ZC_iword(zleline[pos]))
@@ -733,20 +741,24 @@ transposewords(UNUSED(char **args))
 		break;
 	    p1 = pos;
 	}
-	pp = temp = (ZLE_STRING_T)zhalloc((p4 - p1)*ZLE_CHAR_SIZE);
-	len = p4 - p3;
-	ZS_memcpy(pp, zleline + p3, len);
-	pp += len;
-	len = p3 - p2;
-	ZS_memcpy(pp, zleline + p2, len);
-	pp += len;
-	ZS_memcpy(pp, zleline + p1, p2 - p1);
+	pt = p1;
+    }
 
-	ZS_memcpy(zleline + p1, temp, p4 - p1);
+    pp = temp = (ZLE_STRING_T)zhalloc((p4 - p1)*ZLE_CHAR_SIZE);
+    len = p4 - p3;
+    ZS_memcpy(pp, zleline + p3, len);
+    pp += len;
+    len = p3 - p2;
+    ZS_memcpy(pp, zleline + p2, len);
+    pp += len;
+    ZS_memcpy(pp, zleline + p1, p2 - p1);
+
+    ZS_memcpy(zleline + p1, temp, p4 - p1);
 
-	zlecs = p4;
-    }
     if (neg)
 	zlecs = ocs;
+    else
+	zlecs = p4;
+
     return 0;
 }
diff --git a/Src/builtin.c b/Src/builtin.c
index ba7136eb4..d54b0f41c 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -48,7 +48,7 @@ static struct builtin builtins[] =
     BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
     BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
     BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmrs", NULL),
-    BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "mktTUwXz", "u"),
+    BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "dmktrRTUwWXz", "u"),
     BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
     BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
     BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
@@ -74,7 +74,7 @@ static struct builtin builtins[] =
     BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
     BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
     BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
-    BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL),
+    BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMstTuUWx:z", NULL),
     BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
     BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
     BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL),
@@ -102,7 +102,7 @@ static struct builtin builtins[] =
 
     BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
     BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:v:x:X:z-", NULL),
-    BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, "v:", NULL),
+    BUILTIN("printf", BINF_SKIPINVALID | BINF_SKIPDASH, bin_print, 1, -1, BIN_PRINTF, "v:", NULL),
     BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
     BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
@@ -251,7 +251,7 @@ int
 execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 {
     char *pp, *name, *optstr;
-    int flags, sense, argc, execop, xtr = isset(XTRACE);
+    int flags, argc, execop, xtr = isset(XTRACE);
     struct options ops;
 
     /* initialise options structure */
@@ -296,6 +296,7 @@ execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 	/* Sort out the options. */
 	if (optstr) {
 	    char *arg = *argv;
+	    int sense; /* 1 for -x, 0 for +x */
 	    /* while arguments look like options ... */
 	    while (arg &&
 		   /* Must begin with - or maybe + */
@@ -389,7 +390,7 @@ execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 		if (*arg) {
 		    if(*arg == Meta)
 			*++arg ^= 32;
-		    zwarnnam(name, "bad option: -%c", *arg);
+		    zwarnnam(name, "bad option: %c%c", "+-"[sense], *arg);
 		    return 1;
 		}
 		arg = *++argv;
@@ -540,18 +541,18 @@ bin_enable(char *name, char **argv, Options ops, int func)
     /* With -m option, treat arguments as glob patterns. */
     if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
+	    queue_signals();
+
 	    /* parse pattern */
 	    tokenize(*argv);
-	    if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
-		queue_signals();
+	    if ((pprog = patcompile(*argv, PAT_STATIC, 0)))
 		match += scanmatchtable(ht, pprog, 0, 0, 0, scanfunc, 0);
-		unqueue_signals();
-	    }
 	    else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv);
 		returnval = 1;
 	    }
+	    unqueue_signals();
 	}
 	/* If we didn't match anything, we return 1. */
 	if (!match)
@@ -795,8 +796,8 @@ set_pwd_env(void)
 	unsetparam_pm(pm, 0, 1);
     }
 
-    setsparam("PWD", ztrdup(pwd));
-    setsparam("OLDPWD", ztrdup(oldpwd));
+    assignsparam("PWD", ztrdup(pwd), 0);
+    assignsparam("OLDPWD", ztrdup(oldpwd), 0);
 
     pm = (Param) paramtab->getnode(paramtab, "PWD");
     if (!(pm->node.flags & PM_EXPORTED))
@@ -973,7 +974,7 @@ cd_do_chdir(char *cnam, char *dest, int hard)
      * Normalize path under Cygwin to avoid messing with
      * DOS style names with drives in them
      */
-    static char buf[PATH_MAX];
+    static char buf[PATH_MAX+1];
 #ifdef HAVE_CYGWIN_CONV_PATH
     cygwin_conv_path(CCP_WIN_A_TO_POSIX | CCP_RELATIVE, dest, buf,
 		     PATH_MAX);
@@ -1273,7 +1274,23 @@ fixdir(char *src)
 #ifdef __CYGWIN__
     char *s0 = src;
 #endif
-    int ret = 0;
+    /* This function is always called with n path containing at
+     * least one slash, either because one was input by the user or
+     * because the caller has prepended either pwd or a cdpath dir.
+     * If asked to make a relative change and pwd is set to ".",
+     * the current directory has been removed out from under us,
+     * so force links to be chased.
+     *
+     * Ordinarily we can't get here with "../" as the first component
+     * but handle the silly special case of ".." in cdpath.
+     *
+     * Order of comparisons here looks funny, but it short-circuits
+     * most rapidly in the event of a false condition.  Set to 2
+     * here so we still obey the (lack of) CHASEDOTS option after
+     * the first "../" is preserved (test chasedots > 1 below).
+     */
+    int chasedots = (src[0] == '.' && pwd[0] == '.' && pwd[1] == '\0' &&
+		     (src[1] == '/' || (src[1] == '.' && src[2] == '/'))) * 2;
 
 /*** if have RFS superroot directory ***/
 #ifdef HAVE_SUPERROOT
@@ -1305,12 +1322,12 @@ fixdir(char *src)
 	    while (dest > d0 + 1 && dest[-1] == '/')
 		dest--;
 	    *dest = '\0';
-	    return ret;
+	    return chasedots;
 	}
 	if (src[0] == '.' && src[1] == '.' &&
 	    (src[2] == '\0' || src[2] == '/')) {
-	    if (isset(CHASEDOTS)) {
-		ret = 1;
+	    if (isset(CHASEDOTS) || chasedots > 1) {
+		chasedots = 1;
 		/* and treat as normal path segment */
 	    } else {
 		if (dest > d0 + 1) {
@@ -1348,6 +1365,7 @@ fixdir(char *src)
 		    dest[-1] = *src++ ^ 32;
 	}
     }
+    /* unreached */
 }
 
 /**/
@@ -1472,6 +1490,7 @@ bin_fc(char *nam, char **argv, Options ops, int func)
     }
 
     if (zleactive) {
+	unqueue_signals();
 	zwarnnam(nam, "no interactive history within ZLE");
 	return 1;
     }
@@ -1610,7 +1629,7 @@ bin_fc(char *nam, char **argv, Options ops, int func)
 		unqueue_signals();
 		if (fcedit(editor, fil)) {
 		    if (stuff(fil))
-			zwarnnam("fc", "%e: %s", errno, s);
+			zwarnnam("fc", "%e: %s", errno, fil);
 		    else {
 			loop(0,1);
 			retval = lastval;
@@ -1990,11 +2009,12 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
      * handled in createparam().  Here we just avoid using it for the
      * present tests if it's unset.
      *
-     * POSIXBUILTINS horror: we need to retain the 'readonly' flag
-     * of an unset parameter.
+     * POSIXBUILTINS horror: we need to retain the 'readonly' or 'export'
+     * flags of an unset parameter.
      */
     usepm = pm && (!(pm->node.flags & PM_UNSET) ||
-		   (isset(POSIXBUILTINS) && (pm->node.flags & PM_READONLY)));
+		   (isset(POSIXBUILTINS) &&
+		    (pm->node.flags & (PM_READONLY|PM_EXPORTED))));
 
     /*
      * We need to compare types with an existing pm if special,
@@ -2117,7 +2137,8 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	/*
 	 * Stricter rules about retaining readonly attribute in this case.
 	 */
-	if ((on & PM_READONLY) && (!usepm || (pm->node.flags & PM_UNSET)) &&
+	if ((on & (PM_READONLY|PM_EXPORTED)) &&
+	    (!usepm || (pm->node.flags & PM_UNSET)) &&
 	    !ASG_VALUEP(asg))
 	    on |= PM_UNSET;
 	else if (usepm && (pm->node.flags & PM_READONLY) &&
@@ -2125,6 +2146,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	    zerr("read-only variable: %s", pm->node.nam);
 	    return NULL;
 	}
+	/* This is handled by createparam():
+	if (usepm && (pm->node.flags & PM_EXPORTED) && !(off & PM_EXPORTED))
+	    on |= PM_EXPORTED;
+	*/
     }
 
     /*
@@ -2266,6 +2291,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	    zerrnam(cname, "%s: restricted", pname);
 	    return pm;
 	}
+	if (pm->node.flags & PM_SINGLE) {
+	    zerrnam(cname, "%s: can only have a single instance", pname);
+	    return pm;
+	}
 	/*
 	 * For specials, we keep the same struct but zero everything.
 	 * Maybe it would be easier to create a new struct but copy
@@ -2787,6 +2816,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	return 0;
     }
     if (off & PM_TIED) {
+	unqueue_signals();
 	zerrnam(name, "use unset to remove tied variables");
 	return 1;
     }
@@ -2892,9 +2922,61 @@ eval_autoload(Shfunc shf, char *name, Options ops, int func)
     }
 
     return !loadautofn(shf, (OPT_ISSET(ops,'k') ? 2 :
-			     (OPT_ISSET(ops,'z') ? 0 : 1)), 1);
+			     (OPT_ISSET(ops,'z') ? 0 : 1)), 1,
+		       OPT_ISSET(ops,'d'));
 }
 
+/* Helper for bin_functions() for -X and -r options */
+
+/**/
+static int
+check_autoload(Shfunc shf, char *name, Options ops, int func)
+{
+    if (OPT_ISSET(ops,'X'))
+    {
+	return eval_autoload(shf, name, ops, func);
+    }
+    if ((OPT_ISSET(ops,'r') || OPT_ISSET(ops,'R')) &&
+	(shf->node.flags & PM_UNDEFINED))
+    {
+	char *dir_path;
+	if (shf->filename && (shf->node.flags & PM_LOADDIR)) {
+	    char *spec_path[2];
+	    spec_path[0] = shf->filename;
+	    spec_path[1] = NULL;
+	    if (getfpfunc(shf->node.nam, NULL, &dir_path, spec_path, 1)) {
+		/* shf->filename is already correct. */
+		return 0;
+	    }
+	    if (!OPT_ISSET(ops,'d')) {
+		if (OPT_ISSET(ops,'R')) {
+		    zerr("%s: function definition file not found",
+			 shf->node.nam);
+		    return 1;
+		}
+		return 0;
+	    }
+	}
+	if (getfpfunc(shf->node.nam, NULL, &dir_path, NULL, 1)) {
+	    dircache_set(&shf->filename, NULL);
+	    if (*dir_path != '/') {
+		dir_path = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
+				    "/", dir_path);
+		dir_path = xsymlink(dir_path, 1);
+	    }
+	    dircache_set(&shf->filename, dir_path);
+	    shf->node.flags |= PM_LOADDIR;
+	    return 0;
+	}
+	if (OPT_ISSET(ops,'R')) {
+	    zerr("%s: function definition file not found",
+		 shf->node.nam);
+	    return 1;
+	}
+	/* with -r, we don't flag an error, just let it be found later. */
+    }
+    return 0;
+}
 
 /* List a user-defined math function. */
 static void
@@ -2911,7 +2993,7 @@ listusermathfunc(MathFunc p)
     else
 	showargs = 0;
 
-    printf("functions -M %s", p->name);
+    printf("functions -M%s %s", (p->flags & MFF_STR) ? "s" : "", p->name);
     if (showargs) {
 	printf(" %d", p->minargs);
 	showargs--;
@@ -2932,6 +3014,66 @@ listusermathfunc(MathFunc p)
 }
 
 
+static void
+add_autoload_function(Shfunc shf, char *funcname)
+{
+    char *nam;
+    if (*funcname == '/' && funcname[1] &&
+	(nam = strrchr(funcname, '/')) && nam[1] &&
+	(shf->node.flags & PM_UNDEFINED)) {
+	char *dir;
+	nam = strrchr(funcname, '/');
+	if (nam == funcname) {
+	    dir = "/";
+	} else {
+	    *nam++ = '\0';
+	    dir = funcname;
+	}
+	dircache_set(&shf->filename, NULL);
+	dircache_set(&shf->filename, dir);
+	shf->node.flags |= PM_LOADDIR;
+	shf->node.flags |= PM_ABSPATH_USED;
+	shfunctab->addnode(shfunctab, ztrdup(nam), shf);
+    } else {
+        Shfunc shf2;
+        Funcstack fs;
+        const char *calling_f = NULL;
+        char buf[PATH_MAX+1];
+
+        /* Find calling function */
+        for (fs = funcstack; fs; fs = fs->prev) {
+            if (fs->tp == FS_FUNC && fs->name && (!shf->node.nam || 0 != strcmp(fs->name,shf->node.nam))) {
+                calling_f = fs->name;
+                break;
+            }
+        }
+
+        /* Get its directory */
+        if (calling_f) {
+            /* Should contain load directory, and be loaded via absolute path */
+            if ((shf2 = (Shfunc) shfunctab->getnode2(shfunctab, calling_f))
+                    && (shf2->node.flags & PM_LOADDIR) && (shf2->node.flags & PM_ABSPATH_USED)
+                    && shf2->filename)
+            {
+                if (strlen(shf2->filename) + strlen(funcname) + 1 < PATH_MAX)
+                {
+                    sprintf(buf, "%s/%s", shf2->filename, funcname);
+                    /* Set containing directory if the function file
+                     * exists (do normal FPATH processing otherwise) */
+                    if (!access(buf, R_OK)) {
+                        dircache_set(&shf->filename, NULL);
+                        dircache_set(&shf->filename, shf2->filename);
+                        shf->node.flags |= PM_LOADDIR;
+                        shf->node.flags |= PM_ABSPATH_USED;
+                    }
+                }
+            }
+        }
+
+	shfunctab->addnode(shfunctab, ztrdup(funcname), shf);
+    }
+}
+
 /* Display or change the attributes of shell functions.   *
  * If called as autoload, it will define a new autoloaded *
  * (undefined) shell function.                            */
@@ -2962,6 +3104,10 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	on |= PM_TAGGED_LOCAL;
     else if (OPT_PLUS(ops,'T'))
 	off |= PM_TAGGED_LOCAL;
+    if (OPT_MINUS(ops,'W'))
+	on |= PM_WARNNESTED;
+    else if (OPT_PLUS(ops,'W'))
+	off |= PM_WARNNESTED;
     roff = off;
     if (OPT_MINUS(ops,'z')) {
 	on |= PM_ZSHSTORED;
@@ -2977,10 +3123,17 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	off |= PM_KSHSTORED;
 	roff |= PM_KSHSTORED;
     }
+    if (OPT_MINUS(ops,'d')) {
+	on |= PM_CUR_FPATH;
+	off |= PM_CUR_FPATH;
+    } else if (OPT_PLUS(ops,'d')) {
+	off |= PM_CUR_FPATH;
+	roff |= PM_CUR_FPATH;
+    }
 
     if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
 	(OPT_ISSET(ops,'x') && !OPT_HASARG(ops,'x')) ||
-	(OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || *argv || !scriptname))) {
+	(OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || !scriptname))) {
 	zwarnnam(name, "invalid option(s)");
 	return 1;
     }
@@ -3019,9 +3172,9 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	} else if (OPT_ISSET(ops,'m')) {
 	    /* List matching functions. */
 	    for (; *argv; argv++) {
+		queue_signals();
 		tokenize(*argv);
 		if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
-		    queue_signals();
 		    for (p = mathfuncs, q = NULL; p; q = p) {
 			MathFunc next;
 			do {
@@ -3040,12 +3193,12 @@ bin_functions(char *name, char **argv, Options ops, int func)
 			if (p)
 			    p = p->next;
 		    }
-		    unqueue_signals();
 		} else {
 		    untokenize(*argv);
 		    zwarnnam(name, "bad pattern : %s", *argv);
 		    returnval = 1;
 		}
+		unqueue_signals();
 	    }
 	} else if (OPT_PLUS(ops,'M')) {
 	    /* Delete functions. -m is allowed but is handled above. */
@@ -3067,11 +3220,18 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	    }
 	} else {
 	    /* Add a function */
-	    int minargs = 0, maxargs = -1;
+	    int minargs, maxargs;
 	    char *funcname = *argv++;
 	    char *modname = NULL;
 	    char *ptr;
 
+	    if (OPT_ISSET(ops,'s')) {
+		minargs = maxargs = 1;
+	    } else {
+		minargs = 0;
+		maxargs = -1;
+	    }
+
 	    ptr = itype_end(funcname, IIDENT, 0);
 	    if (idigit(*funcname) || funcname == ptr || *ptr) {
 		zwarnnam(name, "-M %s: bad math function name", funcname);
@@ -3085,6 +3245,10 @@ bin_functions(char *name, char **argv, Options ops, int func)
 			     *argv);
 		    return 1;
 		}
+		if (OPT_ISSET(ops,'s') && minargs != 1) {
+		    zwarnnam(name, "-Ms: must take a single string argument");
+		    return 1;
+		}
 		maxargs = minargs;
 		argv++;
 	    }
@@ -3098,6 +3262,10 @@ bin_functions(char *name, char **argv, Options ops, int func)
 			     *argv);
 		    return 1;
 		}
+		if (OPT_ISSET(ops,'s') && maxargs != 1) {
+		    zwarnnam(name, "-Ms: must take a single string argument");
+		    return 1;
+		}
 		argv++;
 	    }
 	    if (*argv)
@@ -3110,6 +3278,8 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	    p = (MathFunc)zshcalloc(sizeof(struct mathfunc));
 	    p->name = ztrdup(funcname);
 	    p->flags = MFF_USERFUNC;
+	    if (OPT_ISSET(ops,'s'))
+		p->flags |= MFF_STR;
 	    p->module = modname ? ztrdup(modname) : NULL;
 	    p->minargs = minargs;
 	    p->maxargs = maxargs;
@@ -3117,6 +3287,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	    queue_signals();
 	    for (q = mathfuncs; q; q = q->next) {
 		if (!strcmp(q->name, funcname)) {
+		    unqueue_signals();
 		    zwarnnam(name, "-M %s: function already exists",
 			     funcname);
 		    zsfree(p->name);
@@ -3134,47 +3305,58 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	return returnval;
     }
 
-    /* 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) {
-	int ret = 0;
-
+    if (OPT_MINUS(ops,'X')) {
+	Funcstack fs;
+	char *funcname = NULL;
+	int ret;
+	if (*argv && argv[1]) {
+	    zwarnnam(name, "-X: too many arguments");
+	    return 1;
+	}
 	queue_signals();
-	if (OPT_MINUS(ops,'X')) {
-	    Funcstack fs;
-	    char *funcname = NULL;
-	    for (fs = funcstack; fs; fs = fs->prev) {
-		if (fs->tp == FS_FUNC) {
-		    /*
-		     * dupstring here is paranoia but unlikely to be
-		     * problematic
-		     */
-		    funcname = dupstring(fs->name);
-		    break;
-		}
+	for (fs = funcstack; fs; fs = fs->prev) {
+	    if (fs->tp == FS_FUNC) {
+		/*
+		 * dupstring here is paranoia but unlikely to be
+		 * problematic
+		 */
+		funcname = dupstring(fs->name);
+		break;
 	    }
-	    if (!funcname)
-	    {
-		zerrnam(name, "bad autoload");
-		ret = 1;
+	}
+	if (!funcname)
+	{
+	    zerrnam(name, "bad autoload");
+	    ret = 1;
+	} else {
+	    if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) {
+		DPUTS(!shf->funcdef,
+		      "BUG: Calling autoload from empty function");
 	    } else {
-		if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) {
-		    DPUTS(!shf->funcdef,
-			  "BUG: Calling autoload from empty function");
-		} else {
-		    shf = (Shfunc) zshcalloc(sizeof *shf);
-		    shfunctab->addnode(shfunctab, ztrdup(funcname), shf);
-		}
-		shf->node.flags = on;
-		ret = eval_autoload(shf, funcname, ops, func);
+		shf = (Shfunc) zshcalloc(sizeof *shf);
+		shfunctab->addnode(shfunctab, ztrdup(funcname), shf);
 	    }
-	} else {
-	    if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
+	    if (*argv) {
+		dircache_set(&shf->filename, NULL);
+		dircache_set(&shf->filename, *argv);
+		on |= PM_LOADDIR;
+	    }
+	    shf->node.flags = on;
+	    ret = eval_autoload(shf, funcname, ops, func);
+	}
+	unqueue_signals();
+	return ret;
+    } else if (!*argv) {
+	/* 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.                         */
+	int ret = 0;
+
+	queue_signals();
+	if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
 		on &= ~PM_UNDEFINED;
 	    scanshfunc(1, on|off, DISABLED, shfunctab->printnode,
 		       pflags, expand);
-	}
 	unqueue_signals();
 	return ret;
     }
@@ -3183,11 +3365,11 @@ bin_functions(char *name, char **argv, Options ops, int func)
     if (OPT_ISSET(ops,'m')) {
 	on &= ~PM_UNDEFINED;
 	for (; *argv; argv++) {
+	    queue_signals();
 	    /* expand argument */
 	    tokenize(*argv);
 	    if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
 		/* with no options, just print all functions matching the glob pattern */
-		queue_signals();
 		if (!(on|off) && !OPT_ISSET(ops,'X')) {
 		    scanmatchshfunc(pprog, 1, 0, DISABLED,
 				   shfunctab->printnode, pflags, expand);
@@ -3200,19 +3382,19 @@ bin_functions(char *name, char **argv, Options ops, int func)
 				!(shf->node.flags & DISABLED)) {
 				shf->node.flags = (shf->node.flags |
 					      (on & ~PM_UNDEFINED)) & ~off;
-				if (OPT_ISSET(ops,'X') &&
-				    eval_autoload(shf, shf->node.nam, ops, func)) {
+				if (check_autoload(shf, shf->node.nam,
+						   ops, func)) {
 				    returnval = 1;
 				}
 			    }
 		    }
 		}
-		unqueue_signals();
 	    } else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv);
 		returnval = 1;
 	    }
+	    unqueue_signals();
 	}
 	return returnval;
     }
@@ -3227,8 +3409,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
 	    if (on|off) {
 		/* turn on/off the given flags */
 		shf->node.flags = (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off;
-		if (OPT_ISSET(ops,'X') &&
-		    eval_autoload(shf, shf->node.nam, ops, func))
+		if (check_autoload(shf, shf->node.nam, ops, func))
 		    returnval = 1;
 	    } else
 		/* no flags, so just print */
@@ -3245,13 +3426,38 @@ bin_functions(char *name, char **argv, Options ops, int func)
 		removetrapnode(signum);
 	    }
 
+	    if (**argv == '/') {
+		char *base = strrchr(*argv, '/') + 1;
+		if (*base &&
+		    (shf = (Shfunc) shfunctab->getnode(shfunctab, base))) {
+		    char *dir;
+		    /* turn on/off the given flags */
+		    shf->node.flags =
+			(shf->node.flags | (on & ~PM_UNDEFINED)) & ~off;
+		    if (shf->node.flags & PM_UNDEFINED) {
+			/* update path if not yet loaded */
+			if (base == *argv + 1)
+			    dir = "/";
+			else {
+			    dir = *argv;
+			    base[-1] = '\0';
+			}
+			dircache_set(&shf->filename, NULL);
+			dircache_set(&shf->filename, dir);
+		    }
+		    if (check_autoload(shf, shf->node.nam, ops, func))
+			returnval = 1;
+		    continue;
+		}
+	    }
+
 	    /* Add a new undefined (autoloaded) function to the *
 	     * hash table with the corresponding flags set.     */
 	    shf = (Shfunc) zshcalloc(sizeof *shf);
 	    shf->node.flags = on;
 	    shf->funcdef = mkautofn(shf);
 	    shfunc_set_sticky(shf);
-	    shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
+	    add_autoload_function(shf, *argv);
 
 	    if (signum != -1) {
 		if (settrap(signum, NULL, ZSIG_FUNC)) {
@@ -3262,8 +3468,7 @@ bin_functions(char *name, char **argv, Options ops, int func)
 		}
 	    }
 
-	    if (ok && OPT_ISSET(ops,'X') &&
-		eval_autoload(shf, shf->node.nam, ops, func))
+	    if (ok && check_autoload(shf, shf->node.nam, ops, func))
 		returnval = 1;
 	} else
 	    returnval = 1;
@@ -3317,11 +3522,11 @@ bin_unset(char *name, char **argv, Options ops, int func)
     /* with -m option, treat arguments as glob patterns */
     if (OPT_ISSET(ops,'m')) {
 	while ((s = *argv++)) {
+	    queue_signals();
 	    /* expand */
 	    tokenize(s);
 	    if ((pprog = patcompile(s, PAT_STATIC, NULL))) {
 		/* Go through the parameter table, and unset any matches */
-		queue_signals();
 		for (i = 0; i < paramtab->hsize; i++) {
 		    for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
 			/* record pointer to next, since we may free this one */
@@ -3334,12 +3539,12 @@ bin_unset(char *name, char **argv, Options ops, int func)
 			}
 		    }
 		}
-		unqueue_signals();
 	    } else {
 		untokenize(s);
 		zwarnnam(name, "bad pattern : %s", s);
 		returnval = 1;
 	    }
+	    unqueue_signals();
 	}
 	/* If we didn't match anything, we return 1. */
 	if (!match)
@@ -3506,6 +3711,7 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 	    pushheap();
 	    matchednodes = newlinklist();
 	}
+	queue_signals();
 	for (; *argv; argv++) {
 	    /* parse the pattern */
 	    tokenize(*argv);
@@ -3515,7 +3721,6 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 		returnval = 1;
 		continue;
 	    }
-	    queue_signals();
 	    if (!OPT_ISSET(ops,'p')) {
 		/* -p option is for path search only.    *
 		 * We're not using it, so search for ... */
@@ -3546,9 +3751,9 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 	    scanmatchtable(cmdnamtab, pprog, 1, 0, 0,
 			   (all ? fetchcmdnamnode : cmdnamtab->printnode),
 			   printflags);
-
-	    unqueue_signals();
+	    run_queued_signals();
 	}
+	unqueue_signals();
 	if (all) {
 	    allmatched = argv = zlinklist2array(matchednodes);
 	    matchednodes = NULL;
@@ -3625,9 +3830,11 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 		    if (wd) {
 			printf("%s: command\n", *argv);
 		    } else {
-			if (v && !csh)
+			if (v && !csh) {
 			    zputs(*argv, stdout), fputs(" is ", stdout);
-			zputs(buf, stdout);
+			    quotedzputs(buf, stdout);
+			} else
+			    zputs(buf, stdout);
 			if (OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'S'))
 			    print_if_link(buf, OPT_ISSET(ops, 'S'));
 			fputc('\n', stdout);
@@ -3636,26 +3843,39 @@ bin_whence(char *nam, char **argv, Options ops, int func)
 		}
 	    }
 	    if (!informed && (wd || v || csh)) {
+		/* this is information and not an error so, as in csh, use stdout */
 		zputs(*argv, stdout);
 		puts(wd ? ": none" : " not found");
 		returnval = 1;
 	    }
 	    popheap();
-	} else if ((cnam = findcmd(*argv, 1))) {
+	} else if (func == BIN_COMMAND && OPT_ISSET(ops,'p') &&
+		   (hn = builtintab->getnode(builtintab, *argv))) {
+	    /*
+	     * Special case for "command -p[vV]" which needs to
+	     * show a builtin in preference to an external command.
+	     */
+	    builtintab->printnode(hn, printflags);
+	    informed = 1;
+	} else if ((cnam = findcmd(*argv, 1,
+				   func == BIN_COMMAND &&
+				   OPT_ISSET(ops,'p')))) {
 	    /* Found external command. */
 	    if (wd) {
 		printf("%s: command\n", *argv);
 	    } else {
-		if (v && !csh)
+		if (v && !csh) {
 		    zputs(*argv, stdout), fputs(" is ", stdout);
-		zputs(cnam, stdout);
+		    quotedzputs(cnam, stdout);
+		} else
+		    zputs(cnam, stdout);
 		if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S'))
 		    print_if_link(cnam, OPT_ISSET(ops,'S'));
 		fputc('\n', stdout);
 	    }
 	    informed = 1;
 	} else {
-	    /* Not found at all. */
+	    /* Not found at all. That's not an error as such so this goes to stdout */
 	    if (v || csh || wd)
 		zputs(*argv, stdout), puts(wd ? ": none" : " not found");
 	    returnval = 1;
@@ -3751,6 +3971,7 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
         if (!(asg = getasg(&argv, NULL))) {
 	    zwarnnam(name, "bad assignment");
 	    returnval = 1;
+	    break;
         } else if (ASG_VALUEP(asg)) {
 	    if(isset(RESTRICTED)) {
 		zwarnnam(name, "restricted: %s", asg->value.scalar);
@@ -3859,11 +4080,11 @@ bin_unhash(char *name, char **argv, Options ops, int func)
      * "unhash -m '*'" is legal, but not recommended.    */
     if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
+	    queue_signals();
 	    /* expand argument */
 	    tokenize(*argv);
 	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		/* remove all nodes matching glob pattern */
-		queue_signals();
 		for (i = 0; i < ht->hsize; i++) {
 		    for (hn = ht->nodes[i]; hn; hn = nhn) {
 			/* record pointer to next, since we may free this one */
@@ -3874,12 +4095,12 @@ bin_unhash(char *name, char **argv, Options ops, int func)
 			}
 		    }
 		}
-		unqueue_signals();
 	    } else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv);
 		returnval = 1;
 	    }
+	    unqueue_signals();
 	}
 	/* If we didn't match anything, we return 1. */
 	if (!match)
@@ -3962,18 +4183,18 @@ bin_alias(char *name, char **argv, Options ops, UNUSED(int func))
      * glob patterns of aliases to display.       */
     if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
+	    queue_signals();
 	    tokenize(*argv);  /* expand argument */
 	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
 		/* display the matching aliases */
-		queue_signals();
 		scanmatchtable(ht, pprog, 1, flags1, flags2,
 			       ht->printnode, printflags);
-		unqueue_signals();
 	    } else {
 		untokenize(*argv);
 		zwarnnam(name, "bad pattern : %s", *argv);
 		returnval = 1;
 	    }
+	    unqueue_signals();
 	}
 	return returnval;
     }
@@ -4048,10 +4269,11 @@ bin_print(char *name, char **args, Options ops, int func)
 {
     int flen, width, prec, type, argc, n, narg, curlen = 0;
     int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0;
-    int flags[6], *len;
+    int flags[6], *len, visarr = 0;
     char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[14], *fmt = NULL;
     char **first, **argp, *curarg, *flagch = "'0+- #", save = '\0', nullstr = '\0';
     size_t rcount = 0, count = 0;
+    size_t *cursplit = 0, *splits = 0;
     FILE *fout = stdout;
 #ifdef HAVE_OPEN_MEMSTREAM
     size_t mcount;
@@ -4083,7 +4305,7 @@ bin_print(char *name, char **args, Options ops, int func)
             return 1; \
         } \
         unlink(tmpf); \
-        if ((fout = fdopen(tempfd, "w+")) == NULL) { \
+        if ((FOUT = fdopen(tempfd, "w+")) == NULL) { \
             close(tempfd); \
             zwarnnam(name, "can't open temp file: %e", errno); \
             return 1; \
@@ -4182,10 +4404,12 @@ bin_print(char *name, char **args, Options ops, int func)
 	    zwarnnam(name, "no pattern specified");
 	    return 1;
 	}
+	queue_signals();
 	tokenize(*args);
 	if (!(pprog = patcompile(*args, PAT_STATIC, NULL))) {
 	    untokenize(*args);
 	    zwarnnam(name, "bad pattern: %s", *args);
+	    unqueue_signals();
 	    return 1;
 	}
 	for (t = p = ++args; *p; p++)
@@ -4193,6 +4417,7 @@ bin_print(char *name, char **args, Options ops, int func)
 		*t++ = *p;
 	*t = NULL;
 	first = args;
+	unqueue_signals();
 	if (fmt && !*args) return 0;
     }
     /* compute lengths, and interpret according to -P, -D, -e, etc. */
@@ -4512,6 +4737,7 @@ bin_print(char *name, char **args, Options ops, int func)
 		    short *words;
 		    if (nwords > 1) {
 			zwarnnam(name, "option -S takes a single argument");
+			unqueue_signals();
 			return 1;
 		    }
 		    words = NULL;
@@ -4581,7 +4807,8 @@ bin_print(char *name, char **args, Options ops, int func)
 			  OPT_ISSET(ops,'N') ? '\0' : ' ', fout);
 	    }
 	}
-	if (!(OPT_ISSET(ops,'n') || OPT_ISSET(ops, 'v') || nnl))
+	if (!(OPT_ISSET(ops,'n') || nnl ||
+	    (OPT_ISSET(ops, 'v') && !OPT_ISSET(ops, 'l'))))
 	    fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
 	if (IS_MSTREAM(fout) && (rcount = READ_MSTREAM(buf,fout)) == -1)
 	    ret = 1;
@@ -4608,11 +4835,23 @@ bin_print(char *name, char **args, Options ops, int func)
      * special cases of printing to a ZLE buffer or the history, however.
      */
 
+    if (OPT_ISSET(ops,'v')) {
+	struct value vbuf;
+	char* s = OPT_ARG(ops,'v');
+	Value v = getvalue(&vbuf, &s, 0);
+	visarr = v && PM_TYPE(v->pm->node.flags) == PM_ARRAY;
+    }
     /* printf style output */
     *spec = '%';
     argp = args;
     do {
     	rcount = count;
+	if (argp > args && visarr) { /* reusing format string */
+	    if (!splits)
+		cursplit = splits = (size_t *)zhalloc(sizeof(size_t) *
+			(arrlen(args) / (argp - args) + 1));
+	    *cursplit++ = count;
+	}
     	if (maxarg) {
 	    first += maxarg;
 	    argc -= maxarg;
@@ -4841,9 +5080,10 @@ bin_print(char *name, char **args, Options ops, int func)
 		break;
 	    case 'q':
 		stringval = curarg ?
-		    quotestring(curarg, NULL, QT_BACKSLASH_SHOWNULL) : &nullstr;
+		    quotestring(metafy(curarg, curlen, META_USEHEAP),
+				QT_BACKSLASH_SHOWNULL) : &nullstr;
 		*d = 's';
-		print_val(stringval);
+		print_val(unmetafy(stringval, &curlen));
 		break;
 	    case 'd':
 	    case 'i':
@@ -4979,18 +5219,30 @@ bin_print(char *name, char **args, Options ops, int func)
 	    if (buf)
 		free(buf);
 	} else {
-	    stringval = metafy(buf, rcount, META_REALLOC);
-	    if (OPT_ISSET(ops,'z')) {
-		zpushnode(bufstack, stringval);
-	    } else if (OPT_ISSET(ops,'v')) {
-		setsparam(OPT_ARG(ops, 'v'), stringval);
+	    if (visarr && splits) {
+		char **arrayval = zshcalloc((cursplit - splits + 2) * sizeof(char *));
+		for (;cursplit >= splits; cursplit--) {
+		    int start = cursplit == splits ? 0 : cursplit[-1];
+		    arrayval[cursplit - splits] =
+			    metafy(buf + start, count - start, META_DUP);
+		    count = start;
+		}
+		setaparam(OPT_ARG(ops, 'v'), arrayval);
+		free(buf);
 	    } else {
-		ent = prepnexthistent();
-		ent->node.nam = stringval;
-		ent->stim = ent->ftim = time(NULL);
-		ent->node.flags = 0;
-		ent->words = (short *)NULL;
-		addhistnode(histtab, ent->node.nam, ent);
+		stringval = metafy(buf, rcount, META_REALLOC);
+		if (OPT_ISSET(ops,'z')) {
+		    zpushnode(bufstack, stringval);
+		} else if (OPT_ISSET(ops,'v')) {
+		    setsparam(OPT_ARG(ops, 'v'), stringval);
+		} else {
+		    ent = prepnexthistent();
+		    ent->node.nam = stringval;
+		    ent->stim = ent->ftim = time(NULL);
+		    ent->node.flags = 0;
+		    ent->words = (short *)NULL;
+		    addhistnode(histtab, ent->node.nam, ent);
+		}
 	    }
 	}
 	unqueue_signals();
@@ -5097,7 +5349,7 @@ bin_getopts(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int fun
 	zoptind = 1;
 	optcind = 0;
     }
-    if(zoptind > arrlen(args))
+    if (arrlen_lt(args, zoptind))
 	/* no more options */
 	return 1;
 
@@ -5252,7 +5504,7 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
 	}
 	/*FALLTHROUGH*/
     case BIN_EXIT:
-	if (locallevel > forklevel) {
+	if (locallevel > forklevel && shell_exiting != -1) {
 	    /*
 	     * We don't exit directly from functions to allow tidying
 	     * up, in particular EXIT traps.  We still need to perform
@@ -5261,6 +5513,9 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
 	     *
 	     * If we are forked, we exit the shell at the function depth
 	     * at which we became a subshell, hence the comparison.
+	     *
+	     * If we are already exiting... give this all up as
+	     * a bad job.
 	     */
 	    if (stopmsg || (zexit(0,2), !stopmsg)) {
 		retflag = 1;
@@ -5307,6 +5562,14 @@ checkjobs(void)
     }
 }
 
+/*
+ * -1 if the shell is already committed to exit.
+ * positive if zexit() was already called.
+ */
+
+/**/
+int shell_exiting;
+
 /* exit the shell.  val is the return value of the shell.  *
  * from_where is
  *   1   if zexit is called because of a signal
@@ -5318,10 +5581,8 @@ checkjobs(void)
 mod_export void
 zexit(int val, int from_where)
 {
-    static int in_exit;
-
     /* Don't do anything recursively:  see below */
-    if (in_exit == -1)
+    if (shell_exiting == -1)
 	return;
 
     if (isset(MONITOR) && !stopmsg && from_where != 1) {
@@ -5334,14 +5595,14 @@ zexit(int val, int from_where)
 	}
     }
     /* Positive in_exit means we have been here before */
-    if (from_where == 2 || (in_exit++ && from_where))
+    if (from_where == 2 || (shell_exiting++ && from_where))
 	return;
 
     /*
-     * We're now committed to exiting.  Set in_exit to -1 to
+     * We're now committed to exiting.  Set shell_exiting to -1 to
      * indicate we shouldn't do any recursive processing.
      */
-    in_exit = -1;
+    shell_exiting = -1;
     /*
      * We want to do all remaining processing regardless of preceding
      * errors, even user interrupts.
@@ -5369,6 +5630,11 @@ zexit(int val, int from_where)
 	}
     }
     lastval = val;
+    /*
+     * Now we are committed to exiting any previous state
+     * is irrelevant.  Ensure trap can run.
+     */
+    errflag = intrap = 0;
     if (sigtrapped[SIGEXIT])
 	dotrap(SIGEXIT);
     callhookfunc("zshexit", NULL, 1, NULL);
diff --git a/Src/compat.c b/Src/compat.c
index 9041c0bed..a130d9264 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -33,7 +33,10 @@
 /* Return pointer to first occurence of string t *
  * in string s.  Return NULL if not present.     */
 
+/**/
 #ifndef HAVE_STRSTR
+
+/**/
 char *
 strstr(const char *s, const char *t)
 {
@@ -48,10 +51,15 @@ strstr(const char *s, const char *t)
     }
     return NULL;
 }
+
+/**/
 #endif
 
 
+/**/
 #ifndef HAVE_GETHOSTNAME
+
+/**/
 int
 gethostname(char *name, size_t namelen)
 {
@@ -65,10 +73,15 @@ gethostname(char *name, size_t namelen)
     strcpy(name, uts.nodename);
     return 0;
 }
+
+/**/
 #endif
 
 
+/**/
 #ifndef HAVE_GETTIMEOFDAY
+
+/**/
 int
 gettimeofday(struct timeval *tv, struct timezone *tz)
 {
@@ -76,20 +89,28 @@ gettimeofday(struct timeval *tv, struct timezone *tz)
     tv->tv_sec = (long)time((time_t) 0);
     return 0;
 }
+
+/**/
 #endif
 
 
 /* compute the difference between two calendar times */
 
+/**/
 #ifndef HAVE_DIFFTIME
+
+/**/
 double
 difftime(time_t t2, time_t t1)
 {
     return ((double)t2 - (double)t1);
 }
+
+/**/
 #endif
 
 
+/**/
 #ifndef HAVE_STRERROR
 extern char *sys_errlist[];
 
@@ -97,11 +118,14 @@ extern char *sys_errlist[];
  * error number, and returns a pointer to that string.    *
  * This is not a particularly robust version of strerror. */
 
+/**/
 char *
 strerror(int errnum)
 {
     return (sys_errlist[errnum]);
 }
+
+/**/
 #endif
 
 
@@ -186,6 +210,7 @@ zpathmax(char *dir)
 }
 #endif /* 0 */
 
+/**/
 #ifdef HAVE_SYSCONF
 /*
  * This is replaced by a macro from system.h if not HAVE_SYSCONF.
@@ -230,6 +255,8 @@ zopenmax(void)
 
     return (max_zsh_fd > openmax) ? max_zsh_fd : openmax;
 }
+
+/**/
 #endif
 
 /*
@@ -270,7 +297,7 @@ zgetdir(struct dirsav *d)
     int len;
 #endif
 
-    buf = zhalloc(bufsiz = PATH_MAX);
+    buf = zhalloc(bufsiz = PATH_MAX+1);
     pos = bufsiz - 1;
     buf[pos] = '\0';
     strcpy(nbuf, "../");
@@ -439,11 +466,11 @@ zgetcwd(void)
 	    free(cwd);
 	}
 #else
-	char *cwdbuf = zalloc(PATH_MAX);
+	char *cwdbuf = zalloc(PATH_MAX+1);
 	ret = getcwd(cwdbuf, PATH_MAX);
 	if (ret)
 	    ret = dupstring(ret);
-	zfree(cwdbuf, PATH_MAX);
+	zfree(cwdbuf, PATH_MAX+1);
 #endif /* GETCWD_CALLS_MALLOC */
     }
 #endif /* HAVE_GETCWD */
@@ -532,6 +559,7 @@ output64(zlong val)
 /**/
 #endif /* ZSH_64_BIT_TYPE */
 
+/**/
 #ifndef HAVE_STRTOUL
 
 /*
@@ -569,6 +597,8 @@ output64(zlong val)
  * Ignores `locale' stuff.  Assumes that the upper and lower case
  * alphabets and digits are each contiguous.
  */
+
+/**/
 unsigned long
 strtoul(nptr, endptr, base)
 	const char *nptr;
@@ -632,329 +662,35 @@ strtoul(nptr, endptr, base)
 		*endptr = any ? s - 1 : nptr;
 	return (acc);
 }
-#endif /* HAVE_STRTOUL */
 
 /**/
-#if defined(BROKEN_WCWIDTH) && (defined(__STDC_ISO_10646__) || defined(__APPLE__))
-
-/*
- * This is an implementation of wcwidth() and wcswidth() (defined in
- * IEEE Std 1002.1-2001) for Unicode.
- *
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
- *
- * In fixed-width output devices, Latin characters all occupy a single
- * "cell" position of equal width, whereas ideographic CJK characters
- * occupy two such cells. Interoperability between terminal-line
- * applications and (teletype-style) character terminals using the
- * UTF-8 encoding requires agreement on which character should advance
- * the cursor by how many cell positions. No established formal
- * standards exist at present on which Unicode character shall occupy
- * how many cell positions on character terminals. These routines are
- * a first attempt of defining such behavior based on simple rules
- * applied to data provided by the Unicode Consortium.
- *
- * For some graphical characters, the Unicode standard explicitly
- * defines a character-cell width via the definition of the East Asian
- * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
- * In all these cases, there is no ambiguity about which width a
- * terminal shall use. For characters in the East Asian Ambiguous (A)
- * class, the width choice depends purely on a preference of backward
- * compatibility with either historic CJK or Western practice.
- * Choosing single-width for these characters is easy to justify as
- * the appropriate long-term solution, as the CJK practice of
- * displaying these characters as double-width comes from historic
- * implementation simplicity (8-bit encoded characters were displayed
- * single-width and 16-bit ones double-width, even for Greek,
- * Cyrillic, etc.) and not any typographic considerations.
- *
- * Much less clear is the choice of width for the Not East Asian
- * (Neutral) class. Existing practice does not dictate a width for any
- * of these characters. It would nevertheless make sense
- * typographically to allocate two character cells to characters such
- * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
- * represented adequately with a single-width glyph. The following
- * routines at present merely assign a single-cell width to all
- * neutral characters, in the interest of simplicity. This is not
- * entirely satisfactory and should be reconsidered before
- * establishing a formal standard in this area. At the moment, the
- * decision which Not East Asian (Neutral) characters should be
- * represented by double-width glyphs cannot yet be answered by
- * applying a simple rule from the Unicode database content. Setting
- * up a proper standard for the behavior of UTF-8 character terminals
- * will require a careful analysis not only of each Unicode character,
- * but also of each presentation form, something the author of these
- * routines has avoided to do so far.
- *
- * http://www.unicode.org/unicode/reports/tr11/
- *
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- *
- * Permission to use, copy, modify, and distribute this software
- * for any purpose and without fee is hereby granted. The author
- * disclaims all warranties with regard to this software.
- *
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-
-struct interval {
-  int first;
-  int last;
-};
-
-/* auxiliary function for binary search in interval table */
-static int bisearch(wchar_t ucs, const struct interval *table, int max) {
-  int min = 0;
-  int mid;
-
-  if (ucs < table[0].first || ucs > table[max].last)
-    return 0;
-  while (max >= min) {
-    mid = (min + max) / 2;
-    if (ucs > table[mid].last)
-      min = mid + 1;
-    else if (ucs < table[mid].first)
-      max = mid - 1;
-    else
-      return 1;
-  }
-
-  return 0;
-}
-
+#endif /* HAVE_STRTOUL */
 
-/* The following two functions define the column width of an ISO 10646
- * character as follows:
- *
- *    - The null character (U+0000) has a column width of 0.
- *
- *    - Other C0/C1 control characters and DEL will lead to a return
- *      value of -1.
- *
- *    - Non-spacing and enclosing combining characters (general
- *      category code Mn or Me in the Unicode database) have a
- *      column width of 0.
- *
- *    - SOFT HYPHEN (U+00AD) has a column width of 1.
- *
- *    - Other format characters (general category code Cf in the Unicode
- *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
- *
- *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
- *      have a column width of 0.
- *
- *    - Spacing characters in the East Asian Wide (W) or East Asian
- *      Full-width (F) category as defined in Unicode Technical
- *      Report #11 have a column width of 2.
- *
- *    - All remaining characters (including all printable
- *      ISO 8859-1 and WGL4 characters, Unicode control characters,
- *      etc.) have a column width of 1.
- *
- * This implementation assumes that wchar_t characters are encoded
- * in ISO 10646.
- */
+/**/
+#ifdef ENABLE_UNICODE9
+#include "./wcwidth9.h"
 
 /**/
 int
-mk_wcwidth(wchar_t ucs)
-{
-  /* sorted list of non-overlapping intervals of non-spacing characters */
-  /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
-  static const struct interval combining[] = {
-    { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-    { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-    { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-    { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-    { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
-    { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-    { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
-    { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-    { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
-    { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
-    { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
-    { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-    { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-    { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-    { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-    { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-    { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
-    { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-    { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-    { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-    { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-    { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-    { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-    { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-    { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-    { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-    { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-    { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-    { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-    { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-    { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-    { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-    { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-    { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-    { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-    { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
-    { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-    { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-    { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-    { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
-    { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
-    { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
-    { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
-    { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
-    { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
-    { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
-    { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
-    { 0xE0100, 0xE01EF }
-  };
-
-  /* test for 8-bit control characters */
-  if (ucs == 0)
-    return 0;
-  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
-    return -1;
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, combining,
-	       sizeof(combining) / sizeof(struct interval) - 1))
-    return 0;
-
-  /* if we arrive here, ucs is not a combining or C0/C1 control character */
-
-  return 1 +
-    (ucs >= 0x1100 &&
-     (ucs <= 0x115f ||                    /* Hangul Jamo init. consonants */
-      ucs == 0x2329 || ucs == 0x232a ||
-      (ucs >= 0x2e80 && ucs <= 0xa4cf &&
-       ucs != 0x303f) ||                  /* CJK ... Yi */
-      (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
-      (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
-      (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
-      (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
-      (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
-      (ucs >= 0xffe0 && ucs <= 0xffe6) ||
-      (ucs >= 0x20000 && ucs <= 0x2fffd) ||
-      (ucs >= 0x30000 && ucs <= 0x3fffd)));
-}
-
-
-/*
- * The following functions are part of the original wcwidth.c:
- * we don't use them but I've kept them in case - pws.
- */
-#if 0
-int mk_wcswidth(const wchar_t *pwcs, size_t n)
-{
-  int w, width = 0;
-
-  for (;*pwcs && n-- > 0; pwcs++)
-    if ((w = mk_wcwidth(*pwcs)) < 0)
-      return -1;
-    else
-      width += w;
-
-  return width;
-}
-
-
-/*
- * The following functions are the same as mk_wcwidth() and
- * mk_wcswidth(), except that spacing characters in the East Asian
- * Ambiguous (A) category as defined in Unicode Technical Report #11
- * have a column width of 2. This variant might be useful for users of
- * CJK legacy encodings who want to migrate to UCS without changing
- * the traditional terminal character-width behaviour. It is not
- * otherwise recommended for general use.
- */
-int mk_wcwidth_cjk(wchar_t ucs)
+u9_wcwidth(wchar_t ucs)
 {
-  /* sorted list of non-overlapping intervals of East Asian Ambiguous
-   * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
-  static const struct interval ambiguous[] = {
-    { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
-    { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
-    { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
-    { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
-    { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
-    { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
-    { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
-    { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
-    { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
-    { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
-    { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
-    { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
-    { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
-    { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
-    { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
-    { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
-    { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
-    { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
-    { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
-    { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
-    { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
-    { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
-    { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
-    { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
-    { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
-    { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
-    { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
-    { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
-    { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
-    { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
-    { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
-    { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
-    { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
-    { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
-    { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
-    { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
-    { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
-    { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
-    { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
-    { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
-    { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
-    { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
-    { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
-    { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
-    { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
-    { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
-    { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
-    { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
-    { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
-    { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
-    { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
-    { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
-  };
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, ambiguous,
-	       sizeof(ambiguous) / sizeof(struct interval) - 1))
-    return 2;
-
-  return mk_wcwidth(ucs);
+  int w = wcwidth9(ucs);
+  if (w < -1)
+    return 1;
+  return w;
 }
 
-
-int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
+/**/
+int
+u9_iswprint(wint_t ucs)
 {
-  int w, width = 0;
-
-  for (;*pwcs && n-- > 0; pwcs++)
-    if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
-      return -1;
-    else
-      width += w;
-
-  return width;
+    if (ucs == 0)
+	return 0;
+    return wcwidth9(ucs) != -1;
 }
-#endif /* 0 */
 
 /**/
-#endif /* BROKEN_WCWIDTH && (__STDC_ISO_10646__ || __APPLE__) */
+#endif	/* ENABLE_UNICODE9 */
 
 /**/
 #if defined(__APPLE__) && defined(BROKEN_ISPRINT)
diff --git a/Src/cond.c b/Src/cond.c
index 0381fe94b..a63841234 100644
--- a/Src/cond.c
+++ b/Src/cond.c
@@ -34,7 +34,7 @@
 int tracingcond;    /* updated by execcond() in exec.c */
 
 static char *condstr[COND_MOD] = {
-    "!", "&&", "||", "==", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
+    "!", "&&", "||", "=", "==", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
     "-ne", "-lt", "-gt", "-le", "-ge", "=~"
 };
 
@@ -138,13 +138,13 @@ evalcond(Estate state, char *fromtest)
 		strs = arrdup(sbuf);
 		l = 2;
 	    }
-	    if (name && name[0] == '-')
-		errname = name;
-	    else if (strs[0] && *strs[0] == '-')
-		errname = strs[0];
+	    if (name && IS_DASH(name[0]))
+		untokenize(errname = name);
+	    else if (strs[0] && IS_DASH(*strs[0]))
+		untokenize(errname = strs[0]);
 	    else
 		errname = "<null>";
-	    if (name && name[0] == '-' &&
+	    if (name && IS_DASH(name[0]) &&
 		(cd = getconddef((ctype == COND_MODI), name + 1, 1))) {
 		if (ctype == COND_MOD &&
 		    (l < cd->min || (cd->max >= 0 && l > cd->max))) {
@@ -171,7 +171,7 @@ evalcond(Estate state, char *fromtest)
 		strs[0] = dupstring(name);
 		name = s;
 
-		if (name && name[0] == '-' &&
+		if (name && IS_DASH(name[0]) &&
 		    (cd = getconddef(0, name + 1, 1))) {
 		    if (l < cd->min || (cd->max >= 0 && l > cd->max)) {
 			zwarnnam(fromtest, "unknown condition: %s",
@@ -196,7 +196,8 @@ evalcond(Estate state, char *fromtest)
 	cond_subst(&left, !fromtest);
 	untokenize(left);
     }
-    if (ctype <= COND_GE && ctype != COND_STREQ && ctype != COND_STRNEQ) {
+    if (ctype <= COND_GE && ctype != COND_STREQ && ctype != COND_STRDEQ &&
+	ctype != COND_STRNEQ) {
 	right = ecgetstr(state, EC_DUPTOK, &htok);
 	if (htok) {
 	    cond_subst(&right, !fromtest);
@@ -208,7 +209,8 @@ evalcond(Estate state, char *fromtest)
 	    fputc(' ',xtrerr);
 	    quotedzputs(left, xtrerr);
 	    fprintf(xtrerr, " %s ", condstr[ctype]);
-	    if (ctype == COND_STREQ || ctype == COND_STRNEQ) {
+	    if (ctype == COND_STREQ || ctype == COND_STRDEQ ||
+		ctype == COND_STRNEQ) {
 		char *rt = dupstring(ecrawstr(state->prog, state->pc, NULL));
 		cond_subst(&rt, !fromtest);
 		quote_tokenized_output(rt, xtrerr);
@@ -287,11 +289,14 @@ evalcond(Estate state, char *fromtest)
 
     switch (ctype) {
     case COND_STREQ:
+    case COND_STRDEQ:
     case COND_STRNEQ:
 	{
 	    int test, npat = state->pc[1];
 	    Patprog pprog = state->prog->pats[npat];
 
+	    queue_signals();
+
 	    if (pprog == dummy_patprog1 || pprog == dummy_patprog2) {
 		char *opat;
 		int save;
@@ -305,6 +310,7 @@ evalcond(Estate state, char *fromtest)
 		if (!(pprog = patcompile(right, (save ? PAT_ZDUP : PAT_STATIC),
 					 NULL))) {
 		    zwarnnam(fromtest, "bad pattern: %s", right);
+		    unqueue_signals();
 		    return 2;
 		}
 		else if (save)
@@ -313,7 +319,9 @@ evalcond(Estate state, char *fromtest)
 	    state->pc += 2;
 	    test = (pprog && pattry(pprog, left));
 
-	    return !(ctype == COND_STREQ ? test : !test);
+	    unqueue_signals();
+
+	    return !(ctype == COND_STRNEQ ? !test : test);
 	}
     case COND_STRLT:
 	return !(strcmp(left, right) < 0);
@@ -348,6 +356,8 @@ evalcond(Estate state, char *fromtest)
 	return (!S_ISSOCK(dostat(left)));
     case 'u':
 	return (!(dostat(left) & S_ISUID));
+    case 'v':
+	return (!issetvar(left));
     case 'w':
 	return (!doaccess(left, W_OK));
     case 'x':
diff --git a/Src/exec.c b/Src/exec.c
index 2dcd5bcf5..debb0aeca 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -46,6 +46,11 @@ enum {
 /**/
 int noerrexit;
 
+/* used to suppress ERREXIT for one occurrence */
+
+/**/
+int this_noerrexit;
+
 /*
  * noerrs = 1: suppress error messages
  * noerrs = 2: don't set errflag on parse error, either
@@ -207,7 +212,7 @@ static int (*execfuncs[WC_COUNT-WC_CURSH]) _((Estate, int)) = {
 
 /* structure for command builtin for when it is used with -v or -V */
 static struct builtin commandbn =
-    BUILTIN(0, 0, bin_whence, 0, -1, BIN_COMMAND, "vV", NULL);
+    BUILTIN("command", 0, bin_whence, 0, -1, BIN_COMMAND, "pvV", NULL);
 
 /* parse string into a list */
 
@@ -424,6 +429,7 @@ execcursh(Estate state, int do_exec)
     cmdpop();
 
     state->pc = end;
+    this_noerrexit = 1;
 
     return lastval;
 }
@@ -437,7 +443,7 @@ static int
 zexecve(char *pth, char **argv, char **newenvp)
 {
     int eno;
-    static char buf[PATH_MAX * 2];
+    static char buf[PATH_MAX * 2+1];
     char **eep;
 
     unmetafy(pth, NULL);
@@ -568,11 +574,49 @@ commandnotfound(char *arg0, LinkList args)
     Shfunc shf = (Shfunc)
 	shfunctab->getnode(shfunctab, "command_not_found_handler");
 
-    if (!shf)
-	return 127;
+    if (!shf) {
+	lastval = 127;
+	return 1;
+    }
 
     pushnode(args, arg0);
-    return doshfunc(shf, args, 1);
+    lastval = doshfunc(shf, args, 1);
+    return 0;
+}
+
+/*
+ * Search the default path for cmd.
+ * pbuf of length plen is the buffer to use.
+ * Return NULL if not found.
+ */
+
+static char *
+search_defpath(char *cmd, char *pbuf, int plen)
+{
+    char *ps = DEFAULT_PATH, *pe = NULL, *s;
+
+    for (ps = DEFAULT_PATH; ps; ps = pe ? pe+1 : NULL) {
+	pe = strchr(ps, ':');
+	if (*ps == '/') {
+	    s = pbuf;
+	    if (pe) {
+		if (pe - ps >= plen)
+		    continue;
+		struncpy(&s, ps, pe-ps);
+	    } else {
+		if (strlen(ps) >= plen)
+		    continue;
+		strucpy(&s, ps);
+	    }
+	    *s++ = '/';
+	    if ((s - pbuf) + strlen(cmd) >= plen)
+		continue;
+	    strucpy(&s, cmd);
+	    if (iscom(pbuf))
+		return pbuf;
+	}
+    }
+    return NULL;
 }
 
 /* execute an external command */
@@ -582,7 +626,7 @@ static void
 execute(LinkList args, int flags, int defpath)
 {
     Cmdnam cn;
-    char buf[MAXCMDLEN], buf2[MAXCMDLEN];
+    char buf[MAXCMDLEN+1], buf2[MAXCMDLEN+1];
     char *s, *z, *arg0;
     char **argv, **pp, **newenvp = NULL;
     int eno = 0, ee;
@@ -663,29 +707,12 @@ execute(LinkList args, int flags, int defpath)
 
     /* for command -p, search the default path */
     if (defpath) {
-	char *s, pbuf[PATH_MAX];
-	char *dptr, *pe, *ps = DEFAULT_PATH;
-
-	for(;ps;ps = pe ? pe+1 : NULL) {
-	    pe = strchr(ps, ':');
-	    if (*ps == '/') {
-		s = pbuf;
-		if (pe)
-		    struncpy(&s, ps, pe-ps);
-		else
-		    strucpy(&s, ps);
-		*s++ = '/';
-		if ((s - pbuf) + strlen(arg0) >= PATH_MAX)
-		    continue;
-		strucpy(&s, arg0);
-		if (iscom(pbuf))
-		    break;
-	    }
-	}
+	char pbuf[PATH_MAX+1];
+	char *dptr;
 
-	if (!ps) {
+	if (!search_defpath(arg0, pbuf, PATH_MAX)) {
 	    if (commandnotfound(arg0, args) == 0)
-		_exit(0);
+		_exit(lastval);
 	    zerr("command not found: %s", arg0);
 	    _exit(127);
 	}
@@ -700,7 +727,7 @@ execute(LinkList args, int flags, int defpath)
     } else {
 
 	if ((cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0))) {
-	    char nn[PATH_MAX], *dptr;
+	    char nn[PATH_MAX+1], *dptr;
 
 	    if (cn->node.flags & HASHED)
 		strcpy(nn, cn->u.cmd);
@@ -749,7 +776,7 @@ execute(LinkList args, int flags, int defpath)
     if (eno)
 	zerr("%e: %s", eno, arg0);
     else if (commandnotfound(arg0, args) == 0)
-	_exit(0);
+	_exit(lastval);
     else
 	zerr("command not found: %s", arg0);
     _exit((eno == EACCES || eno == ENOEXEC) ? 126 : 127);
@@ -760,32 +787,40 @@ execute(LinkList args, int flags, int defpath)
 /*
  * Get the full pathname of an external command.
  * If the second argument is zero, return the first argument if found;
- * if non-zero, return the path using heap memory.  (RET_IF_COM(X), above).
+ * if non-zero, return the path using heap memory.  (RET_IF_COM(X),
+ * above).
+ * If the third argument is non-zero, use the system default path
+ * instead of the current path.
  */
 
 /**/
 mod_export char *
-findcmd(char *arg0, int docopy)
+findcmd(char *arg0, int docopy, int default_path)
 {
     char **pp;
     char *z, *s, buf[MAXCMDLEN];
     Cmdnam cn;
 
+    if (default_path)
+    {
+	if (search_defpath(arg0, buf, MAXCMDLEN))
+	    return docopy ? dupstring(buf) : arg0;
+	return NULL;
+    }
     cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
-    if (!cn && isset(HASHCMDS))
+    if (!cn && isset(HASHCMDS) && !isrelative(arg0))
 	cn = hashcmd(arg0, path);
     if ((int) strlen(arg0) > PATH_MAX)
 	return NULL;
-    for (s = arg0; *s; s++)
-	if (*s == '/') {
-	    RET_IF_COM(arg0);
-	    if (arg0 == s || unset(PATHDIRS)) {
-		return NULL;
-	    }
-	    break;
+    if ((s = strchr(arg0, '/'))) {
+	RET_IF_COM(arg0);
+	if (arg0 == s || unset(PATHDIRS) || !strncmp(arg0, "./", 2) ||
+	    !strncmp(arg0, "../", 3)) {
+	    return NULL;
 	}
+    }
     if (cn) {
-	char nn[PATH_MAX];
+	char nn[PATH_MAX+1];
 
 	if (cn->node.flags & HASHED)
 	    strcpy(nn, cn->u.cmd);
@@ -818,6 +853,11 @@ findcmd(char *arg0, int docopy)
     return NULL;
 }
 
+/*
+ * Return TRUE if the given path denotes an executable regular file, or a
+ * symlink to one.
+ */
+
 /**/
 int
 iscom(char *s)
@@ -847,6 +887,11 @@ isreallycom(Cmdnam cn)
     return iscom(fullnam);
 }
 
+/*
+ * Return TRUE if the given path contains a dot or dot-dot component
+ * and does not start with a slash.
+ */
+
 /**/
 int
 isrelative(char *s)
@@ -866,7 +911,7 @@ mod_export Cmdnam
 hashcmd(char *arg0, char **pp)
 {
     Cmdnam cn;
-    char *s, buf[PATH_MAX];
+    char *s, buf[PATH_MAX+1];
     char **pq;
 
     for (; *pp; pp++)
@@ -930,9 +975,8 @@ entersubsh(int flags)
     int sig, monitor, job_control_ok;
 
     if (!(flags & ESUB_KEEPTRAP))
-	for (sig = 0; sig < VSIGCOUNT; sig++)
-	    if (!(sigtrapped[sig] & ZSIG_FUNC) &&
-		sig != SIGDEBUG && sig != SIGZERR)
+	for (sig = 0; sig < SIGCOUNT; sig++)
+	    if (!(sigtrapped[sig] & ZSIG_FUNC))
 		unsettrap(sig);
     monitor = isset(MONITOR);
     job_control_ok = monitor && (flags & ESUB_JOB_CONTROL) && isset(POSIXJOBS);
@@ -1023,6 +1067,18 @@ entersubsh(int flags)
     }
     if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
 	signal_default(SIGQUIT);
+    /*
+     * sigtrapped[sig] == ZSIG_IGNORED for signals that remain ignored,
+     * but other trapped signals are temporarily blocked when intrap,
+     * and must be unblocked before continuing into the subshell.  This
+     * is orthogonal to what the default handler for the signal may be.
+     *
+     * Start loop at 1 because 0 is SIGEXIT
+     */
+    if (intrap)
+	for (sig = 1; sig < SIGCOUNT; sig++)
+	    if (sigtrapped[sig] && sigtrapped[sig] != ZSIG_IGNORED)
+		signal_unblock(signal_mask(sig));
     if (!job_control_ok)
 	opts[MONITOR] = 0;
     opts[USEZLE] = 0;
@@ -1199,6 +1255,8 @@ execlist(Estate state, int dont_change_job, int exiting)
     }
     while (wc_code(code) == WC_LIST && !breaks && !retflag && !errflag) {
 	int donedebug;
+	int this_donetrap = 0;
+	this_noerrexit = 0;
 
 	ltype = WC_LIST_TYPE(code);
 	csp = cmdsp;
@@ -1282,9 +1340,17 @@ execlist(Estate state, int dont_change_job, int exiting)
 	    goto sublist_done;
 	}
 	while (wc_code(code) == WC_SUBLIST) {
+	    int isend = (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END);
 	    next = state->pc + WC_SUBLIST_SKIP(code);
 	    if (!oldnoerrexit)
-		noerrexit = (WC_SUBLIST_TYPE(code) != WC_SUBLIST_END);
+		noerrexit = !isend;
+	    if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_NOT) {
+		/* suppress errexit for "! this_command" */
+		if (isend)
+		    this_noerrexit = 1;
+		/* suppress errexit for ! <list-of-shell-commands> */
+		noerrexit = 1;
+	    }
 	    switch (WC_SUBLIST_TYPE(code)) {
 	    case WC_SUBLIST_END:
 		/* End of sublist; just execute, ignoring status. */
@@ -1314,10 +1380,10 @@ execlist(Estate state, int dont_change_job, int exiting)
 			/* We've skipped to the end of the list, not executing *
 			 * the final pipeline, so don't perform error handling *
 			 * for this sublist.                                   */
-			donetrap = 1;
+			this_donetrap = 1;
 			goto sublist_done;
 		    } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
-			donetrap = 1;
+			this_donetrap = 1;
 			/*
 			 * Treat this in the same way as if we reached
 			 * the end of the sublist normally.
@@ -1347,10 +1413,10 @@ execlist(Estate state, int dont_change_job, int exiting)
 			/* We've skipped to the end of the list, not executing *
 			 * the final pipeline, so don't perform error handling *
 			 * for this sublist.                                   */
-			donetrap = 1;
+			this_donetrap = 1;
 			goto sublist_done;
 		    } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
-			donetrap = 1;
+			this_donetrap = 1;
 			/*
 			 * Treat this in the same way as if we reached
 			 * the end of the sublist normally.
@@ -1400,7 +1466,7 @@ sublist_done:
 	/* Check whether we are suppressing traps/errexit *
 	 * (typically in init scripts) and if we haven't  *
 	 * already performed them for this sublist.       */
-	if (!noerrexit && !donetrap) {
+	if (!noerrexit && !this_noerrexit && !donetrap && !this_donetrap) {
 	    if (sigtrapped[SIGZERR] && lastval) {
 		dotrap(SIGZERR);
 		donetrap = 1;
@@ -1546,6 +1612,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 	    zclose(opipe[0]);
 	}
 	if (how & Z_DISOWN) {
+	    pipecleanfilelist(jobtab[thisjob].filelist, 0);
 	    deletejob(jobtab + thisjob, 1);
 	    thisjob = -1;
 	}
@@ -1570,6 +1637,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 
 	    if (nowait) {
 		if(!pline_level) {
+		    int jobsub;
 		    struct process *pn, *qn;
 
 		    curjob = newjob;
@@ -1582,6 +1650,20 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		    if (!jn->procs->next || lpforked == 2) {
 			jn->gleader = list_pipe_pid;
 			jn->stat |= STAT_SUBLEADER;
+			/*
+			 * Pick up any subjob that's still lying around
+			 * as it's now our responsibility.
+			 * If we find it we're a SUPERJOB.
+			 */
+			for (jobsub = 1; jobsub <= maxjob; jobsub++) {
+			    Job jnsub = jobtab + jobsub;
+			    if (jnsub->stat & STAT_SUBJOB_ORPHANED) {
+				jn->other = jobsub;
+				jn->stat |= STAT_SUPERJOB;
+				jnsub->stat &= ~STAT_SUBJOB_ORPHANED;
+				jnsub->other = list_pipe_pid;
+			    }
+			}
 		    }
 		    for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
 			if (WIFSTOPPED(pn->status))
@@ -1593,7 +1675,8 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		    }
 
 		    jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
-		    jn->stat |= STAT_STOPPED | STAT_CHANGED | STAT_LOCKED;
+		    jn->stat |= STAT_STOPPED | STAT_CHANGED | STAT_LOCKED |
+			STAT_INUSE;
 		    printjob(jn, !!isset(LONGLISTJOBS), 1);
 		}
 		else if (newjob != list_pipe_job)
@@ -1636,7 +1719,13 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		    int synch[2];
 		    struct timeval bgtime;
 
+		    /*
+		     * A pipeline with the shell handling the right
+		     * hand side was stopped.  We'll fork to allow
+		     * it to continue.
+		     */
 		    if (pipe(synch) < 0 || (pid = zfork(&bgtime)) == -1) {
+			/* Failure */
 			if (pid < 0) {
 			    close(synch[0]);
 			    close(synch[1]);
@@ -1650,6 +1739,18 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 			thisjob = newjob;
 		    }
 		    else if (pid) {
+			/*
+			 * Parent: job control is here.  If the job
+			 * started for the RHS of the pipeline is still
+			 * around, then its a SUBJOB and the job for
+			 * earlier parts of the pipeeline is its SUPERJOB.
+			 * The newly forked shell isn't recorded as a
+			 * separate job here, just as list_pipe_pid.
+			 * If the superjob exits (it may already have
+			 * done so, see child branch below), we'll use
+			 * list_pipe_pid to form the basis of a
+			 * replacement job --- see SUBLEADER code above.
+			 */
 			char dummy;
 
 			lpforked =
@@ -1668,7 +1769,9 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 			    jobtab[list_pipe_job].other = newjob;
 			    jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
 			    jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
-			    jn->other = pid;
+			    jn->other = list_pipe_pid;	/* see zsh.h */
+			    if (hasprocs(list_pipe_job))
+				jn->gleader = jobtab[list_pipe_job].gleader;
 			}
 			if ((list_pipe || last1) && hasprocs(list_pipe_job))
 			    killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
@@ -1677,13 +1780,21 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		    else {
 			close(synch[0]);
 			entersubsh(ESUB_ASYNC);
-			if (jobtab[list_pipe_job].procs) {
-			    if (setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader)
-				== -1) {
-				setpgrp(0L, mypgrp = getpid());
-			    }
-			} else
-			    setpgrp(0L, mypgrp = getpid());
+			/*
+			 * At this point, we used to attach this process
+			 * to the process group of list_pipe_job (the
+			 * new superjob) any time that was still available.
+			 * That caused problems in at least two
+			 * cases because this forked shell was then
+			 * suspended with the right hand side of the
+			 * pipeline, and the SIGSTOP below suspended
+			 * it a second time when it was continued.
+			 *
+			 * It's therefore not clear entirely why you'd ever
+			 * do anything other than the following, but no
+			 * doubt we'll find out...
+			 */
+			setpgrp(0L, mypgrp = getpid());
 			close(synch[1]);
 			kill(getpid(), SIGSTOP);
 			list_pipe = 0;
@@ -1720,6 +1831,8 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		deletejob(jn, 0);
 	    thisjob = pj;
 	}
+	else
+	    unqueue_signals();
 	if ((slflags & WC_SUBLIST_NOT) && !errflag)
 	    lastval = !lastval;
     }
@@ -1737,6 +1850,7 @@ execpline2(Estate state, wordcode pcode,
 {
     pid_t pid;
     int pipes[2];
+    struct execcmd_params eparams;
 
     if (breaks || retflag)
 	return;
@@ -1746,7 +1860,7 @@ execpline2(Estate state, wordcode pcode,
 	lineno = WC_PIPE_LINENO(pcode) - 1;
 
     if (pline_level == 1) {
-	if ((how & Z_ASYNC) || (!sfcontext && !sourcelevel))
+	if ((how & Z_ASYNC) || !sfcontext)
 	    strcpy(list_pipe_text,
 		   getjobtext(state->prog,
 			      state->pc + (WC_PIPE_TYPE(pcode) == WC_PIPE_END ?
@@ -1754,17 +1868,16 @@ execpline2(Estate state, wordcode pcode,
 	else
 	    list_pipe_text[0] = '\0';
     }
-    if (WC_PIPE_TYPE(pcode) == WC_PIPE_END)
-	execcmd(state, input, output, how, last1 ? 1 : 2);
-    else {
+    if (WC_PIPE_TYPE(pcode) == WC_PIPE_END) {
+	execcmd_analyse(state, &eparams);
+	execcmd_exec(state, &eparams, input, output, how, last1 ? 1 : 2);
+    } else {
 	int old_list_pipe = list_pipe;
 	int subsh_close = -1;
-	Wordcode next = state->pc + (*state->pc), pc;
-	wordcode code;
+	Wordcode next = state->pc + (*state->pc), start_pc;
 
-	state->pc++;
-	for (pc = state->pc; wc_code(code = *pc) == WC_REDIR;
-	     pc += WC_REDIR_WORDS(code));
+	start_pc = ++state->pc;
+	execcmd_analyse(state, &eparams);
 
 	if (mpipe(pipes) < 0) {
 	    /* FIXME */
@@ -1773,7 +1886,8 @@ execpline2(Estate state, wordcode pcode,
 	/* if we are doing "foo | bar" where foo is a current *
 	 * shell command, do foo in a subshell and do the     *
 	 * rest of the pipeline in the current shell.         */
-	if (wc_code(code) >= WC_CURSH && (how & Z_SYNC)) {
+	if ((eparams.type >= WC_CURSH || !eparams.args)
+	    && (how & Z_SYNC)) {
 	    int synch[2];
 	    struct timeval bgtime;
 
@@ -1791,7 +1905,7 @@ execpline2(Estate state, wordcode pcode,
 	    } else if (pid) {
 		char dummy, *text;
 
-		text = getjobtext(state->prog, state->pc);
+		text = getjobtext(state->prog, start_pc);
 		addproc(pid, text, 0, &bgtime);
 		close(synch[1]);
 		read_loop(synch[0], &dummy, 1);
@@ -1802,14 +1916,18 @@ execpline2(Estate state, wordcode pcode,
 		entersubsh(((how & Z_ASYNC) ? ESUB_ASYNC : 0)
 			   | ESUB_PGRP | ESUB_KEEPTRAP);
 		close(synch[1]);
-		execcmd(state, input, pipes[1], how, 1);
+		if (sigtrapped[SIGEXIT])
+		{
+		    unsettrap(SIGEXIT);
+		}
+		execcmd_exec(state, &eparams, input, pipes[1], how, 1);
 		_exit(lastval);
 	    }
 	} else {
 	    /* otherwise just do the pipeline normally. */
 	    addfilelist(NULL, pipes[0]);
 	    subsh_close = pipes[0];
-	    execcmd(state, input, pipes[1], how, 0);
+	    execcmd_exec(state, &eparams, input, pipes[1], how, 0);
 	}
 	zclose(pipes[1]);
 	state->pc = next;
@@ -2273,9 +2391,7 @@ addvars(Estate state, Wordcode pc, int addflags)
      * to be restored after the command, since then the assignment
      * is implicitly scoped.
      */
-    flags = (!(addflags & ADDVAR_RESTORE) &&
-	     locallevel > forklevel && isset(WARNCREATEGLOBAL)) ?
-	ASSPM_WARN_CREATE : 0;
+    flags = !(addflags & ADDVAR_RESTORE) ? ASSPM_WARN : 0;
     xtr = isset(XTRACE);
     if (xtr) {
 	printprompt4();
@@ -2465,55 +2581,51 @@ resolvebuiltin(const char *cmdarg, HashNode hn)
     return hn;
 }
 
+/*
+ * We are about to execute a command at the lowest level of the
+ * hierarchy.  Analyse the parameters from the wordcode.
+ */
+
 /**/
 static void
-execcmd(Estate state, int input, int output, int how, int last1)
+execcmd_analyse(Estate state, Execcmd_params eparams)
 {
-    HashNode hn = NULL;
-    LinkList args, filelist = NULL;
-    LinkNode node;
-    Redir fn;
-    struct multio *mfds[10];
-    char *text;
-    int save[10];
-    int fil, dfil, is_cursh, type, do_exec = 0, redir_err = 0, i, htok = 0;
-    int nullexec = 0, assign = 0, forked = 0, postassigns = 0;
-    int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
-    /* Various flags to the command. */
-    int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
-    LinkList redir;
     wordcode code;
-    Wordcode beg = state->pc, varspc, assignspc = (Wordcode)0;
-    FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
+    int i;
 
-    doneps4 = 0;
-    redir = (wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
+    eparams->beg = state->pc;
+    eparams->redir =
+	(wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
     if (wc_code(*state->pc) == WC_ASSIGN) {
 	cmdoutval = 0;
-	varspc = state->pc;
+	eparams->varspc = state->pc;
 	while (wc_code((code = *state->pc)) == WC_ASSIGN)
 	    state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
 			  3 : WC_ASSIGN_NUM(code) + 2);
     } else
-	varspc = NULL;
+	eparams->varspc = NULL;
 
     code = *state->pc++;
 
-    type = wc_code(code);
+    eparams->type = wc_code(code);
+    eparams->postassigns = 0;
 
     /* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
      * But for that we would need to check/change all builtins so that
      * they don't modify their argument strings. */
-    switch (type) {
+    switch (eparams->type) {
     case WC_SIMPLE:
-	args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok);
+	eparams->args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP,
+				  &eparams->htok);
+	eparams->assignspc = NULL;
 	break;
 
     case WC_TYPESET:
-	args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP, &htok);
-	postassigns = *state->pc++;
-	assignspc = state->pc;
-	for (i = 0; i < postassigns; i++) {
+	eparams->args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP,
+				  &eparams->htok);
+	eparams->postassigns = *state->pc++;
+	eparams->assignspc = state->pc;
+	for (i = 0; i < eparams->postassigns; i++) {
 	    code = *state->pc;
 	    DPUTS(wc_code(code) != WC_ASSIGN,
 		  "BUG: miscounted typeset assignments");
@@ -2523,8 +2635,71 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	break;
 
     default:
-	args = NULL;
+	eparams->args = NULL;
+	eparams->assignspc = NULL;
+	eparams->htok = 0;
+	break;
     }
+}
+
+/*
+ * Transfer the first node of args to preargs, performing
+ * prefork expansion on the way if necessary.
+ */
+static void execcmd_getargs(LinkList preargs, LinkList args, int expand)
+{
+    if (!firstnode(args)) {
+	return;
+    } else if (expand) {
+	local_list0(svl);
+	init_list0(svl);
+	/* not init_list1, as we need real nodes */
+	addlinknode(&svl, uremnode(args, firstnode(args)));
+	/* Analysing commands, so vanilla options to prefork */
+	prefork(&svl, 0, NULL);
+	joinlists(preargs, &svl);
+    } else {
+        addlinknode(preargs, uremnode(args, firstnode(args)));
+    }
+}
+
+/*
+ * Execute a command at the lowest level of the hierarchy.
+ */
+
+/**/
+static void
+execcmd_exec(Estate state, Execcmd_params eparams,
+	     int input, int output, int how, int last1)
+{
+    HashNode hn = NULL;
+    LinkList filelist = NULL;
+    LinkNode node;
+    Redir fn;
+    struct multio *mfds[10];
+    char *text;
+    int save[10];
+    int fil, dfil, is_cursh, do_exec = 0, redir_err = 0, i;
+    int nullexec = 0, magic_assign = 0, forked = 0;
+    int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
+    /* Various flags to the command. */
+    int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
+    FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
+    /*
+     * Retrieve parameters for quick reference (they are unique
+     * to us so we can modify the structure if we want).
+     */
+    LinkList args = eparams->args;
+    LinkList redir = eparams->redir;
+    Wordcode varspc = eparams->varspc;
+    int type = eparams->type;
+    /*
+     * preargs comes from expanding the head of the args list
+     * in order to check for prefix commands.
+     */
+    LinkList preargs;
+
+    doneps4 = 0;
 
     /*
      * If assignment but no command get the status from variable
@@ -2577,9 +2752,19 @@ execcmd(Estate state, int input, int output, int how, int last1)
      * command if it contains some tokens (e.g. x=ex; ${x}port), so this *
      * only works in simple cases.  has_token() is called to make sure   *
      * this really is a simple case.                                     */
-    if (type == WC_SIMPLE || type == WC_TYPESET) {
-	while (args && nonempty(args)) {
-	    char *cmdarg = (char *) peekfirst(args);
+    if ((type == WC_SIMPLE || type == WC_TYPESET) && args) {
+	/*
+	 * preargs contains args that have been expanded by prefork.
+	 * Running execcmd_getargs() causes the any argument available
+	 * in args to be exanded where necessary and transferred to
+	 * preargs.  We call execcmd_getargs() every time we need to
+	 * analyse an argument not available in preargs, though there is
+	 * no guarantee a further argument will be available.
+	 */
+	preargs = newlinklist();
+	execcmd_getargs(preargs, args, eparams->htok);
+	while (nonempty(preargs)) {
+	    char *cmdarg = (char *) peekfirst(preargs);
 	    checked = !has_token(cmdarg);
 	    if (!checked)
 		break;
@@ -2613,39 +2798,118 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		/* autoload the builtin if necessary */
 		if (!(hn = resolvebuiltin(cmdarg, hn)))
 		    return;
-		assign = (hn->flags & BINF_MAGICEQUALS);
+		if (type != WC_TYPESET)
+		    magic_assign = (hn->flags & BINF_MAGICEQUALS);
 		break;
 	    }
 	    checked = 0;
-	    if ((cflags & BINF_COMMAND) && nextnode(firstnode(args))) {
-		/* check for options to command builtin */
-		char *next = (char *) getdata(nextnode(firstnode(args)));
-		char *cmdopt;
-		if (next && *next == '-' && strlen(next) == 2 &&
-		        (cmdopt = strchr("pvV", next[1])))
-		{
-		    if (*cmdopt == 'p') {
-			uremnode(args, firstnode(args));
-			use_defpath = 1;
-			if (nextnode(firstnode(args)))
-			    next = (char *) getdata(nextnode(firstnode(args)));
-		    } else {
-			hn = &commandbn.node;
-			is_builtin = 1;
+	    /*
+	     * We usually don't need the argument containing the
+	     * precommand modifier itself.  Exception: when "command"
+	     * will implemented by a call to "whence", in which case
+	     * we'll simply re-insert the argument.
+	     */
+	    uremnode(preargs, firstnode(preargs));
+	    if (!firstnode(preargs)) {
+		execcmd_getargs(preargs, args, eparams->htok);
+		if (!firstnode(preargs))
+		    break;
+	    }
+	    if ((cflags & BINF_COMMAND)) {
+		/*
+		 * Check for options to "command".
+		 * If just -p, this is handled here: use the default
+		 * path to execute.
+		 * If -v or -V, possibly with -p, dispatch to bin_whence
+		 * but with flag to indicate special handling of -p.
+		 * Otherwise, just leave marked as BINF_COMMAND
+		 * modifier with no additional action.
+		 */
+		LinkNode argnode, oldnode, pnode = NULL;
+		char *argdata, *cmdopt;
+		int has_p = 0, has_vV = 0, has_other = 0;
+		argnode = firstnode(preargs);
+		argdata = (char *) getdata(argnode);
+		while (IS_DASH(*argdata)) {
+		    /* Just to be definite, stop on single "-", too, */
+		    if (!argdata[1] ||
+			(IS_DASH(argdata[1]) && !argdata[2]))
+			break;
+		    for (cmdopt = argdata+1; *cmdopt; cmdopt++) {
+			switch (*cmdopt) {
+			case 'p':
+			    /*
+			     * If we've got this multiple times (command
+			     * -p -p) we'll treat the second -p as a
+			     * command because we only remove one below.
+			     * Don't think that's a big issue, and it's
+			     * also traditional behaviour.
+			     */
+			    has_p = 1;
+			    pnode = argnode;
+			    break;
+			case 'v':
+			case 'V':
+			    has_vV = 1;
+			    break;
+			default:
+			    has_other = 1;
+			    break;
+			}
+		    }
+		    if (has_other) {
+			/* Don't know how to handle this, so don't */
+			has_p = has_vV = 0;
 			break;
 		    }
+
+		    oldnode = argnode;
+		    argnode = nextnode(argnode);
+		    if (!argnode) {
+			execcmd_getargs(preargs, args, eparams->htok);
+			if (!(argnode = nextnode(oldnode)))
+			    break;
+		    }
+		    argdata = (char *) getdata(argnode);
 		}
-		if (!strcmp(next, "--"))
-		     uremnode(args, firstnode(args));
-	    }
-	    if ((cflags & BINF_EXEC) && nextnode(firstnode(args))) {
+		if (has_vV) {
+		    /*
+		     * Leave everything alone, dispatch to whence.
+		     * We need to put the name back in the list.
+		     */
+		    pushnode(preargs, "command");
+		    hn = &commandbn.node;
+		    is_builtin = 1;
+		    break;
+		} else if (has_p) {
+		    /* Use default path */
+		    use_defpath = 1;
+		    /*
+		     * We don't need this node as we're not treating
+		     * "command" as a builtin this time.
+		     */
+		    if (pnode)
+			uremnode(preargs, pnode);
+		}
+		/*
+		 * Else just any trailing
+		 * end-of-options marker.  This can only occur
+		 * if we just had -p or something including more
+		 * than just -p, -v and -V, in which case we behave
+		 * as if this is command [non-option-stuff].  This
+		 * isn't a good place for standard option handling.
+		 */
+		if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
+		     uremnode(preargs, argnode);
+	    } else if (cflags & BINF_EXEC) {
 		/*
 		 * Check for compatibility options to exec builtin.
 		 * It would be nice to do these more generically,
 		 * but currently we don't have a mechanism for
 		 * precommand modifiers.
 		 */
-		char *next = (char *) getdata(nextnode(firstnode(args)));
+		LinkNode argnode = firstnode(preargs), oldnode;
+		char *argdata = (char *) getdata(argnode);
 		char *cmdopt, *exec_argv0 = NULL;
 		/*
 		 * Careful here: we want to make sure a final dash
@@ -2655,17 +2919,23 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		 * people aren't likely to mix the option style
 		 * with the zsh style.
 		 */
-		while (next && *next == '-' && strlen(next) >= 2) {
-		    if (!firstnode(args)) {
+		while (argdata && IS_DASH(*argdata) && strlen(argdata) >= 2) {
+		    oldnode = argnode;
+		    argnode = nextnode(oldnode);
+		    if (!argnode) {
+			execcmd_getargs(preargs, args, eparams->htok);
+			argnode = nextnode(oldnode);
+		    }
+		    if (!argnode) {
 			zerr("exec requires a command to execute");
 			lastval = 1;
 			errflag |= ERRFLAG_ERROR;
 			goto done;
 		    }
-		    uremnode(args, firstnode(args));
-		    if (!strcmp(next, "--"))
+		    uremnode(preargs, oldnode);
+		    if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
 			break;
-		    for (cmdopt = &next[1]; *cmdopt; ++cmdopt) {
+		    for (cmdopt = &argdata[1]; *cmdopt; ++cmdopt) {
 			switch (*cmdopt) {
 			case 'a':
 			    /* argument is ARGV0 string */
@@ -2674,21 +2944,25 @@ execcmd(Estate state, int input, int output, int how, int last1)
 				/* position on last non-NULL character */
 				cmdopt += strlen(cmdopt+1);
 			    } else {
-				if (!firstnode(args)) {
+				if (!argnode) {
 				    zerr("exec requires a command to execute");
 				    lastval = 1;
 				    errflag |= ERRFLAG_ERROR;
 				    goto done;
 				}
-				if (!nextnode(firstnode(args))) {
+				if (!nextnode(argnode))
+				    execcmd_getargs(preargs, args,
+						    eparams->htok);
+				if (!nextnode(argnode)) {
 				    zerr("exec flag -a requires a parameter");
 				    lastval = 1;
 				    errflag |= ERRFLAG_ERROR;
 				    goto done;
 				}
-				exec_argv0 = (char *)
-				    getdata(nextnode(firstnode(args)));
-				uremnode(args, firstnode(args));
+				exec_argv0 = (char *) getdata(argnode);
+				oldnode = argnode;
+				argnode = nextnode(argnode);
+				uremnode(args, oldnode);
 			    }
 			    break;
 			case 'c':
@@ -2704,8 +2978,9 @@ execcmd(Estate state, int input, int output, int how, int last1)
 			    return;
 			}
 		    }
-		    if (firstnode(args) && nextnode(firstnode(args)))
-			next = (char *) getdata(nextnode(firstnode(args)));
+		    if (!argnode)
+			break;
+		    argdata = (char *) getdata(argnode);
 		}
 		if (exec_argv0) {
 		    char *str, *s;
@@ -2717,21 +2992,41 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		    zputenv(str);
 		}
 	    }
-	    uremnode(args, firstnode(args));
 	    hn = NULL;
 	    if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
 		break;
+	    if (!nonempty(preargs))
+		execcmd_getargs(preargs, args, eparams->htok);
 	}
-    }
+    } else
+	preargs = NULL;
 
     /* if we get this far, it is OK to pay attention to lastval again */
     if (noerrexit == 2 && !is_shfunc)
 	noerrexit = 0;
 
-    /* Do prefork substitutions */
-    esprefork = (assign || isset(MAGICEQUALSUBST)) ? PREFORK_TYPESET : 0;
-    if (args && htok)
-	prefork(args, esprefork, NULL);
+    /* Do prefork substitutions.
+     *
+     * Decide if we need "magic" handling of ~'s etc. in
+     * assignment-like arguments.
+     * - If magic_assign is set, we are using a builtin of the
+     *   tyepset family, but did not recognise this as a keyword,
+     *   so need guess-o-matic behaviour.
+     * - Otherwise, if we did recognise the keyword, we never need
+     *   guess-o-matic behaviour as the argument was properly parsed
+     *   as such.
+     * - Otherwise, use the behaviour specified by the MAGIC_EQUAL_SUBST
+     *   option.
+     */
+    esprefork = (magic_assign ||
+		 (isset(MAGICEQUALSUBST) && type != WC_TYPESET)) ?
+		 PREFORK_TYPESET : 0;
+    if (args) {
+	if (eparams->htok)
+	    prefork(args, esprefork, NULL);
+	if (preargs)
+	    args = joinlists(preargs, args);
+    }
 
     if (type == WC_SIMPLE || type == WC_TYPESET) {
 	int unglobbed = 0;
@@ -2874,8 +3169,8 @@ execcmd(Estate state, int input, int output, int how, int last1)
 
     /* Get the text associated with this command. */
     if ((how & Z_ASYNC) ||
-	(!sfcontext && !sourcelevel && (jobbing || (how & Z_TIMED))))
-	text = getjobtext(state->prog, beg);
+	(!sfcontext && (jobbing || (how & Z_TIMED))))
+	text = getjobtext(state->prog, eparams->beg);
     else
 	text = NULL;
 
@@ -2933,7 +3228,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	if (is_shfunc)
 	    shf = (Shfunc)hn;
 	else {
-	    shf = loadautofn(state->prog->shf, 1, 0);
+	    shf = loadautofn(state->prog->shf, 1, 0, 0);
 	    if (shf)
 		state->prog->shf = shf;
 	    else {
@@ -3134,7 +3429,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	    forked = 1;
     }
 
-    if ((esglob = !(cflags & BINF_NOGLOB)) && args && htok) {
+    if ((esglob = !(cflags & BINF_NOGLOB)) && args && eparams->htok) {
 	LinkList oargs = args;
 	globlist(args, 0);
 	args = oargs;
@@ -3448,7 +3743,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	}
 	if (type == WC_FUNCDEF) {
 	    Eprog redir_prog;
-	    if (!redir && wc_code(*beg) == WC_REDIR)  {
+	    if (!redir && wc_code(*eparams->beg) == WC_REDIR)  {
 		/*
 		 * We're not using a redirection from the currently
 		 * parsed environment, which is what we'd do for an
@@ -3458,7 +3753,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		struct estate s;
 
 		s.prog = state->prog;
-		s.pc = beg;
+		s.pc = eparams->beg;
 		s.strs = state->prog->strs;
 
 		/*
@@ -3500,7 +3795,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		     * if it's got "command" in front.
 		     * If it's a normal command --- save.
 		     */
-		    if (is_shfunc || (hn->flags & BINF_PSPECIAL))
+		    if (is_shfunc || (hn->flags & (BINF_PSPECIAL|BINF_ASSIGN)))
 			do_save = (orig_cflags & BINF_COMMAND);
 		    else
 			do_save = 1;
@@ -3509,7 +3804,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		     * Save if it's got "command" in front or it's
 		     * not a magic-equals assignment.
 		     */
-		    if ((cflags & BINF_COMMAND) || !assign)
+		    if ((cflags & (BINF_COMMAND|BINF_ASSIGN)) || !magic_assign)
 			do_save = 1;
 		}
 		if (do_save && varspc)
@@ -3542,13 +3837,15 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	    } else {
 		/* It's a builtin */
 		LinkList assigns = (LinkList)0;
+		int postassigns = eparams->postassigns;
 		if (forked)
 		    closem(FDT_INTERNAL);
 		if (postassigns) {
 		    Wordcode opc = state->pc;
-		    state->pc = assignspc;
+		    state->pc = eparams->assignspc;
 		    assigns = newlinklist();
 		    while (postassigns--) {
+			int htok;
 			wordcode ac = *state->pc++;
 			char *name = ecgetstr(state, EC_DUPTOK, &htok);
 			Asgment asg;
@@ -3657,7 +3954,8 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		    state->pc = opc;
 		}
 		dont_queue_signals();
-		lastval = execbuiltin(args, assigns, (Builtin) hn);
+		if (!errflag)
+		    lastval = execbuiltin(args, assigns, (Builtin) hn);
 		if (do_save & BINF_COMMAND)
 		    errflag &= ~ERRFLAG_ERROR;
 		restore_queue_signals(q);
@@ -3694,12 +3992,15 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		restore_params(restorelist, removelist);
 
 	} else {
-	    if (!forked)
-		setiparam("SHLVL", --shlvl);
-	    if (do_exec) {
+	    if (!subsh) {
+	        /* for either implicit or explicit "exec", decrease $SHLVL
+		 * as we're now done as a shell */
+		if (!forked)
+		    setiparam("SHLVL", --shlvl);
+
 		/* 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)
+		if (do_exec && isset(RCS) && interact && !nohistsave)
 		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 	    }
 	    if (type == WC_SIMPLE || type == WC_TYPESET) {
@@ -3790,6 +4091,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	 * classify as a builtin) we treat all errors as fatal.
 	 * The "command" builtin is not special so resets this behaviour.
 	 */
+	forked |= zsh_subshell;
     fatal:
 	if (redir_err || errflag) {
 	    if (!isset(INTERACTIVE)) {
@@ -4031,7 +4333,7 @@ gethere(char **strp, int typ)
 
 	parsestr(&buf);
 
-	if (!errflag) {
+	if (!(errflag & ERRFLAG_ERROR)) {
 	    /* Retain any user interrupt error */
 	    errflag = ef | (errflag & ERRFLAG_INT);
 	}
@@ -4284,11 +4586,26 @@ getoutputfile(char *cmd, char **eptr)
 	    untokenize(s);
     }
 
-    addfilelist(nam, 0);
+    if (!s)             /* Unclear why we need to do this before open() */
+	child_block();  /* but it has been so for a long time: leave it */
 
-    if (!s)
-	child_block();
-    fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
+    if ((fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600)) < 0) {
+	zerr("process substitution failed: %e", errno);
+	free(nam);
+	if (!s)
+	    child_unblock();
+	return NULL;
+    } else {
+	char *suffix = getsparam("TMPSUFFIX");
+	if (suffix && *suffix && !strstr(suffix, "/")) {
+	    suffix = dyncat(nam, unmeta(suffix));
+	    if (link(nam, suffix) == 0) {
+		addfilelist(nam, 0);
+		nam = ztrdup(suffix);
+	    }
+	}
+    }
+    addfilelist(nam, 0);
 
     if (s) {
 	/* optimised here-string */
@@ -4299,7 +4616,7 @@ getoutputfile(char *cmd, char **eptr)
 	return nam;
     }
 
-    if (fd < 0 || (cmdoutpid = pid = zfork(NULL)) == -1) {
+    if ((cmdoutpid = pid = zfork(NULL)) == -1) {
 	/* fork or open error */
 	child_unblock();
 	return nam;
@@ -4615,6 +4932,8 @@ exectime(Estate state, UNUSED(int do_exec))
 
 /* Define a shell function */
 
+static const char *const ANONYMOUS_FUNCTION_NAME = "(anon)";
+
 /**/
 static int
 execfuncdef(Estate state, Eprog redir_prog)
@@ -4688,6 +5007,7 @@ execfuncdef(Estate state, Eprog redir_prog)
 	shf = (Shfunc) zalloc(sizeof(*shf));
 	shf->funcdef = prog;
 	shf->node.flags = 0;
+	/* No dircache here, not a directory */
 	shf->filename = ztrdup(scriptfilename);
 	shf->lineno = lineno;
 	/*
@@ -4720,7 +5040,7 @@ execfuncdef(Estate state, Eprog redir_prog)
 		    freeeprog(shf->funcdef);
 		    if (shf->redir) /* shouldn't be */
 			freeeprog(shf->redir);
-		    zsfree(shf->filename);
+		    dircache_set(&shf->filename, NULL);
 		    zfree(shf, sizeof(*shf));
 		    state->pc = end;
 		    return 1;
@@ -4732,7 +5052,7 @@ execfuncdef(Estate state, Eprog redir_prog)
 
 	    if (!args)
 		args = newlinklist();
-	    shf->node.nam = "(anon)";
+	    shf->node.nam = (char *) ANONYMOUS_FUNCTION_NAME;
 	    pushnode(args, shf->node.nam);
 
 	    execshfunc(shf, args);
@@ -4751,7 +5071,7 @@ execfuncdef(Estate state, Eprog redir_prog)
 	    freeeprog(shf->funcdef);
 	    if (shf->redir) /* shouldn't be */
 		freeeprog(shf->redir);
-	    zsfree(shf->filename);
+	    dircache_set(&shf->filename, NULL);
 	    zfree(shf, sizeof(*shf));
 	    break;
 	} else {
@@ -4760,7 +5080,7 @@ execfuncdef(Estate state, Eprog redir_prog)
 		(signum = getsignum(s + 4)) != -1) {
 		if (settrap(signum, NULL, ZSIG_FUNC)) {
 		    freeeprog(shf->funcdef);
-		    zsfree(shf->filename);
+		    dircache_set(&shf->filename, NULL);
 		    zfree(shf, sizeof(*shf));
 		    state->pc = end;
 		    return 1;
@@ -4909,11 +5229,12 @@ execautofn_basic(Estate state, UNUSED(int do_exec))
      * defined yet.
      */
     if (funcstack && !funcstack->filename)
-	funcstack->filename = dupstring(shf->filename);
+	funcstack->filename = getshfuncfile(shf);
 
     oldscriptname = scriptname;
     oldscriptfilename = scriptfilename;
-    scriptname = scriptfilename = dupstring(shf->node.nam);
+    scriptname = dupstring(shf->node.nam);
+    scriptfilename = getshfuncfile(shf);
     execode(shf->funcdef, 1, 0, "loadautofunc");
     scriptname = oldscriptname;
     scriptfilename = oldscriptfilename;
@@ -4927,25 +5248,71 @@ execautofn(Estate state, UNUSED(int do_exec))
 {
     Shfunc shf;
 
-    if (!(shf = loadautofn(state->prog->shf, 1, 0)))
+    if (!(shf = loadautofn(state->prog->shf, 1, 0, 0)))
 	return 1;
 
     state->prog->shf = shf;
     return execautofn_basic(state, 0);
 }
 
+/*
+ * Helper function to install the source file name of a shell function
+ * just autoloaded.
+ *
+ * We attempt to do this efficiently as the typical case is the
+ * directory part is a well-known directory, which is cached, and
+ * the non-directory part is the same as the node name.
+ */
+
+/**/
+static void
+loadautofnsetfile(Shfunc shf, char *fdir)
+{
+    /*
+     * If shf->filename is already the load directory ---
+     * keep it as we can still use it to get the load file.
+     * This makes autoload with an absolute path particularly efficient.
+     */
+    if (!(shf->node.flags & PM_LOADDIR) ||
+	strcmp(shf->filename, fdir) != 0) {
+	/* Old directory name not useful... */
+	dircache_set(&shf->filename, NULL);
+	if (fdir) {
+	    /* ...can still cache directory */
+	    shf->node.flags |= PM_LOADDIR;
+	    dircache_set(&shf->filename, fdir);
+	} else {
+	    /* ...no separate directory part to cache, for some reason. */
+	    shf->node.flags &= ~PM_LOADDIR;
+	    shf->filename = ztrdup(shf->node.nam);
+	}
+    }
+}
+
 /**/
 Shfunc
-loadautofn(Shfunc shf, int fksh, int autol)
+loadautofn(Shfunc shf, int fksh, int autol, int current_fpath)
 {
     int noalias = noaliases, ksh = 1;
     Eprog prog;
-    char *fname;
+    char *fdir;			/* Directory path where func found */
 
     pushheap();
 
     noaliases = (shf->node.flags & PM_UNALIASED);
-    prog = getfpfunc(shf->node.nam, &ksh, &fname);
+    if (shf->filename && shf->filename[0] == '/' &&
+	(shf->node.flags & PM_LOADDIR))
+    {
+	char *spec_path[2];
+	spec_path[0] = dupstring(shf->filename);
+	spec_path[1] = NULL;
+	prog = getfpfunc(shf->node.nam, &ksh, &fdir, spec_path, 0);
+	if (prog == &dummy_eprog &&
+	    (current_fpath || (shf->node.flags & PM_CUR_FPATH)))
+	    prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
+    }
+    else
+	prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
     noaliases = noalias;
 
     if (ksh == 1) {
@@ -4964,7 +5331,6 @@ loadautofn(Shfunc shf, int fksh, int autol)
 	return NULL;
     }
     if (!prog) {
-	zsfree(fname);
 	popheap();
 	return NULL;
     }
@@ -4978,7 +5344,7 @@ loadautofn(Shfunc shf, int fksh, int autol)
 	    else
 		shf->funcdef = dupeprog(prog, 0);
 	    shf->node.flags &= ~PM_UNDEFINED;
-	    shf->filename = fname;
+	    loadautofnsetfile(shf, fdir);
 	} else {
 	    VARARR(char, n, strlen(shf->node.nam) + 1);
 	    strcpy(n, shf->node.nam);
@@ -4990,7 +5356,6 @@ loadautofn(Shfunc shf, int fksh, int autol)
 		zwarn("%s: function not defined by file", n);
 		locallevel++;
 		popheap();
-		zsfree(fname);
 		return NULL;
 	    }
 	}
@@ -5001,7 +5366,7 @@ loadautofn(Shfunc shf, int fksh, int autol)
 	else
 	    shf->funcdef = dupeprog(stripkshdef(prog, shf->node.nam), 0);
 	shf->node.flags &= ~PM_UNDEFINED;
-	shf->filename = fname;
+	loadautofnsetfile(shf, fdir);
     }
     popheap();
 
@@ -5165,8 +5530,20 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 
 	if (flags & (PM_TAGGED|PM_TAGGED_LOCAL))
 	    opts[XTRACE] = 1;
-	else if (oflags & PM_TAGGED_LOCAL)
-	    opts[XTRACE] = 0;
+	else if (oflags & PM_TAGGED_LOCAL) {
+	    if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME /* pointer comparison */)
+		flags |= PM_TAGGED_LOCAL;
+	    else
+		opts[XTRACE] = 0;
+	}
+	if (flags & PM_WARNNESTED)
+	    opts[WARNNESTEDVAR] = 1;
+	else if (oflags & PM_WARNNESTED) {
+	    if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME)
+		flags |= PM_WARNNESTED;
+	    else
+		opts[WARNNESTEDVAR] = 0;
+	}
 	ooflags = oflags;
 	/*
 	 * oflags is static, because we compare it on the next recursive
@@ -5217,7 +5594,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 	funcstack = &fstack;
 
 	fstack.flineno = shfunc->lineno;
-	fstack.filename = dupstring(shfunc->filename);
+	fstack.filename = getshfuncfile(shfunc);
 
 	prog = shfunc->funcdef;
 	if (prog->flags & EF_RUN) {
@@ -5285,6 +5662,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 	    opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
 	    opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
 	    opts[LOCALLOOPS] = saveopts[LOCALLOOPS];
+	    opts[WARNNESTEDVAR] = saveopts[WARNNESTEDVAR];
 	}
 
 	if (opts[LOCALLOOPS]) {
@@ -5318,8 +5696,11 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
      * the only likely case where we need that second test is
      * when we have an "always" block.  The endparamscope() has
      * already happened, hence the "+1" here.
+     *
+     * If we are in an exit trap, finish it first... we wouldn't set
+     * exit_pending if we were already in one.
      */
-    if (exit_pending && exit_level >= locallevel+1) {
+    if (exit_pending && exit_level >= locallevel+1 && !in_exit_trap) {
 	if (locallevel > forklevel) {
 	    /* Still functions to return: force them to do so. */
 	    retflag = 1;
@@ -5367,6 +5748,7 @@ runshfunc(Eprog prog, FuncWrap wrap, char *name)
 	if (!cont) {
 	    if (ou)
 		zfree(ou, ouu);
+	    unqueue_signals();
 	    return;
 	}
 	wrap = wrap->next;
@@ -5382,21 +5764,30 @@ runshfunc(Eprog prog, FuncWrap wrap, char *name)
     unqueue_signals();
 }
 
-/* Search fpath for an undefined function.  Finds the file, and returns the *
- * list of its contents.                                                    */
+/*
+ * Search fpath for an undefined function.  Finds the file, and returns the
+ * list of its contents.
+ *
+ * If test is 0, load the function.
+ *
+ * If test_only is 1, don't load function, just test for it:
+ * Non-null return means function was found
+ *
+ * *fdir points to path at which found (as passed in, not duplicated)
+ */
 
 /**/
 Eprog
-getfpfunc(char *s, int *ksh, char **fname)
+getfpfunc(char *s, int *ksh, char **fdir, char **alt_path, int test_only)
 {
-    char **pp, buf[PATH_MAX];
+    char **pp, buf[PATH_MAX+1];
     off_t len;
     off_t rlen;
     char *d;
     Eprog r;
     int fd;
 
-    pp = fpath;
+    pp = alt_path ? alt_path : fpath;
     for (; *pp; pp++) {
 	if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
 	    continue;
@@ -5404,9 +5795,9 @@ getfpfunc(char *s, int *ksh, char **fname)
 	    sprintf(buf, "%s/%s", *pp, s);
 	else
 	    strcpy(buf, s);
-	if ((r = try_dump_file(*pp, s, buf, ksh))) {
-	    if (fname)
-		*fname = ztrdup(buf);
+	if ((r = try_dump_file(*pp, s, buf, ksh, test_only))) {
+	    if (fdir)
+		*fdir = *pp;
 	    return r;
 	}
 	unmetafy(buf, NULL);
@@ -5414,6 +5805,12 @@ getfpfunc(char *s, int *ksh, char **fname)
 	    struct stat st;
 	    if (!fstat(fd, &st) && S_ISREG(st.st_mode) &&
 		(len = lseek(fd, 0, 2)) != -1) {
+		if (test_only) {
+		    close(fd);
+		    if (fdir)
+			*fdir = *pp;
+		    return &dummy_eprog;
+		}
 		d = (char *) zalloc(len + 1);
 		lseek(fd, 0, 0);
 		if ((rlen = read(fd, d, len)) >= 0) {
@@ -5427,8 +5824,8 @@ getfpfunc(char *s, int *ksh, char **fname)
 		    r = parse_string(d, 1);
 		    scriptname = oldscriptname;
 
-		    if (fname)
-			*fname = ztrdup(buf);
+		    if (fdir)
+			*fdir = *pp;
 
 		    zfree(d, len + 1);
 
@@ -5441,7 +5838,7 @@ getfpfunc(char *s, int *ksh, char **fname)
 		close(fd);
 	}
     }
-    return &dummy_eprog;
+    return test_only ? NULL : &dummy_eprog;
 }
 
 /* Handle the most common type of ksh-style autoloading, when doing a      *
@@ -5519,7 +5916,7 @@ cancd(char *s)
     char *t;
 
     if (*s != '/') {
-	char sbuf[PATH_MAX], **cp;
+	char sbuf[PATH_MAX+1], **cp;
 
 	if (cancd2(s))
 	    return s;
@@ -5600,6 +5997,7 @@ execsave(void)
     es->trapisfunc = trapisfunc;
     es->traplocallevel = traplocallevel;
     es->noerrs = noerrs;
+    es->this_noerrexit = this_noerrexit;
     es->underscore = ztrdup(zunderscore);
     es->next = exstack;
     exstack = es;
@@ -5634,6 +6032,7 @@ execrestore(void)
     trapisfunc = en->trapisfunc;
     traplocallevel = en->traplocallevel;
     noerrs = en->noerrs;
+    this_noerrexit = en->this_noerrexit;
     setunderscore(en->underscore);
     zsfree(en->underscore);
     free(en);
diff --git a/Src/glob.c b/Src/glob.c
index c15bcd195..aeb5fd204 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -41,7 +41,10 @@
 typedef struct gmatch *Gmatch;
 
 struct gmatch {
+    /* Metafied file name */
     char *name;
+    /* Unmetafied file name; embedded nulls can't occur in file names */
+    char *uname;
     /*
      * Array of sort strings:  one for each GS_EXEC sort type in
      * the glob qualifiers.
@@ -280,7 +283,7 @@ addpath(char *s, int l)
 static int
 statfullpath(const char *s, struct stat *st, int l)
 {
-    char buf[PATH_MAX];
+    char buf[PATH_MAX+1];
 
     DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
 	  "BUG: statfullpath(): pathname too long");
@@ -776,7 +779,7 @@ parsepat(char *str)
 
     /* Now there is no (#X) in front, we can check the path. */
     if (!pathbuf)
-	pathbuf = zalloc(pathbufsz = PATH_MAX);
+	pathbuf = zalloc(pathbufsz = PATH_MAX+1);
     DPUTS(pathbufcwd, "BUG: glob changed directory");
     if (*str == '/') {		/* pattern has absolute path */
 	str++;
@@ -911,7 +914,8 @@ gmatchcmp(Gmatch a, Gmatch b)
     for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
 	switch (s->tp & ~GS_DESC) {
 	case GS_NAME:
-	    r = zstrcmp(b->name, a->name, gf_numsort ? SORTIT_NUMERICALLY : 0);
+	    r = zstrcmp(b->uname, a->uname,
+			gf_numsort ? SORTIT_NUMERICALLY : 0);
 	    break;
 	case GS_DEPTH:
 	    {
@@ -1170,7 +1174,7 @@ checkglobqual(char *str, int sl, int nobareglob, char **sp)
 }
 
 /* Main entry point to the globbing code for filename globbing. *
- * np points to a node in the list list which will be expanded  *
+ * np points to a node in the list which will be expanded  *
  * into a series of nodes.                                      */
 
 /**/
@@ -1278,14 +1282,7 @@ zglob(LinkList list, LinkNode np, int nountok)
 		*ptr = '-';
 	while (*s && !newcolonmod) {
 	    func = (int (*) _((char *, Statptr, off_t, char *)))0;
-	    if (idigit(*s)) {
-		/* Store numeric argument for qualifier */
-		func = qualflags;
-		data = 0;
-		sdata = NULL;
-		while (idigit(*s))
-		    data = data * 010 + (*s++ - '0');
-	    } else if (*s == ',') {
+	    if (*s == ',') {
 		/* A comma separates alternative sets of qualifiers */
 		s++;
 		sense = 0;
@@ -1317,6 +1314,7 @@ zglob(LinkList list, LinkNode np, int nountok)
 		    sense ^= 1;
 		    break;
 		case '-':
+		case Dash:
 		    /* Toggle matching of symbolic links */
 		    sense ^= 2;
 		    break;
@@ -1611,7 +1609,7 @@ zglob(LinkList list, LinkNode np, int nountok)
 			    ++s;
 		    }
 		    /* See if it's greater than, equal to, or less than */
-		    if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
+		    if ((g_range = *s == '+' ? 1 : IS_DASH(*s) ? -1 : 0))
 			++s;
 		    data = qgetnum(&s);
 		    break;
@@ -1859,6 +1857,7 @@ zglob(LinkList list, LinkNode np, int nountok)
 	int nexecs = 0;
 	struct globsort *sortp;
 	struct globsort *lastsortp = gf_sortlist + gf_nsorts;
+	Gmatch gmptr;
 
 	/* First find out if there are any GS_EXECs, counting them. */
 	for (sortp = gf_sortlist; sortp < lastsortp; sortp++)
@@ -1910,6 +1909,23 @@ zglob(LinkList list, LinkNode np, int nountok)
 	    }
 	}
 
+	/*
+	 * Where necessary, create unmetafied version of names
+	 * for comparison.  If no Meta characters just point
+	 * to original string.  All on heap.
+	 */
+	for (gmptr = matchbuf; gmptr < matchptr; gmptr++)
+	{
+	    if (strchr(gmptr->name, Meta))
+	    {
+		int dummy;
+		gmptr->uname = dupstring(gmptr->name);
+		unmetafy(gmptr->uname, &dummy);
+	    } else {
+		gmptr->uname = gmptr->name;
+	    }
+	}
+
 	/* Sort arguments in to lexical (and possibly numeric) order. *
 	 * This is reversed to facilitate insertion into the list.    */
 	qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch),
@@ -2010,13 +2026,13 @@ hasbraces(char *str)
 		if (bracechardots(str-1, NULL, NULL))
 		    return 1;
 		lbr = str - 1;
-		if (*str == '-')
+		if (IS_DASH(*str))
 		    str++;
 		while (idigit(*str))
 		    str++;
 		if (*str == '.' && str[1] == '.') {
 		    str++; str++;
-		    if (*str == '-')
+		    if (IS_DASH(*str))
 			str++;
 		    while (idigit(*str))
 			str++;
@@ -2025,7 +2041,7 @@ hasbraces(char *str)
 			return 1;
 		    else if (*str == '.' && str[1] == '.') {
 			str++; str++;
-			if (*str == '-')
+			if (IS_DASH(*str))
 			    str++;
 			while (idigit(*str))
 			    str++;
@@ -2108,7 +2124,7 @@ xpandredir(struct redir *fn, LinkList redirtab)
 	fn->name = s;
 	untokenize(s);
 	if (fn->type == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) {
-	    if (s[0] == '-' && !s[1])
+	    if (IS_DASH(s[0]) && !s[1])
 		fn->type = REDIR_CLOSE;
 	    else if (s[0] == 'p' && !s[1])
 		fn->fd2 = -2;
@@ -2314,12 +2330,14 @@ xpandbraces(LinkList list, LinkNode *np)
 	     * str+1 is the first number in the range, dots+2 the last,
 	     * and dots2+2 is the increment if that's given. */
 	    /* TODO: sorry about this */
-	    int minw = (str[1] == '0' || (str[1] == '-' && str[2] == '0'))
+	    int minw = (str[1] == '0' ||
+			(IS_DASH(str[1]) && str[2] == '0'))
 		       ? wid1
-		       : (dots[2] == '0' || (dots[2] == '-' && dots[3] == '0'))
+		       : (dots[2] == '0' ||
+			  (IS_DASH(dots[2]) && dots[3] == '0'))
 		       ? wid2
 		       : (dots2 && (dots2[2] == '0' ||
-				    (dots2[2] == '-' && dots2[3] == '0')))
+				    (IS_DASH(dots2[2]) && dots2[3] == '0')))
 		       ? wid3
 		       : 0;
 	    if (rincr < 0) {
@@ -2377,7 +2395,8 @@ xpandbraces(LinkList list, LinkNode *np)
 		c2 = ztokens[c2 - STOUC(Pound)];
 	    if ((char) c2 == Meta)
 		c2 = 32 ^ p[1];
-	    if (c1 == '-' && lastch >= 0 && p < str2 && lastch <= (int)c2) {
+	    if (IS_DASH((char)c1) && lastch >= 0 &&
+		p < str2 && lastch <= (int)c2) {
 		while (lastch < (int)c2)
 		    ccl[lastch++] = 1;
 		lastch = -1;
@@ -2447,13 +2466,20 @@ xpandbraces(LinkList list, LinkNode *np)
 int
 matchpat(char *a, char *b)
 {
-    Patprog p = patcompile(b, PAT_STATIC, NULL);
+    Patprog p;
+    int ret;
 
-    if (!p) {
+    queue_signals();	/* Protect PAT_STATIC */
+
+    if (!(p = patcompile(b, PAT_STATIC, NULL))) {
 	zerr("bad pattern: %s", b);
-	return 0;
-    }
-    return pattry(p, a);
+	ret = 0;
+    } else
+      ret = pattry(p, a);
+
+    unqueue_signals();
+
+    return ret;
 }
 
 /* do the ${foo%%bar}, ${foo#bar} stuff */
@@ -2903,7 +2929,7 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 	     * move forward along string until we get a match. *
 	     * Again there's no optimisation.                  */
 	    mb_charinit();
-	    for (ioff = 0, t = s, umlen = umltot; t < send ; ioff++) {
+	    for (ioff = 0, t = s, umlen = umltot; t <= send ; ioff++) {
 		set_pat_start(p, t-s);
 		if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
 		    *sp = get_match_ret(&imd, t-s, umltot);
@@ -2911,6 +2937,8 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 		}
 		if (fl & SUB_START)
 		    break;
+		if (t == send)
+		    break;
 		umlen -= iincchar(&t, send - t);
 	    }
 	    if (!(fl & SUB_START) && pattrylen(p, send, 0, 0,
@@ -2943,7 +2971,7 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 	    do {
 		/* loop over all matches for global substitution */
 		matched = 0;
-		for (; t < send; ioff++) {
+		for (; t <= send; ioff++) {
 		    /* Find the longest match from this position. */
 		    set_pat_start(p, t-s);
 		    if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
@@ -2992,15 +3020,19 @@ igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 			 * which is already marked for replacement.
 			 */
 			matched = 1;
+			if (t == send)
+			    break;
 			while (t < mpos) {
 			    ioff++;
 			    umlen -= iincchar(&t, send - t);
 			}
 			break;
 		    }
+		    if (t == send)
+			break;
 		    umlen -= iincchar(&t, send - t);
 		}
-	    } while (matched);
+	    } while (matched && t < send);
 	    /*
 	     * check if we can match a blank string, if so do it
 	     * at the start.  Goodness knows if this is a good idea
@@ -3484,6 +3516,10 @@ zshtokenize(char *s, int flags)
     for (; *s; s++) {
       cont:
 	switch (*s) {
+	case Meta:
+	    /* skip both Meta and following character */
+	    s++;
+	    break;
 	case Bnull:
 	case Bnullkeep:
 	case '\\':
@@ -3502,7 +3538,7 @@ zshtokenize(char *s, int flags)
 	    }
 	    t = s;
 	    while (idigit(*++s));
-	    if (*s != '-')
+	    if (!IS_DASH(*s))
 		goto cont;
 	    while (idigit(*++s));
 	    if (*s != '>')
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 2d5af5be0..c34744cd8 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -889,7 +889,7 @@ freeshfuncnode(HashNode hn)
 	freeeprog(shf->funcdef);
     if (shf->redir)
 	freeeprog(shf->redir);
-    zsfree(shf->filename);
+    dircache_set(&shf->filename, NULL);
     if (shf->sticky) {
 	if (shf->sticky->n_on_opts)
 	    zfree(shf->sticky->on_opts,
@@ -926,10 +926,13 @@ printshfuncnode(HashNode hn, int printflags)
 	       (f->node.flags & PM_UNDEFINED) ?
 	       " is an autoload shell function" :
 	       " is a shell function");
-	if (f->filename && (printflags & PRINT_WHENCE_VERBOSE) &&
-	    strcmp(f->filename, f->node.nam) != 0) {
+	if ((printflags & PRINT_WHENCE_VERBOSE) && f->filename) {
 	    printf(" from ");
 	    quotedzputs(f->filename, stdout);
+	    if (f->node.flags & PM_LOADDIR) {
+		printf("/");
+		quotedzputs(f->node.nam, stdout);
+	    }
 	}
 	putchar('\n');
 	return;
@@ -949,16 +952,20 @@ printshfuncnode(HashNode hn, int printflags)
 	    zoutputtab(stdout);
 	}
 	if (!t) {
-	    char *fopt = "UtTkz";
+	    char *fopt = "UtTkzc";
 	    int flgs[] = {
 		PM_UNALIASED, PM_TAGGED, PM_TAGGED_LOCAL,
-		PM_KSHSTORED, PM_ZSHSTORED, 0
+		PM_KSHSTORED, PM_ZSHSTORED, PM_CUR_FPATH, 0
 	    };
 	    int fl;;
 
 	    zputs("builtin autoload -X", stdout);
 	    for (fl=0;fopt[fl];fl++)
 		if (f->node.flags & flgs[fl]) putchar(fopt[fl]);
+	    if (f->filename && (f->node.flags & PM_LOADDIR)) {
+		putchar(' ');
+		zputs(f->filename, stdout);
+	    }
 	} else {
 	    zputs(t, stdout);
 	    zsfree(t);
@@ -1037,6 +1044,24 @@ printshfuncexpand(HashNode hn, int printflags, int expand)
     text_expand_tabs = save_expand;
 }
 
+/*
+ * Get a heap-duplicated name of the shell function, for
+ * use in tracing.
+ */
+
+/**/
+mod_export char *
+getshfuncfile(Shfunc shf)
+{
+    if (shf->node.flags & PM_LOADDIR) {
+	return zhtricat(shf->filename, "/", shf->node.nam);
+    } else if (shf->filename) {
+	return dupstring(shf->filename);
+    } else {
+	return NULL;
+    }
+}
+
 /**************************************/
 /* Reserved Word Hash Table Functions */
 /**************************************/
@@ -1291,9 +1316,9 @@ printaliasnode(HashNode hn, int printflags)
 	else if (a->node.flags & ALIAS_GLOBAL)
 	    printf("-g ");
 
-	/* If an alias begins with `-', then we must output `-- ' *
+	/* If an alias begins with `-' or `+', then we must output `-- '
 	 * first, so that it is not interpreted as an option.     */
-	if(a->node.nam[0] == '-')
+	if(a->node.nam[0] == '-' || a->node.nam[0] == '+')
 	    printf("-- ");
     }
 
@@ -1423,6 +1448,7 @@ freehistdata(Histent he, int unlink)
     if (!(he->node.flags & (HIST_DUP | HIST_TMPSTORE)))
 	removehashnode(histtab, he->node.nam);
 
+    DPUTS(he->node.nam == chline, "Attempt to free chline in history data");
     zsfree(he->node.nam);
     if (he->nwords)
 	zfree(he->words, he->nwords*2*sizeof(short));
@@ -1438,3 +1464,150 @@ freehistdata(Histent he, int unlink)
 	}
     }
 }
+
+
+/***********************************************************************
+ * Directory name cache mechanism
+ *
+ * The idea of this is that there are various shell structures,
+ * notably functions, that record the directories with which they
+ * are associated.  Rather than store the full string each time,
+ * we store a pointer to the same location and count the references.
+ * This is optimised so that retrieval is quick at the expense of
+ * searching the list when setting up the structure, which is a much
+ * rarer operation.
+ *
+ * There is nothing special about the fact that the strings are
+ * directories, except for the assumptions for efficiency that many
+ * structures will point to the same one, and that there are not too
+ * many different directories associated with the shell.
+ **********************************************************************/
+
+struct dircache_entry
+{
+    /* Name of directory in cache */
+    char *name;
+    /* Number of references to it */
+    int refs;
+};
+
+/*
+ * dircache is the cache, of length dircache_size.
+ * dircache_lastentry is the last entry used, an optimisation
+ * for multiple references to the same directory, e.g
+ * "autoload /blah/blah/\*".
+ */
+static struct dircache_entry *dircache, *dircache_lastentry;
+static int dircache_size;
+
+/*
+ * Set *name to point to a cached version of value.
+ * value is copied so may come from any source.
+ *
+ * If value is NULL, look for the existing value of *name (safe if this
+ * too is NULL) and remove a reference to it from the cache. If it's
+ * not found in the cache, it's assumed to be an allocated string and
+ * freed --- this currently occurs for a shell function that's been
+ * loaded as the filename is now a full path, not just a directory,
+ * though we may one day optimise this to a cached directory plus a
+ * name, too.  Note --- the function does *not* otherwise check
+ * if *name points to something already cached, so this is
+ * necessary any time *name may already be in the cache.
+ */
+
+/**/
+mod_export void
+dircache_set(char **name, char *value)
+{
+    struct dircache_entry *dcptr, *dcnew;
+
+    if (!value) {
+	if (!*name)
+	    return;
+	if (!dircache_size) {
+	    zsfree(*name);
+	    *name = NULL;
+	    return;
+	}
+
+	for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
+	{
+	    /* Must be a pointer much, not a string match */
+	    if (*name == dcptr->name)
+	    {
+		--dcptr->refs;
+		if (!dcptr->refs) {
+		    ptrdiff_t ind = dcptr - dircache;
+		    zsfree(dcptr->name);
+		    --dircache_size;
+
+		    if (!dircache_size) {
+			zfree(dircache, sizeof(*dircache));
+			dircache = NULL;
+			dircache_lastentry = NULL;
+			*name = NULL;
+			return;
+		    }
+		    dcnew = (struct dircache_entry *)
+			zalloc(dircache_size * sizeof(*dcnew));
+		    if (ind)
+			memcpy(dcnew, dircache, ind * sizeof(*dcnew));
+		    if (ind < dircache_size)
+			memcpy(dcnew + ind, dcptr + 1,
+			       (dircache_size - ind) * sizeof(*dcnew));
+		    zfree(dircache, (dircache_size+1)*sizeof(*dcnew));
+		    dircache = dcnew;
+		    dircache_lastentry = NULL;
+		}
+		*name = NULL;
+		return;
+	    }
+	}
+	zsfree(*name);
+	*name = NULL;
+    } else {
+	/*
+	 * As the function path has been resolved to a particular
+	 * location, we'll store it as an absolute path.
+	 */
+	if (*value != '/') {
+	    value = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
+			     "/", value);
+	    value = xsymlink(value, 1);
+	}
+	/*
+	 * We'll maintain the cache at exactly the right size rather
+	 * than overallocating.  The rationale here is that typically
+	 * we'll get a lot of functions in a small number of directories
+	 * so the complexity overhead of maintaining a separate count
+	 * isn't really matched by the efficiency gain.
+ 	 */
+	if (dircache_lastentry &&
+	    !strcmp(value, dircache_lastentry->name)) {
+	    *name = dircache_lastentry->name;
+	    ++dircache_lastentry->refs;
+	    return;
+	} else if (!dircache_size) {
+	    dircache_size = 1;
+	    dcptr = dircache =
+		(struct dircache_entry *)zalloc(sizeof(*dircache));
+	} else {
+	    for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
+	    {
+		if (!strcmp(value, dcptr->name)) {
+		    *name = dcptr->name;
+		    ++dcptr->refs;
+		    return;
+		}
+	    }
+	    ++dircache_size;
+	    dircache = (struct dircache_entry *)
+		zrealloc(dircache, sizeof(*dircache) * dircache_size);
+	    dcptr = dircache + dircache_size - 1;
+	}
+	dcptr->name = ztrdup(value);
+	*name = dcptr->name;
+	dcptr->refs = 1;
+	dircache_lastentry = dcptr;
+    }
+}
diff --git a/Src/hist.c b/Src/hist.c
index 5fc40bd67..4c1039b67 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -87,6 +87,9 @@ mod_export zlong curhist;
 /**/
 struct histent curline;
 
+/***/
+int curline_linked;
+
 /* current line count of allocated history entries */
 
 /**/
@@ -261,6 +264,9 @@ hist_context_save(struct hist_stack *hs, int toplevel)
      */
     hs->cstack = cmdstack;
     hs->csp = cmdsp;
+    hs->curline_linked = curline_linked;
+
+    unlinkcurline();
 
     stophist = 0;
     chline = NULL;
@@ -300,6 +306,9 @@ hist_context_restore(const struct hist_stack *hs, int toplevel)
 	zfree(cmdstack, CMDSTACKSZ);
     cmdstack = hs->cstack;
     cmdsp = hs->csp;
+    unlinkcurline();
+    if (hs->curline_linked)
+	linkcurline();
 }
 
 /*
@@ -653,6 +662,7 @@ histsubchar(int c)
 		(c == '}' ||  c == ';' || c == '\'' || c == '"' || c == '`')) {
 	      /* Neither event nor word designator, no expansion */
 	      safeinungetc(c);
+	      unqueue_signals();
 	      return bangchar;
 	    }
 	    *ptr = 0;
@@ -989,6 +999,7 @@ nohwe(void)
 
 /* these functions handle adding/removing curline to/from the hist_ring */
 
+/**/
 static void
 linkcurline(void)
 {
@@ -1001,11 +1012,15 @@ linkcurline(void)
 	hist_ring = &curline;
     }
     curline.histnum = ++curhist;
+    curline_linked = 1;
 }
 
+/**/
 static void
 unlinkcurline(void)
 {
+    if (!curline_linked)
+	return;
     curline.up->down = curline.down;
     curline.down->up = curline.up;
     if (hist_ring == &curline) {
@@ -1015,6 +1030,7 @@ unlinkcurline(void)
 	    hist_ring = curline.up;
     }
     curhist--;
+    curline_linked = 0;
 }
 
 /* initialize the history mechanism */
@@ -1034,10 +1050,11 @@ hbegin(int dohist)
 	stophist = (!interact || unset(SHINSTDIN)) ? 2 : 0;
     else
 	stophist = 0;
+    DPUTS(chline != NULL, "chline set at start of history");
     /*
      * pws: We used to test for "|| (inbufflags & INP_ALIAS)"
      * in this test, but at this point we don't have input
-     * set up up so this can trigger unnecessarily.
+     * set up so this can trigger unnecessarily.
      * I don't see how the test at this point could ever be
      * useful, since we only get here when we're initialising
      * the history mechanism, before we've done any input.
@@ -1291,11 +1308,10 @@ putoldhistentryontop(short keep_going)
 Histent
 prepnexthistent(void)
 {
-    Histent he; 
-    int curline_in_ring = hist_ring == &curline;
+    Histent he;
+    int relink_curline = curline_linked;
 
-    if (curline_in_ring)
-	unlinkcurline();
+    unlinkcurline();
     if (hist_ring && hist_ring->node.flags & HIST_TMPSTORE) {
 	curhist--;
 	freehistnode(&hist_ring->node);
@@ -1319,7 +1335,7 @@ prepnexthistent(void)
 	he = hist_ring;
     }
     he->histnum = ++curhist;
-    if (curline_in_ring)
+    if (relink_curline)
 	linkcurline();
     return he;
 }
@@ -1394,8 +1410,7 @@ hend(Eprog prog)
     queue_signals();
     if (histdone & HISTFLAG_SETTY)
 	settyinfo(&shttyinfo);
-    if (!(histactive & HA_NOINC))
-	unlinkcurline();
+    unlinkcurline();
     if (histactive & HA_NOINC) {
 	zfree(chline, hlinesz);
 	zfree(chwords, chwordlen*sizeof(short));
@@ -1417,7 +1432,7 @@ hend(Eprog prog)
 	DPUTS(hptr < chline, "History end pointer off start of line");
 	*hptr = '\0';
     }
-    {
+    if (*chline) {
 	LinkList hookargs = newlinklist();
 	int save_errflag = errflag;
 	errflag = 0;
@@ -1426,6 +1441,7 @@ hend(Eprog prog)
 	addlinknode(hookargs, chline);
 	callhookfunc("zshaddhistory", hookargs, 1, &hookret);
 
+	errflag &= ~ERRFLAG_ERROR;
 	errflag |= save_errflag;
     }
     /* For history sharing, lock history file once for both read and write */
@@ -1842,7 +1858,7 @@ chrealpath(char **junkptr)
 # ifdef REALPATH_ACCEPTS_NULL
     char *lastpos, *nonreal, *real;
 # else
-    char *lastpos, *nonreal, pathbuf[PATH_MAX];
+    char *lastpos, *nonreal, pathbuf[PATH_MAX+1];
     char *real = pathbuf;
 # endif
 #endif
@@ -3622,7 +3638,7 @@ int
 pushhiststack(char *hf, zlong hs, zlong shs, int level)
 {
     struct histsave *h;
-    int curline_in_ring = (histactive & HA_ACTIVE) && hist_ring == &curline;
+    int relink_curline = curline_linked;
 
     if (histsave_stack_pos == histsave_stack_size) {
 	histsave_stack_size += 5;
@@ -3630,8 +3646,7 @@ pushhiststack(char *hf, zlong hs, zlong shs, int level)
 			    histsave_stack_size * sizeof (struct histsave));
     }
 
-    if (curline_in_ring)
-	unlinkcurline();
+    unlinkcurline();
 
     h = &histsave_stack[histsave_stack_pos++];
 
@@ -3666,7 +3681,7 @@ pushhiststack(char *hf, zlong hs, zlong shs, int level)
     savehistsiz = shs;
     inithist(); /* sets histtab */
 
-    if (curline_in_ring)
+    if (relink_curline)
 	linkcurline();
 
     return histsave_stack_pos;
@@ -3678,13 +3693,12 @@ int
 pophiststack(void)
 {
     struct histsave *h;
-    int curline_in_ring = (histactive & HA_ACTIVE) && hist_ring == &curline;
+    int relink_curline = curline_linked;
 
     if (histsave_stack_pos == 0)
 	return 0;
 
-    if (curline_in_ring)
-	unlinkcurline();
+    unlinkcurline();
 
     deletehashtable(histtab);
     zsfree(lasthist.text);
@@ -3707,7 +3721,7 @@ pophiststack(void)
     histsiz = h->histsiz;
     savehistsiz = h->savehistsiz;
 
-    if (curline_in_ring)
+    if (relink_curline)
 	linkcurline();
 
     return histsave_stack_pos + 1;
diff --git a/Src/init.c b/Src/init.c
index 20a07eb0a..d8c26aca2 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -376,12 +376,12 @@ parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp,
 	    *argv = "--";
 	while (*++*argv) {
 	    if (**argv == '-') {
-		if(!argv[0][1]) {
+		if (!argv[0][1]) {
 		    /* The pseudo-option `--' signifies the end of options. */
 		    argv++;
 		    goto doneoptions;
 		}
-		if(*argv != args+1 || **argv != '-')
+		if (nam || *argv != args+1 || **argv != '-')
 		    goto badoptionstring;
 		/* GNU-style long options */
 		++*argv;
@@ -712,7 +712,7 @@ init_term(void)
     if (tgetent(termbuf, term) != TGETENT_SUCCESS)
 #endif
     {
-	if (isset(INTERACTIVE))
+	if (interact)
 	    zerr("can't find terminal definition for %s", term);
 	errflag &= ~ERRFLAG_ERROR;
 	termflags |= TERM_BAD;
@@ -790,7 +790,7 @@ init_term(void)
 	    tcstr[TCCLEARSCREEN] = ztrdup("\14");
 	    tclen[TCCLEARSCREEN] = 1;
 	}
-	rprompt_indent = 1;
+	rprompt_indent = 1; /* If you change this, update rprompt_indent_unsetfn() */
 	/* The following is an attempt at a heuristic,
 	 * but it fails in some cases */
 	/* rprompt_indent = ((hasam && !hasbw) || hasye || !tccan(TCLEFT)); */
@@ -1205,19 +1205,22 @@ run_init_scripts(void)
 	if (islogin)
 	    source("/etc/profile");
 	if (unset(PRIVILEGED)) {
-	    char *s = getsparam("ENV");
 	    if (islogin)
 		sourcehome(".profile");
-	    noerrs = 2;
-	    if (s) {
-		s = dupstring(s);
-		if (!parsestr(&s)) {
-		    singsub(&s);
-		    noerrs = 0;
-		    source(s);
+
+	    if (interact) {
+		noerrs = 2;
+		char *s = getsparam("ENV");
+		if (s) {
+		    s = dupstring(s);
+		    if (!parsestr(&s)) {
+			singsub(&s);
+			noerrs = 0;
+			source(s);
+		    }
 		}
+		noerrs = 0;
 	    }
-	    noerrs = 0;
 	} else
 	    source("/etc/suid_profile");
     } else {
@@ -1227,7 +1230,7 @@ run_init_scripts(void)
 
 	if (isset(RCS) && unset(PRIVILEGED))
 	{
-	    if (isset(INTERACTIVE)) {
+	    if (interact) {
 		/*
 		 * Always attempt to load the newuser module to perform
 		 * checks for new zsh users.  Don't care if we can't load it.
@@ -1439,8 +1442,10 @@ sourcehome(char *s)
     queue_signals();
     if (EMULATION(EMULATE_SH|EMULATE_KSH) || !(h = getsparam_u("ZDOTDIR"))) {
 	h = home;
-	if (!h)
+	if (!h) {
+	    unqueue_signals();
 	    return;
+	}
     }
 
     {
diff --git a/Src/input.c b/Src/input.c
index eb968ea72..92abaec92 100644
--- a/Src/input.c
+++ b/Src/input.c
@@ -670,3 +670,30 @@ ingetptr(void)
 {
     return inbufptr;
 }
+
+/*
+ * Check if the current input line, including continuations, is
+ * expanding an alias.  This does not detect alias expansions that
+ * have been fully processed and popped from the input stack.
+ * If there is an alias, the most recently expanded is returned,
+ * else NULL.
+ */
+
+/**/
+char *input_hasalias(void)
+{
+    int flags = inbufflags;
+    struct instacks *instackptr = instacktop;
+
+    for (;;)
+    {
+	if (!(flags & INP_CONT))
+	    break;
+	instackptr--;
+	if (instackptr->alias)
+	    return instackptr->alias->node.nam;
+	flags = instackptr->flags;
+    }
+
+    return NULL;
+}
diff --git a/Src/jobs.c b/Src/jobs.c
index 2a9dbe7d6..d1b98ac4d 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -128,7 +128,7 @@ makerunning(Job jn)
     Process pn;
 
     jn->stat &= ~STAT_STOPPED;
-    for (pn = jn->procs; pn; pn = pn->next)
+    for (pn = jn->procs; pn; pn = pn->next) {
 #if 0
 	if (WIFSTOPPED(pn->status) && 
 	    (!(jn->stat & STAT_SUPERJOB) || pn->next))
@@ -136,6 +136,7 @@ makerunning(Job jn)
 #endif
         if (WIFSTOPPED(pn->status))
 	    pn->status = SP_RUNNING;
+    }
 
     if (jn->stat & STAT_SUPERJOB)
 	makerunning(jobtab + jn->other);
@@ -231,12 +232,13 @@ super_job(int sub)
 static int
 handle_sub(int job, int fg)
 {
+    /* job: superjob; sj: subjob. */
     Job jn = jobtab + job, sj = jobtab + jn->other;
 
     if ((sj->stat & STAT_DONE) || (!sj->procs && !sj->auxprocs)) {
 	struct process *p;
-		    
-	for (p = sj->procs; p; p = p->next)
+
+	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));
@@ -246,6 +248,7 @@ handle_sub(int job, int fg)
 		kill(sj->other, WTERMSIG(p->status));
 		break;
 	    }
+	}
 	if (!p) {
 	    int cp;
 
@@ -884,37 +887,62 @@ should_report_time(Job j)
     struct value vbuf;
     Value v;
     char *s = "REPORTTIME";
-    zlong reporttime;
+    zlong reporttime = -1;
+#ifdef HAVE_GETRUSAGE
+    char *sm = "REPORTMEMORY";
+    zlong reportmemory = -1;
+#endif
 
     /* if the time keyword was used */
     if (j->stat & STAT_TIMED)
 	return 1;
 
     queue_signals();
-    if (!(v = getvalue(&vbuf, &s, 0)) ||
-	(reporttime = getintvalue(v)) < 0) {
-	unqueue_signals();
-	return 0;
-    }
+    if ((v = getvalue(&vbuf, &s, 0)))
+	reporttime = getintvalue(v);
+#ifdef HAVE_GETRUSAGE
+    if ((v = getvalue(&vbuf, &sm, 0)))
+	reportmemory = getintvalue(v);
+#endif
     unqueue_signals();
+    if (reporttime < 0
+#ifdef HAVE_GETRUSAGE
+	&& reportmemory < 0
+#endif
+	)
+	return 0;
     /* can this ever happen? */
     if (!j->procs)
 	return 0;
     if (zleactive)
 	return 0;
 
+    if (reporttime >= 0)
+    {
 #ifdef HAVE_GETRUSAGE
-    reporttime -= j->procs->ti.ru_utime.tv_sec + j->procs->ti.ru_stime.tv_sec;
-    if (j->procs->ti.ru_utime.tv_usec +
-	j->procs->ti.ru_stime.tv_usec >= 1000000)
-	reporttime--;
-    return reporttime <= 0;
+	reporttime -= j->procs->ti.ru_utime.tv_sec +
+	    j->procs->ti.ru_stime.tv_sec;
+	if (j->procs->ti.ru_utime.tv_usec +
+	    j->procs->ti.ru_stime.tv_usec >= 1000000)
+	    reporttime--;
+	if (reporttime <= 0)
+	    return 1;
 #else
-    {
-	clktck = get_clktck();
-	return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime);
+	{
+	    clktck = get_clktck();
+	    if ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime)
+		return 1;
+	}
+#endif
     }
+
+#ifdef HAVE_GETRUSAGE
+    if (reportmemory >= 0 &&
+	j->procs->ti.ru_maxrss / 1024 > reportmemory)
+	return 1;
 #endif
+
+    return 0;
 }
 
 /* !(lng & 3) means jobs    *
@@ -1291,6 +1319,11 @@ deletejob(Job jn, int disowning)
 	attachtty(mypgrp);
 	adjustwinsize(0);
     }
+    if (jn->stat & STAT_SUPERJOB) {
+	Job jno = jobtab + jn->other;
+	if (jno->stat & STAT_SUBJOB)
+	    jno->stat |= STAT_SUBJOB_ORPHANED;
+    }
 
     freejob(jn, 1);
 }
@@ -1447,7 +1480,7 @@ zwaitjob(int job, int wait_cmd)
 	     */
 	    pipecleanfilelist(jn->filelist, 0);
 	}
-	while (!errflag && jn->stat &&
+	while (!(errflag & ERRFLAG_ERROR) && jn->stat &&
 	       !(jn->stat & STAT_DONE) &&
 	       !(interact && (jn->stat & STAT_STOPPED))) {
 	    signal_suspend(SIGCHLD, wait_cmd);
@@ -1469,6 +1502,13 @@ zwaitjob(int job, int wait_cmd)
 	      that's the one related to ^C.  But that doesn't work.
 	      There's something more here we don't understand.  --pws
 
+	      The change above to ignore ERRFLAG_INT in the loop test
+	      solves a problem wherein child processes that ignore the
+	      INT signal were never waited-for.  Clearing the flag here
+	      still seems the wrong thing, but perhaps ERRFLAG_INT
+	      should be saved and restored around signal_suspend() to
+	      prevent it being lost within a signal trap?  --Bart
+
            errflag = 0; */
 
 	    if (subsh) {
@@ -2527,6 +2567,10 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func))
 	argv++;
     }
 
+    /* Discard the standard "-" and "--" option breaks */
+    if (*argv && (*argv)[0] == '-' && (!(*argv)[1] || (*argv)[1] == '-'))
+	argv++;
+
     if (!*argv) {
     	zwarnnam(nam, "not enough arguments");
 	return 1;
diff --git a/Src/lex.c b/Src/lex.c
index 25b372a3c..b2d9b3f42 100644
--- a/Src/lex.c
+++ b/Src/lex.c
@@ -613,7 +613,7 @@ gettok(void)
     if (lexstop)
 	return (errflag) ? LEXERR : ENDINPUT;
     isfirstln = 0;
-    if ((lexflags & LEXFLAGS_ZLE))
+    if ((lexflags & LEXFLAGS_ZLE) && !(inbufflags & INP_ALIAS))
 	wordbeg = inbufct - (qbang && c == bangchar);
     hwbegin(-1-(qbang && c == bangchar));
     /* word includes the last character read and possibly \ before ! */
@@ -1359,17 +1359,13 @@ gettokstr(int c, int sub)
 	case LX2_DASH:
 	    /*
 	     * - shouldn't be treated as a special character unless
-	     * we're in a pattern.  Howeve,simply  counting "[" doesn't
-	     * work as []a-z] is a valid expression and we don't know
-	     * down here what this "[" is for as $foo[stuff] is valid
-	     * in zsh.  So just detect an opening [, which is enough
-	     * to turn this into a pattern; the Dash will be harmlessly
-	     * untokenised if not wanted.
+	     * we're in a pattern.  Unfortunately, working out for
+	     * sure in complicated expressions whether we're in a
+	     * pattern is tricky.  So we'll make it special and
+	     * turn it back any time we don't need it special.
+	     * This is not ideal as it's a lot of work.
 	     */
-	    if (seen_brct)
-		c = Dash;
-           else
-               c = '-';
+	    c = Dash;
            break;
        case LX2_BANG:
            /*
@@ -1795,6 +1791,10 @@ gotword(void)
 	if (zlemetacs >= nwb) {
 	    wb = nwb;
 	    we = nwe;
+	} else {
+	    wb = zlemetacs + addedx;
+	    if (we < wb)
+		we = wb;
 	}
 	lexflags = 0;
     }
@@ -1839,9 +1839,9 @@ checkalias(void)
 	    suf > zshlextext && suf[-1] != Meta &&
 	    (an = (Alias)sufaliastab->getnode(sufaliastab, suf+1)) &&
 	    !an->inuse && incmdpos) {
-	    inpush(dupstring(zshlextext), INP_ALIAS, NULL);
+	    inpush(dupstring(zshlextext), INP_ALIAS, an);
 	    inpush(" ", INP_ALIAS, NULL);
-	    inpush(an->text, INP_ALIAS, an);
+	    inpush(an->text, INP_ALIAS, NULL);
 	    lexstop = 0;
 	    return 1;
 	}
@@ -2060,9 +2060,7 @@ skipcomm(void)
     int new_lexstop, new_lex_add_raw;
     int save_infor = infor;
     struct lexbufstate new_lexbuf;
-    int noalias = noaliases;
 
-    noaliases = 1;
     infor = 0;
     cmdpush(CS_CMDSUBST);
     SETPARBEGIN
@@ -2134,8 +2132,17 @@ skipcomm(void)
     lexflags &= ~LEXFLAGS_ZLE;
     dbparens = 0;	/* restored by zcontext_restore_partial() */
 
-    if (!parse_event(OUTPAR) || tok != OUTPAR)
-	lexstop = 1;
+    if (!parse_event(OUTPAR) || tok != OUTPAR) {
+	if (strin) {
+	    /*
+	     * Get the rest of the string raw since we don't
+	     * know where this token ends.
+	     */
+	    while (!lexstop)
+		(void)ingetc();
+	} else
+	    lexstop = 1;
+    }
      /* Outpar lexical token gets added in caller if present */
 
     /*
@@ -2180,7 +2187,6 @@ skipcomm(void)
 	SETPAREND
     cmdpop();
     infor = save_infor;
-    noaliases = noalias;
 
     return lexstop;
 #endif
diff --git a/Src/linklist.c b/Src/linklist.c
index 3aa8125d9..85d9bb367 100644
--- a/Src/linklist.c
+++ b/Src/linklist.c
@@ -348,6 +348,35 @@ newsizedlist(int size)
 }
 
 /*
+ * Join two linked lists.  Neither may be null, though either
+ * may be empty.
+ *
+ * It is assumed the pieces come from the heap, but if not it is
+ * safe to free LinkList second.
+ */
+
+/**/
+mod_export LinkList
+joinlists(LinkList first, LinkList second)
+{
+    LinkNode moveme = firstnode(second);
+    if (moveme) {
+	if (firstnode(first)) {
+	    LinkNode anchor = lastnode(first);
+	    anchor->next = moveme;
+	    moveme->prev = anchor;
+	} else {
+	    first->list.first = moveme;
+	    moveme->prev = &first->node;
+	}
+	first->list.last = second->list.last;
+
+	second->list.first = second->list.last = NULL;
+    }
+    return first;
+}
+
+/*
  * Return the node whose data is the pointer "dat", else NULL.
  * Can be used as a boolean test.
  */
diff --git a/Src/loop.c b/Src/loop.c
index 19d7f733e..f7eae307b 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -208,6 +208,7 @@ execfor(Estate state, int do_exec)
     loops--;
     simple_pline = old_simple_pline;
     state->pc = end;
+    this_noerrexit = 1;
     return lastval;
 }
 
@@ -289,6 +290,8 @@ execselect(Estate state, UNUSED(int do_exec))
 	    	}
 	    } else
 		str = (char *)getlinknode(bufstack);
+            if (!str && !errflag)
+                setsparam("REPLY", ztrdup("")); /* EOF (user pressed Ctrl+D) */
 	    if (!str || errflag) {
 		if (breaks)
 		    breaks--;
@@ -333,6 +336,7 @@ execselect(Estate state, UNUSED(int do_exec))
     loops--;
     simple_pline = old_simple_pline;
     state->pc = end;
+    this_noerrexit = 1;
     return lastval;
 }
 
@@ -437,13 +441,12 @@ execwhile(Estate state, UNUSED(int do_exec))
             if (!((lastval == 0) ^ isuntil)) {
                 if (breaks)
                     breaks--;
-                lastval = oldval;
+		if (!retflag)
+		    lastval = oldval;
                 break;
             }
-            if (retflag) {
-                lastval = oldval;
+            if (retflag)
                 break;
-            }
 
 	    /* In case the loop body is also a functional no-op,
 	     * make sure signal handlers recognize ^C as above. */
@@ -471,6 +474,7 @@ execwhile(Estate state, UNUSED(int do_exec))
     popheap();
     loops--;
     state->pc = end;
+    this_noerrexit = 1;
     return lastval;
 }
 
@@ -522,6 +526,7 @@ execrepeat(Estate state, UNUSED(int do_exec))
     loops--;
     simple_pline = old_simple_pline;
     state->pc = end;
+    this_noerrexit = 1;
     return lastval;
 }
 
@@ -568,9 +573,11 @@ execif(Estate state, int do_exec)
 	cmdpop();
     } else {
 	noerrexit = olderrexit;
-	lastval = 0;
+	if (!retflag)
+	    lastval = 0;
     }
     state->pc = end;
+    this_noerrexit = 1;
 
     return lastval;
 }
@@ -582,7 +589,7 @@ execcase(Estate state, int do_exec)
     Wordcode end, next;
     wordcode code = state->pc[-1];
     char *word, *pat;
-    int npat, save, nalts, ialt, patok;
+    int npat, save, nalts, ialt, patok, anypatok;
     Patprog *spprog, pprog;
 
     end = state->pc + WC_CASE_SKIP(code);
@@ -590,7 +597,7 @@ execcase(Estate state, int do_exec)
     word = ecgetstr(state, EC_DUP, NULL);
     singsub(&word);
     untokenize(word);
-    lastval = 0;
+    anypatok = 0;
 
     cmdpush(CS_CASE);
     while (state->pc < end) {
@@ -613,7 +620,9 @@ execcase(Estate state, int do_exec)
 	    spprog = state->prog->pats + npat;
 	    pprog = NULL;
 	    pat = NULL;
-	
+
+	    queue_signals();
+
 	    if (isset(XTRACE)) {
 		int htok = 0;
 		pat = dupstring(ecrawstr(state->prog, state->pc, &htok));
@@ -647,9 +656,11 @@ execcase(Estate state, int do_exec)
 		    *spprog = pprog;
 	    }
 	    if (pprog && pattry(pprog, word))
-		patok = 1;
+		patok = anypatok = 1;
 	    state->pc += 2;
 	    nalts--;
+
+	    unqueue_signals();
 	}
 	state->pc += 2 * nalts;
 	if (isset(XTRACE)) {
@@ -660,7 +671,7 @@ execcase(Estate state, int do_exec)
 	    execlist(state, 1, ((WC_CASE_TYPE(code) == WC_CASE_OR) &&
 				do_exec));
 	    while (!retflag && wc_code(code) == WC_CASE &&
-		   WC_CASE_TYPE(code) == WC_CASE_AND) {
+		   WC_CASE_TYPE(code) == WC_CASE_AND && state->pc < end) {
 		state->pc = next;
 		code = *state->pc++;
 		next = state->pc + WC_CASE_SKIP(code);
@@ -678,6 +689,10 @@ execcase(Estate state, int do_exec)
 
     state->pc = end;
 
+    if (!anypatok)
+	lastval = 0;
+    this_noerrexit = 1;
+
     return lastval;
 }
 
diff --git a/Src/math.c b/Src/math.c
index 37981cf22..f9613001a 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -463,7 +463,7 @@ lexconstant(void)
     char *nptr;
 
     nptr = ptr;
-    if (*nptr == '-')
+    if (IS_DASH(*nptr))
 	nptr++;
 
     if (*nptr == '0') {
@@ -527,7 +527,7 @@ lexconstant(void)
 	}
 	if (*nptr == 'e' || *nptr == 'E') {
 	    nptr++;
-	    if (*nptr == '+' || *nptr == '-')
+	    if (*nptr == '+' || IS_DASH(*nptr))
 		nptr++;
 	    while (idigit(*nptr) || *nptr == '_')
 		nptr++;
@@ -599,7 +599,8 @@ zzlex(void)
 	    }
 	    return (unary) ? UPLUS : PLUS;
 	case '-':
-	    if (*ptr == '-') {
+	case Dash:
+	    if (IS_DASH(*ptr)) {
 		ptr++;
 		return (unary) ? PREMINUS : POSTMINUS;
 	    }
@@ -974,7 +975,7 @@ callmathfunc(char *o)
     a[strlen(a) - 1] = '\0';
 
     if ((f = getmathfunc(n, 1))) {
-	if (f->flags & MFF_STR) {
+	if ((f->flags & (MFF_STR|MFF_USERFUNC)) == MFF_STR) {
 	    return f->sfunc(n, a, f->funcid);
 	} else {
 	    int argc = 0;
@@ -987,22 +988,34 @@ callmathfunc(char *o)
 		addlinknode(l, n);
 	    }
 
-	    while (iblank(*a))
-		a++;
+	    if (f->flags & MFF_STR) {
+		if (!*a) {
+		    addlinknode(l, dupstring(""));
+		    argc++;
+		}
+	    } else {
+		while (iblank(*a))
+		    a++;
+	    }
 	    while (*a) {
 		if (*a) {
 		    argc++;
 		    if (f->flags & MFF_USERFUNC) {
 			/* need to pass strings */
 			char *str;
-			marg = mathevall(a, MPREC_ARG, &a);
-			if (marg.type & MN_FLOAT) {
-			    /* convfloat is off the heap */
-			    str = convfloat(marg.u.d, 0, 0, NULL);
+			if (f->flags & MFF_STR) {
+			    str = dupstring(a);
+			    a = "";
 			} else {
-			    char buf[BDIGBUFSIZE];
-			    convbase(buf, marg.u.l, 10);
-			    str = dupstring(buf);
+			    marg = mathevall(a, MPREC_ARG, &a);
+			    if (marg.type & MN_FLOAT) {
+				/* convfloat is off the heap */
+				str = convfloat(marg.u.d, 0, 0, NULL);
+			    } else {
+				char buf[BDIGBUFSIZE];
+				convbase(buf, marg.u.l, 10);
+				str = dupstring(buf);
+			    }
 			}
 			addlinknode(l, str);
 		    } else {
diff --git a/Src/mem.c b/Src/mem.c
index e31145e1e..840bbb6e4 100644
--- a/Src/mem.c
+++ b/Src/mem.c
@@ -157,13 +157,13 @@ mod_export Heapid last_heap_id;
  * Assumes old_heaps() will come along and restore it later
  * (outputs an error if old_heaps() is called out of sequence).
  */
-LinkList heaps_saved;
+static LinkList heaps_saved;
 
 /*
  * Debugging verbosity.  This must be set from a debugger.
  * An 'or' of bits from the enum heap_debug_verbosity.
  */
-volatile int heap_debug_verbosity;
+static volatile int heap_debug_verbosity;
 
 /*
  * Generate a heap identifier that's unique up to unsigned integer wrap.
@@ -497,7 +497,8 @@ popheap(void)
 		    continue;
 		}
 		h->next = NULL;
-	    }
+	    } else if (hl == h)	/* This is the last arena of all */
+		hl = NULL;
 #ifdef USE_MMAP
 	    munmap((void *) h, h->size);
 #else
@@ -903,27 +904,36 @@ memory_validate(Heapid heap_id)
 
     queue_signals();
     for (h = heaps; h; h = h->next) {
-	if (h->heap_id == heap_id)
+	if (h->heap_id == heap_id) {
+	    unqueue_signals();
 	    return 0;
+	}
 	for (hs = heaps->sp; hs; hs = hs->next) {
-	    if (hs->heap_id == heap_id)
+	    if (hs->heap_id == heap_id) {
+		unqueue_signals();
 		return 0;
+	    }
 	}
     }
 
     if (heaps_saved) {
 	for (node = firstnode(heaps_saved); node; incnode(node)) {
 	    for (h = (Heap)getdata(node); h; h = h->next) {
-		if (h->heap_id == heap_id)
+		if (h->heap_id == heap_id) {
+		    unqueue_signals();
 		    return 0;
+		}
 		for (hs = heaps->sp; hs; hs = hs->next) {
-		    if (hs->heap_id == heap_id)
+		    if (hs->heap_id == heap_id) {
+			unqueue_signals();
 			return 0;
+		    }
 		}
 	    }
 	}
     }
 
+    unqueue_signals();
     return 1;
 }
 /**/
@@ -966,18 +976,10 @@ zalloc(size_t size)
 mod_export void *
 zshcalloc(size_t size)
 {
-    void *ptr;
-
+    void *ptr = zalloc(size);
     if (!size)
 	size = 1;
-    queue_signals();
-    if (!(ptr = (void *) malloc(size))) {
-	zerr("fatal error: out of memory");
-	exit(1);
-    }
-    unqueue_signals();
     memset(ptr, 0, size);
-
     return ptr;
 }
 
diff --git a/Src/module.c b/Src/module.c
index 368254c29..41f142adb 100644
--- a/Src/module.c
+++ b/Src/module.c
@@ -2242,6 +2242,7 @@ load_module(char const *name, Feature_enables enablesarr, int silent)
 	return 0;
     }
     if (m->node.flags & MOD_BUSY) {
+	unqueue_signals();
 	zerr("circular dependencies for module ;%s", name);
 	return 1;
     }
@@ -3350,6 +3351,8 @@ setfeatureenables(Module m, Features f, int *e)
     if (f->mf_size) {
 	if (setmathfuncs(m->node.nam, f->mf_list, f->mf_size, e))
 	    ret = 1;
+	if (e)
+	    e += f->mf_size;
     }
     if (f->pd_size) {
 	if (setparamdefs(m->node.nam, f->pd_list, f->pd_size, e))
diff --git a/Src/options.c b/Src/options.c
index 18619c851..2b5795bab 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -78,6 +78,7 @@ mod_export HashTable optiontab;
  */
 static struct optname optns[] = {
 {{NULL, "aliases",	      OPT_EMULATE|OPT_ALL},	 ALIASESOPT},
+{{NULL, "aliasfuncdef",       OPT_EMULATE|OPT_BOURNE},	 ALIASFUNCDEF},
 {{NULL, "allexport",	      OPT_EMULATE},		 ALLEXPORT},
 {{NULL, "alwayslastprompt",   OPT_ALL},			 ALWAYSLASTPROMPT},
 {{NULL, "alwaystoend",	      0},			 ALWAYSTOEND},
@@ -257,6 +258,7 @@ static struct optname optns[] = {
 {{NULL, "verbose",	      0},			 VERBOSE},
 {{NULL, "vi",		      0},			 VIMODE},
 {{NULL, "warncreateglobal",   OPT_EMULATE},		 WARNCREATEGLOBAL},
+{{NULL, "warnnestedvar",      OPT_EMULATE},		 WARNNESTEDVAR},
 {{NULL, "xtrace",	      0},			 XTRACE},
 {{NULL, "zle",		      OPT_SPECIAL},		 USEZLE},
 {{NULL, "braceexpand",	      OPT_ALIAS}, /* ksh/bash */ -IGNOREBRACES},
@@ -645,7 +647,7 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 
 	    /* Expand the current arg. */
 	    tokenize(s);
-	    if (!(pprog = patcompile(s, PAT_STATIC, NULL))) {
+	    if (!(pprog = patcompile(s, PAT_HEAPDUP, NULL))) {
 		zwarnnam(nam, "bad pattern: %s", *args);
 		continue;
 	    }
diff --git a/Src/params.c b/Src/params.c
index 2e4dd4ee6..8d6cd0ee5 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -89,6 +89,7 @@ char *ifs,		/* $IFS         */
      *postedit,		/* $POSTEDIT    */
      *term,		/* $TERM        */
      *zsh_terminfo,     /* $TERMINFO    */
+     *zsh_terminfodirs, /* $TERMINFO_DIRS */
      *ttystrname,	/* $TTY         */
      *pwd;		/* $PWD         */
 
@@ -129,6 +130,11 @@ struct timeval shtimer;
 /**/
 mod_export int termflags;
 
+/* Forward declaration */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp);
+
 /* Standard methods for get/set/unset pointers in parameters */
 
 /**/
@@ -210,6 +216,8 @@ static const struct gsu_scalar term_gsu =
 { termgetfn, termsetfn, stdunsetfn };
 static const struct gsu_scalar terminfo_gsu =
 { terminfogetfn, terminfosetfn, stdunsetfn };
+static const struct gsu_scalar terminfodirs_gsu =
+{ terminfodirsgetfn, terminfodirssetfn, stdunsetfn };
 static const struct gsu_scalar wordchars_gsu =
 { wordcharsgetfn, wordcharssetfn, stdunsetfn };
 static const struct gsu_scalar ifs_gsu =
@@ -240,6 +248,9 @@ static const struct gsu_integer argc_gsu =
 static const struct gsu_array pipestatus_gsu =
 { pipestatgetfn, pipestatsetfn, stdunsetfn };
 
+static const struct gsu_integer rprompt_indent_gsu =
+{ intvargetfn, zlevarsetfn, rprompt_indent_unsetfn };
+
 /* Nodes for special parameters for parameter hash table */
 
 #ifdef HAVE_UNION_INIT
@@ -285,8 +296,9 @@ IPDEF2("histchars", histchars_gsu, PM_DONTIMPORT),
 IPDEF2("HOME", home_gsu, PM_UNSET),
 IPDEF2("TERM", term_gsu, PM_UNSET),
 IPDEF2("TERMINFO", terminfo_gsu, PM_UNSET),
+IPDEF2("TERMINFO_DIRS", terminfodirs_gsu, PM_UNSET),
 IPDEF2("WORDCHARS", wordchars_gsu, 0),
-IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT),
+IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT | PM_RESTRICTED),
 IPDEF2("_", underscore_gsu, PM_DONTIMPORT),
 IPDEF2("KEYBOARD_HACK", keyboard_hack_gsu, PM_DONTIMPORT),
 IPDEF2("0", argzero_gsu, 0),
@@ -325,7 +337,7 @@ IPDEF4("ZSH_SUBSHELL", &zsh_subshell),
 #define IPDEF5U(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(F),10,0,0,NULL,NULL,NULL,0}
 IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu),
 IPDEF5("LINES", &zterm_lines, zlevar_gsu),
-IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, zlevar_gsu),
+IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, rprompt_indent_gsu),
 IPDEF5("SHLVL", &shlvl, varinteger_gsu),
 
 /* Don't import internal integer status variables. */
@@ -334,8 +346,9 @@ IPDEF6("OPTIND", &zoptind, varinteger_gsu),
 IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
 IPDEF6("TRY_BLOCK_INTERRUPT", &try_interrupt, varinteger_gsu),
 
-#define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,0,NULL,NULL,NULL,0}
-#define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,0,NULL,NULL,NULL,0}
+#define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7R(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_DONTIMPORT_SUID},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
 IPDEF7("OPTARG", &zoptarg),
 IPDEF7("NULLCMD", &nullcmd),
 IPDEF7U("POSTEDIT", &postedit),
@@ -347,10 +360,21 @@ IPDEF7("PS2", &prompt2),
 IPDEF7U("RPS2", &rprompt2),
 IPDEF7U("RPROMPT2", &rprompt2),
 IPDEF7("PS3", &prompt3),
-IPDEF7("PS4", &prompt4),
+IPDEF7R("PS4", &prompt4),
 IPDEF7("SPROMPT", &sprompt),
 
-#define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,0,0,NULL,C,NULL,0}
+#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0}
+#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
+IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+
+/*
+ * This empty row indicates the end of parameters available in
+ * all emulations.
+ */
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+
+#define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,0,NULL,C,NULL,0}
 IPDEF8("CDPATH", &cdpath, "cdpath", 0),
 IPDEF8("FIGNORE", &fignore, "fignore", 0),
 IPDEF8("FPATH", &fpath, "fpath", 0),
@@ -363,18 +387,7 @@ IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, "zsh_eval_context", PM_READONLY),
 /* MODULE_PATH is not imported for security reasons */
 IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
 
-#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,0,NULL,C,NULL,0}
-#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
-IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
-IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
-
-/*
- * This empty row indicates the end of parameters available in
- * all emulations.
- */
-{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,0,NULL,NULL,NULL,0},
-
-#define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,0,NULL,NULL,NULL,0}
+#define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
 
 /*
  * The following parameters are not available in sh/ksh compatibility *
@@ -413,6 +426,26 @@ IPDEF10("pipestatus", pipestatus_gsu),
 };
 
 /*
+ * Alternative versions of colon-separated path parameters for
+ * sh emulation.  These don't link to the array versions.
+ */
+static initparam special_params_sh[] = {
+IPDEF8("CDPATH", &cdpath, NULL, 0),
+IPDEF8("FIGNORE", &fignore, NULL, 0),
+IPDEF8("FPATH", &fpath, NULL, 0),
+IPDEF8("MAILPATH", &mailpath, NULL, 0),
+IPDEF8("WATCH", &watch, NULL, 0),
+IPDEF8("PATH", &path, NULL, PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, NULL, 0),
+IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, NULL, PM_READONLY),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, NULL, PM_DONTIMPORT|PM_RESTRICTED),
+
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
  * Special way of referring to the positional parameters.  Unlike $*
  * and $@, this is not readonly.  This parameter is not directly
  * visible in user space.
@@ -628,6 +661,36 @@ getvaluearr(Value v)
 	return NULL;
 }
 
+/* Return whether the variable is set         *
+ * checks that array slices are within range  *
+ * used for [[ -v ... ]] condition test       */
+
+/**/
+int
+issetvar(char *name)
+{
+    struct value vbuf;
+    Value v;
+    int slice;
+    char **arr;
+
+    if (!(v = getvalue(&vbuf, &name, 1)) || *name)
+	return 0; /* no value or more chars after the variable name */
+    if (v->isarr & ~SCANPM_ARRONLY)
+	return v->end > 1; /* for extracted elements, end gives us a count */
+
+    slice = v->start != 0 || v->end != -1;
+    if (PM_TYPE(v->pm->node.flags) != PM_ARRAY || !slice)
+	return !slice && !(v->pm->node.flags & PM_UNSET);
+
+    if (!v->end) /* empty array slice */
+	return 0;
+    /* get the array and check end is within range */
+    if (!(arr = getvaluearr(v)))
+	return 0;
+    return arrlen_ge(arr, v->end < 0 ? - v->end : v->end);
+}
+
 /*
  * Split environment string into (name, value) pair.
  * this is used to avoid in-place editing of environment table
@@ -662,19 +725,27 @@ split_env_string(char *env, char **name, char **value)
 	return 0;
 }
 
-int
-arrcachelen(Param pm)
+/**
+ * Check parameter flags to see if parameter shouldn't be imported
+ * from environment at start.
+ *
+ * return 1: don't import: 0: ok to import.
+ */
+static int dontimport(int flags)
 {
-    int len;
-
-    len = pm->length;
-    if (len == 0 && pm->u.arr) {
-	len = arrlen(pm->u.arr);
-	pm->length = len;
-    }
-    return len;
+    /* If explicitly marked as don't export */
+    if (flags & PM_DONTIMPORT)
+	return 1;
+    /* If value already exported */
+    if (flags & PM_EXPORTED)
+	return 1;
+    /* If security issue when importing and running with some privilege */
+    if ((flags & PM_DONTIMPORT_SUID) && isset(PRIVILEGED))
+	return 1;
+    /* OK to import */
+    return 0;
 }
-    
+
 /* Set up parameter hash table.  This will add predefined  *
  * parameter entries as well as setting up parameter table *
  * entries for environment variables we inherit.           */
@@ -704,9 +775,13 @@ createparamtable(void)
     /* Add the special parameters to the hash table */
     for (ip = special_params; ip->node.nam; ip++)
 	paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
-    if (!EMULATION(EMULATE_SH|EMULATE_KSH))
+    if (EMULATION(EMULATE_SH|EMULATE_KSH)) {
+	for (ip = special_params_sh; ip->node.nam; ip++)
+	    paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+    } else {
 	while ((++ip)->node.nam)
 	    paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+    }
 
     argvparam = (Param) &argvparam_pm;
 
@@ -766,8 +841,13 @@ createparamtable(void)
 	    envp2 = environ; *envp2; envp2++) {
 	if (split_env_string(*envp2, &iname, &ivalue)) {
 	    if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) {
+		/*
+		 * Parameters that aren't already in the parameter table
+		 * aren't special to the shell, so it's always OK to
+		 * import.  Otherwise, check parameter flags.
+		 */
 		if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
-		     !(pm->node.flags & PM_DONTIMPORT || pm->node.flags & PM_EXPORTED)) &&
+		     !dontimport(pm->node.flags)) &&
 		    (pm = assignsparam(iname, metafy(ivalue, -1, META_DUP),
 				       ASSPM_ENV_IMPORT))) {
 		    pm->node.flags |= PM_EXPORTED;
@@ -785,7 +865,7 @@ createparamtable(void)
     }
     popheap();
 #ifndef USE_SET_UNSET_ENV
-    *envp = '\0';
+    *envp = NULL;
 #endif
     opts[ALLEXPORT] = oae;
 
@@ -898,7 +978,10 @@ createparam(char *name, int flags)
 		zerr("%s: restricted", name);
 		return NULL;
 	    }
-	    if (!(oldpm->node.flags & PM_UNSET) || (oldpm->node.flags & PM_SPECIAL)) {
+	    if (!(oldpm->node.flags & PM_UNSET) ||
+		(oldpm->node.flags & PM_SPECIAL) ||
+		/* POSIXBUILTINS horror: we need to retain 'export' flags */
+		(isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) {
 		oldpm->node.flags &= ~PM_UNSET;
 		if ((oldpm->node.flags & PM_SPECIAL) && oldpm->ename) {
 		    Param altpm =
@@ -1127,7 +1210,7 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
        int *prevcharlen, int *nextcharlen)
 {
     int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash;
-    int keymatch = 0, needtok = 0, arglen, len;
+    int keymatch = 0, needtok = 0, arglen, len, inpar = 0;
     char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c;
     zlong num = 1, beg = 0, r = 0, quote_arg = 0;
     Patprog pprog = NULL;
@@ -1266,8 +1349,9 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
     }
 
     for (t = s, i = 0;
-	 (c = *t) && ((c != Outbrack &&
-		       (ishash || c != ',')) || i); t++) {
+	 (c = *t) &&
+	     ((c != Outbrack && (ishash || c != ',')) || i || inpar);
+	 t++) {
 	/* Untokenize inull() except before brackets and double-quotes */
 	if (inull(c)) {
 	    c = t[1];
@@ -1288,6 +1372,10 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
 	    i++;
 	else if (c == ']' || c == Outbrack)
 	    i--;
+	if (c == '(' || c == Inpar)
+	    inpar++;
+	else if (c == ')' || c == Outpar)
+	    inpar--;
 	if (ispecial(c))
 	    needtok = 1;
     }
@@ -1723,6 +1811,18 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
     return r;
 }
 
+/*
+ * Parse a subscript.
+ *
+ * pptr: In/Out parameter.  On entry, *ptr points to a "[foo]" string.  On exit
+ * it will point one past the closing bracket.
+ *
+ * v: In/Out parameter.  Its .start and .end members (at least) will be updated
+ * with the parsed indices.
+ *
+ * flags: can be either SCANPM_DQUOTED or zero.  Other bits are not used.
+ */
+
 /**/
 int
 getindex(char **pptr, Value v, int flags)
@@ -1929,7 +2029,9 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 	*s++ = '$';
     else if (c == Star)
 	*s++ = '*';
-    else if (c == '#' || c == '-' || c == '?' || c == '$' ||
+    else if (IS_DASH(c))
+	*s++ = '-';
+    else if (c == '#' || c == '?' || c == '$' ||
 	     c == '!' || c == '@' || c == '*')
 	s++;
     else
@@ -2026,6 +2128,7 @@ getstrvalue(Value v)
 {
     char *s, **ss;
     char buf[BDIGBUFSIZE];
+    int len;
 
     if (!v)
 	return hcalloc(1);
@@ -2050,21 +2153,10 @@ getstrvalue(Value v)
 	if (v->isarr)
 	    s = sepjoin(ss, NULL, 1);
 	else {
-	    if (v->pm->node.flags & PM_CACHELEN) {
-		int len = arrcachelen(v->pm);
-		if (v->pm->node.flags & PM_CHECKLEN)
-		    assert(v->pm->length == arrlen(ss));
-		if (v->start < 0)
-		    v->start += len;
-		s = (v->start >= len || v->start < 0) ?
-		    (char *) hcalloc(1) : ss[v->start];
-	    } else {
-		int len = arrlen(ss);
-		if (v->start < 0)
-		    v->start += len;
-		s = (v->start >= len || v->start < 0) ?
-		    (char *) hcalloc(1) : ss[v->start];
-	    }
+	    if (v->start < 0)
+		v->start += arrlen(ss);
+	    s = (arrlen_le(ss, v->start) || v->start < 0) ?
+		(char *) hcalloc(1) : ss[v->start];
 	}
 	return s;
     case PM_INTEGER:
@@ -2213,23 +2305,27 @@ getstrvalue(Value v)
     if (v->start == 0 && v->end == -1)
 	return s;
 
+    len = strlen(s);
     if (v->start < 0) {
-	v->start += strlen(s);
+	v->start += len;
 	if (v->start < 0)
 	    v->start = 0;
     }
     if (v->end < 0) {
-	v->end += strlen(s);
+	v->end += len;
 	if (v->end >= 0) {
 	    char *eptr = s + v->end;
 	    if (*eptr)
 		v->end += MB_METACHARLEN(eptr);
 	}
     }
-    s = (v->start > (int)strlen(s)) ? dupstring("") : dupstring(s + v->start);
+
+    s = (v->start > len) ? dupstring("") :
+	dupstring_wlen(s + v->start, len - v->start);
+
     if (v->end <= v->start)
 	s[0] = '\0';
-    else if (v->end - v->start <= (int)strlen(s))
+    else if (v->end - v->start <= len - v->start)
 	s[v->end - v->start] = '\0';
 
     return s;
@@ -2258,38 +2354,35 @@ getarrvalue(Value v)
     s = getvaluearr(v);
     if (v->start == 0 && v->end == -1)
 	return s;
-    if (v->pm->node.flags & PM_CACHELEN) {
-	int len = arrcachelen(v->pm);
-	if (v->pm->node.flags & PM_CHECKLEN)
-	    assert(v->pm->length == arrlen(s));
-	if (v->start < 0)
-	    v->start += v->pm->length;
-	if (v->end < 0)
-	    v->end += v->pm->length + 1;
-	if (v->start > v->pm->length || v->start < 0)
-	    s = arrdup(nular);
-	else
-	    s = arrdup(s + v->start);
-	if (v->end <= v->start)
-	    s[0] = NULL;
-	//XXX[badarrays] s just changed above but here we use the same
-	//               cached length possible cause of problems
-	else if (v->end - v->start <= v->pm->length)
-	    s[v->end - v->start] = NULL;
-    } else {
-	if (v->start < 0)
-	   v->start += arrlen(s);
-	if (v->end < 0)
-	   v->end += arrlen(s) + 1;
-	if (v->start > arrlen(s) || v->start < 0)
-	   s = arrdup(nular);
-	else
-	   s = arrdup(s + v->start);
-	if (v->end <= v->start)
-	   s[0] = NULL;
-	else if (v->end - v->start <= arrlen(s))
-	   s[v->end - v->start] = NULL;
+    if (v->start < 0)
+	v->start += arrlen(s);
+    if (v->end < 0)
+	v->end += arrlen(s) + 1;
+
+    /* Null if 1) array too short, 2) index still negative */
+    if (v->end <= v->start) {
+	s = arrdup_max(nular, 0);
+    }
+    else if (v->start < 0) {
+	s = arrdup_max(nular, 1);
+    }
+    else if (arrlen_le(s, v->start)) {
+	/* Handle $ary[i,i] consistently for any $i > $#ary
+	 * and $ary[i,j] consistently for any $j > $i > $#ary
+	 */
+	s = arrdup_max(nular, v->end - (v->start + 1));
+    }
+    else {
+        /* Copy to a point before the end of the source array:
+         * arrdup_max will copy at most v->end - v->start elements,
+         * starting from v->start element. Original code said:
+	 *  s[v->end - v->start] = NULL
+         * which means that there are exactly the same number of
+         * elements as the value of the above *0-based* index.
+         */
+	s = arrdup_max(s + v->start, v->end - v->start);
     }
+
     return s;
 }
 
@@ -2418,10 +2511,11 @@ assignstrvalue(Value v, char *val, int flags)
 		v->pm->width = strlen(val);
 	} else {
 	    char *z, *x;
-	    int zlen;
+            int zlen, vlen, newsize;
+
+            z = v->pm->gsu.s->getfn(v->pm);
+            zlen = strlen(z);
 
-	    z = dupstring(v->pm->gsu.s->getfn(v->pm));
-	    zlen = strlen(z);
 	    if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS))
 		v->start--, v->end--;
 	    if (v->start < 0) {
@@ -2451,12 +2545,34 @@ assignstrvalue(Value v, char *val, int flags)
 	    }
 	    else if (v->end > zlen)
 		v->end = zlen;
-	    x = (char *) zalloc(v->start + strlen(val) + zlen - v->end + 1);
-	    strncpy(x, z, v->start);
-	    strcpy(x + v->start, val);
-	    strcat(x + v->start, z + v->end);
-	    v->pm->gsu.s->setfn(v->pm, x);
-	    zsfree(val);
+
+            vlen = strlen(val);
+            /* Characters preceding start index +
+               characters of what is assigned +
+               characters following end index */
+            newsize = v->start + vlen + (zlen - v->end);
+
+            /* Does new size differ? */
+            if (newsize != zlen || v->pm->gsu.s->setfn != strsetfn) {
+                x = (char *) zalloc(newsize + 1);
+                strncpy(x, z, v->start);
+                strcpy(x + v->start, val);
+                strcat(x + v->start, z + v->end);
+                v->pm->gsu.s->setfn(v->pm, x);
+            } else {
+		Param pm = v->pm;
+                /* Size doesn't change, can limit actions to only
+                 * overwriting bytes in already allocated string */
+                strncpy(z + v->start, val, vlen);
+		/* Implement remainder of strsetfn */
+		if (!(pm->node.flags & PM_HASHELEM) &&
+		    ((pm->node.flags & PM_NAMEDDIR) ||
+		     isset(AUTONAMEDIRS))) {
+		    pm->node.flags |= PM_NAMEDDIR;
+		    adduserdir(pm->node.nam, z, 0, 0);
+		}
+            }
+            zsfree(val);
 	}
 	break;
     case PM_INTEGER:
@@ -2641,24 +2757,85 @@ setarrvalue(Value v, char **val)
 	    v->end = v->start;
 
 	post_assignment_length = v->start + arrlen(val);
-	if (v->end <= pre_assignment_length)
-	    post_assignment_length += pre_assignment_length - v->end + 1;
-
-	p = new = (char **) zshcalloc(sizeof(char *)
-		                      * (post_assignment_length + 1));
-
-	for (i = 0; i < v->start; i++)
-	    *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup("");
-	for (r = val; *r;)
-	    *p++ = ztrdup(*r++);
-	if (v->end < pre_assignment_length)
-	    for (q = old + v->end; *q;)
-		*p++ = ztrdup(*q++);
-	*p = NULL;
-
-	v->pm->gsu.a->setfn(v->pm, new);
-	v->pm->length = post_assignment_length;
-	freearray(val);
+	if (v->end < pre_assignment_length) {
+	    /* 
+	     * Allocate room for array elements between the end of the slice `v'
+	     * and the original array's end.
+	     */
+	    post_assignment_length += pre_assignment_length - v->end;
+	}
+
+	if (pre_assignment_length == post_assignment_length
+	    && v->pm->gsu.a->setfn == arrsetfn
+	    /* ... and isn't something that arrsetfn() treats specially */
+	    && 0 == (v->pm->node.flags & (PM_SPECIAL|PM_UNIQUE))
+	    && NULL == v->pm->ename)
+	{
+	    /* v->start is 0-based */
+	    p = old + v->start;
+	    for (r = val; *r;) {
+		/* Free previous string */
+		zsfree(*p);
+		/* Give away ownership of the string */
+		*p++ = *r++;
+	    }
+	} else {
+            /* arr+=( ... )
+             * arr[${#arr}+x,...]=( ... ) */
+            if (post_assignment_length > pre_assignment_length &&
+                    pre_assignment_length <= v->start &&
+                    pre_assignment_length > 0 &&
+                    v->pm->gsu.a->setfn == arrsetfn)
+            {
+                p = new = (char **) zrealloc(old, sizeof(char *)
+                                           * (post_assignment_length + 1));
+
+                p += pre_assignment_length; /* after old elements */
+
+                /* Consider 1 < 0, case for a=( 1 ); a[1,..] =
+                 *          1 < 1, case for a=( 1 ); a[2,..] = */
+                if (pre_assignment_length < v->start) {
+                    for (i = pre_assignment_length; i < v->start; i++) {
+                        *p++ = ztrdup("");
+                    }
+                }
+
+                for (r = val; *r;) {
+                    /* Give away ownership of the string */
+                    *p++ = *r++;
+                }
+
+                /* v->end doesn't matter:
+                 * a=( 1 2 ); a[4,100]=( a b ); echo "${(q@)a}"
+                 * 1 2 '' a b */
+                *p = NULL;
+
+                v->pm->u.arr = NULL;
+                v->pm->gsu.a->setfn(v->pm, new);
+            } else {
+                p = new = (char **) zalloc(sizeof(char *)
+                                           * (post_assignment_length + 1));
+                for (i = 0; i < v->start; i++)
+                    *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup("");
+                for (r = val; *r;) {
+                    /* Give away ownership of the string */
+                    *p++ = *r++;
+                }
+                if (v->end < pre_assignment_length)
+                    for (q = old + v->end; *q;)
+                        *p++ = ztrdup(*q++);
+                *p = NULL;
+
+                v->pm->gsu.a->setfn(v->pm, new);
+            }
+
+	    DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu",
+		   post_assignment_length, (unsigned long)(p - new));
+	}
+
+        /* Ownership of all strings has been
+         * given away, can plainly free */
+	free(val);
     }
 }
 
@@ -2773,20 +2950,51 @@ gethkparam(char *s)
     return NULL;
 }
 
+/*
+ * Function behind WARNCREATEGLOBAL and WARNNESTEDVAR option.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable is created.
+ * Apply heuristics to see if this variable was just created
+ * globally but in a local context.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable already exists and is set.
+ * Apply heuristics to see if this variable is setting
+ * a variable that was created in a less nested function
+ * or globally.
+ */
+
 /**/
 static void
-check_warn_create(Param pm, const char *pmtype)
+check_warn_pm(Param pm, const char *pmtype, int created,
+	      int may_warn_about_nested_vars)
 {
     Funcstack i;
 
-    if (pm->level != 0 || (pm->node.flags & PM_SPECIAL))
+    if (!may_warn_about_nested_vars && !created)
+	return;
+
+    if (created && isset(WARNCREATEGLOBAL)) {
+	if (locallevel <= forklevel || pm->level != 0)
+	    return;
+    } else if (!created && isset(WARNNESTEDVAR)) {
+	if (pm->level >= locallevel)
+	    return;
+    } else
+	return;
+
+    if (pm->node.flags & PM_SPECIAL)
 	return;
 
     for (i = funcstack; i; i = i->prev) {
 	if (i->tp == FS_FUNC) {
+	    char *msg;
 	    DPUTS(!i->name, "funcstack entry with no name");
-	    zwarn("%s parameter %s created globally in function %s",
-		  pmtype, pm->node.nam, i->name);
+	    msg = created ?
+		"%s parameter %s created globally in function %s" :
+		"%s parameter %s set in enclosing scope in function %s";
+	    zwarn(msg, pmtype, pm->node.nam, i->name);
 	    break;
 	}
     }
@@ -2802,7 +3010,7 @@ assignsparam(char *s, char *val, int flags)
     char *ss, *copy, *var;
     size_t lvar;
     mnumber lhs, rhs;
-    int sstart;
+    int sstart, created = 0;
 
     if (!isident(s)) {
 	zerr("not an identifier: %s", s);
@@ -2813,32 +3021,38 @@ assignsparam(char *s, char *val, int flags)
     queue_signals();
     if ((ss = strchr(s, '['))) {
 	*ss = '\0';
-	if (!(v = getvalue(&vbuf, &s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1))) {
 	    createparam(t, PM_ARRAY);
-	else {
+	    created = 1;
+	} else {
 	    if (v->pm->node.flags & PM_READONLY) {
 		zerr("read-only variable: %s", v->pm->node.nam);
 		*ss = '[';
 		zsfree(val);
+		unqueue_signals();
 		return NULL;
 	    }
-	    flags &= ~ASSPM_WARN_CREATE;
+	    /*
+	     * Parameter defined here is a temporary bogus one.
+	     * Don't warn about anything.
+	     */
+	    flags &= ~ASSPM_WARN;
 	}
 	*ss = '[';
 	v = NULL;
     } else {
-	if (!(v = getvalue(&vbuf, &s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1))) {
 	    createparam(t, PM_SCALAR);
-	else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) ||
+	    created = 1;
+	} else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) ||
 	    	 (v->pm->node.flags & PM_HASHED)) &&
 		 !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) && 
 		 unset(KSHARRAYS)) {
 	    unsetparam(t);
 	    createparam(t, PM_SCALAR);
+	    /* not regarded as a new creation */
 	    v = NULL;
 	}
-	else
-	    flags &= ~ASSPM_WARN_CREATE;
     }
     if (!v && !(v = getvalue(&vbuf, &t, 1))) {
 	unqueue_signals();
@@ -2846,8 +3060,8 @@ assignsparam(char *s, char *val, int flags)
 	/* errflag |= ERRFLAG_ERROR; */
 	return NULL;
     }
-    if (flags & ASSPM_WARN_CREATE)
-	check_warn_create(v->pm, "scalar");
+    if (flags & ASSPM_WARN)
+	check_warn_pm(v->pm, "scalar", created, 1);
     if (flags & ASSPM_AUGMENT) {
 	if (v->start == 0 && v->end == -1) {
 	    switch (PM_TYPE(v->pm->node.flags)) {
@@ -2931,9 +3145,7 @@ assignsparam(char *s, char *val, int flags)
 mod_export Param
 setsparam(char *s, char *val)
 {
-    return assignsparam(
-	s, val, isset(WARNCREATEGLOBAL) && locallevel > forklevel ?
-	ASSPM_WARN_CREATE : 0);
+    return assignsparam(s, val, ASSPM_WARN);
 }
 
 /**/
@@ -2944,6 +3156,8 @@ assignaparam(char *s, char **val, int flags)
     Value v;
     char *t = s;
     char *ss;
+    int created = 0;
+    int may_warn_about_nested_vars = 1;
 
     if (!isident(s)) {
 	zerr("not an identifier: %s", s);
@@ -2954,10 +3168,12 @@ assignaparam(char *s, char **val, int flags)
     queue_signals();
     if ((ss = strchr(s, '['))) {
 	*ss = '\0';
-	if (!(v = getvalue(&vbuf, &s, 1)))
+	if (!(v = getvalue(&vbuf, &s, 1))) {
 	    createparam(t, PM_ARRAY);
-	else
-	    flags &= ~ASSPM_WARN_CREATE;
+	    created = 1;
+	} else {
+	    may_warn_about_nested_vars = 0;
+	}
 	*ss = '[';
 	if (v && PM_TYPE(v->pm->node.flags) == PM_HASHED) {
 	    unqueue_signals();
@@ -2969,9 +3185,10 @@ assignaparam(char *s, char **val, int flags)
 	}
 	v = NULL;
     } else {
-	if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING)))
+	if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
 	    createparam(t, PM_ARRAY);
-	else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) &&
+	    created = 1;
+	} else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) &&
 		 !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) {
 	    int uniq = v->pm->node.flags & PM_UNIQUE;
 	    if (flags & ASSPM_AUGMENT) {
@@ -2991,8 +3208,6 @@ assignaparam(char *s, char **val, int flags)
 	    createparam(t, PM_ARRAY | uniq);
 	    v = NULL;
 	}
-	else
-	    flags &= ~ASSPM_WARN_CREATE;
     }
     if (!v)
 	if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
@@ -3002,8 +3217,8 @@ assignaparam(char *s, char **val, int flags)
 	    return NULL;
 	}
 
-    if (flags & ASSPM_WARN_CREATE)
-	check_warn_create(v->pm, "array");
+    if (flags & ASSPM_WARN)
+	check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
     if (flags & ASSPM_AUGMENT) {
     	if (v->start == 0 && v->end == -1) {
 	    if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
@@ -3049,9 +3264,7 @@ assignaparam(char *s, char **val, int flags)
 mod_export Param
 setaparam(char *s, char **aval)
 {
-    return assignaparam(
-	s, aval, isset(WARNCREATEGLOBAL) && locallevel > forklevel ?
-	ASSPM_WARN_CREATE : 0);
+    return assignaparam(s, aval, ASSPM_WARN);
 }
 
 /**/
@@ -3079,9 +3292,8 @@ sethparam(char *s, char **val)
 	return NULL;
     queue_signals();
     if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
-	DPUTS(!v, "BUG: assigning to undeclared associative array");
 	createparam(t, PM_HASHED);
-	checkcreate = isset(WARNCREATEGLOBAL) && locallevel > forklevel;
+	checkcreate = 1;
     } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED) &&
 	     !(v->pm->node.flags & PM_SPECIAL)) {
 	unsetparam(t);
@@ -3095,8 +3307,7 @@ sethparam(char *s, char **val)
 	    /* errflag |= ERRFLAG_ERROR; */
 	    return NULL;
 	}
-    if (checkcreate)
-	check_warn_create(v->pm, "associative array");
+    check_warn_pm(v->pm, "associative array", checkcreate, 1);
     setarrvalue(v, val);
     unqueue_signals();
     return v->pm;
@@ -3105,11 +3316,12 @@ sethparam(char *s, char **val)
 
 /*
  * Set a generic shell number, floating point or integer.
+ * Option to warn on setting.
  */
 
 /**/
-Param
-setnparam(char *s, mnumber val)
+mod_export Param
+assignnparam(char *s, mnumber val, int flags)
 {
     struct value vbuf;
     Value v;
@@ -3158,16 +3370,44 @@ setnparam(char *s, mnumber val)
 	if (!(v = getvalue(&vbuf, &t, 1))) {
 	    DPUTS(!v, "BUG: value not found for new parameter");
 	    /* errflag |= ERRFLAG_ERROR; */
+	    unqueue_signals();
 	    return NULL;
 	}
-	if (!was_unset && isset(WARNCREATEGLOBAL) && locallevel > forklevel)
-	    check_warn_create(v->pm, "numeric");
+	if (flags & ASSPM_WARN)
+	    check_warn_pm(v->pm, "numeric", !was_unset, 1);
+    } else {
+	if (flags & ASSPM_WARN)
+	    check_warn_pm(v->pm, "numeric", 0, 1);
     }
     setnumvalue(v, val);
     unqueue_signals();
     return v->pm;
 }
 
+/*
+ * Set a generic shell number, floating point or integer.
+ * Warn on setting based on option.
+ */
+
+/**/
+mod_export Param
+setnparam(char *s, mnumber val)
+{
+    return assignnparam(s, val, ASSPM_WARN);
+}
+
+/* Simplified interface to assignnparam */
+
+/**/
+mod_export Param
+assigniparam(char *s, zlong val, int flags)
+{
+    mnumber mnval;
+    mnval.type = MN_INTEGER;
+    mnval.u.l = val;
+    return assignnparam(s, mnval, flags);
+}
+
 /* Simplified interface to setnparam */
 
 /**/
@@ -3177,7 +3417,7 @@ setiparam(char *s, zlong val)
     mnumber mnval;
     mnval.type = MN_INTEGER;
     mnval.u.l = val;
-    return setnparam(s, mnval);
+    return assignnparam(s, mnval, ASSPM_WARN);
 }
 
 /*
@@ -3196,10 +3436,7 @@ setiparam_no_convert(char *s, zlong val)
      */
     char buf[BDIGBUFSIZE];
     convbase(buf, val, 10);
-    return assignsparam(
-	s, ztrdup(buf),
-	isset(WARNCREATEGLOBAL) && locallevel > forklevel ?
-	ASSPM_WARN_CREATE : 0);
+    return assignsparam(s, ztrdup(buf), ASSPM_WARN);
 }
 
 /* Unset a parameter */
@@ -3219,7 +3456,11 @@ unsetparam(char *s)
     unqueue_signals();
 }
 
-/* Unset a parameter */
+/* Unset a parameter
+ *
+ * altflag: if true, don't remove pm->ename from the environment
+ * exp: See stdunsetfn()
+ */
 
 /**/
 mod_export int
@@ -3416,6 +3657,8 @@ strsetfn(Param pm, char *x)
 	pm->node.flags |= PM_NAMEDDIR;
 	adduserdir(pm->node.nam, x, 0, 0);
     }
+    /* If you update this function, you may need to update the
+     * `Implement remainder of strsetfn' block in assignstrvalue(). */
 }
 
 /* Function to get value of an array parameter */
@@ -3443,6 +3686,8 @@ arrsetfn(Param pm, char **x)
     /* Arrays tied to colon-arrays may need to fix the environment */
     if (pm->ename && x)
 	arrfixenv(pm->ename, x);
+    /* If you extend this function, update the list of conditions in
+     * setarrvalue(). */
 }
 
 /* Function to get value of an association parameter */
@@ -3579,6 +3824,16 @@ zlevarsetfn(Param pm, zlong x)
 	adjustwinsize(2 + (p == &zterm_columns));
 }
 
+
+/* Implements gsu_integer.unsetfn for ZLE_RPROMPT_INDENT; see stdunsetfn() */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp)
+{
+    stdunsetfn(pm, exp);
+    rprompt_indent = 1; /* Keep this in sync with init_term() */
+}
+
 /* Function to set value of generic special scalar    *
  * parameter.  data is pointer to a character pointer *
  * representing the scalar (string).                  */
@@ -3678,8 +3933,7 @@ colonarrsetfn(Param pm, char *x)
 	*dptr = colonsplit(x, pm->node.flags & PM_UNIQUE);
     else
 	*dptr = mkarray(NULL);
-    if (pm->ename)
-	arrfixenv(pm->node.nam, *dptr);
+    arrfixenv(pm->node.nam, *dptr);
     zsfree(x);
 }
 
@@ -4035,7 +4289,7 @@ uidsetfn(UNUSED(Param pm), zlong x)
 {
 #ifdef HAVE_SETUID
     if (setuid((uid_t)x))
-	zwarn("failed to change user ID: %e", errno);
+	zerr("failed to change user ID: %e", errno);
 #endif
 }
 
@@ -4056,7 +4310,7 @@ euidsetfn(UNUSED(Param pm), zlong x)
 {
 #ifdef HAVE_SETEUID
     if (seteuid((uid_t)x))
-	zwarn("failed to change effective user ID: %e", errno);
+	zerr("failed to change effective user ID: %e", errno);
 #endif
 }
 
@@ -4077,7 +4331,7 @@ gidsetfn(UNUSED(Param pm), zlong x)
 {
 #ifdef HAVE_SETUID
     if (setgid((gid_t)x))
-	zwarn("failed to change group ID: %e", errno);
+	zerr("failed to change group ID: %e", errno);
 #endif
 }
 
@@ -4098,7 +4352,7 @@ egidsetfn(UNUSED(Param pm), zlong x)
 {
 #ifdef HAVE_SETEUID
     if (setegid((gid_t)x))
-	zwarn("failed to change effective group ID: %e", errno);
+	zerr("failed to change effective group ID: %e", errno);
 #endif
 }
 
@@ -4434,7 +4688,7 @@ void
 homesetfn(UNUSED(Param pm), char *x)
 {
     zsfree(home);
-    if (x && isset(CHASELINKS) && (home = xsymlink(x)))
+    if (x && isset(CHASELINKS) && (home = xsymlink(x, 0)))
 	zsfree(x);
     else
 	home = x ? x : ztrdup("");
@@ -4533,6 +4787,33 @@ terminfosetfn(Param pm, char *x)
     term_reinit_from_pm();
 }
 
+/* Function to get value of special parameter `TERMINFO_DIRS' */
+
+/**/
+char *
+terminfodirsgetfn(UNUSED(Param pm))
+{
+    return zsh_terminfodirs ? zsh_terminfodirs : dupstring("");
+}
+
+/* Function to set value of special parameter `TERMINFO_DIRS' */
+
+/**/
+void
+terminfodirssetfn(Param pm, char *x)
+{
+    zsfree(zsh_terminfodirs);
+    zsh_terminfodirs = x;
+
+    /*
+     * terminfo relies on the value being exported before
+     * we reinitialise the terminal.  This is a bit inefficient.
+     */
+    if ((pm->node.flags & PM_EXPORTED) && x)
+	addenv(pm, x);
+
+    term_reinit_from_pm();
+}
 /* Function to get value for special parameter `pipestatus' */
 
 /**/
@@ -4768,6 +5049,7 @@ addenv(Param pm, char *value)
      if (pm->env)
          zsfree(pm->env);
      pm->env = newenv;
+     pm->node.flags |= PM_EXPORTED;
 #else
     /*
      * Under Cygwin we must use putenv() to maintain consistency.
@@ -5263,10 +5545,6 @@ printparamvalue(Param p, int printflags)
 {
     char *t, **u;
 
-    if (p->node.flags & PM_AUTOLOAD) {
-	putchar('\n');
-	return;
-    }
     if (printflags & PRINT_KV_PAIR)
 	putchar(' ');
     else
@@ -5350,9 +5628,13 @@ printparamnode(HashNode hn, int printflags)
 	     */
 	    printflags |= PRINT_NAMEONLY;
 	}
+	else if (p->node.flags & PM_EXPORTED)
+	    printflags |= PRINT_NAMEONLY;
 	else
 	    return;
     }
+    if (p->node.flags & PM_AUTOLOAD)
+	printflags |= PRINT_NAMEONLY;
 
     if (printflags & PRINT_TYPESET) {
 	if ((p->node.flags & (PM_READONLY|PM_SPECIAL)) ==
@@ -5364,7 +5646,15 @@ printparamnode(HashNode hn, int printflags)
 	     */
 	    return;
 	}
-	printf("typeset ");
+	if (locallevel && p->level >= locallevel) {
+	    printf("typeset ");	    /* printf("local "); */
+	} else if ((p->node.flags & PM_EXPORTED) &&
+		   !(p->node.flags & (PM_ARRAY|PM_HASHED))) {
+	    printf("export ");
+	} else if (locallevel) {
+	    printf("typeset -g ");
+	} else
+	    printf("typeset ");
     }
 
     /* Print the attributes of the parameter */
@@ -5377,7 +5667,9 @@ printparamnode(HashNode hn, int printflags)
 	    if (pmptr->flags & PMTF_TEST_LEVEL) {
 		if (p->level)
 		    doprint = 1;
-	    } else if (p->node.flags & pmptr->binflag)
+	    } else if ((pmptr->binflag != PM_EXPORTED || p->level ||
+			(p->node.flags & (PM_LOCAL|PM_ARRAY|PM_HASHED))) &&
+		       (p->node.flags & pmptr->binflag))
 		doprint = 1;
 
 	    if (doprint) {
@@ -5389,9 +5681,8 @@ printparamnode(HashNode hn, int printflags)
 			}
 			putchar(pmptr->typeflag);
 		    }
-		} else {
+		} else
 		    printf("%s ", pmptr->string);
-		}
 		if ((pmptr->flags & PMTF_USE_BASE) && p->base) {
 		    printf("%d ", p->base);
 		    doneminus = 0;
diff --git a/Src/parse.c b/Src/parse.c
index 94ac04922..8769baae4 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -309,7 +309,6 @@ parse_context_restore(const struct parse_stack *ps, int toplevel)
     incond = ps->incond;
     inredir = ps->inredir;
     incasepat = ps->incasepat;
-    incasepat = ps->incasepat;
     isnewlin = ps->isnewlin;
     infor = ps->infor;
     inrepeat_ = ps->inrepeat_;
@@ -595,7 +594,7 @@ par_event(int endtok)
     if (tok == ENDINPUT)
 	return 0;
     if (tok == endtok)
-	return 0;
+	return 1;
 
     p = ecadd(0);
 
@@ -1739,6 +1738,7 @@ par_simple(int *cmplx, int nr)
 {
     int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
     int c = *cmplx, nrediradd, assignments = 0, ppost = 0, is_typeset = 0;
+    char *hasalias = input_hasalias();
     wordcode postassigns = 0;
 
     r = ecused;
@@ -1810,6 +1810,8 @@ par_simple(int *cmplx, int nr)
 	} else
 	    break;
 	zshlex();
+	if (!hasalias)
+	    hasalias = input_hasalias();
     }
     if (tok == AMPER || tok == AMPERBANG)
 	YYERROR(oecused);
@@ -1834,12 +1836,14 @@ par_simple(int *cmplx, int nr)
 
 		if (*ptr == Outbrace && ptr > tokstr + 1)
 		{
-		    if (itype_end(tokstr+1, IIDENT, 0) >= ptr - 1)
+		    if (itype_end(tokstr+1, IIDENT, 0) >= ptr)
 		    {
 			char *toksave = tokstr;
 			char *idstring = dupstrpfx(tokstr+1, eptr-tokstr-1);
 			redir_var = 1;
 			zshlex();
+			if (!hasalias)
+			    hasalias = input_hasalias();
 
 			if (IS_REDIROP(tok) && tokfd == -1)
 			{
@@ -1875,6 +1879,8 @@ par_simple(int *cmplx, int nr)
 		    argc++;
 		}
 		zshlex();
+		if (!hasalias)
+		    hasalias = input_hasalias();
 	    }
 	} else if (IS_REDIROP(tok)) {
 	    *cmplx = c = 1;
@@ -1903,6 +1909,8 @@ par_simple(int *cmplx, int nr)
 	    ecstr(name);
 	    ecstr(str);
 	    zshlex();
+	    if (!hasalias)
+		hasalias = input_hasalias();
 	} else if (tok == ENVARRAY) {
 	    int n, parr;
 
@@ -1937,6 +1945,11 @@ par_simple(int *cmplx, int nr)
 	    /* Error if preceding assignments */
 	    if (assignments || postassigns)
 		YYERROR(oecused);
+	    if (hasalias && !isset(ALIASFUNCDEF) && argc &&
+		hasalias != input_hasalias()) {
+		zwarn("defining function based on alias `%s'", hasalias);
+		YYERROR(oecused);
+	    }
 
 	    *cmplx = c;
 	    lineno = 0;
@@ -2017,10 +2030,21 @@ par_simple(int *cmplx, int nr)
 		/* Unnamed function */
 		int parg = ecadd(0);
 		ecadd(0);
-		while (tok == STRING) {
-		    ecstr(tokstr);
-		    argc++;
-		    zshlex();
+		while (tok == STRING || IS_REDIROP(tok)) {
+		    if (tok == STRING)
+		    {
+			ecstr(tokstr);
+			argc++;
+			zshlex();
+		    } else {
+			*cmplx = c = 1;
+			nrediradd = par_redir(&r, NULL);
+			p += nrediradd;
+			if (ppost)
+			    ppost += nrediradd;
+			sr += nrediradd;
+			parg += nrediradd;
+		    }
 		}
 		if (argc > 0)
 		    *cmplx = 1;
@@ -2130,7 +2154,7 @@ par_redir(int *rp, char *idstring)
 	 * the definition of WC_REDIR_WORDS. */
 	ecispace(r, ncodes);
 	*rp = r + ncodes;
-	ecbuf[r] = WCB_REDIR(type);
+	ecbuf[r] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK);
 	ecbuf[r + 1] = fd1;
 
 	/*
@@ -2304,6 +2328,19 @@ par_cond_1(void)
 }
 
 /*
+ * Return 1 if condition matches.  This also works for non-elided options.
+ *
+ * input is test string, may begin - or Dash.
+ * cond is condition following the -.
+ */
+static int check_cond(const char *input, const char *cond)
+{
+    if (!IS_DASH(input[0]))
+	return 0;
+    return !strcmp(input + 1, cond);
+}
+
+/*
  * cond_2	: BANG cond_2
 				| INPAR { SEPER } cond_2 { SEPER } OUTPAR
 				| STRING STRING STRING
@@ -2329,7 +2366,7 @@ par_cond_2(void)
 	    s1 = tokstr;
 	    condlex();
 	    /* ksh behavior: [ -t ] means [ -t 1 ]; bash disagrees */
-	    if (unset(POSIXBUILTINS) && !strcmp(s1, "-t"))
+	    if (unset(POSIXBUILTINS) && check_cond(s1, "t"))
 		return par_cond_double(s1, dupstring("1"));
 	    return par_cond_double(dupstring("-n"), s1);
 	}
@@ -2339,7 +2376,7 @@ par_cond_2(void)
 	    if (!strcmp(*testargs, "=")  ||
 		!strcmp(*testargs, "==") ||
 		!strcmp(*testargs, "!=") ||
-		(**testargs == '-' && get_cond_num(*testargs + 1) >= 0)) {
+		(IS_DASH(**testargs) && get_cond_num(*testargs + 1) >= 0)) {
 		s1 = tokstr;
 		condlex();
 		s2 = tokstr;
@@ -2361,8 +2398,8 @@ par_cond_2(void)
 	 * In "test" compatibility mode, "! -a ..." and "! -o ..."
 	 * are treated as "[string] [and] ..." and "[string] [or] ...".
 	 */
-	if (!(n_testargs > 1 &&
-	      (!strcmp(*testargs, "-a") || !strcmp(*testargs, "-o"))))
+	if (!(n_testargs > 1 && (check_cond(*testargs, "a") ||
+				 check_cond(*testargs, "o"))))
 	{
 	    condlex();
 	    ecadd(WCB_COND(COND_NOT, 0));
@@ -2384,9 +2421,9 @@ par_cond_2(void)
 	return r;
     }
     s1 = tokstr;
-    dble = (s1 && *s1 == '-'
+    dble = (s1 && IS_DASH(*s1)
 	    && (!n_testargs
-		|| strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1)
+		|| strspn(s1+1, "abcdefghknoprstuvwxzLONGS") == 1)
 	    && !s1[2]);
     if (tok != STRING) {
 	/* Check first argument for [[ STRING ]] re-interpretation */
@@ -2398,7 +2435,7 @@ par_cond_2(void)
 	    YYERROR(ecused);
     }
     condlex();
-    if (n_testargs == 2 && tok != STRING && tokstr && s1[0] == '-') {
+    if (n_testargs == 2 && tok != STRING && tokstr && IS_DASH(s1[0])) {
 	/*
 	 * Something like "test -z" followed by a token.
 	 * We'll turn the token into a string (we've also
@@ -2433,9 +2470,9 @@ par_cond_2(void)
 	} else
 	    YYERROR(ecused);
     }
-    s2 = tokstr;   
+    s2 = tokstr;
     if (!n_testargs)
-	dble = (s2 && *s2 == '-' && !s2[2]);
+	dble = (s2 && IS_DASH(*s2) && !s2[2]);
     incond++;			/* parentheses do globbing */
     do condlex(); while (COND_SEP());
     incond--;			/* parentheses do grouping */
@@ -2463,9 +2500,9 @@ par_cond_2(void)
 static int
 par_cond_double(char *a, char *b)
 {
-    if (a[0] != '-' || !a[1])
+    if (!IS_DASH(a[0]) || !a[1])
 	COND_ERROR("parse error: condition expected: %s", a);
-    else if (!a[2] && strspn(a+1, "abcdefgknoprstuwxzhLONGS") == 1) {
+    else if (!a[2] && strspn(a+1, "abcdefgknoprstuvwxzhLONGS") == 1) {
 	ecadd(WCB_COND(a[1], 0));
 	ecstr(b);
     } else {
@@ -2498,12 +2535,17 @@ par_cond_triple(char *a, char *b, char *c)
 {
     int t0;
 
-    if ((b[0] == Equals || b[0] == '=') &&
-	(!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2]))) {
+    if ((b[0] == Equals || b[0] == '=') && !b[1]) {
 	ecadd(WCB_COND(COND_STREQ, 0));
 	ecstr(a);
 	ecstr(c);
 	ecadd(ecnpats++);
+    } else if ((b[0] == Equals || b[0] == '=') &&
+	       (b[1] == Equals || b[1] == '=') && !b[2]) {
+	ecadd(WCB_COND(COND_STRDEQ, 0));
+	ecstr(a);
+	ecstr(c);
+	ecadd(ecnpats++);
     } else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2]) {
 	ecadd(WCB_COND(COND_STRNEQ, 0));
 	ecstr(a);
@@ -2516,7 +2558,7 @@ par_cond_triple(char *a, char *b, char *c)
 	ecadd(WCB_COND(COND_REGEX, 0));
 	ecstr(a);
 	ecstr(c);
-    } else if (b[0] == '-') {
+    } else if (IS_DASH(b[0])) {
 	if ((t0 = get_cond_num(b + 1)) > -1) {
 	    ecadd(WCB_COND(t0 + COND_NT, 0));
 	    ecstr(a);
@@ -2527,7 +2569,7 @@ par_cond_triple(char *a, char *b, char *c)
 	    ecstr(a);
 	    ecstr(c);
 	}
-    } else if (a[0] == '-' && a[1]) {
+    } else if (IS_DASH(a[0]) && a[1]) {
 	ecadd(WCB_COND(COND_MOD, 2));
 	ecstr(a);
 	ecstr(b);
@@ -2542,7 +2584,7 @@ par_cond_triple(char *a, char *b, char *c)
 static int
 par_cond_multi(char *a, LinkList l)
 {
-    if (a[0] != '-' || !a[1])
+    if (!IS_DASH(a[0]) || !a[1])
 	COND_ERROR("condition expected: %s", a);
     else {
 	LinkNode n;
@@ -3238,10 +3280,10 @@ build_dump(char *nam, char *dump, char **files, int ali, int map, int flags)
     for (hlen = FD_PRELEN, tlen = 0; *files; files++) {
 	struct stat st;
 
-	if (!strcmp(*files, "-k")) {
+	if (check_cond(*files, "k")) {
 	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_KSHLOAD;
 	    continue;
-	} else if (!strcmp(*files, "-z")) {
+	} else if (check_cond(*files, "z")) {
 	    flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_ZSHLOAD;
 	    continue;
 	}
@@ -3320,7 +3362,7 @@ cur_add_func(char *nam, Shfunc shf, LinkList names, LinkList progs,
 	    return 1;
 	}
 	noaliases = (shf->node.flags & PM_UNALIASED);
-	if (!(prog = getfpfunc(shf->node.nam, NULL, NULL)) ||
+	if (!(prog = getfpfunc(shf->node.nam, NULL, NULL, NULL, 0)) ||
 	    prog == &dummy_eprog) {
 	    noaliases = ona;
 	    zwarnnam(nam, "can't load function: %s", shf->node.nam);
@@ -3395,6 +3437,7 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map,
 
 	for (; *names; names++) {
 	    tokenize(pat = dupstring(*names));
+	    /* Signal-safe here, caller queues signals */
 	    if (!(pprog = patcompile(pat, PAT_STATIC, NULL))) {
 		zwarnnam(nam, "bad pattern: %s", *names);
 		close(dfd);
@@ -3562,7 +3605,7 @@ load_dump_file(char *dump, struct stat *sbuf, int other, int len)
 
 /**/
 Eprog
-try_dump_file(char *path, char *name, char *file, int *ksh)
+try_dump_file(char *path, char *name, char *file, int *ksh, int test_only)
 {
     Eprog prog;
     struct stat std, stc, stn;
@@ -3571,7 +3614,7 @@ try_dump_file(char *path, char *name, char *file, int *ksh)
 
     if (strsfx(FD_EXT, path)) {
 	queue_signals();
-	prog = check_dump_file(path, NULL, name, ksh);
+	prog = check_dump_file(path, NULL, name, ksh, test_only);
 	unqueue_signals();
 	return prog;
     }
@@ -3590,14 +3633,14 @@ try_dump_file(char *path, char *name, char *file, int *ksh)
     if (!rd &&
 	(rc || std.st_mtime > stc.st_mtime) &&
 	(rn || std.st_mtime > stn.st_mtime) &&
-	(prog = check_dump_file(dig, &std, name, ksh))) {
+	(prog = check_dump_file(dig, &std, name, ksh, test_only))) {
 	unqueue_signals();
 	return prog;
     }
     /* No digest file. Now look for the per-function compiled file. */
     if (!rc &&
 	(rn || stc.st_mtime > stn.st_mtime) &&
-	(prog = check_dump_file(wc, &stc, name, ksh))) {
+	(prog = check_dump_file(wc, &stc, name, ksh, test_only))) {
 	unqueue_signals();
 	return prog;
     }
@@ -3625,7 +3668,7 @@ try_source_file(char *file)
 
     if (strsfx(FD_EXT, file)) {
 	queue_signals();
-	prog = check_dump_file(file, NULL, tail, NULL);
+	prog = check_dump_file(file, NULL, tail, NULL, 0);
 	unqueue_signals();
 	return prog;
     }
@@ -3636,7 +3679,7 @@ try_source_file(char *file)
 
     queue_signals();
     if (!rc && (rn || stc.st_mtime > stn.st_mtime) &&
-	(prog = check_dump_file(wc, &stc, tail, NULL))) {
+	(prog = check_dump_file(wc, &stc, tail, NULL, 0))) {
 	unqueue_signals();
 	return prog;
     }
@@ -3649,7 +3692,8 @@ try_source_file(char *file)
 
 /**/
 static Eprog
-check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh)
+check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh,
+		int test_only)
 {
     int isrec = 0;
     Wordcode d;
@@ -3691,6 +3735,11 @@ check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh)
     if ((h = dump_find_func(d, name))) {
 	/* Found the name. If the file is already mapped, return the eprog,
 	 * otherwise map it and just go up. */
+	if (test_only)
+	{
+	    /* This is all we need.  Just return dummy. */
+	    return &dummy_eprog;
+	}
 
 #ifdef USE_MMAP
 
@@ -3727,7 +3776,7 @@ check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh)
 
 #endif
 
-	    {
+	{
 	    Eprog prog;
 	    Patprog *pp;
 	    int np, fd, po = h->npats * sizeof(Patprog);
diff --git a/Src/pattern.c b/Src/pattern.c
index 4e2f2369f..fc7c73739 100644
--- a/Src/pattern.c
+++ b/Src/pattern.c
@@ -668,13 +668,9 @@ patcompile(char *exp, int inflags, char **endexp)
 			    if (imeta(*mtest))
 				nmeta++;
 			if (nmeta) {
-			    char *oldpatout = patout;
 			    patadd(NULL, 0, nmeta, 0);
-			    /*
-			     * Yuk.
-			     */
 			    p = (Patprog)patout;
-			    opnd = patout + (opnd - oldpatout);
+			    opnd = dupstring_wlen(opnd, oplen);
 			    dst = patout + startoff;
 			}
 
@@ -686,6 +682,8 @@ patcompile(char *exp, int inflags, char **endexp)
 				*dst++ = *opnd++;
 			    }
 			}
+			/* Only one string in a PAT_PURES, so now done. */
+			break;
 		    }
 		}
 		p->size = dst - patout;
@@ -1523,7 +1521,7 @@ patcomppiece(int *flagp, int paren)
 		patparse = nptr;
 		len |= 1;
 	    }
-	    DPUTS(*patparse != '-', "BUG: - missing from numeric glob");
+	    DPUTS(!IS_DASH(*patparse), "BUG: - missing from numeric glob");
 	    patparse++;
 	    if (idigit(*patparse)) {
 		to = (zrange_t) zstrtol((char *)patparse,
@@ -3627,7 +3625,7 @@ mb_patmatchrange(char *range, wchar_t ch, int zmb_ind, wint_t *indptr, int *mtp)
 		    return 1;
 		break;
 	    case PP_PRINT:
-		if (iswprint(ch))
+		if (WC_ISPRINT(ch))
 		    return 1;
 		break;
 	    case PP_PUNCT:
diff --git a/Src/prompt.c b/Src/prompt.c
index e811f8e42..a8bb10329 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -395,11 +395,11 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 			test = 1;
 		    break;
 		case 'v':
-		    if (arrlen(psvar) >= arg)
+		    if (arrlen_ge(psvar, arg))
 			test = 1;
 		    break;
 		case 'V':
-		    if (arrlen(psvar) >= arg) {
+		    if (psvar && *psvar && arrlen_ge(psvar, arg)) {
 			if (*psvar[(arg ? arg : 1) - 1])
 			    test = 1;
 		    }
@@ -491,8 +491,10 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		if (!arg)
 		    arg++;
 		queue_signals();
-		if (!(hostnam = getsparam("HOST")))
+		if (!(hostnam = getsparam("HOST"))) {
+		    unqueue_signals();
 		    break;
+		}
 		if (arg < 0) {
 		    for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
 			if (ss[-1] == '.' && !++arg)
@@ -523,8 +525,6 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		break;
 	    case 'b':
 		txtchangeset(txtchangep, TXTNOBOLDFACE, TXTBOLDFACE);
-		txtchangeset(txtchangep, TXTNOSTANDOUT, TXTSTANDOUT);
-		txtchangeset(txtchangep, TXTNOUNDERLINE, TXTUNDERLINE);
 		txtunset(TXTBOLDFACE);
 		tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
 		break;
@@ -542,7 +542,8 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		arg = parsecolorchar(arg, 1);
 		if (arg >= 0 && !(arg & TXTNOFGCOLOUR)) {
 		    txtchangeset(txtchangep, arg & TXT_ATTR_FG_ON_MASK,
-				 TXTNOFGCOLOUR);
+				 TXTNOFGCOLOUR | TXT_ATTR_FG_COL_MASK);
+		    txtunset(TXT_ATTR_FG_COL_MASK);
 		    txtset(arg & TXT_ATTR_FG_ON_MASK);
 		    set_colour_attribute(arg, COL_SEQ_FG, TSC_PROMPT);
 		    break;
@@ -557,7 +558,8 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		arg = parsecolorchar(arg, 0);
 		if (arg >= 0 && !(arg & TXTNOBGCOLOUR)) {
 		    txtchangeset(txtchangep, arg & TXT_ATTR_BG_ON_MASK,
-				 TXTNOBGCOLOUR);
+				 TXTNOBGCOLOUR | TXT_ATTR_BG_COL_MASK);
+		    txtunset(TXT_ATTR_BG_COL_MASK);
 		    txtset(arg & TXT_ATTR_BG_ON_MASK);
 		    set_colour_attribute(arg, COL_SEQ_BG, TSC_PROMPT);
 		    break;
@@ -736,7 +738,7 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		    arg = 1;
 		else if (arg < 0)
 		    arg += arrlen(psvar) + 1;
-		if (arg > 0 && arrlen(psvar) >= arg)
+		if (arg > 0 && arrlen_ge(psvar, arg))
 		    stradd(psvar[arg - 1]);
 		break;
 	    case 'E':
@@ -918,6 +920,7 @@ addbufspc(int need)
 	if(need & 255)
 	    need = (need | 255) + 1;
 	bv->buf = realloc(bv->buf, bv->bufspc += need);
+	memset(bv->buf + bv->bufspc - need, 0, need);
 	bv->bp = bv->buf + bo;
 	if(bo1 != -1)
 	    bv->bp1 = bv->buf + bo1;
@@ -1041,6 +1044,10 @@ tsetcap(int cap, int flags)
 		tsetcap(TCSTANDOUTBEG, flags);
 	    if (txtisset(TXTUNDERLINE))
 		tsetcap(TCUNDERLINEBEG, flags);
+	    if (txtisset(TXTFGCOLOUR))
+		set_colour_attribute(txtattrmask, COL_SEQ_FG, TSC_PROMPT);
+	    if (txtisset(TXTBGCOLOUR))
+		set_colour_attribute(txtattrmask, COL_SEQ_BG, TSC_PROMPT);
 	}
     }
 }
diff --git a/Src/signals.c b/Src/signals.c
index 2eefc07de..cad40f4eb 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -55,6 +55,11 @@ mod_export Eprog siglists[VSIGCOUNT];
 /**/
 mod_export int nsigtrapped;
 
+/* Running an exit trap? */
+
+/**/
+int in_exit_trap;
+
 /*
  * Flag that exit trap has been set in POSIX mode.
  * The setter's expectation is therefore that it is run
@@ -72,6 +77,10 @@ mod_export int queueing_enabled, queue_front, queue_rear;
 mod_export int signal_queue[MAX_QUEUE_SIZE];
 /**/
 mod_export sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
+#ifdef DEBUG
+/**/
+mod_export int queue_in;
+#endif
 
 /* Variables used by trap queueing */
 
@@ -518,6 +527,11 @@ wait_for_processes(void)
 #if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
 		struct timezone dummy_tz;
 		gettimeofday(&pn->endtime, &dummy_tz);
+#ifdef WIFCONTINUED
+		if (WIFCONTINUED(status))
+		    pn->status = SP_RUNNING;
+		else
+#endif
 		pn->status = status;
 		pn->ti = ru;
 #else
@@ -646,6 +660,7 @@ zhandler(int sig)
 		inerrflush();
 		check_cursh_sig(SIGINT);
             }
+	    lastval = 128 + SIGINT;
         }
         break;
 
@@ -723,7 +738,7 @@ killjb(Job jn, int sig)
 {
     Process pn;
     int err = 0;
- 
+
     if (jobbing) {
         if (jn->stat & STAT_SUPERJOB) {
             if (sig == SIGCONT) {
@@ -731,11 +746,21 @@ killjb(Job jn, int sig)
                     if (killpg(pn->pid, sig) == -1)
 			if (kill(pn->pid, sig) == -1 && errno != ESRCH)
 			    err = -1;
- 
+
+		/*
+		 * Note this does not kill the last process,
+		 * which is assumed to be the one controlling the
+		 * subjob, i.e. the forked zsh that was originally
+		 * list_pipe_pid...
+		 */
                 for (pn = jn->procs; pn->next; pn = pn->next)
                     if (kill(pn->pid, sig) == -1 && errno != ESRCH)
 			err = -1;
 
+		/*
+		 * ...we only continue that once the external processes
+		 * currently associated with the subjob are finished.
+		 */
 		if (!jobtab[jn->other].procs && pn)
 		    if (kill(pn->pid, sig) == -1 && errno != ESRCH)
 			err = -1;
@@ -744,7 +769,7 @@ killjb(Job jn, int sig)
             }
             if (killpg(jobtab[jn->other].gleader, sig) == -1 && errno != ESRCH)
 		err = -1;
-		
+
 	    if (killpg(jn->gleader, sig) == -1 && errno != ESRCH)
 		err = -1;
 
@@ -796,7 +821,11 @@ dosavetrap(int sig, int level)
 	    newshf->node.nam = ztrdup(shf->node.nam);
 	    newshf->node.flags = shf->node.flags;
 	    newshf->funcdef = dupeprog(shf->funcdef, 0);
-	    newshf->filename = ztrdup(shf->filename);
+	    if (shf->node.flags & PM_LOADDIR) {
+		dircache_set(&newshf->filename, shf->filename);
+	    } else {
+		newshf->filename = ztrdup(shf->filename);
+	    }
 	    if (shf->sticky) {
 		newshf->sticky = sticky_emulation_dup(shf->sticky, 0);
 	    } else
@@ -1411,7 +1440,13 @@ dotrap(int sig)
 
     dont_queue_signals();
 
+    if (sig == SIGEXIT)
+	++in_exit_trap;
+
     dotrapargs(sig, sigtrapped+sig, funcprog);
 
+    if (sig == SIGEXIT)
+	--in_exit_trap;
+
     restore_queue_signals(q);
 }
diff --git a/Src/signals.h b/Src/signals.h
index d68096891..1904f4326 100644
--- a/Src/signals.h
+++ b/Src/signals.h
@@ -82,8 +82,6 @@
 
 #define MAX_QUEUE_SIZE 128
 
-#define queue_signals()    (queueing_enabled++)
-
 #define run_queued_signals() do { \
     while (queue_front != queue_rear) {      /* while signals in queue */ \
 	sigset_t oset; \
@@ -94,12 +92,35 @@
     } \
 } while (0)
 
+#ifdef DEBUG
+
+#define queue_signals()    (queue_in++, queueing_enabled++)
+
 #define unqueue_signals()  do { \
     DPUTS(!queueing_enabled, "BUG: unqueue_signals called but not queueing"); \
+    --queue_in; \
     if (!--queueing_enabled) run_queued_signals(); \
 } while (0)
 
-#define queue_signal_level() queueing_enabled
+#define dont_queue_signals() do { \
+    queue_in = queueing_enabled; \
+    queueing_enabled = 0; \
+    run_queued_signals(); \
+} while (0)
+
+#define restore_queue_signals(q) do { \
+    DPUTS2(queueing_enabled && queue_in != q, \
+         "BUG: q = %d != queue_in = %d", q, queue_in); \
+    queue_in = (queueing_enabled = (q)); \
+} while (0)
+
+#else /* !DEBUG */
+
+#define queue_signals()    (queueing_enabled++)
+
+#define unqueue_signals()  do { \
+    if (!--queueing_enabled) run_queued_signals(); \
+} while (0)
 
 #define dont_queue_signals() do { \
     queueing_enabled = 0; \
@@ -108,6 +129,10 @@
 
 #define restore_queue_signals(q) (queueing_enabled = (q))
 
+#endif /* DEBUG */
+
+#define queue_signal_level() queueing_enabled
+
 #ifdef BSD_SIGNALS
 #define signal_block(S) sigblock(S)
 #else
diff --git a/Src/string.c b/Src/string.c
index 04e7446c9..9e14ef949 100644
--- a/Src/string.c
+++ b/Src/string.c
@@ -41,6 +41,37 @@ dupstring(const char *s)
     return t;
 }
 
+/* Duplicate string on heap when length is known */
+
+/**/
+mod_export char *
+dupstring_wlen(const char *s, unsigned len)
+{
+    char *t;
+
+    if (!s)
+	return NULL;
+    t = (char *) zhalloc(len + 1);
+    memcpy(t, s, len);
+    t[len] = '\0';
+    return t;
+}
+
+/* Duplicate string on heap, returning length of string */
+
+/**/
+mod_export char *
+dupstring_glen(const char *s, unsigned *len_ret)
+{
+    char *t;
+
+    if (!s)
+	return NULL;
+    t = (char *) zhalloc((*len_ret = strlen((char *)s)) + 1);
+    strcpy(t, s);
+    return t;
+}
+
 /**/
 mod_export char *
 ztrdup(const char *s)
diff --git a/Src/subst.c b/Src/subst.c
index 7081d467d..a73ac4737 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -415,7 +415,9 @@ globlist(LinkList list, int nountok)
 	next = nextnode(node);
 	zglob(list, node, nountok);
     }
-    if (badcshglob == 1)
+    if (noerrs)
+	badcshglob = 0;
+    else if (badcshglob == 1)
 	zerr("no match");
 }
 
@@ -448,7 +450,7 @@ singsub(char **s)
  * NULL to use IFS).  The return value is true iff the expansion resulted
  * in an empty list.
  *
- * *ms_flags is set to bits in the enum above as neeed.
+ * *ms_flags is set to bits in the enum above as needed.
  */
 
 /**/
@@ -483,6 +485,8 @@ multsub(char **s, int pf_flags, char ***a, int *isarr, char *sep,
 	for ( ; *x; x += l) {
 	    int rawc = -1;
 	    convchar_t c;
+	    if (*x == Dash)
+		*x = '-';
 	    if (itok(STOUC(*x))) {
 		/* token, can't be separator, must be single byte */
 		rawc = *x;
@@ -624,22 +628,22 @@ filesub(char **namptr, int assign)
 char *
 equalsubstr(char *str, int assign, int nomatch)
 {
-    char *pp, *cnam, *cmdstr, *ret;
+    char *pp, *cnam, *cmdstr;
 
     for (pp = str; !isend2(*pp); pp++)
 	;
     cmdstr = dupstrpfx(str, pp-str);
     untokenize(cmdstr);
     remnulargs(cmdstr);
-    if (!(cnam = findcmd(cmdstr, 1))) {
+    if (!(cnam = findcmd(cmdstr, 1, 0))) {
 	if (nomatch)
 	    zerr("%s not found", cmdstr);
 	return NULL;
     }
-    ret = dupstring(cnam);
     if (*pp)
-	ret = dyncat(ret, pp);
-    return ret;
+	return dyncat(cnam, pp);
+    else
+	return cnam;		/* already duplicated */
 }
 
 /**/
@@ -652,6 +656,8 @@ filesubstr(char **namptr, int assign)
 	char *ptr, *tmp, *res, *ptr2;
 	int val;
 
+	if (str[1] == Dash)
+	    str[1] = '-';
 	val = zstrtol(str + 1, &ptr, 10);
 	if (isend(str[1])) {   /* ~ */
 	    *namptr = dyncat(home ? home : "", str + 1);
@@ -688,19 +694,19 @@ filesubstr(char **namptr, int assign)
 	    *namptr = dyncat(ds, ptr);
 	    return 1;
 	} else if ((ptr = itype_end(str+1, IUSER, 0)) != str+1) {   /* ~foo */
-	    char *hom, save;
+	    char *untok, *hom;
 
-	    save = *ptr;
-	    if (!isend(save))
+	    if (!isend(*ptr))
 		return 0;
-	    *ptr = 0;
-	    if (!(hom = getnameddir(++str))) {
-		if (isset(NOMATCH))
-		    zerr("no such user or named directory: %s", str);
-		*ptr = save;
+	    untok = dupstring(++str);
+	    untok[ptr-str] = 0;
+	    untokenize(untok);
+
+	    if (!(hom = getnameddir(untok))) {
+		if (isset(NOMATCH) && isset(EXECOPT))
+		    zerr("no such user or named directory: %s", untok);
 		return 0;
 	    }
-	    *ptr = save;
 	    *namptr = dyncat(hom, ptr);
 	    return 1;
 	}
@@ -1766,7 +1772,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      */
     c = *s;
     if (itype_end(s, IIDENT, 1) == s && *s != '#' && c != Pound &&
-	c != '-' && c != '!' && c != '$' && c != String && c != Qstring &&
+	!IS_DASH(c) &&
+	c != '!' && c != '$' && c != String && c != Qstring &&
 	c != '?' && c != Quest &&
 	c != '*' && c != Star && c != '@' && c != '{' &&
 	c != Inbrace && c != '=' && c != Equals && c != Hat &&
@@ -1895,13 +1902,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		    if (quotetype == QT_DOLLARS ||
 			quotetype == QT_BACKSLASH_PATTERN)
 			goto flagerr;
-		    if (s[1] == '-' || s[1] == '+') {
+		    if (IS_DASH(s[1]) || s[1] == '+') {
 			if (quotemod)
 			    goto flagerr;
 			s++;
 			quotemod = 1;
-			quotetype = (*s == '-') ? QT_SINGLE_OPTIONAL :
-			    QT_QUOTEDZPUTS;
+			quotetype = (*s == '+') ? QT_QUOTEDZPUTS :
+			    QT_SINGLE_OPTIONAL;
 		    } else {
 			if (quotetype == QT_SINGLE_OPTIONAL) {
 			    /* extra q's after '-' not allowed */
@@ -2208,9 +2215,9 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		     * properly in the first place we wouldn't
 		     * have this nonsense.
 		     */
-		    || ((cc == '#' || cc == Pound) &&
-			s[2] == Outbrace)
-		    || cc == '-' || (cc == ':' && s[2] == '-')
+		    || ((cc == '#' || cc == Pound) && s[2] == Outbrace)
+		    || IS_DASH(cc)
+		    || (cc == ':' && IS_DASH(s[2]))
 		    || (isstring(cc) && (s[2] == Inbrace || s[2] == Inpar)))) {
 	    getlen = 1 + whichlen, s++;
 	    /*
@@ -2372,7 +2379,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	 * This is the inner handling for the case referred to above
 	 * where we have something like ${${(P)name}...}.
 	 *
-	 * Treat this as as a normal value here; all transformations on
+	 * Treat this as a normal value here; all transformations on
 	 * result are in outer instance.
 	 */
 	aspar = 0;
@@ -2558,12 +2565,17 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		    if (v->pm->node.flags & PM_CHECKLEN)
 			assert(tmplen == arrlen(v->pm->gsu.a->getfn(v->pm)));
 		} else
-		    tmplen = arrlen(v->pm->gsu.a->getfn(v->pm));
+		    tmplen = -1;
 
-		if (v->start < 0)
+		if (v->start < 0) {
+		    tmplen = arrlen(v->pm->gsu.a->getfn(v->pm));
 		    v->start += tmplen + ((v->flags & VALFLAG_INV) ? 1 : 0);
-		if (!(v->flags & VALFLAG_INV) &&
-		    (v->start >= tmplen || v->start < 0))
+		}
+		if (!(v->flags & VALFLAG_INV))
+		    if (v->start < 0 ||
+			(tmplen != -1
+			 ? v->start >= tmplen
+			 : arrlen_le(v->pm->gsu.a->getfn(v->pm), v->start)))
 		    vunset = 1;
 	    }
 	    if (!vunset) {
@@ -2604,14 +2616,17 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      * Again, this duplicates tests for characters we're about to
      * examine properly later on.
      */
-    if (inbrace &&
-	(c = *s) != '-' && c != '+' && c != ':' && c != '%'  && c != '/' &&
-	c != '=' && c != Equals &&
-	c != '#' && c != Pound &&
-	c != '?' && c != Quest &&
-	c != '}' && c != Outbrace) {
-	zerr("bad substitution");
-	return NULL;
+    if (inbrace) {
+	c = *s;
+	if (!IS_DASH(c) &&
+	    c != '+' && c != ':' && c != '%'  && c != '/' &&
+	    c != '=' && c != Equals &&
+	    c != '#' && c != Pound &&
+	    c != '?' && c != Quest &&
+	    c != '}' && c != Outbrace) {
+	    zerr("bad substitution");
+	    return NULL;
+	}
     }
     /*
      * Join arrays up if we're in quotes and there isn't some
@@ -2689,8 +2704,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
     /* Check for ${..?..} or ${..=..} or one of those. *
      * Only works if the name is in braces.            */
 
-    if (inbrace && ((c = *s) == '-' ||
-		    c == '+' ||
+    if (inbrace && ((c = *s) == '+' ||
+		    IS_DASH(c) ||
 		    c == ':' ||	/* i.e. a doubled colon */
 		    c == '=' || c == Equals ||
 		    c == '%' ||
@@ -2801,6 +2816,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	    vunset = 1;
 	/* Fall Through! */
 	case '-':
+	case Dash:
 	    if (vunset) {
 		int split_flags;
 		val = dupstring(s);
@@ -2900,6 +2916,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 			    aval = paramvalarr(pm->gsu.h->getfn(pm), hkeys|hvals);
 		    } else
 			setaparam(idbeg, a);
+		    isarr = 1;
+		    arrasg = 0;
 		} else {
 		    untokenize(val);
 		    setsparam(idbeg, ztrdup(val));
@@ -2924,6 +2942,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
                 if (isset(EXECOPT)) {
                     *idend = '\0';
                     zerr("%s: %s", idbeg, *s ? s : "parameter not set");
+                    /*
+                     * In interactive shell we need to return to
+                     * top-level prompt --- don't clear this error
+                     * after handling a command as we do with
+                     * most errors.
+                     */
+                    errflag |= ERRFLAG_HARD;
                     if (!interact) {
                         if (mypid == getpid()) {
                             /*
@@ -3060,7 +3085,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		ziplen = 1;
 		ziplen = !!sval;
 	    }
-	    if (!isarr) aval = mkarray(val);
+	    if (!isarr) {
+		aval = mkarray(val);
+		isarr = 1;
+	    }
 	    if (zip) {
 		char **out;
 		int alen, outlen, i = 0;
@@ -3082,7 +3110,6 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		    out[i*2] = NULL;
 		    aval = out;
 		    copied = 1;
-		    isarr = 1;
 		}
 	    } else {
 		if (unset(UNSET)) {
@@ -3460,13 +3487,26 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      * exception is that ${name:-word} and ${name:+word} will have already
      * done any requested splitting of the word value with quoting preserved.
      */
-    if (ssub || (spbreak && isarr >= 0) || spsep || sep) {
+    if (ssub || spbreak || spsep || sep) {
+	int force_split = !ssub && (spbreak || spsep);
 	if (isarr) {
-	    val = sepjoin(aval, sep, 1);
-	    isarr = 0;
-	    ms_flags = 0;
+	    /* sep non-null here means F or j flag, force join */
+	    if (nojoin == 0 || sep) {
+		val = sepjoin(aval, sep, 1);
+		isarr = 0;
+	    } else if (force_split &&
+		       (spsep || nojoin == 2 || (!ifs && isarr < 0))) {
+		/* Hack to simulate splitting individual elements:
+		 * forced joining as previously determined, or
+		 * join on what we later use to forcibly split
+		 */
+		val = sepjoin(aval, (nojoin == 1 ? NULL : spsep), 1);
+		isarr = 0;
+	    }
+	    if (!isarr)
+		ms_flags = 0;
 	}
-	if (!ssub && (spbreak || spsep)) {
+	if (force_split && !isarr) {
 	    aval = sepsplit(val, spsep, 0, 1);
 	    if (!aval || !aval[0])
 		val = dupstring("");
@@ -3533,7 +3573,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	}
 	/*
 	 * TODO:  It would be really quite nice to abstract the
-	 * isarr and !issarr code into a function which gets
+	 * isarr and !isarr code into a function which gets
 	 * passed a pointer to a function with the effect of
 	 * the promptexpand bit.  Then we could use this for
 	 * a lot of stuff and bury val/aval/isarr inside a structure
@@ -3609,20 +3649,20 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		    char *tmp;
 
 		    for (; *ap; ap++) {
-			tmp = quotestring(*ap, NULL, quotetype);
+			tmp = quotestring(*ap, quotetype);
 			sl = strlen(tmp);
 			*ap = (char *) zhalloc(pre + sl + post + 1);
 			strcpy((*ap) + pre, tmp);
 			if (pre)
 			    ap[0][pre - 1] = ap[0][pre + sl] =
 				(quotetype != QT_DOUBLE ? '\'' : '"');
-			ap[0][pre + sl + 1] = '\0';
+			ap[0][pre + sl + post] = '\0';
 			if (quotetype == QT_DOLLARS)
 			  ap[0][0] = '$';
 		    }
 		} else
 		    for (; *ap; ap++)
-			*ap = quotestring(*ap, NULL, QT_BACKSLASH_SHOWNULL);
+			*ap = quotestring(*ap, QT_BACKSLASH_SHOWNULL);
 	    } else {
 		int one = noerrs, oef = errflag, haserr = 0;
 
@@ -3652,18 +3692,18 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		} else if (quotetype > QT_BACKSLASH) {
 		    int sl;
 		    char *tmp;
-		    tmp = quotestring(val, NULL, quotetype);
+		    tmp = quotestring(val, quotetype);
 		    sl = strlen(tmp);
-		    val = (char *) zhalloc(pre + sl + 2);
+		    val = (char *) zhalloc(pre + sl + post + 1);
 		    strcpy(val + pre, tmp);
 		    if (pre)
 			val[pre - 1] = val[pre + sl] =
 			    (quotetype != QT_DOUBLE ? '\'' : '"');
-		    val[pre + sl + 1] = '\0';
+		    val[pre + sl + post] = '\0';
 		    if (quotetype == QT_DOLLARS)
 		      val[0] = '$';
 		} else
-		    val = quotestring(val, NULL, QT_BACKSLASH_SHOWNULL);
+		    val = quotestring(val, QT_BACKSLASH_SHOWNULL);
 	    } else {
 		int one = noerrs, oef = errflag, haserr;
 
@@ -3750,6 +3790,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      * as a scalar.)
      */
 
+    if (isarr && ssub) {
+	/* prefork() wants a scalar, so join no matter what else */
+	val = sepjoin(aval, NULL, 1);
+	isarr = 0;
+	l->list.flags &= ~LF_ARRAY;
+    }
+
     /*
      * If a multsub result had whitespace at the start and we're
      * splitting and there's a previous string, now's the time to do so.
@@ -3763,6 +3810,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	insertlinknode(l, n, dupstring(fstr)); /* appended, no incnode */
 	*fstr = '\0';
     }
+    if (arrasg && !isarr) {
+	/*
+	 * Caller requested this be forced to an array even if scalar.
+	 * Any point in distinguishing arrasg == 2 (assoc array) here?
+	 */
+	l->list.flags |= LF_ARRAY;
+	aval = hmkarray(val);
+	isarr = 1;
+	DPUTS(!val, "value is NULL in paramsubst, empty array");
+    }
     if (isarr) {
 	char *x;
 	char *y;
@@ -4028,6 +4085,19 @@ arithsubst(char *a, char **bptr, char *rest)
     return t;
 }
 
+/* This function implements colon modifiers.
+ *
+ * STR is an in/out parameter.  On entry it is the string (e.g., path)
+ * to modified.  On return it is the modified path.
+ *
+ * PTR is an in/out parameter.  On entry it contains the string of colon
+ * modifiers.  On return it points past the last recognised modifier.
+ *
+ * Example:
+ *     ENTRY:   *str is "."   *ptr is ":AN"
+ *     RETURN:  *str is "/home/foobar" (equal to $PWD)   *ptr points to the "N"
+ */
+
 /**/
 void
 modify(char **str, char **ptr)
@@ -4063,6 +4133,7 @@ modify(char **str, char **ptr)
 	    case 'u':
 	    case 'q':
 	    case 'Q':
+	    case 'P':
 		c = **ptr;
 		break;
 
@@ -4254,7 +4325,7 @@ modify(char **str, char **ptr)
 			    subst(&copy, hsubl, hsubr, gbal);
 			break;
 		    case 'q':
-			copy = quotestring(copy, NULL, QT_BACKSLASH_SHOWNULL);
+			copy = quotestring(copy, QT_BACKSLASH_SHOWNULL);
 			break;
 		    case 'Q':
 			{
@@ -4269,6 +4340,16 @@ modify(char **str, char **ptr)
 			    untokenize(copy);
 			}
 			break;
+		    case 'P':
+			if (*copy != '/') {
+			    char *here = zgetcwd();
+			    if (here[strlen(here)-1] != '/')
+				copy = zhtricat(metafy(here, -1, META_HEAPDUP), "/", copy);
+			    else
+				copy = dyncat(here, copy);
+			}
+			copy = xsymlink(copy, 1);
+			break;
 		    }
 		    tc = *tt;
 		    *tt = '\0';
@@ -4330,7 +4411,7 @@ modify(char **str, char **ptr)
 			subst(str, hsubl, hsubr, gbal);
 		    break;
 		case 'q':
-		    *str = quotestring(*str, NULL, QT_BACKSLASH);
+		    *str = quotestring(*str, QT_BACKSLASH);
 		    break;
 		case 'Q':
 		    {
@@ -4345,6 +4426,16 @@ modify(char **str, char **ptr)
 			untokenize(*str);
 		    }
 		    break;
+		case 'P':
+		    if (**str != '/') {
+			char *here = zgetcwd();
+			if (here[strlen(here)-1] != '/')
+			    *str = zhtricat(metafy(here, -1, META_HEAPDUP), "/", *str);
+			else
+			    *str = dyncat(here, *str);
+		    }
+		    *str = xsymlink(*str, 1);
+		    break;
 		}
 	    }
 	    if (rec < 0) {
diff --git a/Src/text.c b/Src/text.c
index cf6d3eb42..3658b1bc6 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -46,7 +46,7 @@ int text_expand_tabs;
  * et seq. in zsh.h.
  */
 static const char *cond_binary_ops[] = {
-    "=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
+    "=", "==", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
     "-ne", "-lt", "-gt", "-le", "-ge", "=~", NULL
 };
 
@@ -934,6 +934,7 @@ gettext2(Estate state)
 			    taddstr(" ");
 			    taddstr(ecgetstr(state, EC_NODUP, NULL));
 			    if (ctype == COND_STREQ ||
+				ctype == COND_STRDEQ ||
 				ctype == COND_STRNEQ)
 				state->pc++;
 			} else {
@@ -1068,11 +1069,11 @@ getredirs(LinkList redirs)
 		     */
 		    if (!has_token(f->name)) {
 			taddchr('\'');
-			taddstr(quotestring(f->name, NULL, QT_SINGLE));
+			taddstr(quotestring(f->name, QT_SINGLE));
 			taddchr('\'');
 		    } else {
 			taddchr('"');
-			taddstr(quotestring(f->name, NULL, QT_DOUBLE));
+			taddstr(quotestring(f->name, QT_DOUBLE));
 			taddchr('"');
 		    }
 		    if (sav)
diff --git a/Src/utils.c b/Src/utils.c
index 28e78c149..579ab3be8 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -84,7 +84,15 @@ set_widearray(char *mb_array, Widechar_array wca)
 
 	mb_charinit();
 	while (*mb_array) {
-	    int mblen = mb_metacharlenconv(mb_array, &wci);
+	    int mblen;
+
+	    if (STOUC(*mb_array) <= 0x7f) {
+		mb_array++;
+		*wcptr++ = (wchar_t)*mb_array;
+		continue;
+	    }
+
+	    mblen = mb_metacharlenconv(mb_array, &wci);
 
 	    if (!mblen)
 		break;
@@ -621,7 +629,7 @@ wcs_nicechar_sel(wchar_t c, size_t *widthp, char **swidep, int quotable)
     }
 
     s = buf;
-    if (!iswprint(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
+    if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
 	if (c == 0x7f) {
 	    if (quotable) {
 		*s++ = '\\';
@@ -726,7 +734,7 @@ wcs_nicechar(wchar_t c, size_t *widthp, char **swidep)
 /**/
 mod_export int is_wcs_nicechar(wchar_t c)
 {
-    if (!iswprint(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
+    if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
 	if (c == 0x7f || c == L'\n' || c == L'\t' || c < 0x20)
 	    return 1;
 	if (c >= 0x80) {
@@ -801,9 +809,9 @@ findpwd(char *s)
     char *t;
 
     if (*s == '/')
-	return xsymlink(s);
+	return xsymlink(s, 0);
     s = tricat((pwd[1]) ? pwd : "", "/", s);
-    t = xsymlink(s);
+    t = xsymlink(s, 0);
     zsfree(s);
     return t;
 }
@@ -837,7 +845,7 @@ ispwd(char *s)
     return 0;
 }
 
-static char xbuf[PATH_MAX*2];
+static char xbuf[PATH_MAX*2+1];
 
 /**/
 static char **
@@ -876,9 +884,9 @@ static int
 xsymlinks(char *s, int full)
 {
     char **pp, **opp;
-    char xbuf2[PATH_MAX*3], xbuf3[PATH_MAX*2];
+    char xbuf2[PATH_MAX*3+1], xbuf3[PATH_MAX*2+1];
     int t0, ret = 0;
-    zulong xbuflen = strlen(xbuf);
+    zulong xbuflen = strlen(xbuf), pplen;
 
     opp = pp = slashsplit(s);
     for (; xbuflen < sizeof(xbuf) && *pp && ret >= 0; pp++) {
@@ -899,10 +907,18 @@ xsymlinks(char *s, int full)
 	    xbuflen--;
 	    continue;
 	}
-	sprintf(xbuf2, "%s/%s", xbuf, *pp);
+	/* Includes null byte. */
+	pplen = strlen(*pp) + 1;
+	if (xbuflen + pplen + 1 > sizeof(xbuf2)) {
+	    *xbuf = 0;
+	    ret = -1;
+	    break;
+	}
+	memcpy(xbuf2, xbuf, xbuflen);
+	xbuf2[xbuflen] = '/';
+	memcpy(xbuf2 + xbuflen + 1, *pp, pplen);
 	t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
 	if (t0 == -1) {
-	    zulong pplen = strlen(*pp) + 1;
 	    if ((xbuflen += pplen) < sizeof(xbuf)) {
 		strcat(xbuf, "/");
 		strcat(xbuf, *pp);
@@ -969,11 +985,13 @@ xsymlinks(char *s, int full)
 /*
  * expand symlinks in s, and remove other weird things:
  * note that this always expands symlinks.
+ *
+ * 'heap' indicates whether to malloc() or allocate on the heap.
  */
 
 /**/
 char *
-xsymlink(char *s)
+xsymlink(char *s, int heap)
 {
     if (*s != '/')
 	return NULL;
@@ -981,8 +999,8 @@ xsymlink(char *s)
     if (xsymlinks(s + 1, 1) < 0)
 	zwarn("path expansion failed, using root directory");
     if (!*xbuf)
-	return ztrdup("/");
-    return ztrdup(xbuf);
+	return heap ? dupstring("/") : ztrdup("/");
+    return heap ? dupstring(xbuf) : ztrdup(xbuf);
 }
 
 /**/
@@ -993,7 +1011,7 @@ print_if_link(char *s, int all)
 	*xbuf = '\0';
 	if (all) {
 	    char *start = s + 1;
-	    char xbuflink[PATH_MAX];
+	    char xbuflink[PATH_MAX+1];
 	    for (;;) {
 		if (xsymlinks(start, 0) > 0) {
 		    printf(" -> ");
@@ -1045,9 +1063,9 @@ substnamedir(char *s)
     Nameddir d = finddir(s);
 
     if (!d)
-	return quotestring(s, NULL, QT_BACKSLASH);
+	return quotestring(s, QT_BACKSLASH);
     return zhtricat("~", d->node.nam, quotestring(s + strlen(d->dir),
-						  NULL, QT_BACKSLASH));
+						  QT_BACKSLASH));
 }
 
 
@@ -1130,7 +1148,7 @@ finddir(char *s)
 	if(homenode.diff==1)
 	    homenode.diff = 0;
 	if(!finddir_full)
-	    finddir_full = zalloc(ffsz = PATH_MAX);
+	    finddir_full = zalloc(ffsz = PATH_MAX+1);
 	finddir_full[0] = 0;
 	return finddir_last = NULL;
     }
@@ -1157,7 +1175,7 @@ finddir(char *s)
     scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
 
     ares = subst_string_by_hook("zsh_directory_name", "d", finddir_full);
-    if (ares && arrlen(ares) >= 2 &&
+    if (ares && arrlen_ge(ares, 2) &&
 	(len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
 	/* better duplicate this string since it's come from REPLY */
 	finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
@@ -1220,13 +1238,13 @@ adduserdir(char *s, char *t, int flags, int always)
 	 * named directory, since these are sometimes used for
 	 * special purposes.
 	 */
-	nd->dir = ztrdup(t);
+	nd->dir = metafy(t, -1, META_DUP);
     } else
-	nd->dir = ztrduppfx(t, eptr - t);
+	nd->dir = metafy(t, eptr - t, META_DUP);
     /* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */
     if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD"))
 	nd->node.flags |= ND_NOABBREV;
-    nameddirtab->addnode(nameddirtab, ztrdup(s), nd);
+    nameddirtab->addnode(nameddirtab, metafy(s, -1, META_DUP), nd);
 }
 
 /* Get a named directory: this function can cause a directory name *
@@ -1260,7 +1278,7 @@ getnameddir(char *name)
 	/* Retrieve an entry from the password table/database for this user. */
 	struct passwd *pw;
 	if ((pw = getpwnam(name))) {
-	    char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir)
+	    char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir, 0)
 		: ztrdup(pw->pw_dir);
 	    if (dir) {
 		adduserdir(name, dir, ND_USERNAME, 1);
@@ -1634,7 +1652,7 @@ checkmailpath(char **s)
 	} else if (S_ISDIR(st.st_mode)) {
 	    LinkList l;
 	    DIR *lock = opendir(unmeta(*s));
-	    char buf[PATH_MAX * 2], **arr, **ap;
+	    char buf[PATH_MAX * 2 + 1], **arr, **ap;
 	    int ct = 1;
 
 	    if (lock) {
@@ -2162,6 +2180,7 @@ gettempfile(const char *prefix, int use_heap, char **tempname)
 #if HAVE_MKSTEMP
     char *suffix = prefix ? ".XXXXXX" : "XXXXXX";
 
+    queue_signals();
     if (!prefix && !(prefix = getsparam("TMPPREFIX")))
 	prefix = DEFAULT_TMPPREFIX;
     if (use_heap)
@@ -2178,6 +2197,7 @@ gettempfile(const char *prefix, int use_heap, char **tempname)
 #else
     int failures = 0;
 
+    queue_signals();
     do {
 	if (!(fn = gettempname(prefix, use_heap))) {
 	    fd = -1;
@@ -2191,6 +2211,8 @@ gettempfile(const char *prefix, int use_heap, char **tempname)
     } while (errno == EEXIST && ++failures < 16);
 #endif
     *tempname = fn;
+
+    unqueue_signals();
     return fd;
 }
 
@@ -2280,6 +2302,46 @@ arrlen(char **s)
     return count;
 }
 
+/* Return TRUE iff arrlen(s) >= lower_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_ge(char **s, unsigned lower_bound)
+{
+    while (lower_bound--)
+	if (!*s++)
+	    return 0 /* FALSE */;
+
+    return 1 /* TRUE */;
+}
+
+/* Return TRUE iff arrlen(s) > lower_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_gt(char **s, unsigned lower_bound)
+{
+    return arrlen_ge(s, 1+lower_bound);
+}
+
+/* Return TRUE iff arrlen(s) <= upper_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_le(char **s, unsigned upper_bound)
+{
+    return arrlen_lt(s, 1+upper_bound);
+}
+
+/* Return TRUE iff arrlen(s) < upper_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_lt(char **s, unsigned upper_bound)
+{
+    return !arrlen_ge(s, upper_bound);
+}
+
 /* Skip over a balanced pair of parenthesis. */
 
 /**/
@@ -2322,7 +2384,7 @@ zstrtol_underscore(const char *s, char **t, int base, int underscore)
     while (inblank(*s))
 	s++;
 
-    if ((neg = (*s == '-')))
+    if ((neg = IS_DASH(*s)))
 	s++;
     else if (*s == '+')
 	s++;
@@ -2534,7 +2596,7 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds)
 #endif
 #endif
 
-    if (fd >= 0 && ret < 0) {
+    if (fd >= 0 && ret < 0 && !errflag) {
 	/*
 	 * Final attempt: set non-blocking read and try to read a character.
 	 * Praise Bill, this works under Cygwin (nothing else seems to).
@@ -2890,9 +2952,7 @@ mod_export void
 spckword(char **s, int hist, int cmd, int ask)
 {
     char *t, *correct_ignore;
-    int x;
     char ic = '\0';
-    int ne;
     int preflen = 0;
     int autocd = cmd && isset(AUTOCD) && strcmp(*s, ".") && strcmp(*s, "..");
 
@@ -2961,6 +3021,7 @@ spckword(char **s, int hist, int cmd, int ask)
     } else {
 	guess = *s;
 	if (*guess == Tilde || *guess == String) {
+	    int ne;
 	    ic = *guess;
 	    if (!*++t)
 		return;
@@ -3005,6 +3066,7 @@ spckword(char **s, int hist, int cmd, int ask)
     if (errflag)
 	return;
     if (best && (int)strlen(best) > 1 && strcmp(best, guess)) {
+	int x;
 	if (ic) {
 	    char *u;
 	    if (preflen) {
@@ -3034,14 +3096,14 @@ spckword(char **s, int hist, int cmd, int ask)
 		free(pptbuf);
 		fflush(shout);
 		zbeep();
-		x = getquery("nyae \t", 0);
+		x = getquery("nyae", 0);
 		if (cmd && x == 'n')
 		    pathchecked = path;
 	    } else
 		x = 'n';
 	} else
 	    x = 'y';
-	if (x == 'y' || x == ' ' || x == '\t') {
+	if (x == 'y') {
 	    *s = dupstring(best);
 	    if (hist)
 		hwrep(best);
@@ -3905,7 +3967,7 @@ inittyptab(void)
 #endif
     /* typtab['.'] |= IIDENT; */ /* Allow '.' in variable names - broken */
     typtab['_'] = IIDENT | IUSER;
-    typtab['-'] = typtab['.'] = IUSER;
+    typtab['-'] = typtab['.'] = typtab[STOUC(Dash)] = IUSER;
     typtab[' '] |= IBLANK | INBLANK;
     typtab['\t'] |= IBLANK | INBLANK;
     typtab['\n'] |= INBLANK;
@@ -4103,42 +4165,50 @@ itype_end(const char *ptr, int itype, int once)
 	(itype != IIDENT || !isset(POSIXIDENTIFIERS))) {
 	mb_charinit();
 	while (*ptr) {
-	    wint_t wc;
-	    int len = mb_metacharlenconv(ptr, &wc);
-
-	    if (!len)
-		break;
-
-	    if (wc == WEOF) {
-		/* invalid, treat as single character */
-		int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
-		/* in this case non-ASCII characters can't match */
-		if (chr > 127 || !zistype(chr,itype))
-		    break;
-	    } else if (len == 1 && isascii(*ptr)) {
-		/* ASCII: can't be metafied, use standard test */
+	    int len;
+	    if (itok(*ptr)) {
+		/* Not untokenised yet --- can happen in raw command line */
+		len = 1;
 		if (!zistype(*ptr,itype))
 		    break;
 	    } else {
-		/*
-		 * Valid non-ASCII character.
-		 */
-		switch (itype) {
-		case IWORD:
-		    if (!iswalnum(wc) &&
-			!wmemchr(wordchars_wide.chars, wc,
-				 wordchars_wide.len))
-			return (char *)ptr;
-		    break;
+		wint_t wc;
+		len = mb_metacharlenconv(ptr, &wc);
 
-		case ISEP:
-		    if (!wmemchr(ifs_wide.chars, wc, ifs_wide.len))
-			return (char *)ptr;
+		if (!len)
 		    break;
 
-		default:
-		    if (!iswalnum(wc))
-			return (char *)ptr;
+		if (wc == WEOF) {
+		    /* invalid, treat as single character */
+		    int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
+		    /* in this case non-ASCII characters can't match */
+		    if (chr > 127 || !zistype(chr,itype))
+			break;
+		} else if (len == 1 && isascii(*ptr)) {
+		    /* ASCII: can't be metafied, use standard test */
+		    if (!zistype(*ptr,itype))
+			break;
+		} else {
+		    /*
+		     * Valid non-ASCII character.
+		     */
+		    switch (itype) {
+		    case IWORD:
+			if (!iswalnum(wc) &&
+			    !wmemchr(wordchars_wide.chars, wc,
+				     wordchars_wide.len))
+			    return (char *)ptr;
+			break;
+
+		    case ISEP:
+			if (!wmemchr(ifs_wide.chars, wc, ifs_wide.len))
+			    return (char *)ptr;
+			break;
+
+		    default:
+			if (!iswalnum(wc))
+			    return (char *)ptr;
+		    }
 		}
 	    }
 	    ptr += len;
@@ -4183,6 +4253,32 @@ arrdup(char **s)
     return y;
 }
 
+/* Duplicate at most max elements of the array s with heap memory */
+
+/**/
+mod_export char **
+arrdup_max(char **s, unsigned max)
+{
+    char **x, **y, **send;
+    int len = 0;
+
+    if (max)
+	len = arrlen(s);
+
+    /* Limit has sense only if not equal to len */
+    if (max > len)
+        max = len;
+
+    y = x = (char **) zhalloc(sizeof(char *) * (max + 1));
+
+    send = s + max;
+    while (s < send)
+	*x++ = dupstring(*s++);
+    *x = NULL;
+
+    return y;
+}
+
 /**/
 mod_export char **
 zarrdup(char **s)
@@ -4700,6 +4796,41 @@ unmeta(const char *file_name)
 }
 
 /*
+ * Unmetafy just one character and store the number of bytes it occupied.
+ */
+/**/
+mod_export convchar_t
+unmeta_one(const char *in, int *sz)
+{
+    convchar_t wc;
+    int newsz;
+#ifdef MULTIBYTE_SUPPORT
+    mbstate_t wstate;
+#endif
+
+    if (!sz)
+	sz = &newsz;
+    *sz = 0;
+
+    if (!in || !*in)
+	return 0;
+
+#ifdef MULTIBYTE_SUPPORT
+    memset(&wstate, 0, sizeof(wstate));
+    *sz = mb_metacharlenconv_r(in, &wc, &wstate);
+#else
+    if (in[0] == Meta) {
+      *sz = 2;
+      wc = STOUC(in[1] ^ 32);
+    } else {
+      *sz = 1;
+      wc = STOUC(in[0]);
+    }
+#endif
+    return wc;
+}
+
+/*
  * Unmetafy and compare two strings, comparing unsigned character values.
  * "a\0" sorts after "a".
  *
@@ -5040,8 +5171,10 @@ mb_niceformat(const char *s, FILE *stream, char **outstrp, int flags)
 	    cnt = 1;
 	    /* FALL THROUGH */
 	default:
-	    if (c == L'\'' && (flags & NICEFLAG_QUOTE))
+	    if (c == L'\'' && (flags & NICEFLAG_QUOTE)) {
 		fmt = "\\'";
+		newl = 2;
+	    }
 	    else
 		fmt = wcs_nicechar_sel(c, &newl, NULL, flags & NICEFLAG_QUOTE);
 	    break;
@@ -5176,6 +5309,12 @@ mb_metacharlenconv_r(const char *s, wint_t *wcp, mbstate_t *mbsp)
     const char *ptr;
     wchar_t wc;
 
+    if (STOUC(*s) <= 0x7f) {
+	if (wcp)
+	    *wcp = (wint_t)*s;
+	return 1;
+    }
+
     for (ptr = s; *ptr; ) {
 	if (*ptr == Meta) {
 	    inchar = *++ptr ^ 32;
@@ -5228,7 +5367,7 @@ mb_metacharlenconv_r(const char *s, wint_t *wcp, mbstate_t *mbsp)
 mod_export int
 mb_metacharlenconv(const char *s, wint_t *wcp)
 {
-    if (!isset(MULTIBYTE)) {
+    if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) {
 	/* treat as single byte, possibly metafied */
 	if (wcp)
 	    *wcp = (wint_t)(*s == Meta ? s[1] ^ 32 : *s);
@@ -5275,7 +5414,7 @@ mb_metastrlenend(char *ptr, int width, char *eptr)
     char inchar, *laststart;
     size_t ret;
     wchar_t wc;
-    int num, num_in_char;
+    int num, num_in_char, complete;
 
     if (!isset(MULTIBYTE))
 	return ztrlen(ptr);
@@ -5283,6 +5422,7 @@ mb_metastrlenend(char *ptr, int width, char *eptr)
     laststart = ptr;
     ret = MB_INVALID;
     num = num_in_char = 0;
+    complete = 1;
 
     memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
     while (*ptr && !(eptr && ptr >= eptr)) {
@@ -5291,6 +5431,18 @@ mb_metastrlenend(char *ptr, int width, char *eptr)
 	else
 	    inchar = *ptr;
 	ptr++;
+
+	if (complete && STOUC(inchar) <= STOUC(0x7f)) {
+	    /*
+	     * We rely on 7-bit US-ASCII as a subset, so skip
+	     * multibyte handling if we have such a character.
+	     */
+	    num++;
+	    laststart = ptr;
+	    num_in_char = 0;
+	    continue;
+	}
+
 	ret = mbrtowc(&wc, &inchar, 1, &mb_shiftstate);
 
 	if (ret == MB_INCOMPLETE) {
@@ -5310,6 +5462,7 @@ mb_metastrlenend(char *ptr, int width, char *eptr)
 	     * so we don't count characters twice.
 	     */
 	    num_in_char++;
+	    complete = 0;
 	} else {
 	    if (ret == MB_INVALID) {
 		/* Reset, treat as single character */
@@ -5332,6 +5485,7 @@ mb_metastrlenend(char *ptr, int width, char *eptr)
 		num++;
 	    laststart = ptr;
 	    num_in_char = 0;
+	    complete = 1;
 	}
     }
 
@@ -5354,6 +5508,12 @@ mb_charlenconv_r(const char *s, int slen, wint_t *wcp, mbstate_t *mbsp)
     const char *ptr;
     wchar_t wc;
 
+    if (slen && STOUC(*s) <= 0x7f) {
+	if (wcp)
+	    *wcp = (wint_t)*s;
+	return 1;
+    }
+
     for (ptr = s; slen;  ) {
 	inchar = *ptr;
 	ptr++;
@@ -5389,7 +5549,7 @@ mb_charlenconv_r(const char *s, int slen, wint_t *wcp, mbstate_t *mbsp)
 mod_export int
 mb_charlenconv(const char *s, int slen, wint_t *wcp)
 {
-    if (!isset(MULTIBYTE)) {
+    if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) {
 	if (wcp)
 	    *wcp = (wint_t)*s;
 	return 1;
@@ -5605,10 +5765,6 @@ addunprintable(char *v, const char *u, const char *uend)
 /*
  * Quote the string s and return the result as a string from the heap.
  *
- * If e is non-zero, the
- * pointer it points to may point to a position in s and in e the position
- * of the corresponding character in the quoted string is returned.
- * 
  * The last argument is a QT_ value defined in zsh.h other than QT_NONE.
  *
  * Most quote styles other than backslash assume the quotes are to
@@ -5621,13 +5777,13 @@ addunprintable(char *v, const char *u, const char *uend)
 
 /**/
 mod_export char *
-quotestring(const char *s, char **e, int instring)
+quotestring(const char *s, int instring)
 {
     const char *u;
     char *v;
     int alloclen;
     char *buf;
-    int sf = 0, shownull = 0;
+    int shownull = 0;
     /*
      * quotesub is used with QT_SINGLE_OPTIONAL.
      * quotesub = 0:  mechanism not active
@@ -5698,10 +5854,6 @@ quotestring(const char *s, char **e, int instring)
 	while (*u) {
 	    uend = u + MB_METACHARLENCONV(u, &cc);
 
-	    if (e && !sf && *e <= u) {
-		*e = v;
-		sf = 1;
-	    }
 	    if (
 #ifdef MULTIBYTE_SUPPORT
 		cc != WEOF &&
@@ -5728,11 +5880,6 @@ quotestring(const char *s, char **e, int instring)
 	}
     } else if (instring == QT_BACKSLASH_PATTERN) {
 	while (*u) {
-	    if (e && !sf && *e == u) {
-		*e = v;
-		sf = 1;
-	    }
-
 	    if (ipattern(*u))
 		*v++ = '\\';
 	    *v++ = *u++;
@@ -5751,8 +5898,6 @@ quotestring(const char *s, char **e, int instring)
 	 */
 	while (*u) {
 	    int dobackslash = 0;
-	    if (e && *e == u)
-		*e = v, sf = 1;
 	    if (*u == Tick || *u == Qtick) {
 		char c = *u++;
 
@@ -5940,10 +6085,6 @@ quotestring(const char *s, char **e, int instring)
 	*v++ = '\'';
     *v = '\0';
 
-    if (e && *e == u)
-	*e = v, sf = 1;
-    DPUTS(e && !sf, "BUG: Wild pointer *e in quotestring()");
-
     v = dupstring(buf);
     zfree(buf, alloclen);
     return v;
@@ -6020,7 +6161,9 @@ quotedzputs(char const *s, FILE *stream)
 	} else
 	    *ptr++ = '\'';
 	while(*s) {
-	    if (*s == Meta)
+	    if (*s == Dash)
+		c = '-';
+	    else if (*s == Meta)
 		c = *++s ^ 32;
 	    else
 		c = *s;
@@ -6057,7 +6200,9 @@ quotedzputs(char const *s, FILE *stream)
     } else {
 	/* use Bourne-style quoting, avoiding empty quoted strings */
 	while (*s) {
-	    if (*s == Meta)
+	    if (*s == Dash)
+		c = '-';
+	    else if (*s == Meta)
 		c = *++s ^ 32;
 	    else
 		c = *s;
@@ -6818,7 +6963,7 @@ strsfx(char *s, char *t)
 static int
 upchdir(int n)
 {
-    char buf[PATH_MAX];
+    char buf[PATH_MAX+1];
     char *s;
     int err = -1;
 
diff --git a/Src/watch.c b/Src/watch.c
index c804913ad..cd7dc643d 100644
--- a/Src/watch.c
+++ b/Src/watch.c
@@ -87,6 +87,15 @@
 
 #if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMPX) && defined(REAL_UTMPX_FILE)
 # define WATCH_STRUCT_UTMP struct utmpx
+# if defined(HAVE_SETUTXENT) && defined(HAVE_GETUTXENT) && defined(HAVE_ENDUTXENT)
+#  define setutent setutxent
+#  define getutent getutxent
+#  define endutent endutxent
+#  ifndef HAVE_GETUTENT
+#   define HAVE_GETUTENT 1
+#  endif
+# endif
+
 /*
  * In utmpx, the ut_name field is replaced by ut_user.
  * Howver, on some systems ut_name may already be defined this
@@ -141,9 +150,9 @@ char const * const default_watchfmt = DEFAULT_WATCHFMT;
 #  define WATCH_WTMP_FILE "/dev/null"
 # endif
 
-static int wtabsz;
-static WATCH_STRUCT_UTMP *wtab;
-static time_t lastutmpcheck;
+static int wtabsz = 0;
+static WATCH_STRUCT_UTMP *wtab = NULL;
+static time_t lastutmpcheck = 0;
 
 /* get the time of login/logout for WATCH */
 
@@ -473,34 +482,60 @@ ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v)
 /* initialize the user List */
 
 /**/
-static void
-readwtab(void)
+static int
+readwtab(WATCH_STRUCT_UTMP **head, int initial_sz)
 {
     WATCH_STRUCT_UTMP *uptr;
-    int wtabmax = 32;
+    int wtabmax = initial_sz < 2 ? 32 : initial_sz;
+    int sz = 0;
+# ifdef HAVE_GETUTENT
+    WATCH_STRUCT_UTMP *tmp;
+# else
     FILE *in;
+# endif
 
-    wtabsz = 0;
+    uptr = *head = (WATCH_STRUCT_UTMP *)
+	zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP));
+# ifdef HAVE_GETUTENT
+    setutent();
+    while ((tmp = getutent()) != NULL) {
+	memcpy(uptr, tmp, sizeof (WATCH_STRUCT_UTMP));
+# else
     if (!(in = fopen(WATCH_UTMP_FILE, "r")))
-	return;
-    uptr = wtab = (WATCH_STRUCT_UTMP *)zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP));
-    while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in))
+	return 0;
+    while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+# endif
 # ifdef USER_PROCESS
-	if   (uptr->ut_type == USER_PROCESS)
+	if (uptr->ut_type == USER_PROCESS)
 # else /* !USER_PROCESS */
-	if   (uptr->ut_name[0])
+	if (uptr->ut_name[0])
 # endif /* !USER_PROCESS */
 	{
 	    uptr++;
-	    if (++wtabsz == wtabmax)
-		uptr = (wtab = (WATCH_STRUCT_UTMP *)realloc((void *) wtab, (wtabmax *= 2) *
-						      sizeof(WATCH_STRUCT_UTMP))) + wtabsz;
+	    if (++sz == wtabmax) {
+		uptr = (WATCH_STRUCT_UTMP *)
+		    realloc(*head, (wtabmax *= 2) * sizeof(WATCH_STRUCT_UTMP));
+		if (uptr == NULL) {
+		    /* memory pressure - so stop consuming and use, what we have
+		     * Other option is to exit() here, as zmalloc does on error */
+		    sz--;
+		    break;
+		}
+		*head = uptr;
+		uptr += sz;
+	    }
 	}
+    }
+# ifdef HAVE_GETUTENT
+    endutent();
+# else
     fclose(in);
+# endif
 
-    if (wtabsz)
-	qsort((void *) wtab, wtabsz, sizeof(WATCH_STRUCT_UTMP),
+    if (sz)
+	qsort((void *) *head, sz, sizeof(WATCH_STRUCT_UTMP),
 	           (int (*) _((const void *, const void *)))ucmp);
+    return sz;
 }
 
 /* Check for login/logout events; executed before *
@@ -510,55 +545,28 @@ readwtab(void)
 void
 dowatch(void)
 {
-    FILE *in;
     WATCH_STRUCT_UTMP *utab, *uptr, *wptr;
     struct stat st;
     char **s;
     char *fmt;
-    int utabsz = 0, utabmax = wtabsz + 4;
-    int uct, wct;
+    int utabsz, uct, wct;
 
     s = watch;
 
     holdintr();
-    if (!wtab) {
-	readwtab();
-	noholdintr();
-	return;
-    }
+    if (!wtab)
+	wtabsz = readwtab(&wtab, 32);
     if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) {
 	noholdintr();
 	return;
     }
     lastutmpcheck = st.st_mtime;
-    uptr = utab = (WATCH_STRUCT_UTMP *) zalloc(utabmax * sizeof(WATCH_STRUCT_UTMP));
-
-    if (!(in = fopen(WATCH_UTMP_FILE, "r"))) {
-	free(utab);
-	noholdintr();
-	return;
-    }
-    while (fread(uptr, sizeof *uptr, 1, in))
-# ifdef USER_PROCESS
-	if (uptr->ut_type == USER_PROCESS)
-# else /* !USER_PROCESS */
-	if (uptr->ut_name[0])
-# endif /* !USER_PROCESS */
-	{
-	    uptr++;
-	    if (++utabsz == utabmax)
-		uptr = (utab = (WATCH_STRUCT_UTMP *)realloc((void *) utab, (utabmax *= 2) *
-						      sizeof(WATCH_STRUCT_UTMP))) + utabsz;
-	}
-    fclose(in);
+    utabsz = readwtab(&utab, wtabsz + 4);
     noholdintr();
     if (errflag) {
 	free(utab);
 	return;
     }
-    if (utabsz)
-	qsort((void *) utab, utabsz, sizeof(WATCH_STRUCT_UTMP),
-	           (int (*) _((const void *, const void *)))ucmp);
 
     wct = wtabsz;
     uct = utabsz;
@@ -571,13 +579,14 @@ dowatch(void)
     queue_signals();
     if (!(fmt = getsparam_u("WATCHFMT")))
 	fmt = DEFAULT_WATCHFMT;
-    while ((uct || wct) && !errflag)
+    while ((uct || wct) && !errflag) {
 	if (!uct || (wct && ucmp(uptr, wptr) > 0))
 	    wct--, watchlog(0, wptr++, s, fmt);
 	else if (!wct || (uct && ucmp(uptr, wptr) < 0))
 	    uct--, watchlog(1, uptr++, s, fmt);
 	else
 	    uptr++, wptr++, wct--, uct--;
+    }
     unqueue_signals();
     free(wtab);
     wtab = utab;
diff --git a/Src/wcwidth9.h b/Src/wcwidth9.h
new file mode 100644
index 000000000..448f548e9
--- /dev/null
+++ b/Src/wcwidth9.h
@@ -0,0 +1,1325 @@
+#ifndef WCWIDTH9_H
+#define WCWIDTH9_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+struct wcwidth9_interval {
+  long first;
+  long last;
+};
+
+static const struct wcwidth9_interval wcwidth9_private[] = {
+  {0x00e000, 0x00f8ff},
+  {0x0f0000, 0x0ffffd},
+  {0x100000, 0x10fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_nonprint[] = {
+  {0x0000, 0x001f},
+  {0x007f, 0x009f},
+  {0x00ad, 0x00ad},
+  {0x070f, 0x070f},
+  {0x180b, 0x180e},
+  {0x200b, 0x200f},
+  {0x2028, 0x2029},
+  {0x202a, 0x202e},
+  {0x206a, 0x206f},
+  {0xd800, 0xdfff},
+  {0xfeff, 0xfeff},
+  {0xfff9, 0xfffb},
+  {0xfffe, 0xffff},
+};
+
+static const struct wcwidth9_interval wcwidth9_combining[] = {
+  {0x0300, 0x036f},
+  {0x0483, 0x0489},
+  {0x0591, 0x05bd},
+  {0x05bf, 0x05bf},
+  {0x05c1, 0x05c2},
+  {0x05c4, 0x05c5},
+  {0x05c7, 0x05c7},
+  {0x0610, 0x061a},
+  {0x064b, 0x065f},
+  {0x0670, 0x0670},
+  {0x06d6, 0x06dc},
+  {0x06df, 0x06e4},
+  {0x06e7, 0x06e8},
+  {0x06ea, 0x06ed},
+  {0x0711, 0x0711},
+  {0x0730, 0x074a},
+  {0x07a6, 0x07b0},
+  {0x07eb, 0x07f3},
+  {0x0816, 0x0819},
+  {0x081b, 0x0823},
+  {0x0825, 0x0827},
+  {0x0829, 0x082d},
+  {0x0859, 0x085b},
+  {0x08d4, 0x08e1},
+  {0x08e3, 0x0903},
+  {0x093a, 0x093c},
+  {0x093e, 0x094f},
+  {0x0951, 0x0957},
+  {0x0962, 0x0963},
+  {0x0981, 0x0983},
+  {0x09bc, 0x09bc},
+  {0x09be, 0x09c4},
+  {0x09c7, 0x09c8},
+  {0x09cb, 0x09cd},
+  {0x09d7, 0x09d7},
+  {0x09e2, 0x09e3},
+  {0x0a01, 0x0a03},
+  {0x0a3c, 0x0a3c},
+  {0x0a3e, 0x0a42},
+  {0x0a47, 0x0a48},
+  {0x0a4b, 0x0a4d},
+  {0x0a51, 0x0a51},
+  {0x0a70, 0x0a71},
+  {0x0a75, 0x0a75},
+  {0x0a81, 0x0a83},
+  {0x0abc, 0x0abc},
+  {0x0abe, 0x0ac5},
+  {0x0ac7, 0x0ac9},
+  {0x0acb, 0x0acd},
+  {0x0ae2, 0x0ae3},
+  {0x0b01, 0x0b03},
+  {0x0b3c, 0x0b3c},
+  {0x0b3e, 0x0b44},
+  {0x0b47, 0x0b48},
+  {0x0b4b, 0x0b4d},
+  {0x0b56, 0x0b57},
+  {0x0b62, 0x0b63},
+  {0x0b82, 0x0b82},
+  {0x0bbe, 0x0bc2},
+  {0x0bc6, 0x0bc8},
+  {0x0bca, 0x0bcd},
+  {0x0bd7, 0x0bd7},
+  {0x0c00, 0x0c03},
+  {0x0c3e, 0x0c44},
+  {0x0c46, 0x0c48},
+  {0x0c4a, 0x0c4d},
+  {0x0c55, 0x0c56},
+  {0x0c62, 0x0c63},
+  {0x0c81, 0x0c83},
+  {0x0cbc, 0x0cbc},
+  {0x0cbe, 0x0cc4},
+  {0x0cc6, 0x0cc8},
+  {0x0cca, 0x0ccd},
+  {0x0cd5, 0x0cd6},
+  {0x0ce2, 0x0ce3},
+  {0x0d01, 0x0d03},
+  {0x0d3e, 0x0d44},
+  {0x0d46, 0x0d48},
+  {0x0d4a, 0x0d4d},
+  {0x0d57, 0x0d57},
+  {0x0d62, 0x0d63},
+  {0x0d82, 0x0d83},
+  {0x0dca, 0x0dca},
+  {0x0dcf, 0x0dd4},
+  {0x0dd6, 0x0dd6},
+  {0x0dd8, 0x0ddf},
+  {0x0df2, 0x0df3},
+  {0x0e31, 0x0e31},
+  {0x0e34, 0x0e3a},
+  {0x0e47, 0x0e4e},
+  {0x0eb1, 0x0eb1},
+  {0x0eb4, 0x0eb9},
+  {0x0ebb, 0x0ebc},
+  {0x0ec8, 0x0ecd},
+  {0x0f18, 0x0f19},
+  {0x0f35, 0x0f35},
+  {0x0f37, 0x0f37},
+  {0x0f39, 0x0f39},
+  {0x0f3e, 0x0f3f},
+  {0x0f71, 0x0f84},
+  {0x0f86, 0x0f87},
+  {0x0f8d, 0x0f97},
+  {0x0f99, 0x0fbc},
+  {0x0fc6, 0x0fc6},
+  {0x102b, 0x103e},
+  {0x1056, 0x1059},
+  {0x105e, 0x1060},
+  {0x1062, 0x1064},
+  {0x1067, 0x106d},
+  {0x1071, 0x1074},
+  {0x1082, 0x108d},
+  {0x108f, 0x108f},
+  {0x109a, 0x109d},
+  {0x135d, 0x135f},
+  {0x1712, 0x1714},
+  {0x1732, 0x1734},
+  {0x1752, 0x1753},
+  {0x1772, 0x1773},
+  {0x17b4, 0x17d3},
+  {0x17dd, 0x17dd},
+  {0x180b, 0x180d},
+  {0x1885, 0x1886},
+  {0x18a9, 0x18a9},
+  {0x1920, 0x192b},
+  {0x1930, 0x193b},
+  {0x1a17, 0x1a1b},
+  {0x1a55, 0x1a5e},
+  {0x1a60, 0x1a7c},
+  {0x1a7f, 0x1a7f},
+  {0x1ab0, 0x1abe},
+  {0x1b00, 0x1b04},
+  {0x1b34, 0x1b44},
+  {0x1b6b, 0x1b73},
+  {0x1b80, 0x1b82},
+  {0x1ba1, 0x1bad},
+  {0x1be6, 0x1bf3},
+  {0x1c24, 0x1c37},
+  {0x1cd0, 0x1cd2},
+  {0x1cd4, 0x1ce8},
+  {0x1ced, 0x1ced},
+  {0x1cf2, 0x1cf4},
+  {0x1cf8, 0x1cf9},
+  {0x1dc0, 0x1df5},
+  {0x1dfb, 0x1dff},
+  {0x20d0, 0x20f0},
+  {0x2cef, 0x2cf1},
+  {0x2d7f, 0x2d7f},
+  {0x2de0, 0x2dff},
+  {0x302a, 0x302f},
+  {0x3099, 0x309a},
+  {0xa66f, 0xa672},
+  {0xa674, 0xa67d},
+  {0xa69e, 0xa69f},
+  {0xa6f0, 0xa6f1},
+  {0xa802, 0xa802},
+  {0xa806, 0xa806},
+  {0xa80b, 0xa80b},
+  {0xa823, 0xa827},
+  {0xa880, 0xa881},
+  {0xa8b4, 0xa8c5},
+  {0xa8e0, 0xa8f1},
+  {0xa926, 0xa92d},
+  {0xa947, 0xa953},
+  {0xa980, 0xa983},
+  {0xa9b3, 0xa9c0},
+  {0xa9e5, 0xa9e5},
+  {0xaa29, 0xaa36},
+  {0xaa43, 0xaa43},
+  {0xaa4c, 0xaa4d},
+  {0xaa7b, 0xaa7d},
+  {0xaab0, 0xaab0},
+  {0xaab2, 0xaab4},
+  {0xaab7, 0xaab8},
+  {0xaabe, 0xaabf},
+  {0xaac1, 0xaac1},
+  {0xaaeb, 0xaaef},
+  {0xaaf5, 0xaaf6},
+  {0xabe3, 0xabea},
+  {0xabec, 0xabed},
+  {0xfb1e, 0xfb1e},
+  {0xfe00, 0xfe0f},
+  {0xfe20, 0xfe2f},
+  {0x101fd, 0x101fd},
+  {0x102e0, 0x102e0},
+  {0x10376, 0x1037a},
+  {0x10a01, 0x10a03},
+  {0x10a05, 0x10a06},
+  {0x10a0c, 0x10a0f},
+  {0x10a38, 0x10a3a},
+  {0x10a3f, 0x10a3f},
+  {0x10ae5, 0x10ae6},
+  {0x11000, 0x11002},
+  {0x11038, 0x11046},
+  {0x1107f, 0x11082},
+  {0x110b0, 0x110ba},
+  {0x11100, 0x11102},
+  {0x11127, 0x11134},
+  {0x11173, 0x11173},
+  {0x11180, 0x11182},
+  {0x111b3, 0x111c0},
+  {0x111ca, 0x111cc},
+  {0x1122c, 0x11237},
+  {0x1123e, 0x1123e},
+  {0x112df, 0x112ea},
+  {0x11300, 0x11303},
+  {0x1133c, 0x1133c},
+  {0x1133e, 0x11344},
+  {0x11347, 0x11348},
+  {0x1134b, 0x1134d},
+  {0x11357, 0x11357},
+  {0x11362, 0x11363},
+  {0x11366, 0x1136c},
+  {0x11370, 0x11374},
+  {0x11435, 0x11446},
+  {0x114b0, 0x114c3},
+  {0x115af, 0x115b5},
+  {0x115b8, 0x115c0},
+  {0x115dc, 0x115dd},
+  {0x11630, 0x11640},
+  {0x116ab, 0x116b7},
+  {0x1171d, 0x1172b},
+  {0x11c2f, 0x11c36},
+  {0x11c38, 0x11c3f},
+  {0x11c92, 0x11ca7},
+  {0x11ca9, 0x11cb6},
+  {0x16af0, 0x16af4},
+  {0x16b30, 0x16b36},
+  {0x16f51, 0x16f7e},
+  {0x16f8f, 0x16f92},
+  {0x1bc9d, 0x1bc9e},
+  {0x1d165, 0x1d169},
+  {0x1d16d, 0x1d172},
+  {0x1d17b, 0x1d182},
+  {0x1d185, 0x1d18b},
+  {0x1d1aa, 0x1d1ad},
+  {0x1d242, 0x1d244},
+  {0x1da00, 0x1da36},
+  {0x1da3b, 0x1da6c},
+  {0x1da75, 0x1da75},
+  {0x1da84, 0x1da84},
+  {0x1da9b, 0x1da9f},
+  {0x1daa1, 0x1daaf},
+  {0x1e000, 0x1e006},
+  {0x1e008, 0x1e018},
+  {0x1e01b, 0x1e021},
+  {0x1e023, 0x1e024},
+  {0x1e026, 0x1e02a},
+  {0x1e8d0, 0x1e8d6},
+  {0x1e944, 0x1e94a},
+  {0xe0100, 0xe01ef},
+};
+
+static const struct wcwidth9_interval wcwidth9_doublewidth[] = {
+  {0x1100, 0x115f},
+  {0x231a, 0x231b},
+  {0x2329, 0x232a},
+  {0x23e9, 0x23ec},
+  {0x23f0, 0x23f0},
+  {0x23f3, 0x23f3},
+  {0x25fd, 0x25fe},
+  {0x2614, 0x2615},
+  {0x2648, 0x2653},
+  {0x267f, 0x267f},
+  {0x2693, 0x2693},
+  {0x26a1, 0x26a1},
+  {0x26aa, 0x26ab},
+  {0x26bd, 0x26be},
+  {0x26c4, 0x26c5},
+  {0x26ce, 0x26ce},
+  {0x26d4, 0x26d4},
+  {0x26ea, 0x26ea},
+  {0x26f2, 0x26f3},
+  {0x26f5, 0x26f5},
+  {0x26fa, 0x26fa},
+  {0x26fd, 0x26fd},
+  {0x2705, 0x2705},
+  {0x270a, 0x270b},
+  {0x2728, 0x2728},
+  {0x274c, 0x274c},
+  {0x274e, 0x274e},
+  {0x2753, 0x2755},
+  {0x2757, 0x2757},
+  {0x2795, 0x2797},
+  {0x27b0, 0x27b0},
+  {0x27bf, 0x27bf},
+  {0x2b1b, 0x2b1c},
+  {0x2b50, 0x2b50},
+  {0x2b55, 0x2b55},
+  {0x2e80, 0x2e99},
+  {0x2e9b, 0x2ef3},
+  {0x2f00, 0x2fd5},
+  {0x2ff0, 0x2ffb},
+  {0x3000, 0x303e},
+  {0x3041, 0x3096},
+  {0x3099, 0x30ff},
+  {0x3105, 0x312d},
+  {0x3131, 0x318e},
+  {0x3190, 0x31ba},
+  {0x31c0, 0x31e3},
+  {0x31f0, 0x321e},
+  {0x3220, 0x3247},
+  {0x3250, 0x32fe},
+  {0x3300, 0x4dbf},
+  {0x4e00, 0xa48c},
+  {0xa490, 0xa4c6},
+  {0xa960, 0xa97c},
+  {0xac00, 0xd7a3},
+  {0xf900, 0xfaff},
+  {0xfe10, 0xfe19},
+  {0xfe30, 0xfe52},
+  {0xfe54, 0xfe66},
+  {0xfe68, 0xfe6b},
+  {0xff01, 0xff60},
+  {0xffe0, 0xffe6},
+  {0x16fe0, 0x16fe0},
+  {0x17000, 0x187ec},
+  {0x18800, 0x18af2},
+  {0x1b000, 0x1b001},
+  {0x1f004, 0x1f004},
+  {0x1f0cf, 0x1f0cf},
+  {0x1f18e, 0x1f18e},
+  {0x1f191, 0x1f19a},
+  {0x1f200, 0x1f202},
+  {0x1f210, 0x1f23b},
+  {0x1f240, 0x1f248},
+  {0x1f250, 0x1f251},
+  {0x1f300, 0x1f320},
+  {0x1f32d, 0x1f335},
+  {0x1f337, 0x1f37c},
+  {0x1f37e, 0x1f393},
+  {0x1f3a0, 0x1f3ca},
+  {0x1f3cf, 0x1f3d3},
+  {0x1f3e0, 0x1f3f0},
+  {0x1f3f4, 0x1f3f4},
+  {0x1f3f8, 0x1f43e},
+  {0x1f440, 0x1f440},
+  {0x1f442, 0x1f4fc},
+  {0x1f4ff, 0x1f53d},
+  {0x1f54b, 0x1f54e},
+  {0x1f550, 0x1f567},
+  {0x1f57a, 0x1f57a},
+  {0x1f595, 0x1f596},
+  {0x1f5a4, 0x1f5a4},
+  {0x1f5fb, 0x1f64f},
+  {0x1f680, 0x1f6c5},
+  {0x1f6cc, 0x1f6cc},
+  {0x1f6d0, 0x1f6d2},
+  {0x1f6eb, 0x1f6ec},
+  {0x1f6f4, 0x1f6f6},
+  {0x1f910, 0x1f91e},
+  {0x1f920, 0x1f927},
+  {0x1f930, 0x1f930},
+  {0x1f933, 0x1f93e},
+  {0x1f940, 0x1f94b},
+  {0x1f950, 0x1f95e},
+  {0x1f980, 0x1f991},
+  {0x1f9c0, 0x1f9c0},
+  {0x20000, 0x2fffd},
+  {0x30000, 0x3fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_ambiguous[] = {
+  {0x00a1, 0x00a1},
+  {0x00a4, 0x00a4},
+  {0x00a7, 0x00a8},
+  {0x00aa, 0x00aa},
+  {0x00ad, 0x00ae},
+  {0x00b0, 0x00b4},
+  {0x00b6, 0x00ba},
+  {0x00bc, 0x00bf},
+  {0x00c6, 0x00c6},
+  {0x00d0, 0x00d0},
+  {0x00d7, 0x00d8},
+  {0x00de, 0x00e1},
+  {0x00e6, 0x00e6},
+  {0x00e8, 0x00ea},
+  {0x00ec, 0x00ed},
+  {0x00f0, 0x00f0},
+  {0x00f2, 0x00f3},
+  {0x00f7, 0x00fa},
+  {0x00fc, 0x00fc},
+  {0x00fe, 0x00fe},
+  {0x0101, 0x0101},
+  {0x0111, 0x0111},
+  {0x0113, 0x0113},
+  {0x011b, 0x011b},
+  {0x0126, 0x0127},
+  {0x012b, 0x012b},
+  {0x0131, 0x0133},
+  {0x0138, 0x0138},
+  {0x013f, 0x0142},
+  {0x0144, 0x0144},
+  {0x0148, 0x014b},
+  {0x014d, 0x014d},
+  {0x0152, 0x0153},
+  {0x0166, 0x0167},
+  {0x016b, 0x016b},
+  {0x01ce, 0x01ce},
+  {0x01d0, 0x01d0},
+  {0x01d2, 0x01d2},
+  {0x01d4, 0x01d4},
+  {0x01d6, 0x01d6},
+  {0x01d8, 0x01d8},
+  {0x01da, 0x01da},
+  {0x01dc, 0x01dc},
+  {0x0251, 0x0251},
+  {0x0261, 0x0261},
+  {0x02c4, 0x02c4},
+  {0x02c7, 0x02c7},
+  {0x02c9, 0x02cb},
+  {0x02cd, 0x02cd},
+  {0x02d0, 0x02d0},
+  {0x02d8, 0x02db},
+  {0x02dd, 0x02dd},
+  {0x02df, 0x02df},
+  {0x0300, 0x036f},
+  {0x0391, 0x03a1},
+  {0x03a3, 0x03a9},
+  {0x03b1, 0x03c1},
+  {0x03c3, 0x03c9},
+  {0x0401, 0x0401},
+  {0x0410, 0x044f},
+  {0x0451, 0x0451},
+  {0x2010, 0x2010},
+  {0x2013, 0x2016},
+  {0x2018, 0x2019},
+  {0x201c, 0x201d},
+  {0x2020, 0x2022},
+  {0x2024, 0x2027},
+  {0x2030, 0x2030},
+  {0x2032, 0x2033},
+  {0x2035, 0x2035},
+  {0x203b, 0x203b},
+  {0x203e, 0x203e},
+  {0x2074, 0x2074},
+  {0x207f, 0x207f},
+  {0x2081, 0x2084},
+  {0x20ac, 0x20ac},
+  {0x2103, 0x2103},
+  {0x2105, 0x2105},
+  {0x2109, 0x2109},
+  {0x2113, 0x2113},
+  {0x2116, 0x2116},
+  {0x2121, 0x2122},
+  {0x2126, 0x2126},
+  {0x212b, 0x212b},
+  {0x2153, 0x2154},
+  {0x215b, 0x215e},
+  {0x2160, 0x216b},
+  {0x2170, 0x2179},
+  {0x2189, 0x2189},
+  {0x2190, 0x2199},
+  {0x21b8, 0x21b9},
+  {0x21d2, 0x21d2},
+  {0x21d4, 0x21d4},
+  {0x21e7, 0x21e7},
+  {0x2200, 0x2200},
+  {0x2202, 0x2203},
+  {0x2207, 0x2208},
+  {0x220b, 0x220b},
+  {0x220f, 0x220f},
+  {0x2211, 0x2211},
+  {0x2215, 0x2215},
+  {0x221a, 0x221a},
+  {0x221d, 0x2220},
+  {0x2223, 0x2223},
+  {0x2225, 0x2225},
+  {0x2227, 0x222c},
+  {0x222e, 0x222e},
+  {0x2234, 0x2237},
+  {0x223c, 0x223d},
+  {0x2248, 0x2248},
+  {0x224c, 0x224c},
+  {0x2252, 0x2252},
+  {0x2260, 0x2261},
+  {0x2264, 0x2267},
+  {0x226a, 0x226b},
+  {0x226e, 0x226f},
+  {0x2282, 0x2283},
+  {0x2286, 0x2287},
+  {0x2295, 0x2295},
+  {0x2299, 0x2299},
+  {0x22a5, 0x22a5},
+  {0x22bf, 0x22bf},
+  {0x2312, 0x2312},
+  {0x2460, 0x24e9},
+  {0x24eb, 0x254b},
+  {0x2550, 0x2573},
+  {0x2580, 0x258f},
+  {0x2592, 0x2595},
+  {0x25a0, 0x25a1},
+  {0x25a3, 0x25a9},
+  {0x25b2, 0x25b3},
+  {0x25b6, 0x25b7},
+  {0x25bc, 0x25bd},
+  {0x25c0, 0x25c1},
+  {0x25c6, 0x25c8},
+  {0x25cb, 0x25cb},
+  {0x25ce, 0x25d1},
+  {0x25e2, 0x25e5},
+  {0x25ef, 0x25ef},
+  {0x2605, 0x2606},
+  {0x2609, 0x2609},
+  {0x260e, 0x260f},
+  {0x261c, 0x261c},
+  {0x261e, 0x261e},
+  {0x2640, 0x2640},
+  {0x2642, 0x2642},
+  {0x2660, 0x2661},
+  {0x2663, 0x2665},
+  {0x2667, 0x266a},
+  {0x266c, 0x266d},
+  {0x266f, 0x266f},
+  {0x269e, 0x269f},
+  {0x26bf, 0x26bf},
+  {0x26c6, 0x26cd},
+  {0x26cf, 0x26d3},
+  {0x26d5, 0x26e1},
+  {0x26e3, 0x26e3},
+  {0x26e8, 0x26e9},
+  {0x26eb, 0x26f1},
+  {0x26f4, 0x26f4},
+  {0x26f6, 0x26f9},
+  {0x26fb, 0x26fc},
+  {0x26fe, 0x26ff},
+  {0x273d, 0x273d},
+  {0x2776, 0x277f},
+  {0x2b56, 0x2b59},
+  {0x3248, 0x324f},
+  {0xe000, 0xf8ff},
+  {0xfe00, 0xfe0f},
+  {0xfffd, 0xfffd},
+  {0x1f100, 0x1f10a},
+  {0x1f110, 0x1f12d},
+  {0x1f130, 0x1f169},
+  {0x1f170, 0x1f18d},
+  {0x1f18f, 0x1f190},
+  {0x1f19b, 0x1f1ac},
+  {0xe0100, 0xe01ef},
+  {0xf0000, 0xffffd},
+  {0x100000, 0x10fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_emoji_width[] = {
+  {0x1f1e6, 0x1f1ff},
+  {0x1f321, 0x1f321},
+  {0x1f324, 0x1f32c},
+  {0x1f336, 0x1f336},
+  {0x1f37d, 0x1f37d},
+  {0x1f396, 0x1f397},
+  {0x1f399, 0x1f39b},
+  {0x1f39e, 0x1f39f},
+  {0x1f3cb, 0x1f3ce},
+  {0x1f3d4, 0x1f3df},
+  {0x1f3f3, 0x1f3f5},
+  {0x1f3f7, 0x1f3f7},
+  {0x1f43f, 0x1f43f},
+  {0x1f441, 0x1f441},
+  {0x1f4fd, 0x1f4fd},
+  {0x1f549, 0x1f54a},
+  {0x1f56f, 0x1f570},
+  {0x1f573, 0x1f579},
+  {0x1f587, 0x1f587},
+  {0x1f58a, 0x1f58d},
+  {0x1f590, 0x1f590},
+  {0x1f5a5, 0x1f5a5},
+  {0x1f5a8, 0x1f5a8},
+  {0x1f5b1, 0x1f5b2},
+  {0x1f5bc, 0x1f5bc},
+  {0x1f5c2, 0x1f5c4},
+  {0x1f5d1, 0x1f5d3},
+  {0x1f5dc, 0x1f5de},
+  {0x1f5e1, 0x1f5e1},
+  {0x1f5e3, 0x1f5e3},
+  {0x1f5e8, 0x1f5e8},
+  {0x1f5ef, 0x1f5ef},
+  {0x1f5f3, 0x1f5f3},
+  {0x1f5fa, 0x1f5fa},
+  {0x1f6cb, 0x1f6cf},
+  {0x1f6e0, 0x1f6e5},
+  {0x1f6e9, 0x1f6e9},
+  {0x1f6f0, 0x1f6f0},
+  {0x1f6f3, 0x1f6f3},
+};
+
+static const struct wcwidth9_interval wcwidth9_not_assigned[] = {
+  {0x0378, 0x0379},
+  {0x0380, 0x0383},
+  {0x038b, 0x038b},
+  {0x038d, 0x038d},
+  {0x03a2, 0x03a2},
+  {0x0530, 0x0530},
+  {0x0557, 0x0558},
+  {0x0560, 0x0560},
+  {0x0588, 0x0588},
+  {0x058b, 0x058c},
+  {0x0590, 0x0590},
+  {0x05c8, 0x05cf},
+  {0x05eb, 0x05ef},
+  {0x05f5, 0x05ff},
+  {0x061d, 0x061d},
+  {0x070e, 0x070e},
+  {0x074b, 0x074c},
+  {0x07b2, 0x07bf},
+  {0x07fb, 0x07ff},
+  {0x082e, 0x082f},
+  {0x083f, 0x083f},
+  {0x085c, 0x085d},
+  {0x085f, 0x089f},
+  {0x08b5, 0x08b5},
+  {0x08be, 0x08d3},
+  {0x0984, 0x0984},
+  {0x098d, 0x098e},
+  {0x0991, 0x0992},
+  {0x09a9, 0x09a9},
+  {0x09b1, 0x09b1},
+  {0x09b3, 0x09b5},
+  {0x09ba, 0x09bb},
+  {0x09c5, 0x09c6},
+  {0x09c9, 0x09ca},
+  {0x09cf, 0x09d6},
+  {0x09d8, 0x09db},
+  {0x09de, 0x09de},
+  {0x09e4, 0x09e5},
+  {0x09fc, 0x0a00},
+  {0x0a04, 0x0a04},
+  {0x0a0b, 0x0a0e},
+  {0x0a11, 0x0a12},
+  {0x0a29, 0x0a29},
+  {0x0a31, 0x0a31},
+  {0x0a34, 0x0a34},
+  {0x0a37, 0x0a37},
+  {0x0a3a, 0x0a3b},
+  {0x0a3d, 0x0a3d},
+  {0x0a43, 0x0a46},
+  {0x0a49, 0x0a4a},
+  {0x0a4e, 0x0a50},
+  {0x0a52, 0x0a58},
+  {0x0a5d, 0x0a5d},
+  {0x0a5f, 0x0a65},
+  {0x0a76, 0x0a80},
+  {0x0a84, 0x0a84},
+  {0x0a8e, 0x0a8e},
+  {0x0a92, 0x0a92},
+  {0x0aa9, 0x0aa9},
+  {0x0ab1, 0x0ab1},
+  {0x0ab4, 0x0ab4},
+  {0x0aba, 0x0abb},
+  {0x0ac6, 0x0ac6},
+  {0x0aca, 0x0aca},
+  {0x0ace, 0x0acf},
+  {0x0ad1, 0x0adf},
+  {0x0ae4, 0x0ae5},
+  {0x0af2, 0x0af8},
+  {0x0afa, 0x0b00},
+  {0x0b04, 0x0b04},
+  {0x0b0d, 0x0b0e},
+  {0x0b11, 0x0b12},
+  {0x0b29, 0x0b29},
+  {0x0b31, 0x0b31},
+  {0x0b34, 0x0b34},
+  {0x0b3a, 0x0b3b},
+  {0x0b45, 0x0b46},
+  {0x0b49, 0x0b4a},
+  {0x0b4e, 0x0b55},
+  {0x0b58, 0x0b5b},
+  {0x0b5e, 0x0b5e},
+  {0x0b64, 0x0b65},
+  {0x0b78, 0x0b81},
+  {0x0b84, 0x0b84},
+  {0x0b8b, 0x0b8d},
+  {0x0b91, 0x0b91},
+  {0x0b96, 0x0b98},
+  {0x0b9b, 0x0b9b},
+  {0x0b9d, 0x0b9d},
+  {0x0ba0, 0x0ba2},
+  {0x0ba5, 0x0ba7},
+  {0x0bab, 0x0bad},
+  {0x0bba, 0x0bbd},
+  {0x0bc3, 0x0bc5},
+  {0x0bc9, 0x0bc9},
+  {0x0bce, 0x0bcf},
+  {0x0bd1, 0x0bd6},
+  {0x0bd8, 0x0be5},
+  {0x0bfb, 0x0bff},
+  {0x0c04, 0x0c04},
+  {0x0c0d, 0x0c0d},
+  {0x0c11, 0x0c11},
+  {0x0c29, 0x0c29},
+  {0x0c3a, 0x0c3c},
+  {0x0c45, 0x0c45},
+  {0x0c49, 0x0c49},
+  {0x0c4e, 0x0c54},
+  {0x0c57, 0x0c57},
+  {0x0c5b, 0x0c5f},
+  {0x0c64, 0x0c65},
+  {0x0c70, 0x0c77},
+  {0x0c84, 0x0c84},
+  {0x0c8d, 0x0c8d},
+  {0x0c91, 0x0c91},
+  {0x0ca9, 0x0ca9},
+  {0x0cb4, 0x0cb4},
+  {0x0cba, 0x0cbb},
+  {0x0cc5, 0x0cc5},
+  {0x0cc9, 0x0cc9},
+  {0x0cce, 0x0cd4},
+  {0x0cd7, 0x0cdd},
+  {0x0cdf, 0x0cdf},
+  {0x0ce4, 0x0ce5},
+  {0x0cf0, 0x0cf0},
+  {0x0cf3, 0x0d00},
+  {0x0d04, 0x0d04},
+  {0x0d0d, 0x0d0d},
+  {0x0d11, 0x0d11},
+  {0x0d3b, 0x0d3c},
+  {0x0d45, 0x0d45},
+  {0x0d49, 0x0d49},
+  {0x0d50, 0x0d53},
+  {0x0d64, 0x0d65},
+  {0x0d80, 0x0d81},
+  {0x0d84, 0x0d84},
+  {0x0d97, 0x0d99},
+  {0x0db2, 0x0db2},
+  {0x0dbc, 0x0dbc},
+  {0x0dbe, 0x0dbf},
+  {0x0dc7, 0x0dc9},
+  {0x0dcb, 0x0dce},
+  {0x0dd5, 0x0dd5},
+  {0x0dd7, 0x0dd7},
+  {0x0de0, 0x0de5},
+  {0x0df0, 0x0df1},
+  {0x0df5, 0x0e00},
+  {0x0e3b, 0x0e3e},
+  {0x0e5c, 0x0e80},
+  {0x0e83, 0x0e83},
+  {0x0e85, 0x0e86},
+  {0x0e89, 0x0e89},
+  {0x0e8b, 0x0e8c},
+  {0x0e8e, 0x0e93},
+  {0x0e98, 0x0e98},
+  {0x0ea0, 0x0ea0},
+  {0x0ea4, 0x0ea4},
+  {0x0ea6, 0x0ea6},
+  {0x0ea8, 0x0ea9},
+  {0x0eac, 0x0eac},
+  {0x0eba, 0x0eba},
+  {0x0ebe, 0x0ebf},
+  {0x0ec5, 0x0ec5},
+  {0x0ec7, 0x0ec7},
+  {0x0ece, 0x0ecf},
+  {0x0eda, 0x0edb},
+  {0x0ee0, 0x0eff},
+  {0x0f48, 0x0f48},
+  {0x0f6d, 0x0f70},
+  {0x0f98, 0x0f98},
+  {0x0fbd, 0x0fbd},
+  {0x0fcd, 0x0fcd},
+  {0x0fdb, 0x0fff},
+  {0x10c6, 0x10c6},
+  {0x10c8, 0x10cc},
+  {0x10ce, 0x10cf},
+  {0x1249, 0x1249},
+  {0x124e, 0x124f},
+  {0x1257, 0x1257},
+  {0x1259, 0x1259},
+  {0x125e, 0x125f},
+  {0x1289, 0x1289},
+  {0x128e, 0x128f},
+  {0x12b1, 0x12b1},
+  {0x12b6, 0x12b7},
+  {0x12bf, 0x12bf},
+  {0x12c1, 0x12c1},
+  {0x12c6, 0x12c7},
+  {0x12d7, 0x12d7},
+  {0x1311, 0x1311},
+  {0x1316, 0x1317},
+  {0x135b, 0x135c},
+  {0x137d, 0x137f},
+  {0x139a, 0x139f},
+  {0x13f6, 0x13f7},
+  {0x13fe, 0x13ff},
+  {0x169d, 0x169f},
+  {0x16f9, 0x16ff},
+  {0x170d, 0x170d},
+  {0x1715, 0x171f},
+  {0x1737, 0x173f},
+  {0x1754, 0x175f},
+  {0x176d, 0x176d},
+  {0x1771, 0x1771},
+  {0x1774, 0x177f},
+  {0x17de, 0x17df},
+  {0x17ea, 0x17ef},
+  {0x17fa, 0x17ff},
+  {0x180f, 0x180f},
+  {0x181a, 0x181f},
+  {0x1878, 0x187f},
+  {0x18ab, 0x18af},
+  {0x18f6, 0x18ff},
+  {0x191f, 0x191f},
+  {0x192c, 0x192f},
+  {0x193c, 0x193f},
+  {0x1941, 0x1943},
+  {0x196e, 0x196f},
+  {0x1975, 0x197f},
+  {0x19ac, 0x19af},
+  {0x19ca, 0x19cf},
+  {0x19db, 0x19dd},
+  {0x1a1c, 0x1a1d},
+  {0x1a5f, 0x1a5f},
+  {0x1a7d, 0x1a7e},
+  {0x1a8a, 0x1a8f},
+  {0x1a9a, 0x1a9f},
+  {0x1aae, 0x1aaf},
+  {0x1abf, 0x1aff},
+  {0x1b4c, 0x1b4f},
+  {0x1b7d, 0x1b7f},
+  {0x1bf4, 0x1bfb},
+  {0x1c38, 0x1c3a},
+  {0x1c4a, 0x1c4c},
+  {0x1c89, 0x1cbf},
+  {0x1cc8, 0x1ccf},
+  {0x1cf7, 0x1cf7},
+  {0x1cfa, 0x1cff},
+  {0x1df6, 0x1dfa},
+  {0x1f16, 0x1f17},
+  {0x1f1e, 0x1f1f},
+  {0x1f46, 0x1f47},
+  {0x1f4e, 0x1f4f},
+  {0x1f58, 0x1f58},
+  {0x1f5a, 0x1f5a},
+  {0x1f5c, 0x1f5c},
+  {0x1f5e, 0x1f5e},
+  {0x1f7e, 0x1f7f},
+  {0x1fb5, 0x1fb5},
+  {0x1fc5, 0x1fc5},
+  {0x1fd4, 0x1fd5},
+  {0x1fdc, 0x1fdc},
+  {0x1ff0, 0x1ff1},
+  {0x1ff5, 0x1ff5},
+  {0x1fff, 0x1fff},
+  {0x2065, 0x2065},
+  {0x2072, 0x2073},
+  {0x208f, 0x208f},
+  {0x209d, 0x209f},
+  {0x20bf, 0x20cf},
+  {0x20f1, 0x20ff},
+  {0x218c, 0x218f},
+  {0x23ff, 0x23ff},
+  {0x2427, 0x243f},
+  {0x244b, 0x245f},
+  {0x2b74, 0x2b75},
+  {0x2b96, 0x2b97},
+  {0x2bba, 0x2bbc},
+  {0x2bc9, 0x2bc9},
+  {0x2bd2, 0x2beb},
+  {0x2bf0, 0x2bff},
+  {0x2c2f, 0x2c2f},
+  {0x2c5f, 0x2c5f},
+  {0x2cf4, 0x2cf8},
+  {0x2d26, 0x2d26},
+  {0x2d28, 0x2d2c},
+  {0x2d2e, 0x2d2f},
+  {0x2d68, 0x2d6e},
+  {0x2d71, 0x2d7e},
+  {0x2d97, 0x2d9f},
+  {0x2da7, 0x2da7},
+  {0x2daf, 0x2daf},
+  {0x2db7, 0x2db7},
+  {0x2dbf, 0x2dbf},
+  {0x2dc7, 0x2dc7},
+  {0x2dcf, 0x2dcf},
+  {0x2dd7, 0x2dd7},
+  {0x2ddf, 0x2ddf},
+  {0x2e45, 0x2e7f},
+  {0x2e9a, 0x2e9a},
+  {0x2ef4, 0x2eff},
+  {0x2fd6, 0x2fef},
+  {0x2ffc, 0x2fff},
+  {0x3040, 0x3040},
+  {0x3097, 0x3098},
+  {0x3100, 0x3104},
+  {0x312e, 0x3130},
+  {0x318f, 0x318f},
+  {0x31bb, 0x31bf},
+  {0x31e4, 0x31ef},
+  {0x321f, 0x321f},
+  {0x32ff, 0x32ff},
+  {0x4db6, 0x4dbf},
+  {0x9fd6, 0x9fff},
+  {0xa48d, 0xa48f},
+  {0xa4c7, 0xa4cf},
+  {0xa62c, 0xa63f},
+  {0xa6f8, 0xa6ff},
+  {0xa7af, 0xa7af},
+  {0xa7b8, 0xa7f6},
+  {0xa82c, 0xa82f},
+  {0xa83a, 0xa83f},
+  {0xa878, 0xa87f},
+  {0xa8c6, 0xa8cd},
+  {0xa8da, 0xa8df},
+  {0xa8fe, 0xa8ff},
+  {0xa954, 0xa95e},
+  {0xa97d, 0xa97f},
+  {0xa9ce, 0xa9ce},
+  {0xa9da, 0xa9dd},
+  {0xa9ff, 0xa9ff},
+  {0xaa37, 0xaa3f},
+  {0xaa4e, 0xaa4f},
+  {0xaa5a, 0xaa5b},
+  {0xaac3, 0xaada},
+  {0xaaf7, 0xab00},
+  {0xab07, 0xab08},
+  {0xab0f, 0xab10},
+  {0xab17, 0xab1f},
+  {0xab27, 0xab27},
+  {0xab2f, 0xab2f},
+  {0xab66, 0xab6f},
+  {0xabee, 0xabef},
+  {0xabfa, 0xabff},
+  {0xd7a4, 0xd7af},
+  {0xd7c7, 0xd7ca},
+  {0xd7fc, 0xd7ff},
+  {0xfa6e, 0xfa6f},
+  {0xfada, 0xfaff},
+  {0xfb07, 0xfb12},
+  {0xfb18, 0xfb1c},
+  {0xfb37, 0xfb37},
+  {0xfb3d, 0xfb3d},
+  {0xfb3f, 0xfb3f},
+  {0xfb42, 0xfb42},
+  {0xfb45, 0xfb45},
+  {0xfbc2, 0xfbd2},
+  {0xfd40, 0xfd4f},
+  {0xfd90, 0xfd91},
+  {0xfdc8, 0xfdef},
+  {0xfdfe, 0xfdff},
+  {0xfe1a, 0xfe1f},
+  {0xfe53, 0xfe53},
+  {0xfe67, 0xfe67},
+  {0xfe6c, 0xfe6f},
+  {0xfe75, 0xfe75},
+  {0xfefd, 0xfefe},
+  {0xff00, 0xff00},
+  {0xffbf, 0xffc1},
+  {0xffc8, 0xffc9},
+  {0xffd0, 0xffd1},
+  {0xffd8, 0xffd9},
+  {0xffdd, 0xffdf},
+  {0xffe7, 0xffe7},
+  {0xffef, 0xfff8},
+  {0xfffe, 0xffff},
+  {0x1000c, 0x1000c},
+  {0x10027, 0x10027},
+  {0x1003b, 0x1003b},
+  {0x1003e, 0x1003e},
+  {0x1004e, 0x1004f},
+  {0x1005e, 0x1007f},
+  {0x100fb, 0x100ff},
+  {0x10103, 0x10106},
+  {0x10134, 0x10136},
+  {0x1018f, 0x1018f},
+  {0x1019c, 0x1019f},
+  {0x101a1, 0x101cf},
+  {0x101fe, 0x1027f},
+  {0x1029d, 0x1029f},
+  {0x102d1, 0x102df},
+  {0x102fc, 0x102ff},
+  {0x10324, 0x1032f},
+  {0x1034b, 0x1034f},
+  {0x1037b, 0x1037f},
+  {0x1039e, 0x1039e},
+  {0x103c4, 0x103c7},
+  {0x103d6, 0x103ff},
+  {0x1049e, 0x1049f},
+  {0x104aa, 0x104af},
+  {0x104d4, 0x104d7},
+  {0x104fc, 0x104ff},
+  {0x10528, 0x1052f},
+  {0x10564, 0x1056e},
+  {0x10570, 0x105ff},
+  {0x10737, 0x1073f},
+  {0x10756, 0x1075f},
+  {0x10768, 0x107ff},
+  {0x10806, 0x10807},
+  {0x10809, 0x10809},
+  {0x10836, 0x10836},
+  {0x10839, 0x1083b},
+  {0x1083d, 0x1083e},
+  {0x10856, 0x10856},
+  {0x1089f, 0x108a6},
+  {0x108b0, 0x108df},
+  {0x108f3, 0x108f3},
+  {0x108f6, 0x108fa},
+  {0x1091c, 0x1091e},
+  {0x1093a, 0x1093e},
+  {0x10940, 0x1097f},
+  {0x109b8, 0x109bb},
+  {0x109d0, 0x109d1},
+  {0x10a04, 0x10a04},
+  {0x10a07, 0x10a0b},
+  {0x10a14, 0x10a14},
+  {0x10a18, 0x10a18},
+  {0x10a34, 0x10a37},
+  {0x10a3b, 0x10a3e},
+  {0x10a48, 0x10a4f},
+  {0x10a59, 0x10a5f},
+  {0x10aa0, 0x10abf},
+  {0x10ae7, 0x10aea},
+  {0x10af7, 0x10aff},
+  {0x10b36, 0x10b38},
+  {0x10b56, 0x10b57},
+  {0x10b73, 0x10b77},
+  {0x10b92, 0x10b98},
+  {0x10b9d, 0x10ba8},
+  {0x10bb0, 0x10bff},
+  {0x10c49, 0x10c7f},
+  {0x10cb3, 0x10cbf},
+  {0x10cf3, 0x10cf9},
+  {0x10d00, 0x10e5f},
+  {0x10e7f, 0x10fff},
+  {0x1104e, 0x11051},
+  {0x11070, 0x1107e},
+  {0x110c2, 0x110cf},
+  {0x110e9, 0x110ef},
+  {0x110fa, 0x110ff},
+  {0x11135, 0x11135},
+  {0x11144, 0x1114f},
+  {0x11177, 0x1117f},
+  {0x111ce, 0x111cf},
+  {0x111e0, 0x111e0},
+  {0x111f5, 0x111ff},
+  {0x11212, 0x11212},
+  {0x1123f, 0x1127f},
+  {0x11287, 0x11287},
+  {0x11289, 0x11289},
+  {0x1128e, 0x1128e},
+  {0x1129e, 0x1129e},
+  {0x112aa, 0x112af},
+  {0x112eb, 0x112ef},
+  {0x112fa, 0x112ff},
+  {0x11304, 0x11304},
+  {0x1130d, 0x1130e},
+  {0x11311, 0x11312},
+  {0x11329, 0x11329},
+  {0x11331, 0x11331},
+  {0x11334, 0x11334},
+  {0x1133a, 0x1133b},
+  {0x11345, 0x11346},
+  {0x11349, 0x1134a},
+  {0x1134e, 0x1134f},
+  {0x11351, 0x11356},
+  {0x11358, 0x1135c},
+  {0x11364, 0x11365},
+  {0x1136d, 0x1136f},
+  {0x11375, 0x113ff},
+  {0x1145a, 0x1145a},
+  {0x1145c, 0x1145c},
+  {0x1145e, 0x1147f},
+  {0x114c8, 0x114cf},
+  {0x114da, 0x1157f},
+  {0x115b6, 0x115b7},
+  {0x115de, 0x115ff},
+  {0x11645, 0x1164f},
+  {0x1165a, 0x1165f},
+  {0x1166d, 0x1167f},
+  {0x116b8, 0x116bf},
+  {0x116ca, 0x116ff},
+  {0x1171a, 0x1171c},
+  {0x1172c, 0x1172f},
+  {0x11740, 0x1189f},
+  {0x118f3, 0x118fe},
+  {0x11900, 0x11abf},
+  {0x11af9, 0x11bff},
+  {0x11c09, 0x11c09},
+  {0x11c37, 0x11c37},
+  {0x11c46, 0x11c4f},
+  {0x11c6d, 0x11c6f},
+  {0x11c90, 0x11c91},
+  {0x11ca8, 0x11ca8},
+  {0x11cb7, 0x11fff},
+  {0x1239a, 0x123ff},
+  {0x1246f, 0x1246f},
+  {0x12475, 0x1247f},
+  {0x12544, 0x12fff},
+  {0x1342f, 0x143ff},
+  {0x14647, 0x167ff},
+  {0x16a39, 0x16a3f},
+  {0x16a5f, 0x16a5f},
+  {0x16a6a, 0x16a6d},
+  {0x16a70, 0x16acf},
+  {0x16aee, 0x16aef},
+  {0x16af6, 0x16aff},
+  {0x16b46, 0x16b4f},
+  {0x16b5a, 0x16b5a},
+  {0x16b62, 0x16b62},
+  {0x16b78, 0x16b7c},
+  {0x16b90, 0x16eff},
+  {0x16f45, 0x16f4f},
+  {0x16f7f, 0x16f8e},
+  {0x16fa0, 0x16fdf},
+  {0x16fe1, 0x16fff},
+  {0x187ed, 0x187ff},
+  {0x18af3, 0x1afff},
+  {0x1b002, 0x1bbff},
+  {0x1bc6b, 0x1bc6f},
+  {0x1bc7d, 0x1bc7f},
+  {0x1bc89, 0x1bc8f},
+  {0x1bc9a, 0x1bc9b},
+  {0x1bca4, 0x1cfff},
+  {0x1d0f6, 0x1d0ff},
+  {0x1d127, 0x1d128},
+  {0x1d1e9, 0x1d1ff},
+  {0x1d246, 0x1d2ff},
+  {0x1d357, 0x1d35f},
+  {0x1d372, 0x1d3ff},
+  {0x1d455, 0x1d455},
+  {0x1d49d, 0x1d49d},
+  {0x1d4a0, 0x1d4a1},
+  {0x1d4a3, 0x1d4a4},
+  {0x1d4a7, 0x1d4a8},
+  {0x1d4ad, 0x1d4ad},
+  {0x1d4ba, 0x1d4ba},
+  {0x1d4bc, 0x1d4bc},
+  {0x1d4c4, 0x1d4c4},
+  {0x1d506, 0x1d506},
+  {0x1d50b, 0x1d50c},
+  {0x1d515, 0x1d515},
+  {0x1d51d, 0x1d51d},
+  {0x1d53a, 0x1d53a},
+  {0x1d53f, 0x1d53f},
+  {0x1d545, 0x1d545},
+  {0x1d547, 0x1d549},
+  {0x1d551, 0x1d551},
+  {0x1d6a6, 0x1d6a7},
+  {0x1d7cc, 0x1d7cd},
+  {0x1da8c, 0x1da9a},
+  {0x1daa0, 0x1daa0},
+  {0x1dab0, 0x1dfff},
+  {0x1e007, 0x1e007},
+  {0x1e019, 0x1e01a},
+  {0x1e022, 0x1e022},
+  {0x1e025, 0x1e025},
+  {0x1e02b, 0x1e7ff},
+  {0x1e8c5, 0x1e8c6},
+  {0x1e8d7, 0x1e8ff},
+  {0x1e94b, 0x1e94f},
+  {0x1e95a, 0x1e95d},
+  {0x1e960, 0x1edff},
+  {0x1ee04, 0x1ee04},
+  {0x1ee20, 0x1ee20},
+  {0x1ee23, 0x1ee23},
+  {0x1ee25, 0x1ee26},
+  {0x1ee28, 0x1ee28},
+  {0x1ee33, 0x1ee33},
+  {0x1ee38, 0x1ee38},
+  {0x1ee3a, 0x1ee3a},
+  {0x1ee3c, 0x1ee41},
+  {0x1ee43, 0x1ee46},
+  {0x1ee48, 0x1ee48},
+  {0x1ee4a, 0x1ee4a},
+  {0x1ee4c, 0x1ee4c},
+  {0x1ee50, 0x1ee50},
+  {0x1ee53, 0x1ee53},
+  {0x1ee55, 0x1ee56},
+  {0x1ee58, 0x1ee58},
+  {0x1ee5a, 0x1ee5a},
+  {0x1ee5c, 0x1ee5c},
+  {0x1ee5e, 0x1ee5e},
+  {0x1ee60, 0x1ee60},
+  {0x1ee63, 0x1ee63},
+  {0x1ee65, 0x1ee66},
+  {0x1ee6b, 0x1ee6b},
+  {0x1ee73, 0x1ee73},
+  {0x1ee78, 0x1ee78},
+  {0x1ee7d, 0x1ee7d},
+  {0x1ee7f, 0x1ee7f},
+  {0x1ee8a, 0x1ee8a},
+  {0x1ee9c, 0x1eea0},
+  {0x1eea4, 0x1eea4},
+  {0x1eeaa, 0x1eeaa},
+  {0x1eebc, 0x1eeef},
+  {0x1eef2, 0x1efff},
+  {0x1f02c, 0x1f02f},
+  {0x1f094, 0x1f09f},
+  {0x1f0af, 0x1f0b0},
+  {0x1f0c0, 0x1f0c0},
+  {0x1f0d0, 0x1f0d0},
+  {0x1f0f6, 0x1f0ff},
+  {0x1f10d, 0x1f10f},
+  {0x1f12f, 0x1f12f},
+  {0x1f16c, 0x1f16f},
+  {0x1f1ad, 0x1f1e5},
+  {0x1f203, 0x1f20f},
+  {0x1f23c, 0x1f23f},
+  {0x1f249, 0x1f24f},
+  {0x1f252, 0x1f2ff},
+  {0x1f6d3, 0x1f6df},
+  {0x1f6ed, 0x1f6ef},
+  {0x1f6f7, 0x1f6ff},
+  {0x1f774, 0x1f77f},
+  {0x1f7d5, 0x1f7ff},
+  {0x1f80c, 0x1f80f},
+  {0x1f848, 0x1f84f},
+  {0x1f85a, 0x1f85f},
+  {0x1f888, 0x1f88f},
+  {0x1f8ae, 0x1f90f},
+  {0x1f91f, 0x1f91f},
+  {0x1f928, 0x1f92f},
+  {0x1f931, 0x1f932},
+  {0x1f93f, 0x1f93f},
+  {0x1f94c, 0x1f94f},
+  {0x1f95f, 0x1f97f},
+  {0x1f992, 0x1f9bf},
+  {0x1f9c1, 0x1ffff},
+  {0x2a6d7, 0x2a6ff},
+  {0x2b735, 0x2b73f},
+  {0x2b81e, 0x2b81f},
+  {0x2cea2, 0x2f7ff},
+  {0x2fa1e, 0xe0000},
+  {0xe0002, 0xe001f},
+  {0xe0080, 0xe00ff},
+  {0xe01f0, 0xeffff},
+  {0xffffe, 0xfffff},
+};
+
+#define WCWIDTH9_ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
+
+static inline bool wcwidth9_intable(const struct wcwidth9_interval *table, size_t n_items, int c) {
+  int mid, bot, top;
+
+  if (c < table[0].first) {
+    return false;
+  }
+
+  bot = 0;
+  top = (int)(n_items - 1);
+  while (top >= bot) {
+    mid = (bot + top) / 2;
+
+    if (table[mid].last < c) {
+      bot = mid + 1;
+    } else if (table[mid].first > c) {
+      top = mid - 1;
+    } else {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+static inline int wcwidth9(int c) {
+  if (c == 0) {
+    return 0;
+  }
+  if (c < 0|| c > 0x10ffff) {
+    return -1;
+  }
+
+  if (wcwidth9_intable(wcwidth9_nonprint, WCWIDTH9_ARRAY_SIZE(wcwidth9_nonprint), c)) {
+    return -1;
+  }
+
+  if (wcwidth9_intable(wcwidth9_combining, WCWIDTH9_ARRAY_SIZE(wcwidth9_combining), c)) {
+    return 0;
+  }
+
+  if (wcwidth9_intable(wcwidth9_not_assigned, WCWIDTH9_ARRAY_SIZE(wcwidth9_not_assigned), c)) {
+    return -1;
+  }
+
+  if (wcwidth9_intable(wcwidth9_private, WCWIDTH9_ARRAY_SIZE(wcwidth9_private), c)) {
+    return -3;
+  }
+
+  if (wcwidth9_intable(wcwidth9_ambiguous, WCWIDTH9_ARRAY_SIZE(wcwidth9_ambiguous), c)) {
+    return -2;
+  }
+
+  if (wcwidth9_intable(wcwidth9_doublewidth, WCWIDTH9_ARRAY_SIZE(wcwidth9_doublewidth), c)) {
+    return 2;
+  }
+
+  if (wcwidth9_intable(wcwidth9_emoji_width, WCWIDTH9_ARRAY_SIZE(wcwidth9_emoji_width), c)) {
+    return 2;
+  }
+
+  return 1;
+}
+
+#endif /* WCWIDTH9_H */
diff --git a/Src/zsh.h b/Src/zsh.h
index fe88efe69..7df5add86 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -238,6 +238,16 @@ struct mathfunc {
 #define PATCHARS "#^*()|[]<>?~\\"
 
 /*
+ * Check for a possibly tokenized dash.
+ *
+ * A dash only needs to be a token in a character range, [a-z], but
+ * it's difficult in general to ensure that.  So it's turned into
+ * a token at the usual point in the lexer.  However, we need
+ * to check for a literal dash at many points.
+ */
+#define IS_DASH(x) ((x) == '-' || (x) == Dash)
+
+/*
  * Types of quote.  This is used in various places, so care needs
  * to be taken when changing them.  (Oooh, don't you look surprised.)
  * - Passed to quotestring() to indicate style.  This is the ultimate
@@ -489,6 +499,7 @@ typedef struct complist  *Complist;
 typedef struct conddef   *Conddef;
 typedef struct dirsav    *Dirsav;
 typedef struct emulation_options *Emulation_options;
+typedef struct execcmd_params *Execcmd_params;
 typedef struct features  *Features;
 typedef struct feature_enables  *Feature_enables;
 typedef struct funcstack *Funcstack;
@@ -622,27 +633,34 @@ struct timedfn {
 /* (1<<4) is used for Z_END, see the wordcode definitions */
 /* (1<<5) is used for Z_SIMPLE, see the wordcode definitions */
 
-/* Condition types. */
+/*
+ * Condition types.
+ *
+ * Careful when changing these: both cond_binary_ops in text.c and
+ * condstr in cond.c depend on these.  (The zsh motto is "two instances
+ * are better than one".  Or something.)
+ */
 
 #define COND_NOT    0
 #define COND_AND    1
 #define COND_OR     2
 #define COND_STREQ  3
-#define COND_STRNEQ 4
-#define COND_STRLT  5
-#define COND_STRGTR 6
-#define COND_NT     7
-#define COND_OT     8
-#define COND_EF     9
-#define COND_EQ    10
-#define COND_NE    11
-#define COND_LT    12
-#define COND_GT    13
-#define COND_LE    14
-#define COND_GE    15
-#define COND_REGEX 16
-#define COND_MOD   17
-#define COND_MODI  18
+#define COND_STRDEQ 4
+#define COND_STRNEQ 5
+#define COND_STRLT  6
+#define COND_STRGTR 7
+#define COND_NT     8
+#define COND_OT     9
+#define COND_EF    10
+#define COND_EQ    11
+#define COND_NE    12
+#define COND_LT    13
+#define COND_GT    14
+#define COND_LE    15
+#define COND_GE    16
+#define COND_REGEX 17
+#define COND_MOD   18
+#define COND_MODI  19
 
 typedef int (*CondHandler) _((char **, int));
 
@@ -976,7 +994,8 @@ struct jobfile {
 
 struct job {
     pid_t gleader;		/* process group leader of this job  */
-    pid_t other;		/* subjob id or subshell pid         */
+    pid_t other;		/* subjob id (SUPERJOB)
+				 * or subshell pid (SUBJOB) */
     int stat;                   /* see STATs below                   */
     char *pwd;			/* current working dir of shell when *
 				 * this job was spawned              */
@@ -1008,6 +1027,8 @@ struct job {
 #define STAT_SUBLEADER  (0x2000) /* is super-job, but leader is sub-shell */
 
 #define STAT_BUILTIN    (0x4000) /* job at tail of pipeline is a builtin */
+#define STAT_SUBJOB_ORPHANED (0x8000)
+                                 /* STAT_SUBJOB with STAT_SUPERJOB exited */
 
 #define SP_RUNNING -1		/* fake status for jobs currently running */
 
@@ -1059,6 +1080,7 @@ struct execstack {
     int trapisfunc;
     int traplocallevel;
     int noerrs;
+    int this_noerrexit;
     char *underscore;
 };
 
@@ -1221,7 +1243,9 @@ struct cmdnam {
 
 struct shfunc {
     struct hashnode node;
-    char *filename;             /* Name of file located in */
+    char *filename;             /* Name of file located in.
+				   For not yet autoloaded file, name
+				   of explicit directory, if not NULL. */
     zlong lineno;		/* line number in above file */
     Eprog funcdef;		/* function definition    */
     Eprog redir;                /* redirections to apply */
@@ -1381,6 +1405,21 @@ struct builtin {
  */
 #define BINF_ASSIGN		(1<<19)
 
+/**
+ * Parameters passed to execcmd().
+ * These are not opaque --- they are also used by the pipeline manager.
+ */
+struct execcmd_params {
+    LinkList args;		/* All command prefixes, arguments & options */
+    LinkList redir;		/* Redirections */
+    Wordcode beg;		/* The code at the start of the command */
+    Wordcode varspc;		/* The code for assignment parsed as such */
+    Wordcode assignspc;		/* The code for assignment parsed as typeset */
+    int type;			/* The WC_* type of the command */
+    int postassigns;		/* The number of assignspc assiguments */
+    int htok;			/* tokens in parameter list */
+};
+
 struct module {
     struct hashnode node;
     union {
@@ -1502,6 +1541,7 @@ struct patstralloc {
 
 /* Flags used in pattern matchers (Patprog) and passed down to patcompile */
 
+#define PAT_HEAPDUP	0x0000	/* Dummy flag for default behavior */
 #define PAT_FILE	0x0001	/* Pattern is a file name */
 #define PAT_FILET	0x0002	/* Pattern is top level file, affects ~ */
 #define PAT_ANY		0x0004	/* Match anything (cheap "*") */
@@ -1562,8 +1602,8 @@ struct zpc_disables_save {
     struct zpc_disables_save *next;
     /*
      * Bit vector of ZPC_COUNT disabled characters.
-     * We'll live dangerously and assumed ZPC_COUNT is no greater
-     * than the number of bits an an unsigned int.
+     * We'll live dangerously and assume ZPC_COUNT is no greater
+     * than the number of bits in an unsigned int.
      */
     unsigned int disables;
 };
@@ -1778,6 +1818,7 @@ struct tieddata {
 #define PM_READONLY	(1<<10)	/* readonly                                 */
 #define PM_TAGGED	(1<<11)	/* tagged                                   */
 #define PM_EXPORTED	(1<<12)	/* exported                                 */
+#define PM_ABSPATH_USED (1<<12) /* (function): loaded using absolute path   */
 
 /* The following are the same since they *
  * both represent -U option to typeset   */
@@ -1785,7 +1826,9 @@ struct tieddata {
 #define PM_UNALIASED	(1<<13)	/* do not expand aliases when autoloading   */
 
 #define PM_HIDE		(1<<14)	/* Special behaviour hidden by local        */
+#define PM_CUR_FPATH    (1<<14) /* (function): can use $fpath with filename */
 #define PM_HIDEVAL	(1<<15)	/* Value not shown in `typeset' commands    */
+#define PM_WARNNESTED   (1<<15) /* (function): non-recursive WARNNESTEDVAR  */
 #define PM_TIED 	(1<<16)	/* array tied to colon-path or v.v.         */
 #define PM_TAGGED_LOCAL (1<<16) /* (function): non-recursive PM_TAGGED      */
 
@@ -1795,6 +1838,9 @@ struct tieddata {
 #define PM_CHECKLEN     (1<<20) /* cached length is checked */
 
 /* Remaining flags do not correspond directly to command line arguments */
+#define PM_DONTIMPORT_SUID (1<<19) /* do not import if running setuid */
+#define PM_LOADDIR      (1<<19) /* (function) filename gives load directory */
+#define PM_SINGLE       (1<<20) /* special can only have a single instance  */
 #define PM_LOCAL	(1<<21) /* this parameter will be made local        */
 #define PM_SPECIAL	(1<<22) /* special builtin parameter                */
 #define PM_DONTIMPORT	(1<<23)	/* do not import this variable              */
@@ -1986,9 +2032,15 @@ struct paramdef {
  * Flags for assignsparam and assignaparam.
  */
 enum {
+    /* Add to rather than override value */
     ASSPM_AUGMENT = 1 << 0,
+    /* Test for warning if creating global variable in function */
     ASSPM_WARN_CREATE = 1 << 1,
-    ASSPM_ENV_IMPORT = 1 << 2
+    /* Test for warning if using nested variable in function */
+    ASSPM_WARN_NESTED = 1 << 2,
+    ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED),
+    /* Import from environment, so exercise care evaluating value */
+    ASSPM_ENV_IMPORT = 1 << 3,
 };
 
 /* node for named directory hash table (nameddirtab) */
@@ -2196,6 +2248,7 @@ struct histent {
 enum {
     OPT_INVALID,
     ALIASESOPT,
+    ALIASFUNCDEF,
     ALLEXPORT,
     ALWAYSLASTPROMPT,
     ALWAYSTOEND,
@@ -2369,6 +2422,7 @@ enum {
     VERBOSE,
     VIMODE,
     WARNCREATEGLOBAL,
+    WARNNESTEDVAR,
     XTRACE,
     USEZLE,
     DVORAK,
@@ -2570,7 +2624,7 @@ struct ttyinfo {
 
 #define txtchangeisset(T,X)	((T) & (X))
 #define txtchangeget(T,A)	(((T) & A ## _MASK) >> A ## _SHIFT)
-#define txtchangeset(T, X, Y)	((void)(T && (*T |= (X), *T &= ~(Y))))
+#define txtchangeset(T, X, Y)	((void)(T && (*T &= ~(Y), *T |= (X))))
 
 /*
  * For outputting sequences to change colour: specify foreground
@@ -2802,7 +2856,14 @@ enum errflag_bits {
     /*
      * User interrupt.
      */
-    ERRFLAG_INT = 2
+    ERRFLAG_INT = 2,
+    /*
+     * Hard error --- return to top-level prompt in interactive
+     * shell.  In non-interactive shell we'll typically already
+     * have exited.  This is reset by "errflag = 0" in
+     * loop(toplevel = 1, ...).
+     */
+    ERRFLAG_HARD = 4
 };
 
 /***********/
@@ -2873,6 +2934,7 @@ struct hist_stack {
     void (*addtoline) _((int));
     unsigned char *cstack;
     int csp;
+    int curline_linked;
 };
 
 /*
@@ -3099,8 +3161,8 @@ typedef wint_t convchar_t;
  * much what the definition tells us.  However, we happen to know this
  * works on MacOS which doesn't define that.
  */
-#if defined(BROKEN_WCWIDTH) && (defined(__STDC_ISO_10646__) || defined(__APPLE__))
-#define WCWIDTH(wc)	mk_wcwidth(wc)
+#ifdef ENABLE_UNICODE9
+#define WCWIDTH(wc)	u9_wcwidth(wc)
 #else
 #define WCWIDTH(wc)	wcwidth(wc)
 #endif
@@ -3145,15 +3207,7 @@ typedef wint_t convchar_t;
  * sense throughout the shell.  I am not aware of a way of
  * detecting the Unicode trait in standard libraries.
  */
-#ifdef BROKEN_WCWIDTH
-/*
- * We can't be quite sure the wcwidth we've provided is entirely
- * in agreement with the system's, so be extra safe.
- */
-#define IS_COMBINING(wc)	(wc != 0 && WCWIDTH(wc) == 0 && !iswcntrl(wc))
-#else
 #define IS_COMBINING(wc)	(wc != 0 && WCWIDTH(wc) == 0)
-#endif
 /*
  * Test for the base of a combining character.
  *
diff --git a/Src/zsh_system.h b/Src/zsh_system.h
index 811340d42..5339b496f 100644
--- a/Src/zsh_system.h
+++ b/Src/zsh_system.h
@@ -37,7 +37,7 @@
 #endif
 #endif
 
-#if defined(__linux) || defined(__GNU__) || defined(__GLIBC__) || defined(LIBC_MUSL)
+#if defined(__linux) || defined(__GNU__) || defined(__GLIBC__) || defined(LIBC_MUSL) || defined(__CYGWIN__)
 /*
  * Turn on numerous extensions.
  * This is in order to get the functions for manipulating /dev/ptmx.
@@ -728,7 +728,7 @@ extern char **environ;
  * We always need setenv and unsetenv in pairs, because
  * we don't know how to do memory management on the values set.
  */
-#if defined(HAVE_SETENV) && defined(HAVE_UNSETENV)
+#if defined(HAVE_SETENV) && defined(HAVE_UNSETENV) && !defined(__APPLE__)
 # define USE_SET_UNSET_ENV
 #endif
 
@@ -882,6 +882,10 @@ extern short ospeed;
 # endif
 #endif
 
+#ifdef HAVE_SRAND_DETERMINISTIC
+# define srand srand_deterministic
+#endif
+
 #ifdef ZSH_VALGRIND
 # include "valgrind/valgrind.h"
 # include "valgrind/memcheck.h"
diff --git a/Src/ztype.h b/Src/ztype.h
index 76589b152..ae7236774 100644
--- a/Src/ztype.h
+++ b/Src/ztype.h
@@ -72,7 +72,11 @@
 
 #ifdef MULTIBYTE_SUPPORT
 #define WC_ZISTYPE(X,Y) wcsitype((X),(Y))
-#define WC_ISPRINT(X)	iswprint(X)
+# ifdef ENABLE_UNICODE9
+#  define WC_ISPRINT(X)	u9_iswprint(X)
+# else
+#  define WC_ISPRINT(X)	iswprint(X)
+# endif
 #else
 #define WC_ZISTYPE(X,Y)	zistype((X),(Y))
 #define WC_ISPRINT(X)	isprint(X)