about summary refs log tree commit diff
path: root/Src/Zle
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2006-10-05 21:53:26 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2006-10-05 21:53:26 +0000
commit289b14113b1675b7b8d844b1f41aff1e2f3a5146 (patch)
tree89020810aae313b94a061156efcc557d7251ba16 /Src/Zle
parent298a8b8130bd3f515c1aa9f8fcd901c878cbb668 (diff)
downloadzsh-289b14113b1675b7b8d844b1f41aff1e2f3a5146.tar.gz
zsh-289b14113b1675b7b8d844b1f41aff1e2f3a5146.tar.xz
zsh-289b14113b1675b7b8d844b1f41aff1e2f3a5146.zip
22819: improved internal use of string quotation,
plus completion bug fix with RCQUOTES
Diffstat (limited to 'Src/Zle')
-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
5 files changed, 289 insertions, 98 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);
 		}
 	    }