about summary refs log tree commit diff
path: root/Src/Zle
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Zle')
-rw-r--r--Src/Zle/compcore.c86
-rw-r--r--Src/Zle/compctl.c76
-rw-r--r--Src/Zle/complist.c134
-rw-r--r--Src/Zle/compresult.c275
-rw-r--r--Src/Zle/zle.h13
-rw-r--r--Src/Zle/zle_hist.c22
-rw-r--r--Src/Zle/zle_main.c5
-rw-r--r--Src/Zle/zle_misc.c23
-rw-r--r--Src/Zle/zle_params.c50
-rw-r--r--Src/Zle/zle_refresh.c71
-rw-r--r--Src/Zle/zle_thingy.c4
-rw-r--r--Src/Zle/zle_tricky.c419
-rw-r--r--Src/Zle/zle_utils.c229
13 files changed, 886 insertions, 521 deletions
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 0c1f13078..df43cc1ac 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -281,7 +281,10 @@ mod_export int lastend;
 
 #define inststr(X) inststrlen((X),1,-1)
 
-/* Main completion entry point, called from zle. */
+/*
+ * Main completion entry point, called from zle. 
+ * At this point the line is already metafied.
+ */
 
 /**/
 int
@@ -292,6 +295,8 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
     char *opm;
     LinkNode n;
 
+    METACHECK();
+
     pushheap();
 
     ainfo = fainfo = NULL;
@@ -329,7 +334,7 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
 		      (isset(LISTPACKED) ? "packed rows" : "rows") :
 		      (isset(LISTPACKED) ? "packed" : ""));
     startauto = isset(AUTOMENU);
-    movetoend = ((zlecs == we || isset(ALWAYSTOEND)) ? 2 : 1);
+    movetoend = ((zlemetacs == we || isset(ALWAYSTOEND)) ? 2 : 1);
     showinglist = 0;
     hasmatched = hasunmatched = 0;
     minmlen = 1000000;
@@ -341,10 +346,10 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
     /* Make sure we have the completion list and compctl. */
     if (makecomplist(s, incmd, lst)) {
 	/* Error condition: feeeeeeeeeeeeep(). */
-	zlecs = 0;
-	foredel(zlell);
+	zlemetacs = 0;
+	foredel(zlemetall);
 	inststr(origline);
-	zlecs = origcs;
+	zlemetacs = origcs;
 	clearlist = 1;
 	ret = 1;
 	minfo.cur = NULL;
@@ -366,10 +371,10 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
 	ret = selfinsert(zlenoargs);
     else if (!useline && uselist) {
 	/* All this and the guy only wants to see the list, sigh. */
-	zlecs = 0;
-	foredel(zlell);
+	zlemetacs = 0;
+	foredel(zlemetall);
 	inststr(origline);
-	zlecs = origcs;
+	zlemetacs = origcs;
 	showinglist = -2;
     } else if (useline == 2 && nmatches > 1) {
 	do_allmatches(1);
@@ -414,10 +419,10 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
 	invalidatelist();
 	if (forcelist)
 	    clearlist = 1;
-	zlecs = 0;
-	foredel(zlell);
+	zlemetacs = 0;
+	foredel(zlemetall);
 	inststr(origline);
-	zlecs = origcs;
+	zlemetacs = origcs;
     }
     /* Print the explanation strings if needed. */
     if (!showinglist && validlist && usemenu != 2 && uselist &&
@@ -430,9 +435,9 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
     for (n = firstnode(matchers); n; incnode(n))
 	freecmatcher((Cmatcher) getdata(n));
 
-    zlell = strlen((char *)zleline);
-    if (zlecs > zlell)
-	zlecs = zlell;
+    zlemetall = strlen((char *)zlemetaline);
+    if (zlemetacs > zlemetall)
+	zlemetacs = zlemetall;
     popheap();
 
     return ret;
@@ -469,6 +474,11 @@ before_complete(UNUSED(Hookdef dummy), int *lst)
     /* We may have to reset the cursor to its position after the   *
      * string inserted by the last completion. */
 
+    /*
+     * Currently this hook runs before metafication.
+     * This is the only hook of the three defined here of
+     * which that is true.
+     */
     if ((fromcomp & FC_INWORD) && (zlecs = lastend) > zlell)
 	zlecs = zlell;
 
@@ -499,10 +509,10 @@ after_complete(UNUSED(Hookdef dummy), int *dat)
 	    minfo.cur = NULL;
 	    if (ret >= 2) {
 		fixsuffix();
-		zlecs = 0;
-		foredel(zlell);
+		zlemetacs = 0;
+		foredel(zlemetall);
 		inststr(origline);
-		zlecs = origcs;
+		zlemetacs = origcs;
 		if (ret == 2) {
 		    clearlist = 1;
 		    invalidatelist();
@@ -525,6 +535,8 @@ callcompfunc(char *s, char *fn)
     int lv = lastval;
     char buf[20];
 
+    METACHECK();
+
     if ((prog = getshfunc(fn)) != &dummy_eprog) {
 	char **p, *tmp;
 	int aadd = 0, usea = 1, icf = incompfunc, osc = sfcontext;
@@ -683,10 +695,10 @@ callcompfunc(char *s, char *fn)
 	    int l;
 
 	    compiprefix = (char *) zalloc((l = wb - parwb) + 1);
-	    memcpy(compiprefix, zleline + parwb, l);
+	    memcpy(compiprefix, zlemetaline + parwb, l);
 	    compiprefix[l] = '\0';
 	    compisuffix = (char *) zalloc((l = parwe - we) + 1);
-	    memcpy(compisuffix, zleline + we, l);
+	    memcpy(compisuffix, zlemetaline + we, l);
 	    compisuffix[l] = '\0';
 
 	    wb = parwb;
@@ -1152,7 +1164,7 @@ check_param(char *s, int set, int test)
 	    }
 	    /* And adjust wb, we, and offs again. */
 	    offs -= b - s;
-	    wb = zlecs - offs;
+	    wb = zlemetacs - offs;
 	    we = wb + e - b;
 	    ispar = (br >= 2 ? 2 : 1);
 	    b[we-wb] = '\0';
@@ -1265,6 +1277,11 @@ comp_str(int *ipl, int *pl, int untok)
     return str;
 }
 
+/*
+ * This is the code behind compset -q, which splits the
+ * the current word as if it were a command line.
+ */
+
 /**/
 int
 set_comp_sep(void)
@@ -1273,11 +1290,13 @@ set_comp_sep(void)
     char *s = comp_str(&lip, &lp, 1);
     LinkList foo = newlinklist();
     LinkNode n;
-    int owe = we, owb = wb, ocs = zlecs, swb, swe, scs, soffs, ne = noerrs;
-    int tl, got = 0, i = 0, j, cur = -1, oll = zlell, sl, css = 0;
+    int owe = we, owb = wb, ocs, swb, swe, scs, soffs, ne = noerrs;
+    int tl, got = 0, i = 0, j, cur = -1, oll, sl, css = 0;
     int remq = 0, dq = 0, odq, sq = 0, osq, issq = 0, sqq = 0, lsq = 0, qa = 0;
     int ois = instring, oib = inbackt, noffs = lp, ona = noaliases;
-    char *tmp, *p, *ns, *ol = (char *) zleline, sav, *qp, *qs, *ts, qc = '\0';
+    char *tmp, *p, *ns, *ol, sav, *qp, *qs, *ts, qc = '\0';
+
+    METACHECK();
 
     s += lip;
     wb += lip;
@@ -1289,13 +1308,16 @@ set_comp_sep(void)
     /* Put the string in the lexer buffer and call the lexer to *
      * get the words we have to expand.                        */
     zleparse = 1;
+    ocs = zlemetacs;
+    oll = zlemetall;
+    ol = (char *)zlemetaline;
     addedx = 1;
     noerrs = 1;
     lexsave();
     tmp = (char *) zhalloc(tl = 3 + strlen(s));
     tmp[0] = ' ';
     memcpy(tmp + 1, s, noffs);
-    tmp[(scs = zlecs = 1 + noffs)] = 'x';
+    tmp[(scs = zlemetacs = 1 + noffs)] = 'x';
     strcpy(tmp + 2 + noffs, s + noffs);
 
     switch (*compqstack) {
@@ -1318,8 +1340,8 @@ set_comp_sep(void)
             if (*p == '\\' && p[1] == '\\') {
                 dq++;
                 chuck(p);
-                if (j > zlecs) {
-                    zlecs++;
+                if (j > zlemetacs) {
+                    zlemetacs++;
                     css++;
                 }
                 if (!*p)
@@ -1329,8 +1351,8 @@ set_comp_sep(void)
     odq = dq;
     osq = sq;
     inpush(dupstrspace(tmp), 0, NULL);
-    zleline = (unsigned char *) tmp;
-    zlell = tl - 1;
+    zlemetaline = (unsigned char *) tmp;
+    zlemetall = tl - 1;
     strinbeg(0);
     noaliases = 1;
     do {
@@ -1383,7 +1405,7 @@ set_comp_sep(void)
 	    swb = wb - 1 - dq - sq;
 	    swe = we - 1 - dq - sq;
             sqq = lsq;
-	    soffs = zlecs - swb - css;
+	    soffs = zlemetacs - swb - css;
 	    chuck(p + soffs);
 	    ns = dupstring(p);
 	}
@@ -1397,9 +1419,9 @@ set_comp_sep(void)
     lexrestore();
     wb = owb;
     we = owe;
-    zlecs = ocs;
-    zleline = (unsigned char *) ol;
-    zlell = oll;
+    zlemetacs = ocs;
+    zlemetaline = (unsigned char *) ol;
+    zlemetall = oll;
     if (cur < 0 || i < 1)
 	return 1;
     owb = offs;
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 4b33a2921..2789068e7 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -198,16 +198,21 @@ compctlread(char *name, char **args, Options ops, char *reply)
 	return 1;
     }
 
+    METACHECK();
+
     if (OPT_ISSET(ops,'l')) {
-	/* -ln gives the index of the word the cursor is currently on, which is
-	available in zlecs (but remember that Zsh counts from one, not zero!) */
+	/*
+	 * -ln gives the index of the word the cursor is currently on, which
+	 * is available in zlemetacs (but remember that Zsh counts from one,
+	 * not zero!)
+	 */
 	if (OPT_ISSET(ops,'n')) {
 	    char nbuf[14];
 
 	    if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
-		printf("%d\n", zlecs + 1);
+		printf("%d\n", zlemetacs + 1);
 	    if (!OPT_ISSET(ops,'e')) {
-		sprintf(nbuf, "%d", zlecs + 1);
+		sprintf(nbuf, "%d", zlemetacs + 1);
 		setsparam(reply, ztrdup(nbuf));
 	    }
 	    return 0;
@@ -215,11 +220,11 @@ compctlread(char *name, char **args, Options ops, char *reply)
 	/* without -n, the current line is assigned to the given parameter as a
 	scalar */
 	if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
-	    zputs((char *) zleline, stdout);
+	    zputs((char *) zlemetaline, stdout);
 	    putchar('\n');
 	}
 	if (!OPT_ISSET(ops,'e'))
-	    setsparam(reply, ztrdup((char *) zleline));
+	    setsparam(reply, ztrdup((char *) zlemetaline));
     } else {
 	int i;
 
@@ -2560,7 +2565,9 @@ makecomplistor(Compctl cc, char *s, int incmd, int compadd, int sub)
 static void
 makecomplistlist(Compctl cc, char *s, int incmd, int compadd)
 {
-    int oloffs = offs, owe = we, owb = wb, ocs = zlecs;
+    int oloffs = offs, owe = we, owb = wb, ocs = zlemetacs;
+
+    METACHECK();
 
     if (cc->ext)
 	/* Handle extended completion. */
@@ -2574,7 +2581,7 @@ makecomplistlist(Compctl cc, char *s, int incmd, int compadd)
     offs = oloffs;
     wb = owb;
     we = owe;
-    zlecs = ocs;
+    zlemetacs = ocs;
 }
 
 /* This add matches for extended completion patterns */
@@ -2751,15 +2758,17 @@ sep_comp_string(char *ss, char *s, int noffs)
 {
     LinkList foo = newlinklist();
     LinkNode n;
-    int owe = we, owb = wb, ocs = zlecs, swb, swe, scs, soffs, ne = noerrs;
-    int sl = strlen(ss), tl, got = 0, i = 0, cur = -1, oll = zlell, remq;
+    int owe = we, owb = wb, ocs = zlemetacs, swb, swe, scs, soffs, ne = noerrs;
+    int sl = strlen(ss), tl, got = 0, i = 0, cur = -1, oll = zlemetall, remq;
     int ois = instring, oib = inbackt, ona = noaliases;
-    char *tmp, *p, *ns, *ol = (char *) zleline, sav, *oaq = autoq, *qp, *qs;
-    char *ts, qc = '\0';
+    char *tmp, *p, *ns, *ol = (char *) zlemetaline, sav, *oaq = autoq;
+    char *qp, *qs, *ts, qc = '\0';
 
     swb = swe = soffs = 0;
     ns = NULL;
 
+    METACHECK();
+
     /* Put the string in the lexer buffer and call the lexer to *
      * get the words we have to expand.                        */
     zleparse = 1;
@@ -2770,13 +2779,13 @@ sep_comp_string(char *ss, char *s, int noffs)
     strcpy(tmp, ss);
     tmp[sl] = ' ';
     memcpy(tmp + sl + 1, s, noffs);
-    tmp[(scs = zlecs = sl + 1 + noffs)] = 'x';
+    tmp[(scs = zlemetacs = sl + 1 + noffs)] = 'x';
     strcpy(tmp + sl + 2 + noffs, s + noffs);
     if ((remq = (*compqstack == '\\')))
 	tmp = rembslash(tmp);
     inpush(dupstrspace(tmp), 0, NULL);
-    zleline = (unsigned char *) tmp;
-    zlell = tl - 1;
+    zlemetaline = (unsigned char *) tmp;
+    zlemetall = tl - 1;
     strinbeg(0);
     noaliases = 1;
     do {
@@ -2807,7 +2816,7 @@ sep_comp_string(char *ss, char *s, int noffs)
 	    cur = i;
 	    swb = wb - 1;
 	    swe = we - 1;
-	    soffs = zlecs - swb;
+	    soffs = zlemetacs - swb;
 	    chuck(p + soffs);
 	    ns = dupstring(p);
 	}
@@ -2821,9 +2830,9 @@ sep_comp_string(char *ss, char *s, int noffs)
     lexrestore();
     wb = owb;
     we = owe;
-    zlecs = ocs;
-    zleline = (unsigned char *) ol;
-    zlell = oll;
+    zlemetacs = ocs;
+    zlemetaline = (unsigned char *) ol;
+    zlemetall = oll;
     if (cur < 0 || i < 1)
 	return 1;
     owb = offs;
@@ -2979,8 +2988,8 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	addlinknode(allccs, cc);
     }
     /* Go to the end of the word if complete_in_word is not set. */
-    if (unset(COMPLETEINWORD) && zlecs != we)
-	zlecs = we, offs = strlen(s);
+    if (unset(COMPLETEINWORD) && zlemetacs != we)
+	zlemetacs = we, offs = strlen(s);
 
     s = dupstring(s);
     delit = ispattern = 0;
@@ -3200,15 +3209,15 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	    ppre = dupstrpfx(rpre, s1 - rpre + 1);
 	psuf = dupstring(s2);
 
-	if (zlecs != wb) {
-	    char save = zleline[zlecs];
+	if (zlemetacs != wb) {
+	    char save = zlemetaline[zlemetacs];
 
-	    zleline[zlecs] = 0;
-	    lppre = dupstring((char *) zleline + wb +
+	    zlemetaline[zlemetacs] = 0;
+	    lppre = dupstring((char *) zlemetaline + wb +
 			      (qipre && *qipre ?
 			       (strlen(qipre) -
 				(*qipre == '\'' || *qipre == '\"')) : 0));
-	    zleline[zlecs] = save;
+	    zlemetaline[zlemetacs] = save;
 	    if (brbeg) {
 		Brinfo bp;
 
@@ -3230,25 +3239,26 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 	    lppre = NULL;
 	    lppl = 0;
 	}
-	if (zlecs != we) {
+	if (zlemetacs != we) {
 	    int end = we;
-	    char save = zleline[end];
+	    char save = zlemetaline[end];
 
 	    if (qisuf && *qisuf) {
 		int ql = strlen(qisuf);
 
 		end -= ql - (qisuf[ql-1] == '\'' || qisuf[ql-1] == '"');
 	    }
-	    zleline[end] = 0;
-	    lpsuf = dupstring((char *) (zleline + zlecs));
-	    zleline[end] = save;
+	    zlemetaline[end] = 0;
+	    lpsuf = dupstring((char *) (zlemetaline + zlemetacs));
+	    zlemetaline[end] = save;
 	    if (brend) {
 		Brinfo bp;
 		char *p;
 		int bl;
 
 		for (bp = brend; bp; bp = bp->next) {
-		    p = lpsuf + (we - zlecs) - bp->qpos - (bl = strlen(bp->str));
+		    p = lpsuf + (we - zlemetacs) - bp->qpos -
+			(bl = strlen(bp->str));
 		    strcpy(p, p + bl);
 		}
 	    }
@@ -3262,7 +3272,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd)
 
 	/* And get the file prefix. */
 	fpre = dupstring(((s1 == s || s1 == rpre || ic) &&
-			  (*s != '/' || zlecs == wb)) ? s1 : s1 + 1);
+			  (*s != '/' || zlemetacs == wb)) ? s1 : s1 + 1);
 	qfpre = quotename(fpre, NULL);
 	/* And the suffix. */
 	fsuf = dupstrpfx(rsuf, s2 - rsuf);
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 3a3d77971..045e86837 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -1771,28 +1771,30 @@ setmstatus(char *status, char *sline, int sll, int scs,
     char *p, *s, *ret = NULL;
     int pl, sl, max;
 
+    METACHECK();
+
     if (csp) {
-        *csp = zlecs;
-        *llp = zlell;
+        *csp = zlemetacs;
+        *llp = zlemetall;
         *lenp = lastend - wb;
 
-        ret = dupstring((char *) zleline);
+        ret = dupstring((char *) zlemetaline);
 
-        p = (char *) zhalloc(zlecs - wb + 1);
-        strncpy(p, (char *) zleline + wb, zlecs - wb);
-        p[zlecs - wb] = '\0';
-        if (lastend < zlecs)
+        p = (char *) zhalloc(zlemetacs - wb + 1);
+        strncpy(p, (char *) zlemetaline + wb, zlemetacs - wb);
+        p[zlemetacs - wb] = '\0';
+        if (lastend < zlemetacs)
             s = "";
         else {
-            s = (char *) zhalloc(lastend - zlecs + 1);
-            strncpy(s, (char *) zleline + zlecs, lastend - zlecs);
-            s[lastend - zlecs] = '\0';
+            s = (char *) zhalloc(lastend - zlemetacs + 1);
+            strncpy(s, (char *) zlemetaline + zlemetacs, lastend - zlemetacs);
+            s[lastend - zlemetacs] = '\0';
         }
-        zlecs = 0;
-        foredel(zlell);
+        zlemetacs = 0;
+        foredel(zlemetall);
         spaceinline(sll);
-        memcpy(zleline, sline, sll);
-        zlecs = scs;
+        memcpy(zlemetaline, sline, sll);
+        zlemetacs = scs;
     } else {
         p = complastprefix;
         s = complastsuffix;
@@ -1970,7 +1972,7 @@ domenuselect(Hookdef dummy, Chdata dat)
     Menustack u = NULL;
     int i = 0, acc = 0, wishcol = 0, setwish = 0, oe = onlyexpl, wasnext = 0;
     int space, lbeg = 0, step = 1, wrap, pl = nlnct, broken = 0, first = 1;
-    int nolist = 0, mode = 0, modecs, modell, modelen;
+    int nolist = 0, mode = 0, modecs, modell, modelen, wasmeta;
     char *s;
     char status[MAX_STATUS], *modeline = NULL;
 
@@ -1990,6 +1992,23 @@ domenuselect(Hookdef dummy, Chdata dat)
 	unqueue_signals();
 	return 0;
     }
+    /*
+     * Lots of the logic here doesn't really make sense if the
+     * line isn't metafied, but the evidence was that only used
+     * to be metafied locally in a couple of places.
+     * It's horrifically difficult to work out where the line
+     * is metafied, so I've resorted to the following.
+     * Unfortunately we need to unmetatfy in zrefresh() when
+     * we want to display something.  Maybe this function can
+     * be done better.
+     */
+    if (zlemetaline != NULL)
+	wasmeta = 1;
+    else {
+	wasmeta = 0;
+	metafy_line();
+    }
+    
     if ((s = getsparam("MENUSCROLL"))) {
 	if (!(step = mathevali(s)))
 	    step = (lines - nlnct) >> 1;
@@ -2010,11 +2029,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 	     * was before completion started.
 	     */
             mode = MM_INTER;
-            zlecs = 0;
-            foredel(zlell);
+            zlemetacs = 0;
+            foredel(zlemetall);
             spaceinline(l);
-            strncpy((char *) zleline, origline, l);
-            zlecs = origcs;
+            strncpy((char *) zlemetaline, origline, l);
+            zlemetacs = origcs;
             setmstatus(status, NULL, 0 , 0, NULL, NULL, NULL);
         } else if (strpfx("search", s)) {
             mode = (strstr(s, "back") ? MM_BSEARCH : MM_FSEARCH);
@@ -2108,15 +2127,15 @@ domenuselect(Hookdef dummy, Chdata dat)
 	     * completion we don't want that, we always want to
 	     * be able to type the next character.
 	     */
-	    modeline = dupstring(zleline);
-            modecs = zlecs;
-            modell = zlell;
+	    modeline = dupstring((char *)zlemetaline);
+            modecs = zlemetacs;
+            modell = zlemetall;
             modelen = minfo.len;
         }
         first = 0;
         if (mode == MM_INTER) {
-	    statusline = stringaszleline((unsigned char *)status,
-					 &statusll, NULL);
+	    statusline = stringaszleline((unsigned char *)status, 0,
+					 &statusll, NULL, NULL);
         } else if (mode) {
             int l = sprintf(status, "%s%sisearch%s: ",
                             ((msearchstate & MS_FAILED) ? "failed " : ""),
@@ -2125,8 +2144,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 
             strncat(status, msearchstr, MAX_STATUS - l - 1);
 
-            statusline = stringaszleline((unsigned char *)status,
-					 &statusll, NULL);
+            statusline = stringaszleline((unsigned char *)status, 0,
+					 &statusll, NULL, NULL);
         } else {
             statusline = NULL;
             statusll = 0;
@@ -2207,11 +2226,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 		 * start.
 		 */
                 mode = MM_INTER;
-                zlecs = 0;
-                foredel(zlell);
+                zlemetacs = 0;
+                foredel(zlemetall);
                 spaceinline(l);
-                strncpy((char *) zleline, origline, l);
-                zlecs = origcs;
+                strncpy((char *) zlemetaline, origline, l);
+                zlemetacs = origcs;
                 setmstatus(status, NULL, 0, 0, NULL, NULL, NULL);
 
                 continue;
@@ -2226,8 +2245,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 
 	    s->prev = u;
 	    u = s;
-	    s->line = dupstring((char *) zleline);
-	    s->cs = zlecs;
+	    s->line = dupstring((char *) zlemetaline);
+	    s->cs = zlemetacs;
 	    s->mline = mline;
 	    s->mlbeg = mlbeg;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
@@ -2266,21 +2285,21 @@ domenuselect(Hookdef dummy, Chdata dat)
 		 * the command line as it is with just the
 		 * characters typed by the user.
 		 */
-                zlecs = 0;
-                foredel(zlell);
+                zlemetacs = 0;
+                foredel(zlemetall);
                 spaceinline(l);
-                strncpy((char *) zleline, origline, l);
-                zlecs = origcs;
+                strncpy((char *) zlemetaline, origline, l);
+                zlemetacs = origcs;
 
                 if (cmd == Th(z_selfinsert))
                     selfinsert(zlenoargs);
                 else
                     selfinsertunmeta(zlenoargs);
 
-                saveline = (char *) zhalloc(zlell);
-                memcpy(saveline, zleline, zlell);
-                savell = zlell;
-                savecs = zlecs;
+                saveline = (char *) zhalloc(zlemetall);
+                memcpy(saveline, zlemetaline, zlemetall);
+                savell = zlemetall;
+                savecs = zlemetacs;
                 iforcemenu = -1;
             } else
                 mode = 0;
@@ -2294,8 +2313,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    if (nmatches < 1 || !minfo.cur || !*(minfo.cur)) {
 		nolist = 1;
                 if (mode == MM_INTER) {
-                    statusline = stringaszleline((unsigned char *)status,
-						 &statusll, NULL);
+                    statusline = stringaszleline((unsigned char *)status, 0,
+						 &statusll, NULL, NULL);
                 } else {
 		    /* paranoia */
 		    statusline = NULL;
@@ -2339,8 +2358,8 @@ domenuselect(Hookdef dummy, Chdata dat)
             mode = 0;
 	    s->prev = u;
 	    u = s;
-	    s->line = dupstring((char *) zleline);
-	    s->cs = zlecs;
+	    s->line = dupstring((char *) zlemetaline);
+	    s->cs = zlemetacs;
 	    s->mline = mline;
 	    s->mlbeg = mlbeg;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
@@ -2394,11 +2413,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 		break;
 
 	    handleundo();
-	    zlecs = 0;
-	    foredel(zlell);
+	    zlemetacs = 0;
+	    foredel(zlemetall);
 	    spaceinline(l = strlen(u->line));
-	    strncpy((char *) zleline, u->line, l);
-	    zlecs = u->cs;
+	    strncpy((char *) zlemetaline, u->line, l);
+	    zlemetacs = u->cs;
 	    menuacc = u->acc;
 	    memcpy(&minfo, &(u->info), sizeof(struct menuinfo));
 	    p = &(minfo.cur);
@@ -2435,8 +2454,8 @@ domenuselect(Hookdef dummy, Chdata dat)
 
             if (nolist) {
                 if (mode == MM_INTER) {
-                    statusline = stringaszleline((unsigned char *)status,
-						 &statusll, NULL);
+                    statusline = stringaszleline((unsigned char *)status, 0,
+						 &statusll, NULL, NULL);
                 } else {
 		    /* paranoia */
 		    statusline = NULL;
@@ -2783,11 +2802,11 @@ domenuselect(Hookdef dummy, Chdata dat)
                 origline = modeline;
                 origcs = modecs;
                 origll = modell;
-                zlecs = 0;
-                foredel(zlell);
+                zlemetacs = 0;
+                foredel(zlemetall);
                 spaceinline(origll);
-                strncpy((char *) zleline, origline, origll);
-                zlecs = origcs;
+                strncpy((char *) zlemetaline, origline, origll);
+                zlemetacs = origcs;
                 minfo.len = modelen;
             } else {
                 mode = 0;
@@ -2880,9 +2899,7 @@ domenuselect(Hookdef dummy, Chdata dat)
 		acc = 1;
 	    break;
 	}
-	metafy_line();
 	do_single(**p);
-	unmetafy_line();
 	mselect = (**p)->gnum;
     }
     if (u)
@@ -2898,9 +2915,7 @@ domenuselect(Hookdef dummy, Chdata dat)
         clearlist = listshown = 1;
     if (acc && validlist && minfo.cur) {
 	menucmp = lastambig = hasoldlist = 0;
-	metafy_line();
 	do_single(*(minfo.cur));
-	unmetafy_line();
     }
     if (wasnext || broken) {
 	menucmp = 2;
@@ -2923,6 +2938,9 @@ domenuselect(Hookdef dummy, Chdata dat)
     mlbeg = -1;
     fdat = NULL;
 
+    if (!wasmeta)
+	unmetafy_line();
+
     return (broken == 2 ? 3 :
 	    ((dat && !broken) ? (acc ? 1 : 2) : (!noselect ^ acc)));
 }
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index b07e71aff..09794813f 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -169,11 +169,13 @@ static char *
 cline_str(Cline l, int ins, int *csp, LinkList posl)
 {
     Cline s;
-    int ocs = zlecs, ncs, pcs, scs, opos = -1, npos;
+    int ocs = zlemetacs, ncs, pcs, scs, opos = -1, npos;
     int pm, pmax, pmm, pma, sm, smax, smm, sma, d, dm, mid;
     int i, j, li = 0, cbr, padd = (ins ? wb - ocs : -ocs);
     Brinfo brp, brs;
 
+    METACHECK();
+
     l = cut_cline(l);
 
     pmm = pma = smm = sma = dm = pcs = scs = 0;
@@ -205,7 +207,7 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	}
 	while (brs && !brs->curpos) {
 	    if (cbr < 0)
-		cbr = zlecs;
+		cbr = zlemetacs;
 	    inststrlen(brs->str, 1, -1);
 	    brs = brs->prev;
 	}
@@ -214,21 +216,21 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
     while (l) {
 	/* Insert the original string if no prefix. */
 	if (l->olen && !(l->flags & CLF_SUF) && !l->prefix) {
-	    pcs = zlecs + l->olen;
+	    pcs = zlemetacs + l->olen;
 	    inststrlen(l->orig, 1, l->olen);
 	} else {
 	    /* Otherwise insert the prefix. */
 	    for (s = l->prefix; s; s = s->next) {
-		pcs = zlecs + s->llen;
+		pcs = zlemetacs + s->llen;
 		if (s->flags & CLF_LINE)
 		    inststrlen(s->line, 1, s->llen);
 		else
 		    inststrlen(s->word, 1, s->wlen);
-		scs = zlecs;
+		scs = zlemetacs;
 
 		if ((s->flags & CLF_DIFF) && (!dm || (s->flags & CLF_MATCHED))) {
-		    d = zlecs; dm = s->flags & CLF_MATCHED;
-		    if (posl && (npos = zlecs + padd) != opos) {
+		    d = zlemetacs; dm = s->flags & CLF_MATCHED;
+		    if (posl && (npos = zlemetacs + padd) != opos) {
 			opos = npos;
 			addlinknode(posl, (void *) ((long) npos));
 		    }
@@ -240,11 +242,11 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	    int ocs, bl;
 
 	    while (brp && li >= brp->curpos) {
-		ocs = zlecs;
+		ocs = zlemetacs;
 		bl = strlen(brp->str);
-		zlecs = pcs - (li - brp->curpos);
+		zlemetacs = pcs - (li - brp->curpos);
 		inststrlen(brp->str, 1, bl);
-		zlecs = ocs + bl;
+		zlemetacs = ocs + bl;
 		pcs += bl;
 		scs += bl;
 		brp = brp->next;
@@ -253,14 +255,14 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	/* Remember the position if this is the first prefix with
 	 * missing characters. */
 	if ((l->flags & CLF_MISS) && !(l->flags & CLF_SUF)) {
-	    if (posl && (npos = zlecs + padd) != opos) {
+	    if (posl && (npos = zlemetacs + padd) != opos) {
 		opos = npos;
 		addlinknode(posl, (void *) ((long) npos));
 	    }
 	    if (((pmax <= (l->max - l->min) || (pma && l->max != l->min)) &&
 		 (!pmm || (l->flags & CLF_MATCHED))) ||
 		((l->flags & CLF_MATCHED) && !pmm)) {
-		pm = zlecs; pmax = l->max - l->min; pmm = l->flags & CLF_MATCHED;
+		pm = zlemetacs; pmax = l->max - l->min; pmm = l->flags & CLF_MATCHED;
 		pma = ((l->prefix || l->suffix) && l->min == cline_sublen(l));
 	    }
 	}
@@ -268,35 +270,35 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	    int ocs, bl;
 
 	    while (brs && li >= brs->curpos) {
-		ocs = zlecs;
+		ocs = zlemetacs;
 		bl = strlen(brs->str);
-		zlecs = scs - (li - brs->curpos);
+		zlemetacs = scs - (li - brs->curpos);
 		if (cbr < 0)
-		    cbr = zlecs;
+		    cbr = zlemetacs;
 		inststrlen(brs->str, 1, bl);
-		zlecs = ocs + bl;
+		zlemetacs = ocs + bl;
 		pcs += bl;
 		brs = brs->prev;
 	    }
 	}
-	pcs = zlecs;
+	pcs = zlemetacs;
 	/* Insert the anchor. */
 	if (l->flags & CLF_LINE)
 	    inststrlen(l->line, 1, l->llen);
 	else
 	    inststrlen(l->word, 1, l->wlen);
-	scs = zlecs;
+	scs = zlemetacs;
 	if (ins) {
 	    int ocs, bl;
 
 	    li += l->llen;
 
 	    while (brp && li >= brp->curpos) {
-		ocs = zlecs;
+		ocs = zlemetacs;
 		bl = strlen(brp->str);
-		zlecs = pcs + l->llen - (li - brp->curpos);
+		zlemetacs = pcs + l->llen - (li - brp->curpos);
 		inststrlen(brp->str, 1, bl);
-		zlecs = ocs + bl;
+		zlemetacs = ocs + bl;
 		pcs += bl;
 		scs += bl;
 		brp = brp->next;
@@ -305,16 +307,16 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	/* Remember the cursor position for suffixes and mids. */
 	if (l->flags & CLF_MISS) {
 	    if (l->flags & CLF_MID)
-		mid = zlecs;
+		mid = zlemetacs;
 	    else if (l->flags & CLF_SUF) {
-		if (posl && (npos = zlecs + padd) != opos) {
+		if (posl && (npos = zlemetacs + padd) != opos) {
 		    opos = npos;
 		    addlinknode(posl, (void *) ((long) npos));
 		}
 		if (((smax <= (l->min - l->max) || (sma && l->max != l->min)) &&
 		     (!smm || (l->flags & CLF_MATCHED))) ||
 		    ((l->flags & CLF_MATCHED) && !smm)) {
-		    sm = zlecs; smax = l->min - l->max; smm = l->flags & CLF_MATCHED;
+		    sm = zlemetacs; smax = l->min - l->max; smm = l->flags & CLF_MATCHED;
 		    sma = ((l->prefix || l->suffix) && l->min == cline_sublen(l));
 		}
 	    }
@@ -323,20 +325,20 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	    int ocs, bl;
 
 	    while (brs && li >= brs->curpos) {
-		ocs = zlecs;
+		ocs = zlemetacs;
 		bl = strlen(brs->str);
-		zlecs = scs - (li - brs->curpos);
+		zlemetacs = scs - (li - brs->curpos);
 		if (cbr < 0)
-		    cbr = zlecs;
+		    cbr = zlemetacs;
 		inststrlen(brs->str, 1, bl);
-		zlecs = ocs + bl;
+		zlemetacs = ocs + bl;
 		pcs += bl;
 		brs = brs->prev;
 	    }
 	}
 	/* And now insert the suffix or the original string. */
 	if (l->olen && (l->flags & CLF_SUF) && !l->suffix) {
-	    pcs = zlecs;
+	    pcs = zlemetacs;
 	    inststrlen(l->orig, 1, l->olen);
 	    if (ins) {
 		int ocs, bl;
@@ -344,22 +346,22 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 		li += l->olen;
 
 		while (brp && li >= brp->curpos) {
-		    ocs = zlecs;
+		    ocs = zlemetacs;
 		    bl = strlen(brp->str);
-		    zlecs = pcs + l->olen - (li - brp->curpos);
+		    zlemetacs = pcs + l->olen - (li - brp->curpos);
 		    inststrlen(brp->str, 1, bl);
-		    zlecs = ocs + bl;
+		    zlemetacs = ocs + bl;
 		    pcs += bl;
 		    brp = brp->next;
 		}
 		while (brs && li >= brs->curpos) {
-		    ocs = zlecs;
+		    ocs = zlemetacs;
 		    bl = strlen(brs->str);
-		    zlecs = pcs + l->olen - (li - brs->curpos);
+		    zlemetacs = pcs + l->olen - (li - brs->curpos);
 		    if (cbr < 0)
-			cbr = zlecs;
+			cbr = zlemetacs;
 		    inststrlen(brs->str, 1, bl);
-		    zlecs = ocs + bl;
+		    zlemetacs = ocs + bl;
 		    pcs += bl;
 		    brs = brs->prev;
 		}
@@ -370,13 +372,13 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	    for (j = -1, i = 0, s = l->suffix; s; s = s->next) {
 		if (j < 0 && (s->flags & CLF_DIFF))
 		    j = i, js = s;
-		pcs = zlecs;
+		pcs = zlemetacs;
 		if (s->flags & CLF_LINE) {
 		    inststrlen(s->line, 0, s->llen);
-		    i += s->llen; scs = zlecs + s->llen;
+		    i += s->llen; scs = zlemetacs + s->llen;
 		} else {
 		    inststrlen(s->word, 0, s->wlen);
-		    i += s->wlen; scs = zlecs + s->wlen;
+		    i += s->wlen; scs = zlemetacs + s->wlen;
 		}
 		if (ins) {
 		    int ocs, bl;
@@ -384,32 +386,32 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 		    li += s->llen;
 
 		    while (brp && li >= brp->curpos) {
-			ocs = zlecs;
+			ocs = zlemetacs;
 			bl = strlen(brp->str);
-			zlecs = pcs + (li - brp->curpos);
+			zlemetacs = pcs + (li - brp->curpos);
 			inststrlen(brp->str, 1, bl);
-			zlecs = ocs + bl;
+			zlemetacs = ocs + bl;
 			pcs += bl;
 			scs += bl;
 			brp = brp->next;
 		    }
 		    while (brs && li >= brs->curpos) {
-			ocs = zlecs;
+			ocs = zlemetacs;
 			bl = strlen(brs->str);
-			zlecs = scs - (li - brs->curpos);
+			zlemetacs = scs - (li - brs->curpos);
 			if (cbr < 0)
-			    cbr = zlecs;
+			    cbr = zlemetacs;
 			inststrlen(brs->str, 1, bl);
-			zlecs = ocs + bl;
+			zlemetacs = ocs + bl;
 			pcs += bl;
 			brs = brs->prev;
 		    }
 		}
 	    }
-	    zlecs += i;
+	    zlemetacs += i;
 	    if (j >= 0 && (!dm || (js->flags & CLF_MATCHED))) {
-		d = zlecs - j; dm = js->flags & CLF_MATCHED;
-		if (posl && (npos = zlecs - j + padd) != opos) {
+		d = zlemetacs - j; dm = js->flags & CLF_MATCHED;
+		if (posl && (npos = zlemetacs - j + padd) != opos) {
 		    opos = npos;
 		    addlinknode(posl, (void *) ((long) npos));
 		}
@@ -417,7 +419,7 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	}
 	l = l->next;
     }
-    if (posl && (npos = zlecs + padd) != opos)
+    if (posl && (npos = zlemetacs + padd) != opos)
 #if 0
 	/* This could be used to put an extra colon before the end-of-word
 	 * position if there is nothing missing. */
@@ -426,23 +428,23 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	addlinknode(posl, (void *) ((long) npos));
 
     if (ins) {
-	int ocs = zlecs;
+	int ocs = zlemetacs;
 
 	for (; brp; brp = brp->next)
 	    inststrlen(brp->str, 1, -1);
 	for (; brs; brs = brs->prev) {
 	    if (cbr < 0)
-		cbr = zlecs;
+		cbr = zlemetacs;
 	    inststrlen(brs->str, 1, -1);
 	}
 	if (mid >= ocs)
-	    mid += zlecs - ocs;
+	    mid += zlemetacs - ocs;
 	if (pm >= ocs)
-	    pm += zlecs - ocs;
+	    pm += zlemetacs - ocs;
 	if (sm >= ocs)
-	    sm += zlecs - ocs;
+	    sm += zlemetacs - ocs;
 	if (d >= ocs)
-	    d += zlecs - ocs;
+	    d += zlemetacs - ocs;
 
 	if (posl) {
 	    LinkNode node;
@@ -451,7 +453,7 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 	    for (node = firstnode(posl); node; incnode(node)) {
 		p = (long) getdata(node);
 		if (p >= ocs)
-		    setdata(node, (void *) (p + zlecs - ocs));
+		    setdata(node, (void *) (p + zlemetacs - ocs));
 	    }
 	}
     }
@@ -461,16 +463,16 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
      * suffix, and finally a place where the matches differ. */
     ncs = (mid >= 0 ? mid :
 	   (cbr >= 0 ? cbr :
-	    (pm >= 0 ? pm : (sm >= 0 ? sm : (d >= 0 ? d : zlecs)))));
+	    (pm >= 0 ? pm : (sm >= 0 ? sm : (d >= 0 ? d : zlemetacs)))));
 
     if (ins != 1) {
 	/* We always inserted the string in the line. If that was not
 	 * requested, we copy it and remove from the line. */
-	char *r = zalloc((i = zlecs - ocs) + 1);
+	char *r = zalloc((i = zlemetacs - ocs) + 1);
 
-	memcpy(r, (char *) (zleline + ocs), i);
+	memcpy(r, (char *) (zlemetaline + ocs), i);
 	r[i] = '\0';
-	zlecs = ocs;
+	zlemetacs = ocs;
 	foredel(i);
 
 	if (csp)
@@ -478,8 +480,8 @@ cline_str(Cline l, int ins, int *csp, LinkList posl)
 
 	return r;
     }
-    lastend = zlecs;
-    zlecs = ncs;
+    lastend = zlemetacs;
+    zlemetacs = ncs;
 
     return NULL;
 }
@@ -579,9 +581,11 @@ unambig_data(int *cp, char **pp, char **ip)
 static int
 instmatch(Cmatch m, int *scs)
 {
-    int l, r = 0, ocs, a = zlecs, brb = 0, bradd, *brpos;
+    int l, r = 0, ocs, a = zlemetacs, brb = 0, bradd, *brpos;
     Brinfo bp;
 
+    METACHECK();
+
     zsfree(lastprebr);
     zsfree(lastpostbr);
     lastprebr = lastpostbr = NULL;
@@ -606,28 +610,28 @@ instmatch(Cmatch m, int *scs)
     /* The string itself. */
     inststrlen(m->str, 1, (l = strlen(m->str)));
     r += l;
-    ocs = zlecs;
+    ocs = zlemetacs;
     /* Re-insert the brace beginnings, if any. */
     if (brbeg) {
-	int pcs = zlecs;
+	int pcs = zlemetacs;
 
 	l = 0;
 	for (bp = brbeg, brpos = m->brpl,
 		 bradd = (m->pre ? strlen(m->pre) : 0);
 	     bp; bp = bp->next, brpos++) {
-	    zlecs = a + *brpos + bradd;
-	    pcs = zlecs;
+	    zlemetacs = a + *brpos + bradd;
+	    pcs = zlemetacs;
 	    l = strlen(bp->str);
 	    bradd += l;
-	    brpcs = zlecs;
+	    brpcs = zlemetacs;
 	    inststrlen(bp->str, 1, l);
 	    r += l;
 	    ocs += l;
 	}
 	lastprebr = (char *) zalloc(pcs - a + 1);
-	memcpy(lastprebr, (char *) zleline + a, pcs - a);
+	memcpy(lastprebr, (char *) zlemetaline + a, pcs - a);
 	lastprebr[pcs - a] = '\0';
-	zlecs = ocs;
+	zlemetacs = ocs;
     }
     /* Path suffix. */
     if (m->psuf) {
@@ -636,24 +640,24 @@ instmatch(Cmatch m, int *scs)
     }
     /* Re-insert the brace end. */
     if (brend) {
-	a = zlecs;
+	a = zlemetacs;
 	for (bp = brend, brpos = m->brsl, bradd = 0; bp; bp = bp->next, brpos++) {
-	    zlecs = a - *brpos;
-	    ocs = brscs = zlecs;
+	    zlemetacs = a - *brpos;
+	    ocs = brscs = zlemetacs;
 	    l = strlen(bp->str);
 	    bradd += l;
 	    inststrlen(bp->str, 1, l);
-	    brb = zlecs;
+	    brb = zlemetacs;
 	    r += l;
 	}
-	zlecs = a + bradd;
+	zlemetacs = a + bradd;
 	if (scs)
 	    *scs = ocs;
     } else {
 	brscs = -1;
 
 	if (scs)
-	    *scs = zlecs;
+	    *scs = zlemetacs;
     }
     /* -S suffix */
     if (m->suf) {
@@ -666,12 +670,12 @@ instmatch(Cmatch m, int *scs)
 	r += l;
     }
     if (brend) {
-	lastpostbr = (char *) zalloc(zlecs - brb + 1);
-	memcpy(lastpostbr, (char *) zleline + brb, zlecs - brb);
-	lastpostbr[zlecs - brb] = '\0';
+	lastpostbr = (char *) zalloc(zlemetacs - brb + 1);
+	memcpy(lastpostbr, (char *) zlemetaline + brb, zlemetacs - brb);
+	lastpostbr[zlemetacs - brb] = '\0';
     }
-    lastend = zlecs;
-    zlecs = ocs;
+    lastend = zlemetacs;
+    zlemetacs = ocs;
 
     return r;
 }
@@ -687,20 +691,20 @@ hasbrpsfx(Cmatch m, char *pre, char *suf)
 	return 1;
     else {
 	char *op = lastprebr, *os = lastpostbr;
-	VARARR(char, oline, zlell);
-	int oll = zlell, ocs = zlecs, ole = lastend, opcs = brpcs, oscs = brscs, ret;
+	VARARR(char, oline, zlemetall);
+	int oll = zlemetall, ocs = zlemetacs, ole = lastend, opcs = brpcs, oscs = brscs, ret;
 
-	memcpy(oline, zleline, zlell);
+	memcpy(oline, zlemetaline, zlemetall);
 
 	lastprebr = lastpostbr = NULL;
 
 	instmatch(m, NULL);
 
-	zlecs = 0;
-	foredel(zlell);
+	zlemetacs = 0;
+	foredel(zlemetall);
 	spaceinline(oll);
-	memcpy(zleline, oline, oll);
-	zlecs = ocs;
+	memcpy(zlemetaline, oline, oll);
+	zlemetacs = ocs;
 	lastend = ole;
 	brpcs = opcs;
 	brscs = oscs;
@@ -754,7 +758,7 @@ do_ambiguous(void)
 	 * completion options.                                             */
 	do_ambig_menu();
     } else if (ainfo) {
-	int atend = (zlecs == we), la, eq, tcs;
+	int atend = (zlemetacs == we), la, eq, tcs;
 	VARARR(char, old, we - wb);
 
 	minfo.cur = NULL;
@@ -763,9 +767,9 @@ do_ambiguous(void)
 	fixsuffix();
 
 	/* First remove the old string from the line. */
-	tcs = zlecs;
-	zlecs = wb;
-	memcpy(old, (char *) zleline + wb, we - wb);
+	tcs = zlemetacs;
+	zlemetacs = wb;
+	memcpy(old, (char *) zlemetaline + wb, we - wb);
 	foredel(we - wb);
 
 	/* Now get the unambiguous string and insert it into the line. */
@@ -776,23 +780,23 @@ do_ambiguous(void)
          * old string. Unless there were matches added with -U, that is. */
 
 	if (lastend < we && !lenchanged && !hasunmatched) {
-	    zlecs = wb;
+	    zlemetacs = wb;
 	    foredel(lastend - wb);
 	    inststrlen(old, 0, we - wb);
 	    lastend = we;
-	    zlecs = tcs;
+	    zlemetacs = tcs;
 	}
 	if (eparq) {
-	    tcs = zlecs;
-	    zlecs = lastend;
+	    tcs = zlemetacs;
+	    zlemetacs = lastend;
 	    for (eq = eparq; eq; eq--)
 		inststrlen("\"", 0, 1);
-	    zlecs = tcs;
+	    zlemetacs = tcs;
 	}
 	/* la is non-zero if listambiguous may be used. Copying and
 	 * comparing the line looks like BFI but it is the easiest
 	 * solution. Really. */
-	la = (zlell != origll || strncmp(origline, (char *) zleline, zlell));
+	la = (zlemetall != origll || strncmp(origline, (char *) zlemetaline, zlemetall));
 
 	/* If REC_EXACT and AUTO_MENU are set and what we inserted is an  *
 	 * exact match, we want menu completion the next time round       *
@@ -800,11 +804,11 @@ do_ambiguous(void)
 	 * taken as an exact match. Also we remember if we just moved the *
 	 * cursor into the word.                                          */
 	fromcomp = ((isset(AUTOMENU) ? FC_LINE : 0) |
-		    ((atend && zlecs != lastend) ? FC_INWORD : 0));
+		    ((atend && zlemetacs != lastend) ? FC_INWORD : 0));
 
 	/* Probably move the cursor to the end. */
 	if (movetoend == 3)
-	    zlecs = lastend;
+	    zlemetacs = lastend;
 
 	/* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
 	 * if the completion is completely ambiguous') is set, and some    *
@@ -947,7 +951,7 @@ do_single(Cmatch m)
 	 * so set the position variables.             */
 	minfo.pos = wb;
 	minfo.we = (movetoend >= 2 || (movetoend == 1 && !menucmp) ||
-		    (!movetoend && zlecs == we));
+		    (!movetoend && zlemetacs == we));
 	minfo.end = we;
     }
     /* If we are already in a menu-completion or if we have done a *
@@ -959,7 +963,7 @@ do_single(Cmatch m)
 	l = we - wb;
 
     minfo.insc = 0;
-    zlecs = minfo.pos;
+    zlemetacs = minfo.pos;
     foredel(l);
 
     if (m->flags & CMF_ALL) {
@@ -969,8 +973,8 @@ do_single(Cmatch m)
 
     /* And then we insert the new string. */
     minfo.len = instmatch(m, &scs);
-    minfo.end = zlecs;
-    zlecs = minfo.pos + minfo.len;
+    minfo.end = zlemetacs;
+    zlemetacs = minfo.pos + minfo.len;
 
     if (m->suf) {
 	havesuff = 1;
@@ -987,13 +991,13 @@ do_single(Cmatch m)
     } else {
 	/* There is no user-specified suffix, *
 	 * so generate one automagically.     */
-	zlecs = scs;
+	zlemetacs = scs;
 	if (partest && (m->flags & CMF_PARBR)) {
 	    int pq;
 
 	    /*{{*/
 	    /* Completing a parameter in braces.  Add a removable `}' suffix. */
-	    zlecs += eparq;
+	    zlemetacs += eparq;
 	    for (pq = parq; pq; pq--)
 		inststrlen("\"", 1, 1);
 	    minfo.insc += parq;
@@ -1005,7 +1009,7 @@ do_single(Cmatch m)
 		havesuff = 1;
 	}
 	if (((m->flags & CMF_FILE) || (partest && isset(AUTOPARAMSLASH))) &&
-	    zlecs > 0 && zleline[zlecs - 1] != '/') {
+	    zlemetacs > 0 && zlemetaline[zlemetacs - 1] != '/') {
 	    /* If we have a filename or we completed a parameter name      *
 	     * and AUTO_PARAM_SLASH is set, lets see if it is a directory. *
 	     * If it is, we append a slash.                                */
@@ -1079,7 +1083,7 @@ do_single(Cmatch m)
 	    }
 	}
 	if (!minfo.insc)
-	    zlecs = minfo.pos + minfo.len - m->qisl;
+	    zlemetacs = minfo.pos + minfo.len - m->qisl;
     }
     /* If completing in a brace expansion... */
     if (brbeg) {
@@ -1092,7 +1096,7 @@ do_single(Cmatch m)
 	} else if (!menucmp) {
 	    /*{{*/
 	    /* Otherwise, add a `,' suffix, and let `}' remove it. */
-	    zlecs = scs;
+	    zlemetacs = scs;
 	    havesuff = 1;
 	    inststrlen(",", 1, 1);
 	    minfo.insc++;
@@ -1121,9 +1125,9 @@ do_single(Cmatch m)
 	makeparamsuffix(((m->flags & CMF_PARBR) ? 1 : 0), minfo.insc - parq);
 
     if ((menucmp && !minfo.we) || !movetoend) {
-	zlecs = minfo.end;
-	if (zlecs + m->qisl == lastend)
-	    zlecs += minfo.insc;
+	zlemetacs = minfo.end;
+	if (zlemetacs + m->qisl == lastend)
+	    zlemetacs += minfo.insc;
     }
     {
 	Cmatch *om = minfo.cur;
@@ -1169,9 +1173,13 @@ do_menucmp(int lst)
 	     (((*minfo.cur)->flags & (CMF_NOLIST | CMF_MULT)) &&
 	      (!(*minfo.cur)->str || !*(*minfo.cur)->str)));
     /* ... and insert it into the command line. */
-    metafy_line();
-    do_single(*(minfo.cur));
-    unmetafy_line();
+    /* Already metafied when called from domenuselect already */
+    if (zlemetaline == NULL) {
+	metafy_line();
+	do_single(*minfo.cur);
+	unmetafy_line();
+    } else
+	do_single(*minfo.cur);
 }
 
 /**/
@@ -1207,6 +1215,15 @@ reverse_menu(UNUSED(Hookdef dummy), UNUSED(void *dummy2))
 mod_export int
 accept_last(void)
 {
+    /* give up trying to work out what state it should be in */
+    int wasmeta;
+    if (zlemetaline != NULL) {
+	wasmeta = 1;
+    } else {
+	wasmeta = 0;
+	metafy_line();
+    }
+
     if (!menuacc) {
 	zsfree(minfo.prebr);
 	minfo.prebr = ztrdup(lastprebr);
@@ -1232,29 +1249,32 @@ accept_last(void)
 
 	iremovesuffix(',', 1);
 
-	l = (brscs >= 0 ? brscs : zlecs) - brpcs;
+	l = (brscs >= 0 ? brscs : zlemetacs) - brpcs;
 
 	zsfree(lastbrbeg->str);
 	lastbrbeg->str = (char *) zalloc(l + 2);
-	memcpy(lastbrbeg->str, zleline + brpcs, l);
+	memcpy(lastbrbeg->str, zlemetaline + brpcs, l);
 	lastbrbeg->str[l] = ',';
 	lastbrbeg->str[l + 1] = '\0';
     } else {
 	int l;
 
-	zlecs = minfo.pos + minfo.len + minfo.insc;
+	zlemetacs = minfo.pos + minfo.len + minfo.insc;
 	iremovesuffix(' ', 1);
-	l = zlecs;
-	zlecs = minfo.pos + minfo.len + minfo.insc - (*(minfo.cur))->qisl;
-	if (zlecs < l)
-	    foredel(l - zlecs);
-	else if (zlecs > zlell)
-	    zlecs = zlell;
+	l = zlemetacs;
+	zlemetacs = minfo.pos + minfo.len + minfo.insc - (*(minfo.cur))->qisl;
+	if (zlemetacs < l)
+	    foredel(l - zlemetacs);
+	else if (zlemetacs > zlemetall)
+	    zlemetacs = zlemetall;
 	inststrlen(" ", 1, 1);
 	minfo.insc = minfo.len = 0;
-	minfo.pos = zlecs;
+	minfo.pos = zlemetacs;
 	minfo.we = 1;
     }
+
+    if (!wasmeta)
+	unmetafy_line();
     return 0;
 }
 
@@ -2182,8 +2202,9 @@ invalidate_list(void)
 {
     invcount++;
     if (validlist) {
-	if (showinglist == -2)
+	if (showinglist == -2) {
 	    zrefresh();
+	}
 	freematches(lastmatches, 1);
 	lastmatches = NULL;
 	hasoldlist = 0;
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
index f23d7aa2c..ff4d81b47 100644
--- a/Src/Zle/zle.h
+++ b/Src/Zle/zle.h
@@ -279,3 +279,16 @@ enum {
     ZSL_COPY = 1,		/* Copy the argument, don't modify it */
     ZSL_TOEND = 2,		/* Go to the end of the new line */
 };
+
+#ifdef DEBUG
+#define STRINGIFY_LITERAL(x)	# x
+#define STRINGIFY(x)		STRINGIFY_LITERAL(x)
+#define ERRMSG(x)		(__FILE__ ":" STRINGIFY(__LINE__) ": " x)
+#define METACHECK()		\
+	DPUTS(zlemetaline == NULL, ERRMSG("line not metafied"))
+#define UNMETACHECK()		\
+	DPUTS(zlemetaline != NULL, ERRMSG("line metafied"))
+#else
+#define METACHECK()
+#define UNMETACHECK()
+#endif
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
index bed66ac0e..c00eafe18 100644
--- a/Src/Zle/zle_hist.c
+++ b/Src/Zle/zle_hist.c
@@ -82,7 +82,8 @@ zletext(Histent ent, struct zle_text *zt)
 	return;
     }
 
-    zt->text = stringaszleline(ent->text, &zt->len, NULL);
+    zt->text = stringaszleline((unsigned char *)ent->text, 0,
+			       &zt->len, NULL, NULL);
     zt->alloced = 1;
 }
 
@@ -351,7 +352,7 @@ historysearchbackward(char **args)
 	return ret;
     }
     if (*args) {
-	str = stringaszleline((unsigned char *)*args, &hp, NULL);
+	str = stringaszleline((unsigned char *)*args, 0, &hp, NULL, NULL);
     } else {
 	if (histline == curhist || histline != srch_hl || zlecs != srch_cs ||
 	    mark != 0 || ZS_memcmp(srch_str, zleline, histpos) != 0) {
@@ -410,7 +411,7 @@ historysearchforward(char **args)
 	return ret;
     }
     if (*args) {
-	str = stringaszleline((unsigned char *)*args, &hp, NULL);
+	str = stringaszleline((unsigned char *)*args, 0, &hp, NULL, NULL);
     } else {
 	if (histline == curhist || histline != srch_hl || zlecs != srch_cs ||
 	    mark != 0 || ZS_memcmp(srch_str, zleline, histpos) != 0) {
@@ -631,7 +632,7 @@ insertlastword(char **args)
     n = zmult;
     zmult = 1;
 
-    zs = stringaszleline((unsigned char *)s, &len, NULL);
+    zs = stringaszleline((unsigned char *)s, 0, &len, NULL, NULL);
     doinsert(zs, len);
     free(zs);
     zmult = n;
@@ -736,8 +737,8 @@ pushlineoredit(char **args)
     if (zmult < 0)
 	return 1;
     if (hline && *hline) {
-	ZLE_STRING_T zhline = stringaszleline((unsigned char *)hline,
-					      &ics, NULL);
+	ZLE_STRING_T zhline = stringaszleline((unsigned char *)hline, 0,
+					      &ics, NULL, NULL);
 
 	sizeline(ics + zlell + 1);
 	/* careful of overlapping copy */
@@ -780,7 +781,7 @@ zgetline(UNUSED(char **args))
 	return 1;
     } else {
 	int cc;
-	ZLE_STRING_T lineadd = stringaszleline(s, &cc, NULL);
+	ZLE_STRING_T lineadd = stringaszleline(s, 0, &cc, NULL, NULL);
 
 	spaceinline(cc);
 	ZS_memcpy(zleline + zlecs, lineadd, cc);
@@ -1257,8 +1258,8 @@ getvisrchstr(void)
 	    	cmd == Th(z_vicmdmode)) {
 	    int newlen;
 	    sbuf[sptr] = ZWC('\0');
-	    visrchstr = zlelineasstring(sbuf + 1, sptr - 1, 0, &newlen,
-					NULL, 0);
+	    visrchstr = (char *)zlelineasstring(sbuf + 1, sptr - 1, 0, &newlen,
+						NULL, 0);
 	    if (!newlen) {
 	        zsfree(visrchstr);
 		visrchstr = ztrdup(vipenultsrchstr);
@@ -1378,7 +1379,8 @@ virepeatsearch(UNUSED(char **args))
 	n = -n;
 	visrchsense = -visrchsense;
     }
-    srcstr = stringaszleline(visrchstr, &srclen, NULL);
+    srcstr = stringaszleline((unsigned char *)visrchstr, 0,
+			     &srclen, NULL, NULL);
     if (!(he = quietgethist(histline)))
 	return 1;
     while ((he = movehistent(he, visrchsense, hist_skip_flags))) {
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 1acff3bbb..2522f67fd 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -42,6 +42,11 @@
 /**/
 mod_export ZLE_STRING_T zleline;
 
+/* Cursor position and line length in zle */
+
+/**/
+mod_export int zlecs, zlell;
+
 /* != 0 if in a shell function called from completion, such that read -[cl]  *
  * will work (i.e., the line is metafied, and the above word arrays are OK). */
 
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
index 44bc611d5..9ade372b1 100644
--- a/Src/Zle/zle_misc.c
+++ b/Src/Zle/zle_misc.c
@@ -69,7 +69,7 @@ selfinsert(UNUSED(char **args))
     tmp = lastchar_wide;
     doinsert(&tmp, 1);
 #else
-    char s = lastchar;
+    unsigned char s = lastchar;
     doinsert(&s, 1);
 #endif
     return 0;
@@ -480,7 +480,8 @@ whatcursorposition(UNUSED(char **args))
 	     * convert a single character, remembering it may
 	     * turn into a multibyte string or be metafied.
 	     */
-	    mbstr = zlelineasstring(zleline+zlecs, 1, 0, &len, NULL, 1);
+	    mbstr = (char *)zlelineasstring(zleline+zlecs, 1, 0,
+					    &len, NULL, 1);
 	    strcpy(s, mbstr);
 	    s += len;
 	}
@@ -654,7 +655,7 @@ copyprevshellword(UNUSED(char **args))
 
     if (p) {
 	int len;
-	ZLE_STRING_T lineadd = stringaszleline(p, &len, NULL);
+	ZLE_STRING_T lineadd = stringaszleline(p, 0, &len, NULL, NULL);
 
 	spaceinline(len);
 	ZS_memcpy(zleline + zlecs, lineadd, len);
@@ -777,7 +778,7 @@ executenamedcommand(char *prmt)
     clearlist = 1;
     /* prmt may be constant */
     prmt = ztrdup(prmt);
-    zprmt = stringaszleline((unsigned char *)prmt, &l, NULL);
+    zprmt = stringaszleline((unsigned char *)prmt, 0, &l, NULL, NULL);
     cmdbuf = zhalloc((l + NAMLEN + 2) * ZLE_CHAR_SIZE);
     ZS_memcpy(cmdbuf, zprmt, l);
     free(zprmt);
@@ -860,7 +861,8 @@ executenamedcommand(char *prmt)
 		Thingy r;
 		unambiguous:
 		*ptr = 0;
-		namedcmdstr = zlelineasstring(cmdbuf, len, 0, NULL, NULL, 0);
+		namedcmdstr = (char *)zlelineasstring(cmdbuf, len, 0,
+						      NULL, NULL, 0);
 		r = rthingy(namedcmdstr);
 		free(namedcmdstr);
 		namedcmdstr = NULL;
@@ -890,7 +892,8 @@ executenamedcommand(char *prmt)
 
 		namedcmdll = newlinklist();
 
-		namedcmdstr = zlelineasstring(cmdbuf, len, 0, NULL, NULL, 0);
+		namedcmdstr = (char *)zlelineasstring(cmdbuf, len, 0,
+						      NULL, NULL, 0);
 		scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0);
 		free(namedcmdstr);
 		namedcmdstr = NULL;
@@ -912,7 +915,9 @@ executenamedcommand(char *prmt)
 		    zmult = zmultsav;
 		} else if (!nextnode(firstnode(namedcmdll))) {
 		    char *peekstr = ztrdup(peekfirst(namedcmdll));
-		    ZLE_STRING_T ztmp = stringaszleline(peekstr, &len, NULL);
+		    ZLE_STRING_T ztmp = 
+			stringaszleline((unsigned char *)peekstr, 0, &len,
+					NULL, NULL);
 		    zsfree(peekstr);
 		    ZS_memcpy(ptr = cmdbuf, ztmp, len);
 		    ptr += len;
@@ -922,7 +927,9 @@ executenamedcommand(char *prmt)
 		} else {
 		    int ltmp;
 		    char *peekstr = ztrdup(peekfirst(namedcmdll));
-		    ZLE_STRING_T ztmp = stringaszleline(peekstr, &ltmp, NULL);
+		    ZLE_STRING_T ztmp =
+			stringaszleline((unsigned char *)peekstr, 0, &ltmp,
+					NULL, NULL);
 		    zsfree(peekstr);
 		    ZS_memcpy(cmdbuf, ztmp, ltmp);
 		    free(ztmp);
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
index f089a5f47..7aef5959d 100644
--- a/Src/Zle/zle_params.c
+++ b/Src/Zle/zle_params.c
@@ -126,6 +126,8 @@ static struct zleparam {
     { NULL, 0, NULL, NULL }
 };
 
+/* ro means parameters are readonly, used from completion */
+
 /**/
 mod_export void
 makezleparams(int ro)
@@ -187,7 +189,10 @@ set_buffer(UNUSED(Param pm), char *x)
 static char *
 get_buffer(UNUSED(Param pm))
 {
-    return (char *)zlelineasstring(zleline, zlell, 0, NULL, NULL, 1);
+    if (zlemetaline != 0)
+	return dupstring((char *)zlemetaline);
+    else
+	return (char *)zlelineasstring(zleline, zlell, 0, NULL, NULL, 1);
 }
 
 /**/
@@ -208,7 +213,17 @@ set_cursor(UNUSED(Param pm), zlong x)
 static zlong
 get_cursor(UNUSED(Param pm))
 {
-    return zlecs;
+    if (zlemetaline != NULL) {
+	/* A lot of work for one number, but still... */
+	ZLE_STRING_T tmpline;
+	int tmpcs, tmpll, tmpsz;
+	tmpline = stringaszleline(zlemetaline, zlemetacs,
+				  &tmpll, &tmpsz, &tmpcs);
+	free(tmpline);
+	return tmpcs;
+    }
+    else
+	return zlecs;
 }
 
 /**/
@@ -238,9 +253,9 @@ set_lbuffer(UNUSED(Param pm), char *x)
     int len;
 
     if (x && *x != ZWC('\0'))
-	y = stringaszleline((unsigned char *)x, &len, NULL);
+	y = stringaszleline((unsigned char *)x, 0, &len, NULL, NULL);
     else
-	y = ZWC(""), len = 0;
+	y = ZWS(""), len = 0;
     sizeline(zlell - zlecs + len);
     ZS_memmove(zleline + len, zleline + zlecs, zlell - zlecs);
     ZS_memcpy(zleline, y, len);
@@ -257,7 +272,10 @@ set_lbuffer(UNUSED(Param pm), char *x)
 static char *
 get_lbuffer(UNUSED(Param pm))
 {
-    return (char *)zlelineasstring(zleline, zlecs, 0, NULL, NULL, 1);
+    if (zlemetaline != NULL)
+	return dupstrpfx((char *)zlemetaline, zlemetacs);
+    else
+	return (char *)zlelineasstring(zleline, zlecs, 0, NULL, NULL, 1);
 }
 
 /**/
@@ -268,9 +286,9 @@ set_rbuffer(UNUSED(Param pm), char *x)
     int len;
 
     if (x && *x != ZWC('\0'))
-	y = stringaszleline((unsigned char *)x, &len, NULL);
+	y = stringaszleline((unsigned char *)x, 0, &len, NULL, NULL);
     else
-	y = ZWC(""), len = 0;
+	y = ZWS(""), len = 0;
     sizeline(zlell = zlecs + len);
     ZS_memcpy(zleline + zlecs, y, len);
     zsfree(x);
@@ -284,8 +302,11 @@ set_rbuffer(UNUSED(Param pm), char *x)
 static char *
 get_rbuffer(UNUSED(Param pm))
 {
-    return (char *)zlelineasstring(zleline + zlecs, zlell - zlecs,
-				   0, NULL, NULL, 1);
+    if (zlemetaline != NULL)
+	return dupstrpfx((char *)zleline + zlemetacs, zlemetall - zlemetacs);
+    else
+	return (char *)zlelineasstring(zleline + zlecs, zlell - zlecs,
+				       0, NULL, NULL, 1);
 }
 
 /**/
@@ -435,7 +456,7 @@ set_cutbuffer(UNUSED(Param pm), char *x)
     cutbuf.flags = 0;
     if (x) {
 	int n;
-	cutbuf.buf = stringaszleline((unsigned char *)x, &n, NULL);
+	cutbuf.buf = stringaszleline((unsigned char *)x, 0, &n, NULL, NULL);
 	cutbuf.len = n;
 	free(x);
     } else {
@@ -490,7 +511,8 @@ set_killring(UNUSED(Param pm), char **x)
 	    int n, len = strlen(*p);
 	    kptr = kring + kpos;
 
-	    kptr->buf = stringaszleline((unsigned char *)*p, &n, NULL);
+	    kptr->buf = stringaszleline((unsigned char *)*p, 0, &n,
+					NULL, NULL);
 	    kptr->len = n;
 
 	    zfree(*p, len+1);
@@ -556,7 +578,7 @@ set_prepost(ZLE_STRING_T *textvar, int *lenvar, char *x)
 	*lenvar = 0;
     }
     if (x) {
-	*textvar = stringaszleline((unsigned char *)x, lenvar, NULL);
+	*textvar = stringaszleline((unsigned char *)x, 0, lenvar, NULL, NULL);
 	free(x);
     }
 }
@@ -610,8 +632,8 @@ static char *
 get_lsearch(UNUSED(Param pm))
 {
     if (previous_search_len)
-	return zlelineasstring(previous_search, previous_search_len, 0,
-			       NULL, NULL, 1);
+	return (char *)zlelineasstring(previous_search, previous_search_len, 0,
+				       NULL, NULL, 1);
     else
 	return "";
 }
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index b45047ac2..da9fe45b7 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -333,7 +333,8 @@ zrefresh(void)
 	*qbuf;			/* tmp					     */
     int tmpcs, tmpll;		/* ditto cursor position and line length */
     int tmpalloced;		/* flag to free tmpline when finished */
-
+    int remetafy;		/* flag that zle line is metafied */
+    
     if (trashedzle)
 	reexpandprompt();
 
@@ -344,6 +345,17 @@ zrefresh(void)
     if (inlist)
 	return;
 
+    /*
+     * zrefresh() is called from all over the place, so we can't
+     * be sure if the line is metafied for completion or not.
+     */
+    if (zlemetaline != NULL) {
+	remetafy = 1;
+	unmetafy_line();
+    }
+    else
+	remetafy = 0;
+
     if (predisplaylen || postdisplaylen) {
 	/* There is extra text to display at the start or end of the line */
 	tmpline = zalloc((zlell + predisplaylen + postdisplaylen)*sizeof(*tmpline));
@@ -743,6 +755,9 @@ singlelineout:
     }
     if (showinglist == -1)
 	showinglist = nlnct;
+
+    if (remetafy)
+	metafy_line();
 }
 
 #define tcinscost(X)   (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS])
@@ -1189,6 +1204,12 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
     int t0,			/* tmp			       */
 	vsiz,			/* size of new video buffer    */
 	nvcs = 0;		/* new video cursor column     */
+#ifdef ZLE_UNICODE_SUPPORT
+    ZLE_STRING_T lpwbuf, lpwp;	/* converted lprompt and pointer */
+    char *lpptr,		/* pointer into multibyte lprompt */
+	lpend;			/* end of multibyte lprompt */
+    mbstate_t ps;		/* shift state */
+#endif
 
     nlnct = 1;
 /* generate the new line buffer completely */
@@ -1208,14 +1229,44 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
     }
 
     /* only use last part of prompt */
-    /* TODO convert prompt to wide char */
 #ifdef ZLE_UNICODE_SUPPORT
-    t0 = mbtowc(vbuf, strchr(lpromptbuf, 0) - lpromptw, lpromptw);
-    if (t0 >= 0) {
-	vbuf[t0] = ZWC('\0');
-	vp = vbuf + t0;
-    } else
-	/* FIXME What to do? */ ;
+    /*
+     * Convert the entire lprompt so that we know how to count
+     * characters.
+     *
+     * TODO screen widths are still not correct, indeed lpromptw knows
+     * nothing about multibyte characters so may be too long.
+     */
+    lpend = strchr(lpromptbuf, 0);
+    /* Worst case number of characters, not null-terminated */
+    lpwp = lpwbuf = (ZLE_STRING_T)zalloc((lpend - lpromptbuf)
+					 * sizeof(*lpwbuf));
+    /* Reset shift state, maybe. */
+    memset(&ps, '\0', sizeof(ps));
+    for (lpptr = lpromptbuf; lpptr < lpend; ) {
+	t0 = mbrtowc(lpwp, lpptr, lpend - lpptr, &ps);
+	if (t0 > 0) {
+	    /* successfully converted */
+	    lpptr += t0;
+	    lpwp++;
+	} else {
+	    /* dunno, try to recover */
+	    lpptr++;
+	    *lpwp++ = ZWC('?');
+	}
+    }
+    if (lpwp - lpwbuf < lpromptw) {
+	/* Not enough characters for lpromptw. */
+	ZS_memcpy(vbuf, lpwbuf, lpwp - lpwbuf);
+	vp = vbuf + (lpwp - lpwbuf);
+	while (vp < vbuf + lpromptw)
+	    *vp++ = ZWC(' ');
+    } else {
+	ZS_memcpy(vbuf, lpwp - lpromptw, lpromptw);
+	vp = vbuf + lpromptw;
+    }
+    *vp = ZWC('\0');
+    zfree(lpwbuf, lpromptw * sizeof(*lpwbuf));
 #else
     memcpy(vbuf, strchr(lpromptbuf, 0) - lpromptw, lpromptw);
     vbuf[lpromptw] = '\0';
@@ -1223,10 +1274,10 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 #endif
 
     for (t0 = 0; t0 < tmpll; t0++) {
-	if (tmpline[t0] == ZWC('\t'))
+	if (tmpline[t0] == ZWC('\t')) {
 	    for (*vp++ = ZWC(' '); (vp - vbuf) & 7; )
 		*vp++ = ZWC(' ');
-	else if (tmpline[t0] == ZWC('\n')) {
+	} else if (tmpline[t0] == ZWC('\n')) {
 	    *vp++ = ZWC('\\');
 	    *vp++ = ZWC('n');
 	} else if (ZC_icntrl(tmpline[t0])) {
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index 5885fb10f..d756b94e6 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -416,8 +416,8 @@ bin_zle_refresh(UNUSED(char *name), char **args, Options ops, UNUSED(char func))
     statusll = 0;
     if (*args) {
 	if (**args) {
-	    statusline = stringaszleline((unsigned char *)*args, &statusll,
-					 NULL);
+	    statusline = stringaszleline((unsigned char *)*args, 0, &statusll,
+					 NULL, NULL);
 	}
 	if (*++args) {
 	    LinkList l = newlinklist();
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index f59823f0b..ba149cfe8 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -33,19 +33,40 @@
 /*
  * The main part of ZLE maintains the line being edited as binary data,
  * but here, where we interface with the lexer and other bits of zsh, we
- * need the line metafied.  The technique used is quite simple: on entry
- * to the expansion/completion system, we metafy the line in place,
- * adjusting zlell and zlecs to match.  All completion and expansion is
- * done on the metafied line.  Immediately before returning, the line is
- * unmetafied again, changing zlell and zlecs back.  (zlell and zlecs
- * might have changed during completion, so they can't be merely saved
- * and restored.)  The various indexes into the line that are used in
- * this file only are not translated: they remain indexes into the
- * metafied line.
+ * need the line metafied and, if necessary, converted from wide
+ * characters into multibyte strings.  On entry to the
+ * expansion/completion system, we metafy the line from zleline into
+ * zlemetaline, with zlell and zlecs adjusted into zlemetall zlemetacs
+ * to match.  zlemetall and zlemetacs refer to raw character positions,
+ * in other words a metafied character contributes 2 to each.  All
+ * completion and expansion is done on the metafied line.  Immediately
+ * before returning, the line is unmetafied again, so that zleline,
+ * zlell and zlecs are once again valid.  (zlell and zlecs might have
+ * changed during completion, so they can't be merely saved and
+ * restored.)  The various indexes into the line that are used in this
+ * file only are not translated: they remain indexes into the metafied
+ * line.
+ *
+ * zlemetaline is always NULL when not in use and non-NULL when in use.
+ * This can be used to test if the line is metafied.  It would be
+ * possible to use zlecs and zlell directly, updated as appropriate when
+ * metafying and unmetafying, instead of zlemetacs and zlemetall,
+ * however the current system seems clearer.
  */
 
 #define inststr(X) inststrlen((X),1,-1)
 
+/*
+ * The state of the line being edited between metafy_line()
+ * unmetafy_line().
+ *
+ * zlemetacs and zlemetall are defined in lex.c.
+ */
+/**/
+mod_export unsigned char *zlemetaline;
+/**/
+mod_export int metalinesz;
+
 /* The line before completion was tried. */
 
 /**/
@@ -153,7 +174,10 @@ mod_export int comprecursive;
 /**/
 int hascompwidgets;
 
-/* Find out if we have to insert a tab (instead of trying to complete). */
+/*
+ * Find out if we have to insert a tab (instead of trying to complete).
+ * The line is not metafied here.
+ */
 
 /**/
 static int
@@ -250,6 +274,7 @@ deletecharorlist(char **args)
     useglob = isset(GLOBCOMPLETE);
     wouldinstab = 0;
 
+    /* Line not yet metafied */
     if (zlecs != zlell) {
 	fixsuffix();
 	invalidatelist();
@@ -583,32 +608,24 @@ docomplete(int lst)
 	return 0;
     }
 
-    /*
-     * TODO: metafy_line() currently tries to metafy in place.
-     * For ZLE_UNICODE_SUPPORT we need to metafy into a separate
-     * string, replacing all use of zleline, zlecs and zlell here
-     * with those values, then restoring at the end.
-     *
-     * The alternative is probably too horrendous to contemplate.
-     */
     metafy_line();
 
-    ocs = zlecs;
-    origline = dupstring((char *) zleline);
-    origcs = zlecs;
-    origll = zlell;
+    ocs = zlemetacs;
+    origline = dupstring((char *) zlemetaline);
+    origcs = zlemetacs;
+    origll = zlemetall;
     if (!isfirstln && chline != NULL) {
 	/* If we are completing in a multi-line buffer (which was not  *
 	 * taken from the history), we have to prepend the stuff saved *
 	 * in chline to the contents of line.                          */
 
-	ol = dupstring((char *)zleline);
+	ol = dupstring((char *)zlemetaline);
 	/* Make sure that chline is zero-terminated. */
 	*hptr = '\0';
-	zlecs = 0;
+	zlemetacs = 0;
 	inststr(chline);
-	chl = zlecs;
-	zlecs += ocs;
+	chl = zlemetacs;
+	zlemetacs += ocs;
     } else
 	ol = NULL;
     inwhat = IN_NOTHING;
@@ -622,35 +639,35 @@ docomplete(int lst)
      * NOTE: get_comp_string() calls pushheap(), but not popheap(). */
     noerrs = 1;
     s = get_comp_string();
-    DPUTS(wb < 0 || zlecs < wb || zlecs > we,
-	  "BUG: 0 <= wb <= zlecs <= we is not true!");
+    DPUTS(wb < 0 || zlemetacs < wb || zlemetacs > we,
+	  "BUG: 0 <= wb <= zlemetacs <= we is not true!");
     noerrs = ne;
     /* For vi mode, reset the start-of-insertion pointer to the beginning *
      * of the word being completed, if it is currently later.  Vi itself  *
      * would never change the pointer in the middle of an insertion, but  *
      * then vi doesn't have completion.  More to the point, this is only  *
      * an emulation.                                                      */
-    if (viinsbegin > ztrsub((char *) zleline + wb, (char *) zleline))
-	viinsbegin = ztrsub((char *) zleline + wb, (char *) zleline);
+    if (viinsbegin > ztrsub((char *) zlemetaline + wb, (char *) zlemetaline))
+	viinsbegin = ztrsub((char *) zlemetaline + wb, (char *) zlemetaline);
     /* If we added chline to the line buffer, reset the original contents. */
     if (ol) {
-	zlecs -= chl;
+	zlemetacs -= chl;
 	wb -= chl;
 	we -= chl;
 	if (wb < 0) {
-	    strcpy((char *) zleline, ol);
-	    zlell = strlen((char *) zleline);
-	    zlecs = ocs;
+	    strcpy((char *) zlemetaline, ol);
+	    zlemetall = strlen((char *) zlemetaline);
+	    zlemetacs = ocs;
 	    popheap();
 	    unmetafy_line();
 	    zsfree(s);
 	    active = 0;
 	    return 1;
 	}
-	ocs = zlecs;
-	zlecs = 0;
+	ocs = zlemetacs;
+	zlemetacs = 0;
 	foredel(chl);
-	zlecs = ocs;
+	zlemetacs = ocs;
     }
     freeheap();
     /* Save the lexer state, in case the completion code uses the lexer *
@@ -694,7 +711,7 @@ docomplete(int lst)
 		    if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
 			if (*++q == Inbrace) {
 			    if (! skipparens(Inbrace, Outbrace, &q) &&
-				q == s + zlecs - wb)
+				q == s + zlemetacs - wb)
 				lst = COMP_EXPAND;
 			} else {
 			    char *t, sav, sav2;
@@ -725,7 +742,7 @@ docomplete(int lst)
 				    q++;
 			    sav = *q;
 			    *q = '\0';
-			    if (zlecs - wb == q - s &&
+			    if (zlemetacs - wb == q - s &&
 				(idigit(sav2) || checkparams(t)))
 				lst = COMP_EXPAND;
 			    *q = sav;
@@ -735,7 +752,7 @@ docomplete(int lst)
 			    lst = COMP_COMPLETE;
 		    } else
 			break;
-		} while (q < s + zlecs - wb);
+		} while (q < s + zlemetacs - wb);
 	    if (lst == COMP_EXPAND_COMPLETE) {
 		/* If it is still not clear if we should use expansion or   *
 		 * completion and there is a `$' or a backtick in the word, *
@@ -760,7 +777,7 @@ docomplete(int lst)
 	    for (q = w; *q; q++)
 		if (INULL(*q))
 		    *q = Nularg;
-	    zlecs = wb;
+	    zlemetacs = wb;
 	    foredel(we - wb);
 
 	    untokenize(x = ox = dupstring(w));
@@ -775,8 +792,8 @@ docomplete(int lst)
 	    /* Do expansion. */
 	    char *ol = (olst == COMP_EXPAND ||
                         olst == COMP_EXPAND_COMPLETE) ?
-		dupstring((char *)zleline) : (char *)zleline;
-	    int ocs = zlecs, ne = noerrs;
+		dupstring((char *)zlemetaline) : (char *)zlemetaline;
+	    int ocs = zlemetacs, ne = noerrs;
 
 	    noerrs = 1;
 	    ret = doexpansion(origword, lst, olst, lincmd);
@@ -786,8 +803,8 @@ docomplete(int lst)
 	    /* If expandorcomplete was invoked and the expansion didn't *
 	     * change the command line, do completion.                  */
 	    if (olst == COMP_EXPAND_COMPLETE &&
-		!strcmp(ol, (char *)zleline)) {
-		zlecs = ocs;
+		!strcmp(ol, (char *)zlemetaline)) {
+		zlemetacs = ocs;
 		errflag = 0;
 
 		if (!compfunc) {
@@ -808,15 +825,15 @@ docomplete(int lst)
             } else {
                 if (ret)
                     clearlist = 1;
-                if (!strcmp(ol, (char *)zleline)) {
+                if (!strcmp(ol, (char *)zlemetaline)) {
                     /* We may have removed some quotes. For completion, other
                      * parts of the code re-install them, but for expansion
                      * we have to do it here. */
-                    zlecs = 0;
-                    foredel(zlell);
+                    zlemetacs = 0;
+                    foredel(zlemetall);
                     spaceinline(origll);
-                    memcpy(zleline, origline, origll);
-                    zlecs = origcs;
+                    memcpy(zlemetaline, origline, origll);
+                    zlemetacs = origcs;
                 }
             }
 	} else
@@ -828,11 +845,11 @@ docomplete(int lst)
     /* Reset the lexer state, pop the heap. */
     lexrestore();
     popheap();
-    unmetafy_line();
 
     dat[0] = lst;
     dat[1] = ret;
     runhookdef(AFTERCOMPLETEHOOK, (void *) dat);
+    unmetafy_line();
 
     active = 0;
     return dat[1];
@@ -865,23 +882,26 @@ addx(char **ptmp)
 {
     int addspace = 0;
 
-    if (!zleline[zlecs] || zleline[zlecs] == '\n' ||
-	(iblank(zleline[zlecs]) && (!zlecs || zleline[zlecs-1] != '\\')) ||
-	zleline[zlecs] == ')' || zleline[zlecs] == '`' ||
-	zleline[zlecs] == '}' ||
-	zleline[zlecs] == ';' || zleline[zlecs] == '|' ||
-	zleline[zlecs] == '&' ||
-	zleline[zlecs] == '>' || zleline[zlecs] == '<' ||
-	(instring && (zleline[zlecs] == '"' || zleline[zlecs] == '\'')) ||
-	(addspace = (comppref && !iblank(zleline[zlecs])))) {
-	*ptmp = (char *)zleline;
-	zleline = (ZLE_STRING_T)zhalloc(strlen((char *)zleline) + 3 +
-					   addspace);
-	memcpy(zleline, *ptmp, zlecs);
-	zleline[zlecs] = 'x';
+    if (!zlemetaline[zlemetacs] || zlemetaline[zlemetacs] == '\n' ||
+	(iblank(zlemetaline[zlemetacs]) &&
+	 (!zlemetacs || zlemetaline[zlemetacs-1] != '\\')) ||
+	zlemetaline[zlemetacs] == ')' || zlemetaline[zlemetacs] == '`' ||
+	zlemetaline[zlemetacs] == '}' ||
+	zlemetaline[zlemetacs] == ';' || zlemetaline[zlemetacs] == '|' ||
+	zlemetaline[zlemetacs] == '&' ||
+	zlemetaline[zlemetacs] == '>' || zlemetaline[zlemetacs] == '<' ||
+	(instring && (zlemetaline[zlemetacs] == '"' ||
+		      zlemetaline[zlemetacs] == '\'')) ||
+	(addspace = (comppref && !iblank(zlemetaline[zlemetacs])))) {
+	*ptmp = (char *)zlemetaline;
+	zlemetaline = (unsigned char *)zhalloc(strlen((char *)zlemetaline)
+					       + 3 + addspace);
+	memcpy(zlemetaline, *ptmp, zlemetacs);
+	zlemetaline[zlemetacs] = 'x';
 	if (addspace)
-	    zleline[zlecs+1] = ' ';
-	strcpy((char *)zleline + zlecs + 1 + addspace, (*ptmp) + zlecs);
+	    zlemetaline[zlemetacs+1] = ' ';
+	strcpy((char *)zlemetaline + zlemetacs + 1 + addspace,
+	       (*ptmp) + zlemetacs);
 	addedx = 1 + addspace;
     } else {
 	addedx = 0;
@@ -902,35 +922,47 @@ dupstrspace(const char *str)
     return t;
 }
 
-/* These functions metafy and unmetafy the ZLE buffer, as described at the *
- * top of this file.  Note that zlell and zlecs are translated.  They *must* be  *
- * called in matching pairs, around all the expansion/completion code.     *
- * Currently, there are four pairs: in history expansion, in the main      *
- * completion function, and one in each of the middle-of-menu-completion   *
- * functions (there's one for each direction).                             */
+/*
+ * These functions metafy and unmetafy the ZLE buffer, as described at
+ * the top of this file.  They *must* be called in matching pairs,
+ * around all the expansion/completion code.
+ *
+ * The variables zleline, zlell and zlecs are metafied into
+ * zlemetaline, zlemetall and zlemetacs.  Only the latter variables
+ * should be referred to from above zle (i.e. in the main shell),
+ * or when using the completion API (if that's not too strong a
+ * way of referring to it).
+ */
 
 /**/
 mod_export void
 metafy_line(void)
 {
-    int len = zlell;
-    char *s;
+    UNMETACHECK();
+
+    zlemetaline = zlelineasstring(zleline, zlell, zlecs,
+				  &zlemetall, &zlemetacs, 0);
+    metalinesz = zlemetall;
 
-    for (s = (char *) zleline; s < (char *) zleline + zlell;)
-	if (imeta(*s++))
-	    len++;
-    sizeline(len);
-    (void) metafy((char *) zleline, zlell, META_NOALLOC);
-    zlell = len;
-    zlecs = metalen((char *) zleline, zlecs);
+    /*
+     * We will always allocate a new zleline based on zlemetaline.
+     */
+    free(zleline);
+    zleline = NULL;
 }
 
 /**/
 mod_export void
 unmetafy_line(void)
 {
-    zlecs = ztrsub((char *) zleline + zlecs, (char *) zleline);
-    (void) unmetafy((char *) zleline, &zlell);
+    METACHECK();
+
+    /* paranoia */
+    zlemetaline[zlemetall] = '\0';
+    zleline = stringaszleline(zlemetaline, zlemetacs, &zlell, &linesz, &zlecs);
+
+    free(zlemetaline);
+    zlemetaline = NULL;
 }
 
 /* Free a brinfo list. */
@@ -1004,6 +1036,8 @@ get_comp_string(void)
     int ona = noaliases, qsub;
     char *s = NULL, *linptr, *tmp, *p, *tt = NULL, rdop[20];
 
+    METACHECK();
+
     freebrinfo(brbeg);
     freebrinfo(brend);
     brbeg = lastbrbeg = brend = lastbrend = NULL;
@@ -1025,7 +1059,8 @@ get_comp_string(void)
      * "...", `...`, or ((...)). Nowadays this is only used to find   *
      * out if we are inside `...`.                                    */
 
-    for (i = j = k = 0, p = (char *)zleline; p < (char *)zleline + zlecs; p++)
+    for (i = j = k = 0, p = (char *)zlemetaline;
+	 p < (char *)zlemetaline + zlemetacs; p++)
 	if (*p == '`' && !(k & 1))
 	    i++;
 	else if (*p == '\"' && !(k & 1) && !(i & 1))
@@ -1037,7 +1072,7 @@ get_comp_string(void)
     inbackt = (i & 1);
     instring = 0;
     addx(&tmp);
-    linptr = (char *)zleline;
+    linptr = (char *)zlemetaline;
     pushheap();
 
  start:
@@ -1146,14 +1181,14 @@ get_comp_string(void)
 	    tt = tokstr ? dupstring(tokstr) : NULL;
 
             if (isset(RCQUOTES) && *tt == Snull) {
-                char *p, *e = tt + zlecs - wb;
+                char *p, *e = tt + zlemetacs - wb;
                 for (p = tt; *p && p < e; p++)
                     if (*p == '\'')
                         qsub++;
             }
 	    /* If we added a `x', remove it. */
 	    if (addedx && tt)
-		chuck(tt + zlecs - wb - qsub);
+		chuck(tt + zlemetacs - wb - qsub);
 	    tt0 = tok;
 	    /* Store the number of this word. */
 	    clwpos = i;
@@ -1201,8 +1236,8 @@ get_comp_string(void)
 	/* If this is the word the cursor is in and we added a `x', *
 	 * remove it.                                               */
 	if (clwpos == i++ && addedx)
-	    chuck(&clwords[i - 1][((zlecs - wb - qsub) >= sl) ?
-				 (sl - 1) : (zlecs - wb - qsub)]);
+	    chuck(&clwords[i - 1][((zlemetacs - wb - qsub) >= sl) ?
+				 (sl - 1) : (zlemetacs - wb - qsub)]);
     } while (tok != LEXERR && tok != ENDINPUT &&
 	     (tok != SEPER || (zleparse && !tt0)));
     /* Calculate the number of words stored in the clwords array. */
@@ -1224,13 +1259,14 @@ get_comp_string(void)
 	/* We are in command or process substitution if we are not in
 	 * a $((...)). */
 	if (parend >= 0 && !tmp)
-	    zleline = (unsigned char *) dupstring(tmp = (char *)zleline);
-	linptr = (char *) zleline + zlell + addedx - parbegin + 1;
-	if ((linptr - (char *) zleline) < 3 || *linptr != '(' ||
+	    zlemetaline = (unsigned char *)
+		dupstring(tmp = (char *)zlemetaline);
+	linptr = (char *) zlemetaline + zlemetall + addedx - parbegin + 1;
+	if ((linptr - (char *) zlemetaline) < 3 || *linptr != '(' ||
 	    linptr[-1] != '(' || linptr[-2] != '$') {
 	    if (parend >= 0) {
-		zlell -= parend;
-		zleline[zlell + addedx] = '\0';
+		zlemetall -= parend;
+		zlemetaline[zlemetall + addedx] = '\0';
 	    }
 	    lexrestore();
 	    tt = NULL;
@@ -1243,7 +1279,7 @@ get_comp_string(void)
     else if (!t0 || t0 == ENDINPUT) {
 	/* There was no word (empty line). */
 	s = ztrdup("");
-	we = wb = zlecs;
+	we = wb = zlemetacs;
 	clwpos = clwnum;
 	t0 = STRING;
     } else if (t0 == STRING) {
@@ -1264,7 +1300,7 @@ get_comp_string(void)
 	*s = sav;
         if (*s == '+')
             s++;
-	if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + zlecs - wb) {
+	if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + zlemetacs - wb) {
 	    s = NULL;
 	    inwhat = IN_MATH;
 	    if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
@@ -1273,7 +1309,7 @@ get_comp_string(void)
 	    else
 		insubscr = 1;
 	} else if (*s == '=') {
-            if (zlecs > wb + (s - tt)) {
+            if (zlemetacs > wb + (s - tt)) {
                 s++;
                 wb += s - tt;
                 s = ztrdup(s);
@@ -1294,17 +1330,17 @@ get_comp_string(void)
 	}
 	lincmd = 1;
     }
-    if (we > zlell)
-	we = zlell;
-    tt = (char *)zleline;
+    if (we > zlemetall)
+	we = zlemetall;
+    tt = (char *)zlemetaline;
     if (tmp) {
-	zleline = (unsigned char *)tmp;
-	zlell = strlen((char *)zleline);
+	zlemetaline = (unsigned char *)tmp;
+	zlemetall = strlen((char *)zlemetaline);
     }
     if (t0 != STRING && inwhat != IN_MATH) {
 	if (tmp) {
 	    tmp = NULL;
-	    linptr = (char *)zleline;
+	    linptr = (char *)zlemetaline;
 	    lexrestore();
 	    addedx = 0;
 	    goto start;
@@ -1325,7 +1361,7 @@ get_comp_string(void)
 	int i = 0;
 	char *nnb = (iident(*s) ? s : s + 1), *nb = NULL, *ne = NULL;
 	
-	for (tt = s; ++tt < s + zlecs - wb;)
+	for (tt = s; ++tt < s + zlemetacs - wb;)
 	    if (*tt == Inbrack) {
 		i++;
 		nb = nnb;
@@ -1354,23 +1390,23 @@ get_comp_string(void)
 	    int lev;
 	    char *p;
 
-	    for (wb = zlecs - 1, lev = 0; wb > 0; wb--)
-		if (zleline[wb] == ']' || zleline[wb] == ')')
+	    for (wb = zlemetacs - 1, lev = 0; wb > 0; wb--)
+		if (zlemetaline[wb] == ']' || zlemetaline[wb] == ')')
 		    lev++;
-		else if (zleline[wb] == '[') {
+		else if (zlemetaline[wb] == '[') {
 		    if (!lev--)
 			break;
-		} else if (zleline[wb] == '(') {
-		    if (!lev && zleline[wb - 1] == '(')
+		} else if (zlemetaline[wb] == '(') {
+		    if (!lev && zlemetaline[wb - 1] == '(')
 			break;
 		    if (lev)
 			lev--;
 		}
-	    p = (char *) zleline + wb;
+	    p = (char *) zlemetaline + wb;
 	    wb++;
 	    if (wb && (*p == '[' || *p == '(') &&
 		!skipparens(*p, (*p == '[' ? ']' : ')'), &p)) {
-		we = (p - (char *) zleline) - 1;
+		we = (p - (char *) zlemetaline) - 1;
 		if (insubscr == 2)
 		    insubscr = 3;
 	    }
@@ -1378,25 +1414,26 @@ get_comp_string(void)
 	    /* In mathematical expression, we complete parameter names  *
 	     * (even if they don't have a `$' in front of them).  So we *
 	     * have to find that name.                                  */
-	    for (we = zlecs; iident(zleline[we]); we++);
-	    for (wb = zlecs; --wb >= 0 && iident(zleline[wb]););
+	    for (we = zlemetacs; iident(zlemetaline[we]); we++);
+	    for (wb = zlemetacs; --wb >= 0 && iident(zlemetaline[wb]););
 	    wb++;
 	}
 	zsfree(s);
 	s = zalloc(we - wb + 1);
-	strncpy(s, (char *) zleline + wb, we - wb);
+	strncpy(s, (char *) zlemetaline + wb, we - wb);
 	s[we - wb] = '\0';
-	if (wb > 2 && zleline[wb - 1] == '[' && iident(zleline[wb - 2])) {
+	if (wb > 2 && zlemetaline[wb - 1] == '[' &&
+	    iident(zlemetaline[wb - 2])) {
 	    int i = wb - 3;
-	    unsigned char sav = zleline[wb - 1];
+	    unsigned char sav = zlemetaline[wb - 1];
 
-	    while (i >= 0 && iident(zleline[i]))
+	    while (i >= 0 && iident(zlemetaline[i]))
 		i--;
 
-	    zleline[wb - 1] = '\0';
+	    zlemetaline[wb - 1] = '\0';
 	    zsfree(varname);
-	    varname = ztrdup((char *) zleline + i + 1);
-	    zleline[wb - 1] = sav;
+	    varname = ztrdup((char *) zlemetaline + i + 1);
+	    zlemetaline[wb - 1] = sav;
 	    if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
 		(keypm->flags & PM_HASHED)) {
 		if (insubscr != 3)
@@ -1407,7 +1444,7 @@ get_comp_string(void)
 	parse_subst_string(s);
     }
     /* This variable will hold the current word in quoted form. */
-    offs = zlecs - wb;
+    offs = zlemetacs - wb;
     if ((p = parambeg(s))) {
 	for (p = s; *p; p++)
 	    if (*p == Dnull)
@@ -1451,34 +1488,34 @@ get_comp_string(void)
     /* While building the quoted form, we also clean up the command line. */
     for (p = s, i = wb, j = 0; *p; p++, i++)
 	if (INULL(*p)) {
-	    if (i < zlecs)
+	    if (i < zlemetacs)
 		offs--;
 	    if (*p == Snull && isset(RCQUOTES))
 		j = 1-j;
 	    if (p[1] || *p != Bnull) {
 		if (*p == Bnull) {
-		    if (zlecs == i + 1)
-			zlecs++, offs++;
+		    if (zlemetacs == i + 1)
+			zlemetacs++, offs++;
 		} else {
-		    ocs = zlecs;
-		    zlecs = i;
+		    ocs = zlemetacs;
+		    zlemetacs = i;
 		    foredel(1);
-		    if ((zlecs = ocs) > i--)
-			zlecs--;
+		    if ((zlemetacs = ocs) > i--)
+			zlemetacs--;
 		    we--;
 		}
 	    } else {
-		ocs = zlecs;
-		zlecs = we;
+		ocs = zlemetacs;
+		zlemetacs = we;
 		backdel(1);
 		if (ocs == we)
-		    zlecs = we - 1;
+		    zlemetacs = we - 1;
 		else
-		    zlecs = ocs;
+		    zlemetacs = ocs;
 		we--;
 	    }
 	    chuck(p--);
-	} else if (j && *p == '\'' && i < zlecs)
+	} else if (j && *p == '\'' && i < zlemetacs)
 	    offs--;
 
     zsfree(origword);
@@ -1746,9 +1783,24 @@ inststrlen(char *str, int move, int len)
     if (len == -1)
 	len = strlen(str);
     spaceinline(len);
-    strncpy((char *)(zleline + zlecs), str, len);
-    if (move)
-	zlecs += len;
+    if (zlemetaline != NULL)
+    {
+	strncpy((char *)(zlemetaline + zlemetacs), str, len);
+	if (move)
+	    zlemetacs += len;
+    }
+    else
+    {
+	unsigned char *instr;
+	ZLE_STRING_T zlestr;
+	int zlelen;
+
+	instr = (unsigned char *)ztrduppfx((char *)str, len);
+	zlestr = stringaszleline(instr, 0, &zlelen, NULL, NULL);
+	ZS_strncpy(zleline + zlecs, zlestr, zlelen);
+	free(zlestr);
+	zsfree((char *)instr);
+    }
     return len;
 }
 
@@ -1801,17 +1853,17 @@ doexpansion(char *s, int lst, int olst, int explincmd)
     if (lst == COMP_LIST_EXPAND) {
 	/* Only the list of expansions was requested. Restore the 
          * command line. */
-        zlecs = 0;
-        foredel(zlell);
+        zlemetacs = 0;
+        foredel(zlemetall);
         spaceinline(origll);
-        memcpy(zleline, origline, origll);
-        zlecs = origcs;
+        memcpy(zlemetaline, origline, origll);
+        zlemetacs = origcs;
         ret = listlist(vl);
         showinglist = 0;
 	goto end;
     }
     /* Remove the current word and put the expansions there. */
-    zlecs = wb;
+    zlemetacs = wb;
     foredel(we - wb);
     while ((ss = (char *)ugetnode(vl))) {
 	ret = 0;
@@ -1820,11 +1872,11 @@ doexpansion(char *s, int lst, int olst, int explincmd)
 	inststr(ss);
 #if 0
 	if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
-	    (zlecs && zleline[zlecs-1] != '/')) {
+	    (zlemetacs && zlemetaline[zlemetacs-1] != '/')) {
 #endif
 	if (nonempty(vl) || !first) {
 	    spaceinline(1);
-	    zleline[zlecs++] = ' ';
+	    zlemetaline[zlemetacs++] = ' ';
 	}
 	first = 0;
     }
@@ -2250,14 +2302,16 @@ doexpandhist(void)
     unsigned char *ol;
     int oll, ocs, ne = noerrs, err, ona = noaliases;
 
+    UNMETACHECK();
+
     pushheap();
     metafy_line();
-    oll = zlell;
-    ocs = zlecs;
-    ol = (unsigned char *)dupstring((char *)zleline);
+    oll = zlemetall;
+    ocs = zlemetacs;
+    ol = (unsigned char *)dupstring((char *)zlemetaline);
     expanding = 1;
-    excs = zlecs;
-    zlell = zlecs = 0;
+    excs = zlemetacs;
+    zlemetall = zlemetacs = 0;
     lexsave();
     /* We push ol as it will remain unchanged */
     inpush((char *) ol, 0, NULL);
@@ -2283,8 +2337,8 @@ doexpandhist(void)
     expanding = 0;
 
     if (!err) {
-	zlecs = excs;
-	if (strcmp((char *)zleline, (char *)ol)) {
+	zlemetacs = excs;
+	if (strcmp((char *)zlemetaline, (char *)ol)) {
 	    unmetafy_line();
 	    /* For vi mode -- reset the beginning-of-insertion pointer   *
 	     * to the beginning of the line.  This seems a little silly, *
@@ -2296,9 +2350,9 @@ doexpandhist(void)
 	}
     }
 
-    strcpy((char *)zleline, (char *)ol);
-    zlell = oll;
-    zlecs = ocs;
+    strcpy((char *)zlemetaline, (char *)ol);
+    zlemetall = oll;
+    zlemetacs = ocs;
     unmetafy_line();
 
     popheap();
@@ -2325,26 +2379,35 @@ fixmagicspace(void)
 int
 magicspace(char **args)
 {
-    char *bangq;
+    ZLE_STRING_T bangq;
+    ZLE_CHAR_T zlebangchar[1];
     int ret;
     fixmagicspace();
-    for (bangq = (char *)zleline; (bangq = strchr(bangq, bangchar));
-	 bangq += 2)
-	if (bangq[1] == '"' && (bangq == (char *)zleline || bangq[-1] != '\\'))
-	    break;
+
 #ifdef ZLE_UNICODE_SUPPORT
     /*
-     * TODO: expansion and completion with Unicode are currently
-     * fundamentally broken.  Most of the code for this hasn't been
-     * commented out, but crashing the shell just because you entered
-     * a space seems to be worth guarding against.
+     * TODO: bangchar should really be a multibyte string representing
+     * a single character, since there's no fundamental reason why
+     * it shouldn't be a Unicode character.  In practice this is
+     * very minor, however.
      */
-    ret = selfinsert(args);
+    if (mbtowc(zlebangchar, (char *)&bangchar, 1) < 0)
+	return selfinsert(args);
 #else
+    zlebangchar[0] = bangchar;
+#endif
+    for (bangq = zleline; bangq < zleline + zlell; bangq++)
+    {
+	if (*bangq != zlebangchar[0])
+	    continue;
+	if (bangq[1] == ZWC('"') &&
+	    (bangq == zleline || bangq[-1] == ZWC('\\')))
+	    break;
+    }
+
     if (!(ret = selfinsert(args)) &&
-	(!bangq || bangq + 2 > (char *)zleline + zlecs))
+	(!bangq || bangq + 2 > zleline + zlecs))
 	doexpandhist();
-#endif
     return ret;
 }
 
@@ -2369,8 +2432,7 @@ getcurcmd(void)
     zleparse = 2;
     lexsave();
     metafy_line();
-    inpush(dupstrspace((char *) zleline), 0, NULL);
-    unmetafy_line();
+    inpush(dupstrspace((char *) zlemetaline), 0, NULL);
     strinbeg(1);
     pushheap();
     do {
@@ -2381,8 +2443,8 @@ getcurcmd(void)
 	if (tok == STRING && curlincmd) {
 	    zsfree(s);
 	    s = ztrdup(tokstr);
-	    cmdwb = zlell - wordbeg;
-	    cmdwe = zlell + 1 - inbufct;
+	    cmdwb = zlemetall - wordbeg;
+	    cmdwe = zlemetall + 1 - inbufct;
 	}
     }
     while (tok != ENDINPUT && tok != LEXERR && zleparse);
@@ -2390,6 +2452,7 @@ getcurcmd(void)
     strinend();
     inpop();
     errflag = zleparse = 0;
+    unmetafy_line();
     lexrestore();
 
     return s;
@@ -2426,8 +2489,13 @@ processcmd(UNUSED(char **args))
 int
 expandcmdpath(UNUSED(char **args))
 {
-    int oldcs = zlecs, na = noaliases;
+    /*
+     * zleline is not metafied for most of this function
+     * (that happens within getcurcmd()).
+     */
+    int oldcs = zlecs, na = noaliases, strll;
     char *s, *str;
+    ZLE_STRING_T zlestr;
 
     noaliases = 1;
     s = getcurcmd();
@@ -2440,8 +2508,11 @@ expandcmdpath(UNUSED(char **args))
 	return 1;
     zlecs = cmdwb;
     foredel(cmdwe - cmdwb);
-    spaceinline(strlen(str));
-    strncpy((char *)zleline + zlecs, str, strlen(str));
+    zlestr = stringaszleline((unsigned char *)str, 0,
+			     &strll, NULL, NULL);
+    spaceinline(strll);
+    ZS_strncpy(zleline + zlecs, zlestr, strll);
+    free(zlestr);
     zlecs = oldcs;
     if (zlecs >= cmdwe - 1)
 	zlecs += cmdwe - cmdwb + strlen(str);
@@ -2461,7 +2532,7 @@ expandorcompleteprefix(char **args)
 
     comppref = 1;
     ret = expandorcomplete(args);
-    if (zlecs && zleline[zlecs - 1] == ' ')
+    if (zlecs && zleline[zlecs - 1] == ZWC(' '))
         makesuffixstr(NULL, "\\-", 0);
     comppref = 0;
     return ret;
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index aa691bcdc..f8c4d2013 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -66,21 +66,36 @@ int linesz;
 void
 sizeline(int sz)
 {
-    while (sz > linesz)
+    int cursz = (zlemetaline != NULL) ? metalinesz : linesz;
+
+    while (sz > cursz)
     {
-	if (linesz < 256)
-	    linesz = 256;
+	if (cursz < 256)
+	    cursz = 256;
 	else
-	    linesz *= 4;
+	    cursz *= 4;
 
-	zleline =
-	    (ZLE_STRING_T)realloc(zleline,
-				  (linesz + 2) * ZLE_CHAR_SIZE);
+	if (zlemetaline != NULL) {
+	    /* One spare character for the NULL */
+	    zlemetaline = (unsigned char *)realloc(zlemetaline, cursz + 1);
+	} else {
+	    /* One spare character for the NULL, one for the newline */
+	    zleline =
+		(ZLE_STRING_T)realloc(zleline,
+				      (cursz + 2) * ZLE_CHAR_SIZE);
+	}
     }
+
+    if (zlemetaline != NULL)
+	metalinesz = cursz;
+    else
+	linesz = cursz;
 }
 
 /*
  * Insert a character, called from main shell.
+ * Note this always operates on the metafied multibyte version of the
+ * line.
  */
 
 /**/
@@ -88,20 +103,7 @@ mod_export void
 zleaddtoline(int chr)
 {
     spaceinline(1);
-#ifdef ZLE_UNICODE_SUPPORT
-    /*
-     * TODO: the main shell has as yet very little notion of multibyte
-     * characters.  Until this gets fixed we just have to assume
-     * this is a complete character.
-     *
-     * Possibly we could get away with attempting to build up a
-     * multibyte character here, storing partial characters between
-     * calls.
-     */
-    zleline[zlecs++] = (ZLE_CHAR_T)chr;
-#else
-    zleline[zlecs++] = chr;
-#endif
+    zlemetaline[zlemetacs++] = chr;
 }
 
 /*
@@ -125,9 +127,11 @@ zleaddtoline(int chr)
 
 /**/
 mod_export unsigned char *
-zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
-		int *outcs, int useheap)
+zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outllp,
+		int *outcsp, int useheap)
 {
+    int outcs, outll;
+
 #ifdef ZLE_UNICODE_SUPPORT
     char *s;
     int i, j;
@@ -135,9 +139,10 @@ zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
 
     s = zalloc(inll * MB_CUR_MAX + 1);
 
+    outcs = 0;
     for(i=0; i < inll; i++, incs--) {
-	if (outcs != NULL && incs == 0)
-	    *outcs = mb_len;
+	if (incs == 0)
+	    outcs = mb_len;
 	j = wctomb(s + mb_len, instr[i]);
 	if (j == -1) {
 	    /* invalid char; what to do? */
@@ -147,8 +152,41 @@ zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
     }
     s[mb_len] = '\0';
 
-    if (outll != NULL)
-	*outll = mb_len;
+    outll = mb_len;
+#else
+    outll = inll;
+    outcs = incs;
+#endif
+
+    /*
+     * *outcsp and *outllp are to be indexes into the final string,
+     * not character offsets, so we need to take account of any
+     * metafiable characters.
+     */
+    if (outcsp != NULL || outllp != NULL) {
+#ifdef ZLE_UNICODE_SUPPORT
+	unsigned char *strp = (unsigned char *)s;
+#else
+	unsigned char *strp = instr;
+#endif
+	unsigned char *stopcs = strp + outcs;
+	unsigned char *stopll = strp + outll;
+
+	while (strp < stopll) {
+	    if (imeta(*strp)) {
+		if (strp < stopcs)
+		    outcs++;
+		outll++;
+	    }
+	    strp++;
+	}
+	if (outcsp != NULL)
+	    *outcsp = outcs;
+	if (outllp != NULL)
+	    *outllp = outll;
+    }
+
+#ifdef ZLE_UNICODE_SUPPORT
     if (useheap)
     {
 	unsigned char *ret =
@@ -163,11 +201,6 @@ zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
 	return (unsigned char *) metafy((char *) s, mb_len, META_REALLOC);
     }
 #else
-    if (outll != NULL)
-	*outll = inll;
-    if (outcs != NULL)
-	*outcs = incs;
-
     return (unsigned char *) metafy((char *) instr, inll,
 				    useheap ? META_HEAPDUP : META_DUP);
 #endif
@@ -186,6 +219,11 @@ zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
  * may take a newline and a null at a later stage.)  These are not
  * included in *outsz.
  *
+ * If outcs is non-NULL, the character position in the original
+ * string incs (a standard string offset, i.e. incremented 2 for
+ * each metafied character) is converted into the corresponding
+ * character position in *outcs.
+ *
  * Note that instr is modified in place, hence should be copied
  * first if necessary;
  *
@@ -196,7 +234,8 @@ zlelineasstring(ZLE_STRING_T instr, int inll, int incs, int *outll,
 
 /**/
 mod_export ZLE_STRING_T
-stringaszleline(unsigned char *instr, int *outll, int *outsz)
+stringaszleline(unsigned char *instr, int incs,
+		int *outll, int *outsz, int *outcs)
 {
     ZLE_STRING_T outstr;
     int ll, sz;
@@ -204,7 +243,23 @@ stringaszleline(unsigned char *instr, int *outll, int *outsz)
     mbstate_t ps;
 #endif
 
-    unmetafy(instr, &ll);
+    if (outcs) {
+	/*
+	 * Take account of Meta characters in the input string
+	 * before we unmetafy it.  This does not yet take account
+	 * of multibyte characters.  If there are none, this
+	 * is all the processing required to calculate outcs.
+	 */
+	unsigned char *inptr = instr, *cspos = instr + incs;
+	while (*inptr && inptr < cspos) {
+	    if (*inptr == STOUC(Meta)) {
+		inptr++;
+		incs--;
+	    }
+	    inptr++;
+	}
+    }
+    unmetafy((char *)instr, &ll);
 
     /*
      * ll is the maximum number of characters there can be in
@@ -246,29 +301,53 @@ stringaszleline(unsigned char *instr, int *outll, int *outsz)
 	    if (*outptr == L'\0' && ret == 0)
 		ret = 1;
 
+	    if (outcs) {
+		int offs = inptr - (char *)instr;
+		if (offs <= incs && incs < offs + ret)
+		    *outcs = outptr - outstr;
+	    }
+
 	    inptr += ret;
 	    outptr++;
 	    ll -= ret;
 	}
 	*outll = outptr - outstr;
-    }
-    else
+    } else {
 	*outll = 0;
+	if (outcs)
+	    *outcs = 0;
+    }
 #else
     memcpy((char *)outstr, (char *)instr, ll);
     *outll = ll;
+    if (outcs)
+	*outcs = incs;
 #endif
 
     return outstr;
 }
 
-
+/*
+ * This function is called when we are playing very nasty tricks
+ * indeed: see bufferwords in hist.c.  Consequently we can make
+ * absolutely no assumption about the state whatsoever, except
+ * that it has one.
+ */
 
 /**/
 mod_export unsigned char *
 zlegetline(int *ll, int *cs)
 {
-    return zlelineasstring(zleline, zlell, zlecs, ll, cs, 0);
+    if (zlemetaline != NULL) {
+	*ll = zlemetall;
+	*cs = zlemetacs;
+	return (unsigned char *)ztrdup((char *)zlemetaline);
+    } else if (zleline) {
+	return zlelineasstring(zleline, zlell, zlecs, ll, cs, 0);
+    } else {
+	*ll = *cs = 0;
+	return (unsigned char *)ztrdup("");
+    }
 }
 
 
@@ -280,14 +359,25 @@ spaceinline(int ct)
 {
     int i;
 
-    sizeline(ct + zlell);
-    for (i = zlell; --i >= zlecs;)
-	zleline[i + ct] = zleline[i];
-    zlell += ct;
-    zleline[zlell] = ZWC('\0');
+    if (zlemetaline) {
+	sizeline(ct + zlemetall);
+	for (i = zlemetall; --i >= zlemetacs;)
+	    zlemetaline[i + ct] = zlemetaline[i];
+	zlemetall += ct;
+	zlemetaline[zlemetall] = '\0';
 
-    if (mark > zlecs)
-	mark += ct;
+	if (mark > zlemetacs)
+	    mark += ct;
+    } else {
+	sizeline(ct + zlell);
+	for (i = zlell; --i >= zlecs;)
+	    zleline[i + ct] = zleline[i];
+	zlell += ct;
+	zleline[zlell] = ZWC('\0');
+
+	if (mark > zlecs)
+	    mark += ct;
+    }
 }
 
 /**/
@@ -299,11 +389,19 @@ shiftchars(int to, int cnt)
     else if (mark > to)
 	mark = to;
 
-    while (to + cnt < zlell) {
-	zleline[to] = zleline[to + cnt];
-	to++;
+    if (zlemetaline) {
+	while (to + cnt < zlemetall) {
+	    zlemetaline[to] = zlemetaline[to + cnt];
+	    to++;
+	}
+	zlemetaline[zlemetall = to] = '\0';
+    } else {
+	while (to + cnt < zlell) {
+	    zleline[to] = zleline[to + cnt];
+	    to++;
+	}
+	zleline[zlell = to] = ZWC('\0');
     }
-    zleline[zlell = to] = ZWC('\0');
 }
 
 /**/
@@ -333,6 +431,7 @@ cut(int i, int ct, int dir)
     if (!ct)
 	return;
 
+    UNMETACHECK();
     if (zmod.flags & MOD_VIBUF) {
 	struct cutbuffer *b = &vibuf[zmod.vibuf];
 
@@ -411,14 +510,20 @@ cut(int i, int ct, int dir)
 mod_export void
 backdel(int ct)
 {
-    shiftchars(zlecs -= ct, ct);
+    if (zlemetaline != NULL)
+	shiftchars(zlemetacs -= ct, ct);
+    else
+	shiftchars(zlecs -= ct, ct);
 }
 
 /**/
 mod_export void
 foredel(int ct)
 {
-    shiftchars(zlecs, ct);
+    if (zlemetaline != NULL)
+	shiftchars(zlemetacs, ct);
+    else
+	shiftchars(zlecs, ct);
 }
 
 /**/
@@ -437,7 +542,7 @@ setline(char *s, int flags)
      */
     free(zleline);
 
-    zleline = stringaszleline(scp, &zlell, &linesz);
+    zleline = stringaszleline((unsigned char *)scp, 0, &zlell, &linesz, NULL);
 
     if ((flags & ZSL_TOEND) && (zlecs = zlell) && invicmdmode())
 	zlecs--;
@@ -760,6 +865,19 @@ freechanges(struct change *p)
 mod_export void
 handleundo(void)
 {
+    int remetafy;
+
+    /*
+     * Yuk: we call this from within the completion system,
+     * so we need to convert back to the form which can be
+     * copied into undo entries.
+     */
+    if (zlemetaline != NULL) {
+	unmetafy_line();
+	remetafy = 1;
+    } else
+	remetafy = 0;
+
     mkundoent();
     if(!nextchanges)
 	return;
@@ -780,6 +898,9 @@ handleundo(void)
     curchange->prev = endnextchanges;
     endnextchanges->next = curchange;
     nextchanges = endnextchanges = NULL;
+
+    if (remetafy)
+	metafy_line();
 }
 
 /* add an entry to the undo system, if anything has changed */
@@ -792,7 +913,8 @@ mkundoent(void)
     int sh = zlell < lastll ? zlell : lastll;
     struct change *ch;
 
-    if(lastll == zlell && !memcmp(lastline, zleline, zlell * ZLE_CHAR_SIZE))
+    UNMETACHECK();
+    if(lastll == zlell && !ZS_memcmp(lastline, zleline, zlell))
 	return;
     for(pre = 0; pre < sh && zleline[pre] == lastline[pre]; )
 	pre++;
@@ -840,6 +962,7 @@ mkundoent(void)
 void
 setlastline(void)
 {
+    UNMETACHECK();
     if(lastlinesz != linesz)
 	lastline = realloc(lastline, (lastlinesz = linesz) * ZLE_CHAR_SIZE);
     ZS_memcpy(lastline, zleline, (lastll = zlell));