about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Src/Zle/compcore.c163
-rw-r--r--Src/Zle/compctl.c95
-rw-r--r--Src/Zle/complete.c24
-rw-r--r--Src/Zle/computil.c7
-rw-r--r--Src/Zle/zle_tricky.c98
-rw-r--r--Src/builtin.c3
-rw-r--r--Src/subst.c36
-rw-r--r--Src/text.c2
-rw-r--r--Src/utils.c57
-rw-r--r--Src/zsh.h35
10 files changed, 377 insertions, 143 deletions
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index 8aa44d319..e397e776b 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -303,11 +303,13 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
     matchers = newlinklist();
 
     zsfree(compqstack);
-    compqstack = ztrdup("\\");
-    if (instring == 2)
-	compqstack[0] = '"';
-    else if (instring)
-	compqstack[0] = '\'';
+    compqstack = zalloc(2);
+    /*
+     * It looks like we may need to do stuff with backslashes even
+     * if instring is QT_NONE.
+     */
+    *compqstack = (instring == QT_NONE) ? QT_BACKSLASH : (char)instring;
+    compqstack[1] = '\0';
 
     hasunqu = 0;
     useline = (wouldinstab ? -1 : (lst != COMP_LIST_COMPLETE));
@@ -650,13 +652,22 @@ callcompfunc(char *s, char *fn)
 	compredirect = ztrdup(compredirect);
 	zsfree(compquote);
 	zsfree(compquoting);
-	if (instring) {
-	    if (instring == 1) {
+	if (instring > QT_BACKSLASH) {
+	    switch (instring) {
+	    case QT_SINGLE:
 		compquote = ztrdup("\'");
 		compquoting = ztrdup("single");
-	    } else {
+		break;
+
+	    case QT_DOUBLE:
 		compquote = ztrdup("\"");
 		compquoting = ztrdup("double");
+		break;
+
+	    case QT_DOLLARS:
+		compquote = ztrdup("$'");
+		compquoting = ztrdup("dollars");
+		break;
 	    }
 	    kset |= CP_QUOTE | CP_QUOTING;
 	} else if (inbackt) {
@@ -1026,8 +1037,7 @@ multiquote(char *s, int ign)
 		p += ign;
 	    while (*p) {
 		if (ign >= 0 || p[1])
-		    s = bslashquote(s, NULL,
-				    (*p == '\'' ? 1 : (*p == '"' ? 2 : 0)));
+		    s = quotestring(s, NULL, *p);
 		p++;
 	    }
 	}
@@ -1290,9 +1300,29 @@ comp_str(int *ipl, int *pl, int untok)
     return str;
 }
 
+/**/
+mod_export char *
+comp_quoting_string(int stype)
+{
+    switch (stype)
+    {
+    case QT_SINGLE:
+	return "'";
+    case QT_DOUBLE:
+	return "\"";
+    case QT_DOLLARS:
+	return "$'";
+    default:			/* shuts up compiler */
+	return "\\";
+    }
+}
+
 /*
  * This is the code behind compset -q, which splits the
  * the current word as if it were a command line.
+ *
+ * This is one of those completion functions that merits the
+ * coveted title "not just ordinarily horrific".
  */
 
 /**/
@@ -1307,7 +1337,7 @@ set_comp_sep(void)
     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, sav, *qp, *qs, *ts, qc = '\0';
+    char *tmp, *p, *ns, *ol, sav, *qp, *qs, *ts;
 
     METACHECK();
 
@@ -1334,21 +1364,27 @@ set_comp_sep(void)
     strcpy(tmp + 2 + noffs, s + noffs);
 
     switch (*compqstack) {
-    case '\\':
+    case QT_NONE:
+#ifdef DEBUG
+	dputs("BUG: head of compstack is NULL");
+#endif
+	break;
+
+    case QT_BACKSLASH:
         remq = 1;
 	tmp = rembslash(tmp);
         break;
-    case '\'':
+
+    case QT_SINGLE:
         issq = 1;
         if (isset(RCQUOTES))
             qa = 1;
         else
             qa = 3;
-
         sq = remsquote(tmp);
-
         break;
-    case '"':
+
+    case QT_DOUBLE:
         for (j = 0, p = tmp; *p; p++, j++)
             if (*p == '\\' && p[1] == '\\') {
                 dq++;
@@ -1360,6 +1396,11 @@ set_comp_sep(void)
                 if (!*p)
                     break;
             }
+	break;
+
+    case QT_DOLLARS:
+	/* TODO */
+	break;
     }
     odq = dq;
     osq = sq;
@@ -1450,19 +1491,36 @@ set_comp_sep(void)
 
     untokenize(ts = dupstring(ns));
 
-    if (*ns == Snull || *ns == Dnull) {
-	instring = (*ns == Snull ? 1 : 2);
+    if (*ns == Snull || *ns == Dnull ||
+	((*ns == String || *ns == Qstring) && ns[1] == Snull)) {
+	char *tsptr = ts, *nsptr = ns, sav;
+	switch (*ns) {
+	case Snull:
+	    instring = QT_SINGLE;
+	    break;
+
+	case Dnull:
+	    instring = QT_DOUBLE;
+	    break;
+
+	default:
+	    instring = QT_DOLLARS;
+	    nsptr++;
+	    tsptr++;
+	    break;
+	}
+
 	inbackt = 0;
 	swb++;
-	if (ns[strlen(ns) - 1] == *ns && ns[1])
+	if (nsptr[strlen(nsptr) - 1] == *nsptr && nsptr[1])
 	    swe--;
 	zsfree(autoq);
-	autoq = ztrdup(compqstack[1] ? "" :
-		       multiquote(*ns == Snull ? "'" : "\"", 1));
-	qc = (*ns == Snull ? '\'' : '"');
-	ts++;
+	sav = *++tsptr;
+	*tsptr = '\0';
+	autoq = ztrdup(compqstack[1] ? "" : multiquote(ts, 1));
+	*(ts = tsptr) = sav;
     } else {
-	instring = 0;
+	instring = QT_NONE;
 	zsfree(autoq);
 	autoq = NULL;
     }
@@ -1496,7 +1554,7 @@ set_comp_sep(void)
     }
     ns = ts;
 
-    if (instring && strchr(compqstack, '\\')) {
+    if (instring && strchr(compqstack, QT_BACKSLASH)) {
 	int rl = strlen(ns), ql = strlen(multiquote(ns, !!compqstack[1]));
 
 	if (ql > rl)
@@ -1525,24 +1583,38 @@ set_comp_sep(void)
     }
     {
 	int set = CP_QUOTE | CP_QUOTING, unset = 0;
+	char compnewchars[2];
 
-	p = tricat((instring ? (instring == 1 ? "'" : "\"") : "\\"),
-		   compqstack, "");
+	compnewchars[0] =
+	    (char)(instring == QT_NONE ? QT_BACKSLASH : instring);
+	compnewchars[1] = '\0';
+	p = tricat(compnewchars, compqstack, "");
 	zsfree(compqstack);
 	compqstack = p;
 
 	zsfree(compquote);
 	zsfree(compquoting);
-	if (instring == 2) {
+	switch (instring) {
+	case QT_DOUBLE:
 	    compquote = "\"";
 	    compquoting = "double";
-	} else if (instring == 1) {
+	    break;
+
+	case QT_SINGLE:
 	    compquote = "'";
 	    compquoting = "single";
-	} else {
+	    break;
+
+	case QT_DOLLARS:
+	    compquote = "$'";
+	    compquoting = "dollars";
+	    break;
+
+	default:
 	    compquote = compquoting = "";
 	    unset = set;
 	    set = 0;
+	    break;
 	}
 	compquote = ztrdup(compquote);
 	compquoting = ztrdup(compquoting);
@@ -1804,20 +1876,33 @@ addmatches(Cadata dat, char **argv)
 	dat->flags |= parflags;
     if (compquote && (qc = *compquote)) {
 	if (qc == '`') {
-	    instring = 0;
+	    instring = QT_NONE;
+	    /*
+	     * Yes, inbackt has always been set to zero here.  I'm
+	     * sure there's a simple explanation.
+	     */
 	    inbackt = 0;
 	    autoq = "";
 	} else {
-	    char buf[2];
+	    switch (qc) {
+	    case '\'':
+		instring = QT_SINGLE;
+		break;
 
-	    instring = (qc == '\'' ? 1 : 2);
+	    case '"':
+		instring = QT_DOUBLE;
+		break;
+
+	    case '$':
+		instring = QT_DOLLARS;
+		break;
+	    }
 	    inbackt = 0;
-	    buf[0] = qc;
-	    buf[1] = '\0';
-	    autoq = multiquote(buf, 1);
+	    autoq = multiquote(compquote, 1);
 	}
     } else {
-	instring = inbackt = 0;
+	instring = QT_NONE;
+	inbackt = 0;
 	autoq = NULL;
     }
     qipre = ztrdup(compqiprefix ? compqiprefix : "");
@@ -2549,8 +2634,8 @@ add_match_data(int alt, char *str, char *orig, Cline line,
                 cm->modec = '\0';
         }
     }
-    if ((*compqstack == '\\' && compqstack[1]) ||
-	(autoq && *compqstack && compqstack[1] == '\\'))
+    if ((*compqstack == QT_BACKSLASH && compqstack[1]) ||
+	(autoq && *compqstack && compqstack[1] == QT_BACKSLASH))
 	cm->flags |= CMF_NOSPACE;
     if (nbrbeg) {
 	int *p;
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index b8ed66260..4cd9d9c65 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -1401,7 +1401,7 @@ printcompctl(char *s, Compctl cc, int printflags, int ispat)
 	    untokenize(p);
 	    quotedzputs(p, stdout);
 	} else
-	    quotedzputs(bslashquote(s, NULL, 0), stdout);
+	    quotedzputs(quotestring(s, NULL, QT_BACKSLASH), stdout);
     }
 
     /* loop through flags w/o args that are set, printing them if so */
@@ -1537,7 +1537,7 @@ printcompctl(char *s, Compctl cc, int printflags, int ispat)
 		char *p = dupstring(s);
 
 		untokenize(p);
-		quotedzputs(bslashquote(p, NULL, 0), stdout);
+		quotedzputs(quotestring(p, NULL, QT_BACKSLASH), stdout);
 	    }
 	}
 	putchar('\n');
@@ -1735,10 +1735,13 @@ static char ic;
 
 static int addwhat;
 
-/* Convenience macro for calling bslashquote() (formerly quotename()). *
- * This uses the instring variable above.                              */
+/*
+ * Convenience macro for calling quotestring (formerly bslashquote()
+ * (formerly quotename())).
+ * This uses the instring variable exported from zle_tricky.c.
+ */
 
-#define quotename(s, e) bslashquote(s, e, instring)
+#define quotename(s, e) quotestring(s, e, instring)
 
 /* Hook functions */
 
@@ -2279,22 +2282,38 @@ makecomplistctl(int flags)
 	char *os = cmdstr, **ow = clwords, **p, **q, qc;
 	int on = clwnum, op = clwpos, ois =  instring, oib = inbackt;
 	char *oisuf = isuf, *oqp = qipre, *oqs = qisuf, *oaq = autoq;
-	char buf[2];
+	char buf[3];
 
 	if (compquote && (qc = *compquote)) {
 	    if (qc == '`') {
-		instring = 0;
+		instring = QT_NONE;
+		/*
+		 * Yes, inbackt has always been set to zero here.  I'm
+		 * sure there's a simple explanation.
+		 */
 		inbackt = 0;
 		autoq = "";
 	    } else {
-		buf[0] = qc;
-		buf[1] = '\0';
-		instring = (qc == '\'' ? 1 : 2);
+		switch (qc) {
+		case '\'':
+		    instring = QT_SINGLE;
+		    break;
+
+		case '"':
+		    instring = QT_DOUBLE;
+		    break;
+
+		case '$':
+		    instring = QT_DOLLARS;
+		    break;
+		}
 		inbackt = 0;
+		strcpy(buf, compquote);
 		autoq = buf;
 	    }
 	} else {
-	    instring = inbackt = 0;
+	    instring = QT_NONE;
+	    inbackt = 0;
 	    autoq = "";
 	}
 	qipre = ztrdup(compqiprefix ? compqiprefix : "");
@@ -2589,7 +2608,7 @@ makecomplistext(Compctl occ, char *os, int incmd)
     int compadd, m = 0, d = 0, t, tt, i, j, a, b, ins;
     char *sc = NULL, *s, *ss;
 
-    ins = (instring ? instring : (inbackt ? 3 : 0));
+    ins = (instring != QT_NONE ? instring : (inbackt ? QT_BACKTICK : 0));
 
     /* This loops over the patterns separated by `-'s. */
     for (compc = occ->ext; compc; compc = compc->next) {
@@ -2607,9 +2626,9 @@ makecomplistext(Compctl occ, char *os, int incmd)
 		    erange = clwnum - 1;
 		    switch (cc->type) {
 		    case CCT_QUOTE:
-			t = ((cc->u.s.s[i][0] == 's' && ins == 1) ||
-			     (cc->u.s.s[i][0] == 'd' && ins == 2) ||
-			     (cc->u.s.s[i][0] == 'b' && ins == 3));
+			t = ((cc->u.s.s[i][0] == 's' && ins == QT_SINGLE) ||
+			     (cc->u.s.s[i][0] == 'd' && ins == QT_DOUBLE) ||
+			     (cc->u.s.s[i][0] == 'b' && ins == QT_BACKTICK));
 			break;
 		    case CCT_POS:
 			tt = clwpos;
@@ -2755,7 +2774,7 @@ sep_comp_string(char *ss, char *s, int noffs)
     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 = zlemetaline, sav, *oaq = autoq;
-    char *qp, *qs, *ts, qc = '\0';
+    char *qp, *qs, *ts;
 
     swb = swe = soffs = 0;
     ns = NULL;
@@ -2774,7 +2793,7 @@ sep_comp_string(char *ss, char *s, int noffs)
     memcpy(tmp + sl + 1, s, noffs);
     tmp[(scs = zlemetacs = sl + 1 + noffs)] = 'x';
     strcpy(tmp + sl + 2 + noffs, s + noffs);
-    if ((remq = (*compqstack == '\\')))
+    if ((remq = (*compqstack == QT_BACKSLASH)))
 	tmp = rembslash(tmp);
     inpush(dupstrspace(tmp), 0, NULL);
     zlemetaline = tmp;
@@ -2841,17 +2860,35 @@ sep_comp_string(char *ss, char *s, int noffs)
 
     untokenize(ts = dupstring(ns));
 
-    if (*ns == Snull || *ns == Dnull) {
-	instring = (*ns == Snull ? 1 : 2);
+    if (*ns == Snull || *ns == Dnull ||
+	((*ns == String || *ns == Qstring) && ns[1] == Snull)) {
+	char *tsptr = ts, *nsptr = ns, sav;
+	switch (*ns) {
+	case Snull:
+	    instring = QT_SINGLE;
+	    break;
+
+	case Dnull:
+	    instring = QT_DOUBLE;
+	    break;
+
+	default:
+	    instring = QT_DOLLARS;
+	    nsptr++;
+	    tsptr++;
+	    break;
+	}
+
 	inbackt = 0;
 	swb++;
-	if (ns[strlen(ns) - 1] == *ns && ns[1])
+	if (nsptr[strlen(nsptr) - 1] == *nsptr && nsptr[1])
 	    swe--;
-	autoq = compqstack[1] ? "" : multiquote(*ns == Snull ? "'" : "\"", 1);
-	qc = (*ns == Snull ? '\'' : '"');
-	ts++;
+	sav = *++tsptr;
+	*tsptr = '\0';
+	autoq = compqstack[1] ? "" : multiquote(ts, 1);
+	*(ts = tsptr) = sav;
     } else {
-	instring = 0;
+	instring = QT_NONE;
 	autoq = "";
     }
     for (p = ns, i = swb; *p; p++, i++) {
@@ -2878,7 +2915,7 @@ sep_comp_string(char *ss, char *s, int noffs)
     }
     ns = ts;
 
-    if (instring && strchr(compqstack, '\\')) {
+    if (instring != QT_NONE && strchr(compqstack, QT_BACKSLASH)) {
 	int rl = strlen(ns), ql = strlen(multiquote(ns, !!compqstack[1]));
 
 	if (ql > rl)
@@ -2904,13 +2941,15 @@ sep_comp_string(char *ss, char *s, int noffs)
 
     {
 	char **ow = clwords, *os = cmdstr, *oqp = qipre, *oqs = qisuf;
-	char *oqst = compqstack;
+	char *oqst = compqstack, compnewchar[2];
 	int olws = clwsize, olwn = clwnum, olwp = clwpos;
 	int obr = brange, oer = erange, oof = offs;
 	unsigned long occ = ccont;
 
-	compqstack = tricat((instring ? (instring == 1 ? "'" : "\"") : "\\"),
-			    compqstack, "");
+	compnewchar[0] = (char)(instring != QT_NONE ? (char)instring :
+				QT_BACKSLASH);
+	compnewchar[1] = '\0';
+	compqstack = tricat(compnewchar, compqstack, "");
 
 	clwsize = clwnum = countlinknodes(foo);
 	clwords = (char **) zalloc((clwnum + 1) * sizeof(char *));
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 46e9a06d8..a73d3cf14 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -992,6 +992,8 @@ static const struct gsu_scalar unambig_pos_gsu =
 { get_unambig_pos, nullstrsetfn, compunsetfn };
 static const struct gsu_scalar insert_pos_gsu =
 { get_insert_pos, nullstrsetfn, compunsetfn };
+static const struct gsu_scalar compqstack_gsu =
+{ get_compqstack, nullstrsetfn, compunsetfn };
 
 static const struct gsu_integer compvarinteger_gsu =
 { intvargetfn, intvarsetfn, compunsetfn };
@@ -1047,7 +1049,7 @@ static struct compparam compkparams[] = {
     { "old_insert", PM_SCALAR, VAL(compoldins), NULL },
     { "vared", PM_SCALAR, VAL(compvared), NULL },
     { "list_lines", PM_INTEGER | PM_READONLY, NULL, GSU(listlines_gsu) },
-    { "all_quotes", PM_SCALAR | PM_READONLY, VAL(compqstack), NULL },
+    { "all_quotes", PM_SCALAR | PM_READONLY, NULL, GSU(compqstack_gsu) },
     { "ignored", PM_INTEGER | PM_READONLY, VAL(compignored), NULL },
     { NULL, 0, NULL, NULL }
 };
@@ -1223,6 +1225,26 @@ get_insert_pos(UNUSED(Param pm))
 }
 
 /**/
+static char *
+get_compqstack(UNUSED(Param pm))
+{
+    char *p, *ptr, *cqp;
+
+    if (!compqstack)		/* TODO: don't think this can happen... */
+	return "";
+
+    ptr = p = zhalloc(2*strlen(compqstack)+1);
+
+    for (cqp = compqstack; *cqp; cqp++) {
+	char *str = comp_quoting_string(*cqp);
+	*ptr++ = *str;
+    }
+    *ptr = '\0';
+
+    return p;
+}
+
+/**/
 static void
 compunsetfn(Param pm, int exp)
 {
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index bde1f79af..f13397e5e 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -3441,8 +3441,7 @@ comp_quote(char *str, int prefix)
     if ((x = (prefix && *str == '=')))
 	*str = 'x';
 
-    ret = bslashquote(str, NULL, (*compqstack == '\'' ? 1 :
-				  (*compqstack == '"' ? 2 : 0)));
+    ret = quotestring(str, NULL, *compqstack);
 
     if (x)
 	*str = *ret = '=';
@@ -4344,7 +4343,7 @@ cf_ignore(char **names, LinkList ign, char *style, char *path)
     for (; (n = *names); names++) {
 	if (!ztat(n, &nst, 0) && S_ISDIR(nst.st_mode)) {
 	    if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
-		addlinknode(ign, bslashquote(n, NULL, 0));
+		addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
 		continue;
 	    }
 	    if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
@@ -4360,7 +4359,7 @@ cf_ignore(char **names, LinkList ign, char *style, char *path)
 		if (found || ((e = strrchr(c, '/')) && e > c + pl &&
 			      !ztat(c, &st, 0) && st.st_dev == nst.st_dev &&
 			      st.st_ino == nst.st_ino))
-		    addlinknode(ign, bslashquote(n, NULL, 0));
+		    addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
 	    }
 	}
     }
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 89277a9e1..09a0be5d8 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -402,15 +402,22 @@ mod_export int insubscr;
 /**/
 mod_export Param keypm;
 
-/* 1 if we are completing in a quoted string (or inside `...`) */
+/*
+ * instring takes one of the QT_* values defined in zsh.h.
+ * It's never QT_TICK, instead we use inbackt.
+ * TODO: can we combine the two?
+ */
 
 /**/
 mod_export int instring, inbackt;
 
-/* Convenience macro for calling bslashquote() (formerly quotename()). *
- * This uses the instring variable above.                              */
+/*
+ * Convenience macro for calling quotestring (formerly bslashquote() (formerly
+ * quotename())).
+ * This uses the instring variable above.
+ */
 
-#define quotename(s, e) bslashquote(s, e, instring)
+#define quotename(s, e) quotestring(s, e, instring)
 
 /* Check if the given string is the name of a parameter and if this *
  * parameter is one worth expanding.                                */
@@ -891,7 +898,7 @@ addx(char **ptmp)
 	zlemetaline[zlemetacs] == ';' || zlemetaline[zlemetacs] == '|' ||
 	zlemetaline[zlemetacs] == '&' ||
 	zlemetaline[zlemetacs] == '>' || zlemetaline[zlemetacs] == '<' ||
-	(instring && (zlemetaline[zlemetacs] == '"' ||
+	(instring != QT_NONE && (zlemetaline[zlemetacs] == '"' ||
 		      zlemetaline[zlemetacs] == '\'')) ||
 	(addspace = (comppref && !iblank(zlemetaline[zlemetacs])))) {
 	*ptmp = zlemetaline;
@@ -1032,7 +1039,18 @@ static char *
 get_comp_string(void)
 {
     int t0, tt0, i, j, k, cp, rd, sl, ocs, ins, oins, ia, parct, varq = 0;
-    int ona = noaliases, qsub;
+    int ona = noaliases;
+    /*
+     * qsub fixes up the offset into the current completion word
+     * for changes made by the lexer.  That currently means the
+     * effect of RCQUOTES on embedded pairs of single quotes.
+     * zlemetacs_qsub takes account of the effect of this offset
+     * on the cursor position; it's only needed when using the
+     * word we got from the lexer, which we only do sometimes because
+     * otherwise it would be too easy.  If looking at zlemetaline we
+     * still use zlemetacs.
+     */
+    int qsub, zlemetacs_qsub = 0;
     char *s = NULL, *tmp, *p, *tt = NULL, rdop[20];
     char *linptr, *u;
 
@@ -1070,7 +1088,7 @@ get_comp_string(void)
 	    u++;
     }
     inbackt = (i & 1);
-    instring = 0;
+    instring = QT_NONE;
     addx(&tmp);
     linptr = zlemetaline;
     pushheap();
@@ -1235,9 +1253,11 @@ get_comp_string(void)
 	    clwords[i][--sl] = '\0';
 	/* If this is the word the cursor is in and we added a `x', *
 	 * remove it.                                               */
-	if (clwpos == i++ && addedx)
-	    chuck(&clwords[i - 1][((zlemetacs - wb - qsub) >= sl) ?
-				 (sl - 1) : (zlemetacs - wb - qsub)]);
+	if (clwpos == i++ && addedx) {
+	    zlemetacs_qsub = zlemetacs - qsub;
+	    chuck(&clwords[i - 1][((zlemetacs_qsub - wb) >= sl) ?
+				 (sl - 1) : (zlemetacs_qsub - wb)]);
+	}
     } while (tok != LEXERR && tok != ENDINPUT &&
 	     (tok != SEPER || (zleparse && !tt0)));
     /* Calculate the number of words stored in the clwords array. */
@@ -1299,7 +1319,8 @@ get_comp_string(void)
 	*s = sav;
         if (*s == '+')
             s++;
-	if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + zlemetacs - wb) {
+	if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt +
+	    zlemetacs_qsub - wb) {
 	    s = NULL;
 	    inwhat = IN_MATH;
 	    if ((keypm = (Param) paramtab->getnode(paramtab, varname)) &&
@@ -1308,7 +1329,7 @@ get_comp_string(void)
 	    else
 		insubscr = 1;
 	} else if (*s == '=') {
-            if (zlemetacs > wb + (s - tt)) {
+            if (zlemetacs_qsub > wb + (s - tt)) {
                 s++;
                 wb += s - tt;
                 s = ztrdup(s);
@@ -1365,7 +1386,7 @@ get_comp_string(void)
 	    nnb = s + MB_METACHARLEN(s);
 	else
 	    nnb = s;
-	for (tt = s; tt < s + zlemetacs - wb;) {
+	for (tt = s; tt < s + zlemetacs_qsub - wb;) {
 	    if (*tt == Inbrack) {
 		i++;
 		nb = nnb;
@@ -1504,21 +1525,46 @@ get_comp_string(void)
                 level--;
         }
     }
-    if ((*s == Snull || *s == Dnull) && !has_real_token(s + 1)) {
-	char *q = (*s == Snull ? "'" : "\""), *n = tricat(qipre, q, "");
+    if ((*s == Snull || *s == Dnull ||
+	((*s == String || *s == Qstring) && s[1] == Snull))
+	&& !has_real_token(s + 1)) {
 	int sl = strlen(s);
+	char *q, *qtptr = s, *n;
+
+	switch (*s) {
+	case Snull:
+	    q = "'";
+	    instring = QT_SINGLE;
+	    break;
+
+	case Dnull:
+	    q = "\"";
+	    instring = QT_DOUBLE;
+	    break;
+
+	default:
+	    q = "$'";
+	    instring = QT_DOLLARS;
+	    qtptr++;
+	    sl--;
+	    break;
+	}
 
-	instring = (*s == Snull ? 1 : 2);
+	n = tricat(qipre, q, "");
 	zsfree(qipre);
 	qipre = n;
-	if (sl > 1 && s[sl - 1] == *s) {
+	if (sl > 1 && qtptr[sl - 1] == *qtptr) {
 	    n = tricat(q, qisuf, "");
 	    zsfree(qisuf);
 	    qisuf = n;
 	}
 	autoq = ztrdup(q);
 
-        if (instring == 2) {
+	/*
+	 * \! in double quotes is extracted by the history code before normal
+	 * parsing, so sanitize it here, too.
+	 */
+        if (instring == QT_DOUBLE) {
             for (q = s; *q; q++)
                 if (*q == '\\' && q[1] == '!')
                     *q = Bnull;
@@ -1651,11 +1697,11 @@ get_comp_string(void)
 
 			new->next = NULL;
 			new->str = dupstrpfx(bbeg, len);
-			new->str = ztrdup(bslashquote(new->str, NULL, instring));
+			new->str = ztrdup(quotename(new->str, NULL));
 			untokenize(new->str);
 			new->pos = begi;
 			*dbeg = '\0';
-			new->qpos = strlen(bslashquote(predup, NULL, instring));
+			new->qpos = strlen(quotename(predup, NULL));
 			*dbeg = '{';
 			i -= len;
 			boffs -= len;
@@ -1700,11 +1746,11 @@ get_comp_string(void)
 			lastbrbeg = new;
 
 			new->str = dupstrpfx(bbeg, len);
-			new->str = ztrdup(bslashquote(new->str, NULL, instring));
+			new->str = ztrdup(quotename(new->str, NULL));
 			untokenize(new->str);
 			new->pos = begi;
 			*dbeg = '\0';
-			new->qpos = strlen(bslashquote(predup, NULL, instring));
+			new->qpos = strlen(quotename(predup, NULL));
 			*dbeg = '{';
 			i -= len;
 			boffs -= len;
@@ -1737,7 +1783,7 @@ get_comp_string(void)
 		    brend = new;
 
 		    new->str = dupstrpfx(bbeg, len);
-		    new->str = ztrdup(bslashquote(new->str, NULL, instring));
+		    new->str = ztrdup(quotename(new->str, NULL));
 		    untokenize(new->str);
 		    new->pos = dp - predup - len + 1;
 		    new->qpos = len;
@@ -1766,11 +1812,11 @@ get_comp_string(void)
 		lastbrbeg = new;
 
 		new->str = dupstrpfx(bbeg, len);
-		new->str = ztrdup(bslashquote(new->str, NULL, instring));
+		new->str = ztrdup(quotename(new->str, NULL));
 		untokenize(new->str);
 		new->pos = begi;
 		*dbeg = '\0';
-		new->qpos = strlen(bslashquote(predup, NULL, instring));
+		new->qpos = strlen(quotename(predup, NULL));
 		*dbeg = '{';
 		boffs -= len;
 		strcpy(dbeg, dbeg + len);
@@ -1785,7 +1831,7 @@ get_comp_string(void)
 		    p = bp->pos;
 		    l = bp->qpos;
 		    bp->pos = strlen(predup + p + l);
-		    bp->qpos = strlen(bslashquote(predup + p + l, NULL, instring));
+		    bp->qpos = strlen(quotename(predup + p + l, NULL));
 		    strcpy(predup + p, predup + p + l);
 		}
 	    }
diff --git a/Src/builtin.c b/Src/builtin.c
index 713e41a98..185365644 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -3964,7 +3964,8 @@ bin_print(char *name, char **args, Options ops, int func)
 		    count += fprintf(fout, "%*c", width, ' ');
 		break;
 	    case 'q':
-		stringval = curarg ? bslashquote(curarg, NULL, 0) : &nullstr;
+		stringval = curarg ?
+		    quotestring(curarg, NULL, QT_BACKSLASH) : &nullstr;
 		*d = 's';
 		print_val(stringval);
 		break;
diff --git a/Src/subst.c b/Src/subst.c
index 0f351df4c..8ef8d446e 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -1394,7 +1394,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
      * quoterr is simply (X) but gets passed around a lot because the
      * combination (eX) needs it.
      */
-    int quotemod = 0, quotetype = 0, quoteerr = 0;
+    int quotemod = 0, quotetype = QT_NONE, quoteerr = 0;
     /*
      * (V) flag: fairly straightforward, except that as with so
      * many flags it's not easy to decide where to put it in the order.
@@ -2835,8 +2835,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
      * the repetitions of the (q) flag.
      */
     if (quotemod) {
-	if (--quotetype > 3)
-	    quotetype = 3;
+	if (quotetype > QT_DOLLARS)
+	    quotetype = QT_DOLLARS;
 	if (isarr) {
 	    char **ap;
 
@@ -2845,24 +2845,25 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	    ap = aval;
 
 	    if (quotemod > 0) {
-		if (quotetype) {
+		if (quotetype > QT_BACKSLASH) {
 		    int sl;
 		    char *tmp;
 
 		    for (; *ap; ap++) {
-			int pre = quotetype != 3 ? 1 : 2;
-			tmp = bslashquote(*ap, NULL, quotetype);
+			int pre = quotetype != QT_DOLLARS ? 1 : 2;
+			tmp = quotestring(*ap, NULL, quotetype);
 			sl = strlen(tmp);
 			*ap = (char *) zhalloc(pre + sl + 2);
 			strcpy((*ap) + pre, tmp);
-			ap[0][pre - 1] = ap[0][pre + sl] = (quotetype != 2 ? '\'' : '"');
+			ap[0][pre - 1] = ap[0][pre + sl] =
+			    (quotetype != QT_DOUBLE ? '\'' : '"');
 			ap[0][pre + sl + 1] = '\0';
-			if (quotetype == 3)
+			if (quotetype == QT_DOLLARS)
 			  ap[0][0] = '$';
 		    }
 		} else
 		    for (; *ap; ap++)
-			*ap = bslashquote(*ap, NULL, 0);
+			*ap = quotestring(*ap, NULL, QT_BACKSLASH);
 	    } else {
 		int one = noerrs, oef = errflag, haserr = 0;
 
@@ -2885,20 +2886,21 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	    if (!copied)
 		val = dupstring(val), copied = 1;
 	    if (quotemod > 0) {
-		if (quotetype) {
-		    int pre = quotetype != 3 ? 1 : 2;
+		if (quotetype > QT_BACKSLASH) {
+		    int pre = quotetype != QT_DOLLARS ? 1 : 2;
 		    int sl;
 		    char *tmp;
-		    tmp = bslashquote(val, NULL, quotetype);
+		    tmp = quotestring(val, NULL, quotetype);
 		    sl = strlen(tmp);
 		    val = (char *) zhalloc(pre + sl + 2);
 		    strcpy(val + pre, tmp);
-		    val[pre - 1] = val[pre + sl] = (quotetype != 2 ? '\'' : '"');
+		    val[pre - 1] = val[pre + sl] =
+			(quotetype != QT_DOUBLE ? '\'' : '"');
 		    val[pre + sl + 1] = '\0';
-		    if (quotetype == 3)
+		    if (quotetype == QT_DOLLARS)
 		      val[0] = '$';
 		} else
-		    val = bslashquote(val, NULL, 0);
+		    val = quotestring(val, NULL, QT_BACKSLASH);
 	    } else {
 		int one = noerrs, oef = errflag, haserr;
 
@@ -3387,7 +3389,7 @@ modify(char **str, char **ptr)
 			    subst(&copy, hsubl, hsubr, gbal);
 			break;
 		    case 'q':
-			copy = bslashquote(copy, NULL, 0);
+			copy = quotestring(copy, NULL, QT_BACKSLASH);
 			break;
 		    case 'Q':
 			{
@@ -3453,7 +3455,7 @@ modify(char **str, char **ptr)
 		    }
 		    break;
 		case 'q':
-		    *str = bslashquote(*str, NULL, 0);
+		    *str = quotestring(*str, NULL, QT_BACKSLASH);
 		    break;
 		case 'Q':
 		    {
diff --git a/Src/text.c b/Src/text.c
index db24d8c9e..0079e9fea 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -807,7 +807,7 @@ getredirs(LinkList redirs)
 		 * quotes certainly isn't right in that case).
 		 */
 		taddchr('\'');
-		taddstr(bslashquote(f->name, NULL, 1));
+		taddstr(quotestring(f->name, NULL, QT_SINGLE));
 		taddchr('\'');
 	    } else
 		taddstr(f->name);
diff --git a/Src/utils.c b/Src/utils.c
index 99b0eb743..0aa0dfe2f 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1357,7 +1357,7 @@ settyinfo(struct ttyinfo *ti)
 }
 
 /* the default tty state */
- 
+
 /**/
 mod_export struct ttyinfo shttyinfo;
 
@@ -1576,13 +1576,13 @@ extern char *_mktemp(char *);
  * NULL, the name is relative to $TMPPREFIX; If it is non-NULL, the
  * unique suffix includes a prefixed '.' for improved readability.  If
  * "use_heap" is true, we allocate the returned name on the heap. */
- 
+
 /**/
 mod_export char *
 gettempname(const char *prefix, int use_heap)
 {
     char *ret, *suffix = prefix ? ".XXXXXX" : "XXXXXX";
- 
+
     queue_signals();
     if (!prefix && !(prefix = getsparam("TMPPREFIX")))
 	prefix = DEFAULT_TMPPREFIX;
@@ -1642,7 +1642,7 @@ gettempfile(const char *prefix, int use_heap, char **tempname)
     *tempname = fn;
     return fd;
 }
- 
+
 /* Check if a string contains a token */
 
 /**/
@@ -1656,7 +1656,7 @@ has_token(const char *s)
 }
 
 /* Delete a character in a string */
- 
+
 /**/
 mod_export void
 chuck(char *str)
@@ -3082,7 +3082,7 @@ itype_end(const char *ptr, int itype, int once)
 		 */
 		switch (itype) {
 		case IWORD:
-		    if (!iswalnum(wc) && 
+		    if (!iswalnum(wc) &&
 			!wmemchr(wordchars_wide.chars, wc,
 				 wordchars_wide.len))
 			return (char *)ptr;
@@ -3820,7 +3820,7 @@ nicezputs(char const *s, FILE *stream)
 	if (itok(c)) {
 	    if (c <= Comma)
 		c = ztokens[c - Pound];
-	    else 
+	    else
 		continue;
 	}
 	if (c == Meta)
@@ -3845,7 +3845,7 @@ niceztrlen(char const *s)
 	if (itok(c)) {
 	    if (c <= Comma)
 		c = ztokens[c - Pound];
-	    else 
+	    else
 		continue;
 	}
 	if (c == Meta)
@@ -4134,27 +4134,32 @@ hasspecial(char const *s)
     return 0;
 }
 
-/* Quote the string s and return the result.  If e is non-zero, the         *
- * pointer it points to may point to a position in s and in e the position  *
- * of the corresponding character in the quoted string is returned.         *
- * The last argument should be zero if this is to be used outside a string, *
- * one if it is to be quoted for the inside of a single quoted string,      *
- * two if it is for the inside of a double quoted string, and               *
- * three if it is for the inside of a $'...' quoted string.                 *
- * The string may be metafied and contain tokens.                           */
+/*
+ * Quote the string s and return the result.
+ *
+ * If e is non-zero, the
+ * pointer it points to may point to a position in s and in e the position
+ * of the corresponding character in the quoted string is returned.
+ * 
+ * The last argument is a QT_ value defined in zsh.h other than QT_NONE.
+ *
+ * The string may be metafied and contain tokens.
+ */
 
 /**/
 mod_export char *
-bslashquote(const char *s, char **e, int instring)
+quotestring(const char *s, char **e, int instring)
 {
     const char *u, *tt;
     char *v;
     char *buf = hcalloc(4 * strlen(s) + 1);
     int sf = 0;
 
+    DPUTS(instring < QT_BACKSLASH || instring > QT_DOLLARS,
+	  "BUG: bad quote type in quotestring");
     tt = v = buf;
     u = s;
-    if (instring == 3) {
+    if (instring == QT_DOLLARS) {
 	/*
 	 * As we test for printability here we need to be able
 	 * to look for multibyte characters.
@@ -4170,7 +4175,7 @@ bslashquote(const char *s, char **e, int instring)
 	    }
 	    if (
 #ifdef MULTIBYTE_SUPPORT
-		cc != WEOF && 
+		cc != WEOF &&
 #endif
 		WC_ISPRINT(cc)) {
 		switch (cc) {
@@ -4276,13 +4281,13 @@ bslashquote(const char *s, char **e, int instring)
 		      (isset(MAGICEQUALSUBST) &&
 		       (u[-1] == '=' || u[-1] == ':')) ||
 		      (*u == '~' && isset(EXTENDEDGLOB))) &&
-		     (!instring ||
+		     (instring == QT_BACKSLASH ||
 		      (isset(BANGHIST) && *u == (char)bangchar &&
-		       instring != 1) ||
-		      (instring == 2 &&
+		       instring != QT_SINGLE) ||
+		      (instring == QT_DOUBLE &&
 		       (*u == '$' || *u == '`' || *u == '\"' || *u == '\\')) ||
-		      (instring == 1 && *u == '\''))) {
-		if (*u == '\n' || (instring == 1 && *u == '\'')) {
+		      (instring == QT_SINGLE && *u == '\''))) {
+		if (*u == '\n' || (instring == QT_SINGLE && *u == '\'')) {
 		    if (unset(RCQUOTES)) {
 			*v++ = '\'';
 			if (*u == '\'')
@@ -4306,7 +4311,7 @@ bslashquote(const char *s, char **e, int instring)
 
     if (e && *e == u)
 	*e = v, sf = 1;
-    DPUTS(e && !sf, "BUG: Wild pointer *e in bslashquote()");
+    DPUTS(e && !sf, "BUG: Wild pointer *e in quotestring()");
 
     return buf;
 }
@@ -4654,7 +4659,7 @@ getkeystring(char *s, int *len, int how, int *misc)
 		    *len = t - buf;
 		    return buf;
 		}
-		t += count;  
+		t += count;
 		continue;
 # else
 #  if defined(HAVE_NL_LANGINFO) && defined(CODESET)
diff --git a/Src/zsh.h b/Src/zsh.h
index 9a76bf9a5..c73ae3b9a 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -178,6 +178,41 @@ struct mathfunc {
 
 #define SPECCHARS "#$^*()=|{}[]`<>?~;&\n\t \\\'\""
 
+/*
+ * Types of quote.  This is used in various places, so care needs
+ * to be taken when changing them.  (Oooh, don't you look surprised.)
+ * - Passed to quotestring() to indicate style.  This is the ultimate
+ *   destiny of most of the other uses of members of the enum.
+ * - In paramsubst(), to count q's in parameter substitution.
+ * - In the completion code, where we maintain a stack of quotation types.
+ */
+enum {
+    /*
+     * No quote.  Not a valid quote, but useful in the substitution
+     * and completion code to indicate we're not doing any quoting.
+     */
+    QT_NONE,
+    /* Backslash: \ */
+    QT_BACKSLASH,
+    /* Single quote: ' */
+    QT_SINGLE,
+    /* Double quote: " */
+    QT_DOUBLE,
+    /* Print-style quote: $' */
+    QT_DOLLARS,
+    /*
+     * Backtick: `
+     * Not understood by many parts of the code; here for a convenience
+     * in those cases where we need to represent a complete set.
+     */
+    QT_BACKTICK,
+};
+
+/*
+ * Lexical tokens: unlike the character tokens above, these never
+ * appear in strings and don't necessarily represent a single character.
+ */
+
 enum {
     NULLTOK,		/* 0  */
     SEPER,