about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOliver Kiddle <opk@zsh.org>2023-01-10 20:53:17 +0100
committerOliver Kiddle <opk@zsh.org>2023-01-10 20:53:17 +0100
commit667ead3a64e590ac758e9f0a053849c7aaccec66 (patch)
tree2ef4d722cc527d11cf81534b27e6af6f5e95c51b
parent996b51515600859ce7f952f22c6262ecd24578e1 (diff)
downloadzsh-667ead3a64e590ac758e9f0a053849c7aaccec66.tar.gz
zsh-667ead3a64e590ac758e9f0a053849c7aaccec66.tar.xz
zsh-667ead3a64e590ac758e9f0a053849c7aaccec66.zip
51258, 51272: refactor handling of terminal attributes, removing OFF flags in zattr
-rw-r--r--ChangeLog8
-rw-r--r--Src/Modules/watch.c64
-rw-r--r--Src/Zle/complist.c60
-rw-r--r--Src/Zle/zle.h6
-rw-r--r--Src/Zle/zle_main.c19
-rw-r--r--Src/Zle/zle_refresh.c316
-rw-r--r--Src/Zle/zle_tricky.c48
-rw-r--r--Src/Zle/zle_utils.c2
-rw-r--r--Src/builtin.c4
-rw-r--r--Src/init.c3
-rw-r--r--Src/input.c2
-rw-r--r--Src/loop.c2
-rw-r--r--Src/prompt.c254
-rw-r--r--Src/subst.c11
-rw-r--r--Src/utils.c7
-rw-r--r--Src/zsh.h45
-rw-r--r--Test/D01prompt.ztst13
-rw-r--r--Test/X04zlehighlight.ztst14
18 files changed, 400 insertions, 478 deletions
diff --git a/ChangeLog b/ChangeLog
index 773971c5a..b3518f1bf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2023-01-10  Oliver Kiddle  <opk@zsh.org>
 
+	* 51258, 51272: Src/Modules/watch.c, Src/Zle/complist.c,
+	Src/Zle/zle.h, Src/Zle/zle_main.c, Src/Zle/zle_refresh.c,
+	Src/Zle/zle_tricky.c, Src/Zle/zle_utils.c, Src/builtin.c,
+	Src/init.c, Src/input.c, Src/loop.c, Src/prompt.c,
+	Src/subst.c, Src/utils.c, Src/zsh.h, Test/D01prompt.ztst,
+	Test/X04zlehighlight.ztst: refactor handling of terminal
+	attributes, removing OFF flags in zattr
+
 	* Nathan Houghton: 51276: Completion/Unix/Type/_diff_options:
 	Fix diff completion for non GNU / FreeBSD platforms
 
diff --git a/Src/Modules/watch.c b/Src/Modules/watch.c
index d45c3cf3d..0de8cbf9a 100644
--- a/Src/Modules/watch.c
+++ b/Src/Modules/watch.c
@@ -255,8 +255,10 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
     while (*fmt)
 	if (*fmt == '\\') {
 	    if (*++fmt) {
-		if (prnt)
+		if (prnt) {
+		    applytextattributes(TSC_RAW);
 		    putchar(*fmt);
+		}
 		++fmt;
 	    } else if (fini)
 		return fmt;
@@ -266,8 +268,10 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 	else if (*fmt == fini)
 	    return ++fmt;
 	else if (*fmt != '%') {
-	    if (prnt)
+	    if (prnt) {
+		applytextattributes(TSC_RAW);
 		putchar(*fmt);
+	    }
 	    ++fmt;
 	} else {
 	    if (*++fmt == BEGIN3)
@@ -277,12 +281,15 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 	    else
 		switch (*(fm2 = fmt++)) {
 		case 'n':
+		    applytextattributes(TSC_RAW);
 		    printf("%.*s", (int)sizeof(u->ut_name), u->ut_name);
 		    break;
 		case 'a':
+		    applytextattributes(TSC_RAW);
 		    printf("%s", (!inout) ? "logged off" : "logged on");
 		    break;
 		case 'l':
+		    applytextattributes(TSC_RAW);
 		    if (!strncmp(u->ut_line, "tty", 3))
 			printf("%.*s", (int)sizeof(u->ut_line) - 3, u->ut_line + 3);
 		    else
@@ -290,6 +297,7 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 		    break;
 # ifdef WATCH_UTMP_UT_HOST
 		case 'm':
+		    applytextattributes(TSC_RAW);
 		    for (p = u->ut_host, i = sizeof(u->ut_host); i && *p; i--, p++) {
 			if (*p == '.' && !idigit(p[1]))
 			    break;
@@ -297,6 +305,7 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 		    }
 		    break;
 		case 'M':
+		    applytextattributes(TSC_RAW);
 		    printf("%.*s", (int)sizeof(u->ut_host), u->ut_host);
 		    break;
 # endif /* WATCH_UTMP_UT_HOST */
@@ -343,9 +352,11 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 		    len = ztrftime(buf, 40, fm2, tm, 0L);
 		    if (len > 0)
 			metafy(buf, len, META_NOALLOC);
+		    applytextattributes(TSC_RAW);
 		    printf("%s", (*buf == ' ') ? buf + 1 : buf);
 		    break;
 		case '%':
+		    applytextattributes(TSC_RAW);
 		    putchar('%');
 		    break;
 		case 'F':
@@ -354,16 +365,13 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 			atr = match_colour((const char**)&fmt, 1, 0);
 			if (*fmt == '}')
 			    fmt++;
-			if (!(atr & (TXT_ERROR | TXTNOFGCOLOUR))) {
-			    txtunset(TXT_ATTR_FG_COL_MASK);
-			    txtset(atr & TXT_ATTR_FG_ON_MASK);
-			    set_colour_attribute(atr, COL_SEQ_FG, TSC_RAW);
+			if (atr && atr != TXT_ERROR) {
+			    tsetattrs(atr);
+			    break;
 			}
-		    }
-		    break;
+		    } /* fall-through */
 		case 'f':
-		    txtunset(TXT_ATTR_FG_ON_MASK);
-		    set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_RAW);
+		    tunsetattrs(TXTFGCOLOUR);
 		    break;
 		case 'K':
 		    if (*fmt == '{') {
@@ -371,49 +379,43 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 			atr = match_colour((const char**)&fmt, 0, 0);
 			if (*fmt == '}')
 			    fmt++;
-			if (!(atr & (TXT_ERROR | TXTNOBGCOLOUR))) {
-			    txtunset(TXT_ATTR_BG_COL_MASK);
-			    txtset(atr & TXT_ATTR_BG_ON_MASK);
-			    set_colour_attribute(atr, COL_SEQ_BG, TSC_RAW);
+			if (atr && atr != TXT_ERROR) {
+			    tsetattrs(atr);
+			    break;
 			}
-		    }
-		    break;
+		    } /* fall-through */
 		case 'k':
-		    txtunset(TXT_ATTR_BG_ON_MASK);
-		    set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_RAW);
+		    tunsetattrs(TXTBGCOLOUR);
 		    break;
 		case 'S':
-		    txtset(TXTSTANDOUT);
-		    tsetcap(TCSTANDOUTBEG, TSC_RAW);
+		    tsetattrs(TXTSTANDOUT);
 		    break;
 		case 's':
-		    txtunset(TXTSTANDOUT);
-		    tsetcap(TCSTANDOUTEND, TSC_RAW|TSC_DIRTY);
+		    tunsetattrs(TXTSTANDOUT);
 		    break;
 		case 'B':
-		    txtset(TXTBOLDFACE);
-		    tsetcap(TCBOLDFACEBEG, TSC_RAW|TSC_DIRTY);
+		    tsetattrs(TXTBOLDFACE);
 		    break;
 		case 'b':
-		    txtunset(TXTBOLDFACE);
-		    tsetcap(TCALLATTRSOFF, TSC_RAW|TSC_DIRTY);
+		    tunsetattrs(TXTBOLDFACE);
 		    break;
 		case 'U':
-		    txtset(TXTUNDERLINE);
-		    tsetcap(TCUNDERLINEBEG, TSC_RAW);
+		    tsetattrs(TXTUNDERLINE);
 		    break;
 		case 'u':
-		    txtunset(TXTUNDERLINE);
-		    tsetcap(TCUNDERLINEEND, TSC_RAW|TSC_DIRTY);
+		    tunsetattrs(TXTUNDERLINE);
 		    break;
 		default:
+		    applytextattributes(TSC_RAW);
 		    putchar('%');
 		    putchar(*fm2);
 		    break;
 		}
 	}
-    if (prnt)
+    if (prnt) {
+	applytextattributes(TSC_RAW);
 	putchar('\n');
+    }
 
     return fmt;
 }
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 6e0eac31f..8bdf1bb29 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -1072,7 +1072,7 @@ static int
 compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 {
     char *p, nc[2*DIGBUFSIZE + 12], nbuf[2*DIGBUFSIZE + 12];
-    int l = 0, cc = 0, b = 0, s = 0, u = 0, m, ask, beg, stat;
+    int l = 0, cc = 0, m, ask, beg, stat;
 
     if ((stat = !fmt)) {
 	if (mlbeg >= 0) {
@@ -1118,48 +1118,46 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 		m = 0;
 		switch (cchar) {
 		case ZWC('%'):
-		    if (dopr == 1)
+		    if (dopr == 1) {
+			applytextattributes(0);
 			putc('%', shout);
+		    }
 		    cc++;
 		    break;
 		case ZWC('n'):
 		    if (!stat) {
 			sprintf(nc, "%d", n);
-			if (dopr == 1)
+			if (dopr == 1) {
+			    applytextattributes(0);
 			    fputs(nc, shout);
+			}
 			/* everything here is ASCII... */
 			cc += strlen(nc);
 		    }
 		    break;
 		case ZWC('B'):
-		    b = 1;
 		    if (dopr)
-			tcout(TCBOLDFACEBEG);
+			tsetattrs(TXTBOLDFACE);
 		    break;
 		case ZWC('b'):
-		    b = 0; m = 1;
 		    if (dopr)
-			tcout(TCALLATTRSOFF);
+			tunsetattrs(TXTBOLDFACE);
 		    break;
 		case ZWC('S'):
-		    s = 1;
 		    if (dopr)
-			tcout(TCSTANDOUTBEG);
+			tsetattrs(TXTSTANDOUT);
 		    break;
 		case ZWC('s'):
-		    s = 0; m = 1;
 		    if (dopr)
-			tcout(TCSTANDOUTEND);
+			tunsetattrs(TXTSTANDOUT);
 		    break;
 		case ZWC('U'):
-		    u = 1;
 		    if (dopr)
-			tcout(TCUNDERLINEBEG);
+			tsetattrs(TXTUNDERLINE);
 		    break;
 		case ZWC('u'):
-		    u = 0; m = 1;
 		    if (dopr)
-			tcout(TCUNDERLINEEND);
+			tunsetattrs(TXTUNDERLINE);
 		    break;
 		case ZWC('F'):
 		case ZWC('K'):
@@ -1173,20 +1171,21 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 		    } else
 			atr = match_colour(NULL, is_fg, arg);
 		    if (atr != TXT_ERROR && dopr)
-			set_colour_attribute(atr, is_fg ? COL_SEQ_FG :
-					     COL_SEQ_BG, 0);
+			tsetattrs(atr);
 		    break;
 		case ZWC('f'):
 		    if (dopr)
-			set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, 0);
+			tunsetattrs(TXTFGCOLOUR);
 		    break;
 		case ZWC('k'):
 		    if (dopr)
-			set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, 0);
+			tunsetattrs(TXTBGCOLOUR);
 		    break;
 		case ZWC('{'):
 		    if (arg)
 			cc += arg;
+		    if (dopr)
+			applytextattributes(0);
 		    for (; *p && (*p != '%' || p[1] != '}'); p++)
 			if (dopr)
 			    putc(*p == Meta ? *++p ^ 32 : *p, shout);
@@ -1197,7 +1196,7 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 		    if (stat) {
 			sprintf(nc, "%d/%d", (n ? mlastm : mselect),
 				listdat.nlist);
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		case ZWC('M'):
@@ -1205,20 +1204,20 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 			sprintf(nbuf, "%d/%d", (n ? mlastm : mselect),
 				listdat.nlist);
 			sprintf(nc, "%-9s", nbuf);
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		case ZWC('l'):
 		    if (stat) {
 			sprintf(nc, "%d/%d", ml + 1, listdat.nlines);
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		case ZWC('L'):
 		    if (stat) {
 			sprintf(nbuf, "%d/%d", ml + 1, listdat.nlines);
 			sprintf(nc, "%-9s", nbuf);
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		case ZWC('p'):
@@ -1230,7 +1229,7 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 				    ((ml + 1) * 100) / listdat.nlines);
 			else
 			    strcpy(nc, "Top");
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		case ZWC('P'):
@@ -1242,25 +1241,19 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 				    ((ml + 1) * 100) / listdat.nlines);
 			else
 			    strcpy(nc, "Top   ");
-			m = 2;
+			m = 1;
 		    }
 		    break;
 		}
-		if (m == 2 && dopr == 1) {
+		if (m && dopr) {
 		    /* nc only contains ASCII text */
 		    int l = strlen(nc);
 
 		    if (l + cc > zterm_columns - 2)
 			nc[l -= l + cc - (zterm_columns - 2)] = '\0';
+		    applytextattributes(0);
 		    fputs(nc, shout);
 		    cc += l;
-		} else if (dopr && m == 1) {
-		    if (b)
-			tcout(TCBOLDFACEBEG);
-		    if (s)
-			tcout(TCSTANDOUTBEG);
-		    if (u)
-			tcout(TCUNDERLINEBEG);
 		}
 	    } else
 		break;
@@ -1276,6 +1269,7 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
 		cc = 0;
 	    }
 	    if (dopr == 1) {
+		applytextattributes(0);
 		if (ml == mlend - 1 && (cc % zterm_columns) ==
 		    zterm_columns - 1) {
 		    dopr = 0;
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
index 97cc7d797..1a3e4c241 100644
--- a/Src/Zle/zle.h
+++ b/Src/Zle/zle.h
@@ -490,11 +490,7 @@ typedef struct {
      */
     REFRESH_CHAR chr;
     /*
-     * Its attributes.  'On' attributes (TXT_ATTR_ON_MASK) are
-     * applied before the character, 'off' attributes (TXT_ATTR_OFF_MASK)
-     * after it.  'On' attributes are present for all characters that
-     * need the effect; 'off' attributes are only present for the
-     * last character in the sequence.
+     * Its attributes.
      */
     zattr atr;
 } REFRESH_ELEMENT;
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 40b902901..39be33939 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -1230,9 +1230,9 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
 	char *pptbuf;
 	int pptlen;
 
-	pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL,
-				       &pmpt_attr),
+	pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL),
 			  &pptlen);
+	pmpt_attr = txtcurrentattrs;
 	write_loop(2, pptbuf, pptlen);
 	free(pptbuf);
 	return shingetline();
@@ -1267,10 +1267,11 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
     fetchttyinfo = 0;
     trashedzle = 0;
     raw_lp = lp;
-    lpromptbuf = promptexpand(lp ? *lp : NULL, 1, NULL, NULL, &pmpt_attr);
+    lpromptbuf = promptexpand(lp ? *lp : NULL, 1, NULL, NULL);
+    pmpt_attr = txtcurrentattrs;
     raw_rp = rp;
-    rpmpt_attr = pmpt_attr;
-    rpromptbuf = promptexpand(rp ? *rp : NULL, 1, NULL, NULL, &rpmpt_attr);
+    rpromptbuf = promptexpand(rp ? *rp : NULL, 1, NULL, NULL);
+    rpmpt_attr = txtcurrentattrs;
     free_prepostdisplay();
 
     zlereadflags = flags;
@@ -2009,8 +2010,8 @@ reexpandprompt(void)
 	    char *new_lprompt, *new_rprompt;
 	    looping = reexpanding;
 
-	    new_lprompt = promptexpand(raw_lp ? *raw_lp : NULL, 1, NULL, NULL,
-				       &pmpt_attr);
+	    new_lprompt = promptexpand(raw_lp ? *raw_lp : NULL, 1, NULL, NULL);
+	    pmpt_attr = txtcurrentattrs;
 	    free(lpromptbuf);
 	    lpromptbuf = new_lprompt;
 
@@ -2018,8 +2019,8 @@ reexpandprompt(void)
 		continue;
 
 	    rpmpt_attr = pmpt_attr;
-	    new_rprompt = promptexpand(raw_rp ? *raw_rp : NULL, 1, NULL, NULL,
-				       &rpmpt_attr);
+	    new_rprompt = promptexpand(raw_rp ? *raw_rp : NULL, 1, NULL, NULL);
+	    rpmpt_attr = txtcurrentattrs;
 	    free(rpromptbuf);
 	    rpromptbuf = new_rprompt;
 	} while (looping != reexpanding);
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index 2db5f0642..ae8e5c109 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -208,7 +208,7 @@ int predisplaylen, postdisplaylen;
  * displayed on screen.
  */
 
-static zattr default_atr_on, special_atr_on;
+static zattr default_attr, special_attr;
 
 /*
  * Array of region highlights, no special termination.
@@ -245,12 +245,12 @@ char *tcout_func_name;
 int cost;
 
 # define SELECT_ADD_COST(X)	(cost += X)
-# define zputc(a)		(zwcputc(a, NULL), cost++)
+# define zputc(a)		(zwcputc(a), cost++)
 # define zwrite(a, b)		(zwcwrite((a), (b)), \
 				 cost += ((b) * ZLE_CHAR_SIZE))
 #else
 # define SELECT_ADD_COST(X)
-# define zputc(a)		zwcputc(a, NULL)
+# define zputc(a)		zwcputc(a)
 # define zwrite(a, b)		zwcwrite((a), (b))
 #endif
 
@@ -316,14 +316,14 @@ static void
 zle_set_highlight(void)
 {
     char **atrs = getaparam("zle_highlight");
-    int special_atr_on_set = 0;
-    int region_atr_on_set = 0;
-    int isearch_atr_on_set = 0;
-    int suffix_atr_on_set = 0;
-    int paste_atr_on_set = 0;
+    int special_attr_set = 0;
+    int region_attr_set = 0;
+    int isearch_attr_set = 0;
+    int suffix_attr_set = 0;
+    int paste_attr_set = 0;
     struct region_highlight *rhp;
 
-    special_atr_on = default_atr_on = 0;
+    special_attr = default_attr = 0;
     if (!region_highlights) {
 	region_highlights = (struct region_highlight *)
 	    zshcalloc(N_SPECIAL_HIGHLIGHTS*sizeof(struct region_highlight));
@@ -340,41 +340,41 @@ zle_set_highlight(void)
 	for (; *atrs; atrs++) {
 	    if (!strcmp(*atrs, "none")) {
 		/* reset attributes for consistency... usually unnecessary */
-		special_atr_on = default_atr_on = 0;
-		special_atr_on_set = 1;
-		paste_atr_on_set = region_atr_on_set =
-		    isearch_atr_on_set = suffix_atr_on_set = 1;
+		special_attr = default_attr = 0;
+		special_attr_set = 1;
+		paste_attr_set = region_attr_set =
+		    isearch_attr_set = suffix_attr_set = 1;
 	    } else if (strpfx("default:", *atrs)) {
-		match_highlight(*atrs + 8, &default_atr_on);
+		match_highlight(*atrs + 8, &default_attr);
 	    } else if (strpfx("special:", *atrs)) {
-		match_highlight(*atrs + 8, &special_atr_on);
-		special_atr_on_set = 1;
+		match_highlight(*atrs + 8, &special_attr);
+		special_attr_set = 1;
 	    } else if (strpfx("region:", *atrs)) {
 		match_highlight(*atrs + 7, &region_highlights[0].atr);
-		region_atr_on_set = 1;
+		region_attr_set = 1;
 	    } else if (strpfx("isearch:", *atrs)) {
 		match_highlight(*atrs + 8, &(region_highlights[1].atr));
-		isearch_atr_on_set = 1;
+		isearch_attr_set = 1;
 	    } else if (strpfx("suffix:", *atrs)) {
 		match_highlight(*atrs + 7, &(region_highlights[2].atr));
-		suffix_atr_on_set = 1;
+		suffix_attr_set = 1;
 	    } else if (strpfx("paste:", *atrs)) {
 		match_highlight(*atrs + 6, &(region_highlights[3].atr));
-		paste_atr_on_set = 1;
+		paste_attr_set = 1;
 	    }
 	}
     }
 
     /* Defaults */
-    if (!special_atr_on_set)
-	special_atr_on = TXTSTANDOUT;
-    if (!region_atr_on_set)
+    if (!special_attr_set)
+	special_attr = TXTSTANDOUT;
+    if (!region_attr_set)
 	region_highlights[0].atr = TXTSTANDOUT;
-    if (!isearch_atr_on_set)
+    if (!isearch_attr_set)
 	region_highlights[1].atr = TXTUNDERLINE;
-    if (!suffix_atr_on_set)
+    if (!suffix_attr_set)
 	region_highlights[2].atr = TXTBOLDFACE;
-    if (!paste_atr_on_set)
+    if (!paste_attr_set)
 	region_highlights[3].atr = TXTSTANDOUT;
 
     allocate_colour_buffer();
@@ -571,22 +571,6 @@ unset_region_highlight(Param pm, int exp)
 }
 
 
-/* The last attributes that were on. */
-static zattr lastatr;
-
-/*
- * Clear the last attributes that we set:  used when we're going
- * to be outputting stuff that shouldn't show up as text.
- */
-static void
-clearattributes(void)
-{
-    if (lastatr) {
-	settextattributes(TXT_ATTR_OFF_FROM_ON(lastatr));
-	lastatr = 0;
-    }
-}
-
 /*
  * Output a termcap capability, clearing any text attributes so
  * as not to mess up the display.
@@ -595,7 +579,7 @@ clearattributes(void)
 static void
 tcoutclear(int cap)
 {
-    clearattributes();
+    cleartextattributes(0);
     tcout(cap);
 }
 
@@ -603,47 +587,20 @@ tcoutclear(int cap)
  * Output the character.  This must come from the new video
  * buffer, nbuf, since we access the multiword buffer nmwbuf
  * directly.
- *
- * curatrp may be NULL, otherwise points to an integer specifying
- * what attributes were turned on for a character output immediately
- * before, in order to optimise output of attribute changes.
  */
 
 /**/
 void
-zwcputc(const REFRESH_ELEMENT *c, zattr *curatrp)
+zwcputc(const REFRESH_ELEMENT *c)
 {
-    /*
-     * Safety: turn attributes off if last heard of turned on.
-     * This differs from *curatrp, which is an optimisation for
-     * writing lots of stuff at once.
-     */
 #ifdef MULTIBYTE_SUPPORT
     mbstate_t mbstate;
     int i;
     VARARR(char, mbtmp, MB_CUR_MAX + 1);
 #endif
 
-    if (lastatr & ~c->atr) {
-	/* Stuff on we don't want, turn it off */
-	settextattributes(TXT_ATTR_OFF_FROM_ON(lastatr & ~c->atr));
-	lastatr = 0;
-    }
-
-    /*
-     * Don't output "on" attributes in a string of characters with
-     * the same attributes.  Be careful in case a different colour
-     * needs setting.
-     */
-    if ((c->atr & TXT_ATTR_ON_MASK) &&
-	(!curatrp ||
-	 ((*curatrp & TXT_ATTR_ON_VALUES_MASK) !=
-	  (c->atr & TXT_ATTR_ON_VALUES_MASK)))) {
-	/* Record just the control flags we might need to turn off... */
-	lastatr = c->atr & TXT_ATTR_ON_MASK;
-	/* ...but set including the values for colour attributes */
-	settextattributes(c->atr & TXT_ATTR_ON_VALUES_MASK);
-    }
+    treplaceattrs(c->atr);
+    applytextattributes(0);
 
 #ifdef MULTIBYTE_SUPPORT
     if (c->atr & TXT_MULTIWORD_MASK) {
@@ -664,35 +621,15 @@ zwcputc(const REFRESH_ELEMENT *c, zattr *curatrp)
 #else
     fputc(c->chr, shout);
 #endif
-
-    /*
-     * Always output "off" attributes since we only turn off at
-     * the end of a chunk of highlighted text.
-     */
-    if (c->atr & TXT_ATTR_OFF_MASK) {
-	settextattributes(c->atr & TXT_ATTR_OFF_MASK);
-	lastatr &= ~((c->atr & TXT_ATTR_OFF_MASK) >> TXT_ATTR_OFF_ON_SHIFT);
-    }
-    if (curatrp) {
-	/*
-	 * Remember the current attributes:  those that are turned
-	 * on, less those that are turned off again.  Include
-	 * colour attributes here in case the colour changes to
-	 * another non-default one.
-	 */
-	*curatrp = (c->atr & TXT_ATTR_ON_VALUES_MASK) &
-	    ~((c->atr & TXT_ATTR_OFF_MASK) >> TXT_ATTR_OFF_ON_SHIFT);
-    }
 }
 
 static int
 zwcwrite(const REFRESH_STRING s, size_t i)
 {
     size_t j;
-    zattr curatr = 0;
 
     for (j = 0; j < i; j++)
-	zwcputc(s + j, &curatr);
+	zwcputc(s + j);
     return i; /* TODO something better for error indication */
 }
 
@@ -939,29 +876,6 @@ snextline(Rparams rpms)
     rpms->sen = rpms->s + winw;
 }
 
-
-/**/
-static void
-settextattributes(zattr atr)
-{
-    if (txtchangeisset(atr, TXTNOBOLDFACE))
-	tsetcap(TCALLATTRSOFF, 0);
-    if (txtchangeisset(atr, TXTNOSTANDOUT))
-	tsetcap(TCSTANDOUTEND, 0);
-    if (txtchangeisset(atr, TXTNOUNDERLINE))
-	tsetcap(TCUNDERLINEEND, 0);
-    if (txtchangeisset(atr, TXTBOLDFACE))
-	tsetcap(TCBOLDFACEBEG, 0);
-    if (txtchangeisset(atr, TXTSTANDOUT))
-	tsetcap(TCSTANDOUTBEG, 0);
-    if (txtchangeisset(atr, TXTUNDERLINE))
-	tsetcap(TCUNDERLINEBEG, 0);
-    if (txtchangeisset(atr, TXTFGCOLOUR|TXTNOFGCOLOUR))
-	set_colour_attribute(atr, COL_SEQ_FG, 0);
-    if (txtchangeisset(atr, TXTBGCOLOUR|TXTNOBGCOLOUR))
-	set_colour_attribute(atr, COL_SEQ_BG, 0);
-}
-
 #ifdef MULTIBYTE_SUPPORT
 /*
  * Add a multiword glyph at the screen location base.
@@ -1043,7 +957,6 @@ zrefresh(void)
     int tmppos;			/* t - tmpline				     */
     int tmpalloced;		/* flag to free tmpline when finished	     */
     int remetafy;		/* flag that zle line is metafied	     */
-    zattr txtchange;		/* attributes set after prompts              */
     int rprompt_off = 1;	/* Offset of rprompt from right of screen    */
     struct rparams rpms;
 #ifdef MULTIBYTE_SUPPORT
@@ -1194,7 +1107,7 @@ zrefresh(void)
 	tsetcap(TCALLATTRSOFF, 0);
 	tsetcap(TCSTANDOUTEND, 0);
 	tsetcap(TCUNDERLINEEND, 0);
-	txtattrmask = 0;
+	txtcurrentattrs = txtpendingattrs = txtunknownattrs = 0;
 
 	if (trashedzle && !clearflag)
 	    reexpandprompt(); 
@@ -1219,8 +1132,8 @@ zrefresh(void)
 	    if (lpromptwof == winw)
 		zputs("\n", shout);	/* works with both hasam and !hasam */
 	} else {
-	    txtchange = pmpt_attr;
-	    settextattributes(txtchange);
+	    treplaceattrs(pmpt_attr);
+	    applytextattributes(0);
 	}
 	if (clearflag) {
 	    zputc(&zr_cr);
@@ -1264,8 +1177,8 @@ zrefresh(void)
     rpms.sen = *nbuf + winw;
     for (t = tmpline, tmppos = 0; tmppos < tmpll; t++, tmppos++) {
 	unsigned ireg;
-	zattr base_atr_on = default_atr_on, base_atr_off = 0;
-	zattr all_atr_on, all_atr_off;
+	zattr base_attr = default_attr;
+	zattr all_attr;
 	struct region_highlight *rhp;
 	/*
 	 * Calculate attribute based on region.
@@ -1282,26 +1195,21 @@ zrefresh(void)
 		tmppos < rhp->end + offset) {
 		if (rhp->atr & (TXTFGCOLOUR|TXTBGCOLOUR)) {
 		    /* override colour with later entry */
-		    base_atr_on = (base_atr_on & ~TXT_ATTR_ON_VALUES_MASK) |
-			rhp->atr;
+		    base_attr = rhp->atr;
 		} else {
 		    /* no colour set yet */
-		    base_atr_on |= rhp->atr;
+		    base_attr |= rhp->atr;
 		}
-		if (tmppos == rhp->end + offset - 1 ||
-		    tmppos == tmpll - 1)
-		    base_atr_off |= TXT_ATTR_OFF_FROM_ON(rhp->atr);
 	    }
 	}
-	if (special_atr_on & (TXTFGCOLOUR|TXTBGCOLOUR)) {
+	if (special_attr & (TXTFGCOLOUR|TXTBGCOLOUR)) {
 	    /* keep colours from special attributes */
-	    all_atr_on = special_atr_on |
-		(base_atr_on & ~TXT_ATTR_COLOUR_ON_MASK);
+	    all_attr = special_attr |
+		(base_attr & ~TXT_ATTR_COLOUR_MASK);
 	} else {
 	    /* keep colours from standard attributes */
-	    all_atr_on = special_atr_on | base_atr_on;
+	    all_attr = special_attr | base_attr;
 	}
-	all_atr_off = TXT_ATTR_OFF_FROM_ON(all_atr_on);
 
 	if (t == scs)			/* if cursor is here, remember it */
 	    rpms.nvcs = rpms.s - nbuf[rpms.nvln = rpms.ln];
@@ -1319,10 +1227,9 @@ zrefresh(void)
 	    } else {
 		do {
 		    rpms.s->chr = ZWC(' ');
-		    rpms.s->atr = base_atr_on;
+		    rpms.s->atr = base_attr;
 		    rpms.s++;
 		} while ((++t0) & 7);
-		rpms.s[-1].atr |= base_atr_off;
 	    }
 	}
 #ifdef MULTIBYTE_SUPPORT
@@ -1341,11 +1248,9 @@ zrefresh(void)
 		    rpms.s->chr = ZWC(' ');
 		    if (!started)
 			started = 1;
-		    rpms.s->atr = all_atr_on;
+		    rpms.s->atr = all_attr;
 		    rpms.s++;
 		} while (rpms.s < rpms.sen);
-		if (started)
-		    rpms.s[-1].atr |= all_atr_off;
 		if (nextline(&rpms, 1))
 		    break;
 		if (t == scs) {
@@ -1369,7 +1274,7 @@ zrefresh(void)
 		 * occurrence.
 		 */
 		rpms.s->chr = ZWC('?');
-		rpms.s->atr = all_atr_on | all_atr_off;
+		rpms.s->atr = all_attr;
 		rpms.s++;
 	    } else {
 		/* We can fit it without reaching the end of the line. */
@@ -1377,7 +1282,7 @@ zrefresh(void)
 		 * As we don't actually output the WEOF, we attach
 		 * any off attributes to the character itself.
 		 */
-		rpms.s->atr = base_atr_on | base_atr_off;
+		rpms.s->atr = base_attr;
 		if (ichars > 1) {
 		    /*
 		     * Glyph includes combining characters.
@@ -1393,7 +1298,7 @@ zrefresh(void)
 		while (--width > 0) {
 		    rpms.s->chr = WEOF;
 		    /* Not used, but be consistent... */
-		    rpms.s->atr = base_atr_on | base_atr_off;
+		    rpms.s->atr = base_attr;
 		    rpms.s++;
 		}
 	    }
@@ -1410,17 +1315,16 @@ zrefresh(void)
 #endif
 	    ) {	/* other control character */
 	    rpms.s->chr = ZWC('^');
-	    rpms.s->atr = all_atr_on;
+	    rpms.s->atr = all_attr;
 	    rpms.s++;
 	    if (rpms.s == rpms.sen) {
 		/* text wrapped */
-		rpms.s[-1].atr |= all_atr_off;
 		if (nextline(&rpms, 1))
 		    break;
 	    }
 	    rpms.s->chr = (((unsigned int)*t & ~0x80u) > 31) ?
 		ZWC('?') : (*t | ZWC('@'));
-	    rpms.s->atr = all_atr_on | all_atr_off;
+	    rpms.s->atr = all_attr;
 	    rpms.s++;
 	}
 #ifdef MULTIBYTE_SUPPORT
@@ -1432,7 +1336,6 @@ zrefresh(void)
 	    char dispchars[11];
 	    char *dispptr = dispchars;
 	    wchar_t wc;
-	    int started = 0;
 
 #ifdef __STDC_ISO_10646__
 	    if (ZSH_INVALID_WCHAR_TEST(*t)) {
@@ -1449,31 +1352,23 @@ zrefresh(void)
 		if (mbtowc(&wc, dispptr, 1) == 1 /* paranoia */)
 		{
 		    rpms.s->chr = wc;
-		    if (!started)
-			started = 1;
-		    rpms.s->atr = all_atr_on;
+		    rpms.s->atr = all_attr;
 		    rpms.s++;
 		    if (rpms.s == rpms.sen) {
 			/* text wrapped */
-			if (started) {
-			    rpms.s[-1].atr |= all_atr_off;
-			    started = 0;
-			}
 			if (nextline(&rpms, 1))
 			    break;
 		    }
 		}
 		dispptr++;
 	    }
-	    if (started)
-		rpms.s[-1].atr |= all_atr_off;
 	    if (*dispptr) /* nextline said stop processing */
 		break;
 	}
 #else
 	else {			/* normal character */
 	    rpms.s->chr = *t;
-	    rpms.s->atr = base_atr_on | base_atr_off;
+	    rpms.s->atr = base_attr;
 	    rpms.s++;
 	}
 #endif
@@ -1499,13 +1394,12 @@ zrefresh(void)
 
     if (statusline) {
 	int outll, outsz;
-	zattr all_atr_on, all_atr_off;
+	zattr all_attr;
 	char *statusdup = ztrdup(statusline);
 	ZLE_STRING_T outputline =
 	    stringaszleline(statusdup, 0, &outll, &outsz, NULL); 
 
-	all_atr_on = special_atr_on;
-	all_atr_off = TXT_ATTR_OFF_FROM_ON(all_atr_on);
+	all_attr = special_attr;
 
 	rpms.tosln = rpms.ln + 1;
 	nbuf[rpms.ln][winw + 1] = zr_zr;	/* text not wrapped */
@@ -1525,7 +1419,7 @@ zrefresh(void)
 		}
 		if (width > rpms.sen - rpms.s) {
 		    rpms.s->chr = ZWC('?');
-		    rpms.s->atr = all_atr_on | all_atr_off;
+		    rpms.s->atr = all_attr;
 		    rpms.s++;
 		} else {
 		    rpms.s->chr = *u;
@@ -1542,7 +1436,7 @@ zrefresh(void)
 #endif
 	    if (ZC_icntrl(*u)) { /* simplified processing in the status line */
 		rpms.s->chr = ZWC('^');
-		rpms.s->atr = all_atr_on;
+		rpms.s->atr = all_attr;
 		rpms.s++;
 		if (rpms.s == rpms.sen) {
 		    nbuf[rpms.ln][winw + 1] = zr_nl;/* text wrapped */
@@ -1550,7 +1444,7 @@ zrefresh(void)
 		}
 		rpms.s->chr = (((unsigned int)*u & ~0x80u) > 31)
 		    ? ZWC('?') : (*u | ZWC('@'));
-		rpms.s->atr = all_atr_on | all_atr_off;
+		rpms.s->atr = all_attr;
 		rpms.s++;
 	    } else {
 		rpms.s->chr = *u;
@@ -1725,7 +1619,6 @@ zrefresh(void)
 
     /* output the right-prompt if appropriate */
 	if (put_rpmpt && !iln && !oput_rpmpt) {
-	    zattr attrchange;
 
 	    moveto(0, winw - rprompt_off - rpromptw);
 	    zputs(rpromptbuf, shout);
@@ -1735,39 +1628,9 @@ zrefresh(void)
 		zputc(&zr_cr);
 		vcs = 0;
 	    }
-	/* reset character attributes to that set by the main prompt */
-	    txtchange = pmpt_attr;
-	    /*
-	     * Keep attributes that have actually changed,
-	     * which are ones off in rpmpt_attr and on in
-	     * pmpt_attr, and vice versa.
-	     */
-	    attrchange = txtchange &
-		(TXT_ATTR_OFF_FROM_ON(rpmpt_attr) |
-		 TXT_ATTR_ON_FROM_OFF(rpmpt_attr));
-	    /*
-	     * Careful in case the colour changed.
-	     */
-	    if (txtchangeisset(txtchange, TXTFGCOLOUR) &&
-		(!txtchangeisset(rpmpt_attr, TXTFGCOLOUR) ||
-		 ((txtchange ^ rpmpt_attr) & TXT_ATTR_FG_COL_MASK)))
-	    {
-		attrchange |=
-		    txtchange & (TXTFGCOLOUR | TXT_ATTR_FG_COL_MASK);
-	    }
-	    if (txtchangeisset(txtchange, TXTBGCOLOUR) &&
-		(!txtchangeisset(rpmpt_attr, TXTBGCOLOUR) ||
-		 ((txtchange ^ rpmpt_attr) & TXT_ATTR_BG_COL_MASK)))
-	    {
-		attrchange |=
-		    txtchange & (TXTBGCOLOUR | TXT_ATTR_BG_COL_MASK);
-	    }
-	    /*
-	     * Now feed these changes into the usual function,
-	     * if necessary.
-	     */
-	    if (attrchange)
-		settextattributes(attrchange);
+	    /* reset character attributes to that set by the main prompt */
+	    treplaceattrs(pmpt_attr);
+	    applytextattributes(0);
 	}
     }
 
@@ -1782,8 +1645,8 @@ individually */
 
 /* reset character attributes */
     if (clearf && postedit) {
-	if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr))
-	    settextattributes(txtchange);
+	treplaceattrs(pmpt_attr ? pmpt_attr : rpmpt_attr);
+	applytextattributes(0);
     }
     clearf = 0;
     oput_rpmpt = put_rpmpt;
@@ -1984,8 +1847,6 @@ refreshline(int ln)
 /* 3: main display loop - write out the buffer using whatever tricks we can */
 
     for (;;) {
-	zattr now_off;
-
 #ifdef MULTIBYTE_SUPPORT
 	if ((!nl->chr || nl->chr != WEOF) && (!ol->chr || ol->chr != WEOF)) {
 #endif
@@ -2087,7 +1948,7 @@ refreshline(int ln)
 			     * deletions, so turn off text attributes.
 			     */
 			    if (first) {
-				clearattributes();
+				cleartextattributes(0);
 				first = 0;
 			    }
 			    tc_delchars(i);
@@ -2176,13 +2037,8 @@ refreshline(int ln)
 	    break;
 	do {
 #endif
-	    /*
-	     * If an attribute was on here but isn't any more,
-	     * output the sequence to turn it off.
-	     */
-	    now_off = ol->atr & ~nl->atr & TXT_ATTR_ON_MASK;
-	    if (now_off)
-		settextattributes(TXT_ATTR_OFF_FROM_ON(now_off));
+	    treplaceattrs(nl->atr);
+	    applytextattributes(0);
 
 	    /*
 	     * This is deliberately called if nl->chr is WEOF
@@ -2560,8 +2416,8 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 
     for (t0 = 0; t0 < tmpll; t0++) {
 	unsigned ireg;
-	zattr base_atr_on = 0, base_atr_off = 0;
-	zattr all_atr_on, all_atr_off;
+	zattr base_attr = 0;
+	zattr all_attr;
 	struct region_highlight *rhp;
 	/*
 	 * Calculate attribute based on region.
@@ -2576,38 +2432,33 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 		offset = predisplaylen; /* increment over it */
 	    if (rhp->start + offset <= t0 &&
 		t0 < rhp->end + offset) {
-		if (base_atr_on & (TXTFGCOLOUR|TXTBGCOLOUR)) {
+		if (base_attr & (TXTFGCOLOUR|TXTBGCOLOUR)) {
 		    /* keep colour already set */
-		    base_atr_on |= rhp->atr & ~TXT_ATTR_COLOUR_ON_MASK;
+		    base_attr |= rhp->atr & ~TXT_ATTR_COLOUR_MASK;
 		} else {
 		    /* no colour set yet */
-		    base_atr_on |= rhp->atr;
+		    base_attr |= rhp->atr;
 		}
-		if (t0 == rhp->end + offset - 1 ||
-		    t0 == tmpll - 1)
-		    base_atr_off |= TXT_ATTR_OFF_FROM_ON(rhp->atr);
 	    }
 	}
-	if (special_atr_on & (TXTFGCOLOUR|TXTBGCOLOUR)) {
+	if (special_attr & (TXTFGCOLOUR|TXTBGCOLOUR)) {
 	    /* keep colours from special attributes */
-	    all_atr_on = special_atr_on |
-		(base_atr_on & ~TXT_ATTR_COLOUR_ON_MASK);
+	    all_attr = special_attr |
+		(base_attr & ~TXT_ATTR_COLOUR_MASK);
 	} else {
 	    /* keep colours from standard attributes */
-	    all_atr_on = special_atr_on | base_atr_on;
+	    all_attr = special_attr | base_attr;
 	}
-	all_atr_off = TXT_ATTR_OFF_FROM_ON(all_atr_on);
 
 	if (tmpline[t0] == ZWC('\t')) {
 	    for (*vp++ = zr_sp; (vp - vbuf) & 7; )
 		*vp++ = zr_sp;
-	    vp[-1].atr |= base_atr_off;
 	} else if (tmpline[t0] == ZWC('\n')) {
 	    vp->chr = ZWC('\\');
-	    vp->atr = all_atr_on;
+	    vp->atr = all_attr;
 	    vp++;
 	    vp->chr = ZWC('n');
-	    vp->atr = all_atr_on | all_atr_off;
+	    vp->atr = all_attr;
 	    vp++;
 #ifdef MULTIBYTE_SUPPORT
 	} else if (WC_ISPRINT(tmpline[t0]) &&
@@ -2623,7 +2474,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 		}
 	    } else
 		ichars = 1;
-	    vp->atr = base_atr_on | base_atr_off;
+	    vp->atr = base_attr;
 	    if (ichars > 1)
 		addmultiword(vp, tmpline+t0, ichars);
 	    else
@@ -2631,7 +2482,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	    vp++;
 	    while (--width > 0) {
 		vp->chr = WEOF;
-		vp->atr = base_atr_on | base_atr_off;
+		vp->atr = base_attr;
 		vp++;
 	    }
 	    t0 += ichars - 1;
@@ -2644,11 +2495,11 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	    ZLE_INT_T t = tmpline[++t0];
 
 	    vp->chr = ZWC('^');
-	    vp->atr = all_atr_on;
+	    vp->atr = all_attr;
 	    vp++;
 	    vp->chr = (((unsigned int)t & ~0x80u) > 31) ?
 		ZWC('?') : (t | ZWC('@'));
-	    vp->atr = all_atr_on | all_atr_off;
+	    vp->atr = all_attr;
 	    vp++;
 	}
 #ifdef MULTIBYTE_SUPPORT
@@ -2656,7 +2507,6 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	    char dispchars[11];
 	    char *dispptr = dispchars;
 	    wchar_t wc;
-	    int started = 0;
 
 	    if ((unsigned)tmpline[t0] > 0xffffU) {
 		sprintf(dispchars, "<%.08x>", (unsigned)tmpline[t0]);
@@ -2666,20 +2516,16 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
 	    while (*dispptr) {
 		if (mbtowc(&wc, dispptr, 1) == 1 /* paranoia */) {
 		    vp->chr = wc;
-		    if (!started)
-			started = 1;
-		    vp->atr = all_atr_on;
+		    vp->atr = all_attr;
 		    vp++;
 		}
 		dispptr++;
 	    }
-	    if (started)
-		vp[-1].atr |= all_atr_off;
 	}
 #else
 	else {
 	    vp->chr = tmpline[t0];
-	    vp->atr = base_atr_on | base_atr_off;
+	    vp->atr = base_attr;
 	    vp++;
 	}
 #endif
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index fdd168763..f94bfce3c 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -2426,7 +2426,7 @@ mod_export int
 printfmt(char *fmt, int n, int dopr, int doesc)
 {
     char *p = fmt, nc[DIGBUFSIZE];
-    int l = 0, cc = 0, b = 0, s = 0, u = 0, m;
+    int l = 0, cc = 0;
 
     MB_METACHARINIT();
     for (; *p; ) {
@@ -2437,48 +2437,45 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 	    if (idigit(*++p))
 		arg = zstrtol(p, &p, 10);
 	    if (*p) {
-		m = 0;
 		switch (*p) {
 		case '%':
-		    if (dopr)
+		    if (dopr) {
+			applytextattributes(0);
 			putc('%', shout);
+		    }
 		    cc++;
 		    break;
 		case 'n':
 		    sprintf(nc, "%d", n);
-		    if (dopr)
+		    if (dopr) {
+			applytextattributes(0);
 			fputs(nc, shout);
+		    }
 		    cc += MB_METASTRWIDTH(nc);
 		    break;
 		case 'B':
-		    b = 1;
 		    if (dopr)
-			tcout(TCBOLDFACEBEG);
+			tsetattrs(TXTBOLDFACE);
 		    break;
 		case 'b':
-		    b = 0; m = 1;
 		    if (dopr)
-			tcout(TCALLATTRSOFF);
+			tunsetattrs(TXTBOLDFACE);
 		    break;
 		case 'S':
-		    s = 1;
 		    if (dopr)
-			tcout(TCSTANDOUTBEG);
+			tsetattrs(TXTSTANDOUT);
 		    break;
 		case 's':
-		    s = 0; m = 1;
 		    if (dopr)
-			tcout(TCSTANDOUTEND);
+			tunsetattrs(TXTSTANDOUT);
 		    break;
 		case 'U':
-		    u = 1;
 		    if (dopr)
-			tcout(TCUNDERLINEBEG);
+			tsetattrs(TXTUNDERLINE);
 		    break;
 		case 'u':
-		    u = 0; m = 1;
 		    if (dopr)
-			tcout(TCUNDERLINEEND);
+			tunsetattrs(TXTUNDERLINE);
 		    break;
 		case 'F':
 		case 'K':
@@ -2491,18 +2488,19 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 		    } else
 			atr = match_colour(NULL, is_fg, arg);
 		    if (atr != TXT_ERROR)
-			set_colour_attribute(atr, is_fg ? COL_SEQ_FG :
-					     COL_SEQ_BG, 0);
+			tsetattrs(atr);
 		    break;
 		case 'f':
-		    set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, 0);
+		    tunsetattrs(TXTFGCOLOUR);
 		    break;
 		case 'k':
-		    set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, 0);
+		    tunsetattrs(TXTBGCOLOUR);
 		    break;
 		case '{':
 		    if (arg)
 			cc += arg;
+		    if (dopr)
+			applytextattributes(0);
 		    for (p++; *p && (*p != '%' || p[1] != '}'); p++) {
 			if (*p == Meta) {
 			    p++;
@@ -2518,14 +2516,6 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 			p--;
 		    break;
 		}
-		if (dopr && m) {
-		    if (b)
-			tcout(TCBOLDFACEBEG);
-		    if (s)
-			tcout(TCSTANDOUTBEG);
-		    if (u)
-			tcout(TCUNDERLINEBEG);
-		}
 	    } else
 		break;
 	    p++;
@@ -2533,6 +2523,7 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 	    if (*p == '\n') {
 		cc++;
 		if (dopr) {
+		    applytextattributes(0);
 		    if (tccan(TCCLEAREOL))
 			tcout(TCCLEAREOL);
 		    else {
@@ -2551,6 +2542,7 @@ printfmt(char *fmt, int n, int dopr, int doesc)
 		convchar_t cchar;
 		int clen = MB_METACHARLENCONV(p, &cchar);
 		if (dopr) {
+		    applytextattributes(0);
 		    while (clen--) {
 			if (*p == Meta) {
 			    p++;
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 2536e9faa..1a580a9e6 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -1250,7 +1250,7 @@ getzlequery(void)
 	REFRESH_ELEMENT re;
 	re.chr = c;
 	re.atr = 0;
-	zwcputc(&re, NULL);
+	zwcputc(&re);
     }
     return c == ZWC('y');
 }
diff --git a/Src/builtin.c b/Src/builtin.c
index 70a950666..4c295d11f 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -4603,6 +4603,8 @@ bin_print(char *name, char **args, Options ops, int func)
     /* compute lengths, and interpret according to -P, -D, -e, etc. */
     argc = arrlen(args);
     len = (int *) hcalloc(argc * sizeof(int));
+    if (OPT_ISSET(ops, 'P'))
+	txtunknownattrs = TXT_ATTR_ALL;
     for (n = 0; n < argc; n++) {
 	/* first \ sequences */
 	if (fmt ||
@@ -4633,7 +4635,7 @@ bin_print(char *name, char **args, Options ops, int func)
 	     */
 	    char *str = unmetafy(
 		promptexpand(metafy(args[n], len[n], META_NOALLOC),
-			     0, NULL, NULL, NULL),
+			     0, NULL, NULL),
 		&len[n]);
 	    args[n] = dupstrpfx(str, len[n]);
 	    free(str);
diff --git a/Src/init.c b/Src/init.c
index 9981d059a..75ef2e094 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -1654,8 +1654,7 @@ VA_DCL
 
 	lp = va_arg(ap, char **);
 
-	pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL,
-				       NULL),
+	pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL),
 			  &pptlen);
 	write_loop(2, pptbuf, pptlen);
 	free(pptbuf);
diff --git a/Src/input.c b/Src/input.c
index d55b05696..5a612669b 100644
--- a/Src/input.c
+++ b/Src/input.c
@@ -402,7 +402,7 @@ inputline(void)
 	    char *pptbuf;
 	    int pptlen;
 	    pptbuf = unmetafy(promptexpand(ingetcpmptl ? *ingetcpmptl : NULL,
-					   0, NULL, NULL, NULL), &pptlen);
+					   0, NULL, NULL), &pptlen);
 	    write_loop(2, pptbuf, pptlen);
 	    free(pptbuf);
 	}
diff --git a/Src/loop.c b/Src/loop.c
index 88c55dd1a..7df379ecf 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -282,7 +282,7 @@ execselect(Estate state, UNUSED(int do_exec))
 		    /* Keep any user interrupt error status */
 		    errflag = oef | (errflag & ERRFLAG_INT);
 	    	} else {
-		    str = promptexpand(prompt3, 0, NULL, NULL, NULL);
+		    str = promptexpand(prompt3, 0, NULL, NULL);
 		    zputs(str, stderr);
 		    free(str);
 		    fflush(stderr);
diff --git a/Src/prompt.c b/Src/prompt.c
index 3cb95039c..880194f87 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -30,10 +30,20 @@
 #include "zsh.mdh"
 #include "prompt.pro"
 
-/* text attribute mask */
+/* current text attributes */
 
 /**/
-mod_export zattr txtattrmask;
+mod_export zattr txtcurrentattrs;
+
+/* pending changes for attributes */
+
+/**/
+mod_export zattr txtpendingattrs;
+
+/* mask of attributes with an unknown state */
+
+/**/
+mod_export zattr txtunknownattrs;
 
 /* the command stack for use with %_ in prompts */
 
@@ -160,15 +170,11 @@ promptpath(char *p, int npath, int tilde)
  * between spacing and non-spacing parts of the prompt, and
  * Nularg, which (in a non-spacing sequence) indicates a
  * `glitch' space.
- *
- * txtchangep gives an integer controlling the attributes of
- * the prompt.  This is for use in zle to maintain the attributes
- * consistently.  Other parts of the shell should not need to use it.
  */
 
 /**/
 mod_export char *
-promptexpand(char *s, int ns, char *rs, char *Rs, zattr *txtchangep)
+promptexpand(char *s, int ns, char *rs, char *Rs)
 {
     struct buf_vars new_vars;
 
@@ -212,7 +218,7 @@ promptexpand(char *s, int ns, char *rs, char *Rs, zattr *txtchangep)
     new_vars.bp1 = NULL;
     new_vars.truncwidth = 0;
 
-    putpromptchar(1, '\0', txtchangep);
+    putpromptchar(1, '\0');
     addbufspc(2);
     if (new_vars.dontcount)
 	*new_vars.bp++ = Outpar;
@@ -253,7 +259,7 @@ parsecolorchar(zattr arg, int is_fg)
 	    *ep = '\0';
 	    /* expand the contents of the argument so you can use
 	     * %v for example */
-	    coll = col = promptexpand(bv->fm, 0, NULL, NULL, NULL);
+	    coll = col = promptexpand(bv->fm, 0, NULL, NULL);
 	    *ep = oc;
 	    arg = match_colour((const char **)&coll, is_fg, 0);
 	    free(col);
@@ -278,7 +284,7 @@ parsecolorchar(zattr arg, int is_fg)
 
 /**/
 static int
-putpromptchar(int doprint, int endchar, zattr *txtchangep)
+putpromptchar(int doprint, int endchar)
 {
     char *ss, *hostnam;
     int t0, arg, test, sep, j, numjobs, len;
@@ -430,10 +436,9 @@ putpromptchar(int doprint, int endchar, zattr *txtchangep)
 		/* Don't do the current truncation until we get back */
 		otruncwidth = bv->truncwidth;
 		bv->truncwidth = 0;
-		if (!putpromptchar(test == 1 && doprint, sep,
-				   txtchangep) || !*++bv->fm ||
-		    !putpromptchar(test == 0 && doprint, ')',
-				   txtchangep)) {
+		if (!putpromptchar(test == 1 && doprint, sep) ||
+				   !*++bv->fm ||
+		    !putpromptchar(test == 0 && doprint, ')')) {
 		    bv->truncwidth = otruncwidth;
 		    return 0;
 		}
@@ -519,71 +524,57 @@ putpromptchar(int doprint, int endchar, zattr *txtchangep)
 		unqueue_signals();
 		break;
 	    case 'S':
-		txtchangeset(txtchangep, TXTSTANDOUT, TXTNOSTANDOUT);
-		txtset(TXTSTANDOUT);
-		tsetcap(TCSTANDOUTBEG, TSC_PROMPT);
+		tsetattrs(TXTSTANDOUT);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 's':
-		txtchangeset(txtchangep, TXTNOSTANDOUT, TXTSTANDOUT);
-		txtunset(TXTSTANDOUT);
-		tsetcap(TCSTANDOUTEND, TSC_PROMPT|TSC_DIRTY);
+		tunsetattrs(TXTSTANDOUT);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 'B':
-		txtchangeset(txtchangep, TXTBOLDFACE, TXTNOBOLDFACE);
-		txtset(TXTBOLDFACE);
-		tsetcap(TCBOLDFACEBEG, TSC_PROMPT|TSC_DIRTY);
+		tsetattrs(TXTBOLDFACE);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 'b':
-		txtchangeset(txtchangep, TXTNOBOLDFACE, TXTBOLDFACE);
-		txtunset(TXTBOLDFACE);
-		tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
+		tunsetattrs(TXTBOLDFACE);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 'U':
-		txtchangeset(txtchangep, TXTUNDERLINE, TXTNOUNDERLINE);
-		txtset(TXTUNDERLINE);
-		tsetcap(TCUNDERLINEBEG, TSC_PROMPT);
+		tsetattrs(TXTUNDERLINE);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 'u':
-		txtchangeset(txtchangep, TXTNOUNDERLINE, TXTUNDERLINE);
-		txtunset(TXTUNDERLINE);
-		tsetcap(TCUNDERLINEEND, TSC_PROMPT|TSC_DIRTY);
+		tunsetattrs(TXTUNDERLINE);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case 'F':
 		atr = parsecolorchar(arg, 1);
-		if (!(atr & (TXT_ERROR | TXTNOFGCOLOUR))) {
-		    txtchangeset(txtchangep, atr & TXT_ATTR_FG_ON_MASK,
-				 TXTNOFGCOLOUR | TXT_ATTR_FG_COL_MASK);
-		    txtunset(TXT_ATTR_FG_COL_MASK);
-		    txtset(atr & TXT_ATTR_FG_ON_MASK);
-		    set_colour_attribute(atr, COL_SEQ_FG, TSC_PROMPT);
+		if (atr && atr != TXT_ERROR) {
+		    tsetattrs(atr);
+		    applytextattributes(TSC_PROMPT);
 		    break;
 		}
 		/* else FALLTHROUGH */
 	    case 'f':
-		txtchangeset(txtchangep, TXTNOFGCOLOUR, TXT_ATTR_FG_ON_MASK);
-		txtunset(TXT_ATTR_FG_ON_MASK);
-		set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_PROMPT);
+		tunsetattrs(TXTFGCOLOUR);
+		applytextattributes(TSC_PROMPT);
 		break;
 	    case 'K':
 		atr = parsecolorchar(arg, 0);
-		if (!(atr & (TXT_ERROR | TXTNOBGCOLOUR))) {
-		    txtchangeset(txtchangep, atr & TXT_ATTR_BG_ON_MASK,
-				 TXTNOBGCOLOUR | TXT_ATTR_BG_COL_MASK);
-		    txtunset(TXT_ATTR_BG_COL_MASK);
-		    txtset(atr & TXT_ATTR_BG_ON_MASK);
-		    set_colour_attribute(atr, COL_SEQ_BG, TSC_PROMPT);
+		if (atr && atr != TXT_ERROR) {
+		    tsetattrs(atr);
+		    applytextattributes(TSC_PROMPT);
 		    break;
 		}
 		/* else FALLTHROUGH */
 	    case 'k':
-		txtchangeset(txtchangep, TXTNOBGCOLOUR, TXT_ATTR_BG_ON_MASK);
-		txtunset(TXT_ATTR_BG_ON_MASK);
-		set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_PROMPT);
+		tunsetattrs(TXTBGCOLOUR);
+	        applytextattributes(TSC_PROMPT);
 		break;
 	    case '[':
 		if (idigit(*++bv->fm))
 		    arg = zstrtol(bv->fm, &bv->fm, 10);
-		if (!prompttrunc(arg, ']', doprint, endchar, txtchangep))
+		if (!prompttrunc(arg, ']', doprint, endchar))
 		    return *bv->fm;
 		break;
 	    case '<':
@@ -596,7 +587,7 @@ putpromptchar(int doprint, int endchar, zattr *txtchangep)
 		    if (arg <= 0)
 			arg = 1;
 		}
-		if (!prompttrunc(arg, *bv->fm, doprint, endchar, txtchangep))
+		if (!prompttrunc(arg, *bv->fm, doprint, endchar))
 		    return *bv->fm;
 		break;
 	    case '{': /*}*/
@@ -1015,7 +1006,7 @@ tsetcap(int cap, int flags)
 {
     if (tccan(cap) && !isset(SINGLELINEZLE) &&
         !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
-	switch (flags & TSC_OUTPUT_MASK) {
+	switch (flags) {
 	case TSC_RAW:
 	    tputs(tcstr[cap], 1, putraw);
 	    break;
@@ -1045,20 +1036,6 @@ tsetcap(int cap, int flags)
 	    }
 	    break;
 	}
-
-	if (flags & TSC_DIRTY) {
-	    flags &= ~TSC_DIRTY;
-	    if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
-		tsetcap(TCBOLDFACEBEG, flags);
-	    if (txtisset(TXTSTANDOUT))
-		tsetcap(TCSTANDOUTBEG, flags);
-	    if (txtisset(TXTUNDERLINE))
-		tsetcap(TCUNDERLINEBEG, flags);
-	    if (txtisset(TXTFGCOLOUR))
-		set_colour_attribute(txtattrmask, COL_SEQ_FG, flags);
-	    if (txtisset(TXTBGCOLOUR))
-		set_colour_attribute(txtattrmask, COL_SEQ_BG, flags);
-	}
     }
 }
 
@@ -1219,8 +1196,7 @@ countprompt(char *str, int *wp, int *hp, int overf)
 
 /**/
 static int
-prompttrunc(int arg, int truncchar, int doprint, int endchar,
-	    zattr *txtchangep)
+prompttrunc(int arg, int truncchar, int doprint, int endchar)
 {
     if (arg > 0) {
 	char ch = *bv->fm, *ptr, *truncstr;
@@ -1267,7 +1243,7 @@ prompttrunc(int arg, int truncchar, int doprint, int endchar,
 	w = bv->bp - bv->buf;
 	bv->fm++;
 	bv->trunccount = bv->dontcount;
-	putpromptchar(doprint, endchar, txtchangep);
+	putpromptchar(doprint, endchar);
 	bv->trunccount = 0;
 	ptr = bv->buf + w;	/* putpromptchar() may have realloc()'d */
 	*bv->bp = '\0';
@@ -1547,7 +1523,7 @@ prompttrunc(int arg, int truncchar, int doprint, int endchar,
 	     * With bv->truncwidth set to zero, we always reach endchar *
 	     * (or the terminating NULL) this time round.         *
 	     */
-	    if (!putpromptchar(doprint, endchar, txtchangep))
+	    if (!putpromptchar(doprint, endchar))
 		return 0;
 	}
 	/* Now we have to trick it into matching endchar again */
@@ -1585,6 +1561,122 @@ cmdpop(void)
 	cmdsp--;
 }
 
+/* functions for handling attributes */
+
+/**/
+mod_export void
+applytextattributes(int flags)
+{
+    zattr change = txtcurrentattrs ^ txtpendingattrs;
+    zattr keepon = ~change & txtpendingattrs & TXT_ATTR_ALL;
+    zattr turnoff = change & ~txtpendingattrs & TXT_ATTR_ALL;
+    int keepcount, turncount = 0;
+
+    /* bail out early if we wouldn't do anything */
+    if (!change)
+	return;
+
+    if (txtunknownattrs) {
+	txtunknownattrs &= ~change; /* changes cease to be unknown */
+	/* can't turn unknown attrs back on so avoid wiping them */
+	keepcount = 1;
+    } else {
+	/* If we want to turn off more attributes than we want to keep on
+	 * then it takes fewer termcap sequences to just turn off all the
+	 * attributes. */
+	for (keepcount = 0; keepon; keepcount++) /* count bits */
+	    keepon &= keepon - 1;
+	for (; turnoff; turncount++)
+	    turnoff &= turnoff - 1;
+    }
+
+    if (keepcount < turncount || (change & ~txtpendingattrs & TXTBOLDFACE)) {
+	tsetcap(TCALLATTRSOFF, flags);
+	/* this cleared all attributes, may need to restore some */
+	change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs;
+	txtunknownattrs = 0;
+    } else {
+	if (change & ~txtpendingattrs & TXTSTANDOUT) {
+	    tsetcap(TCSTANDOUTEND, flags);
+	    /* in some cases, that clears all attributes */
+	    change = (txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs) |
+		    (TXTUNDERLINE & change);
+	}
+	if (change & ~txtpendingattrs & TXTUNDERLINE) {
+	    tsetcap(TCUNDERLINEEND, flags);
+	    /* in some cases, that clears all attributes */
+	    change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs;
+	}
+    }
+    if (change & txtpendingattrs & TXTBOLDFACE)
+	tsetcap(TCBOLDFACEBEG, flags);
+    if (change & txtpendingattrs & TXTSTANDOUT)
+	tsetcap(TCSTANDOUTBEG, flags);
+    if (change & txtpendingattrs & TXTUNDERLINE)
+	tsetcap(TCUNDERLINEBEG, flags);
+
+    if (change & TXT_ATTR_FG_MASK)
+	set_colour_attribute(txtpendingattrs, COL_SEQ_FG, flags);
+    if (change & TXT_ATTR_BG_MASK)
+	set_colour_attribute(txtpendingattrs, COL_SEQ_BG, flags);
+
+    txtcurrentattrs = txtpendingattrs;
+}
+
+/**/
+mod_export void
+cleartextattributes(int flags)
+{
+    treplaceattrs(0);
+    applytextattributes(flags);
+}
+
+/**/
+mod_export void
+treplaceattrs(zattr newattrs)
+{
+    if (txtunknownattrs) {
+	/* Set current attributes to the opposite of the new ones
+	 * for any that are unknown so that applytextattributes()
+	 * detects them as changed. */
+	txtcurrentattrs &= ~txtunknownattrs;
+	txtcurrentattrs |= txtunknownattrs & ~newattrs;
+    }
+
+    txtpendingattrs = newattrs;
+}
+
+/**/
+mod_export void
+tsetattrs(zattr newattrs)
+{
+    /* assume any unknown attributes that we're now setting were unset */
+    txtcurrentattrs &= ~(newattrs & txtunknownattrs);
+
+    txtpendingattrs |= newattrs & TXT_ATTR_ALL;
+    if (newattrs & TXTFGCOLOUR) {
+	txtpendingattrs &= ~TXT_ATTR_FG_MASK;
+	txtpendingattrs |= newattrs & TXT_ATTR_FG_MASK;
+    }
+    if (newattrs & TXTBGCOLOUR) {
+	txtpendingattrs &= ~TXT_ATTR_BG_MASK;
+	txtpendingattrs |= newattrs & TXT_ATTR_BG_MASK;
+    }
+}
+
+/**/
+mod_export void
+tunsetattrs(zattr newattrs)
+{
+    /* assume any unknown attributes that we're now unsetting were set */
+    txtcurrentattrs |= newattrs & txtunknownattrs;
+
+    txtpendingattrs &= ~(newattrs & TXT_ATTR_ALL);
+    if (newattrs & TXTFGCOLOUR)
+	txtpendingattrs &= ~TXT_ATTR_FG_MASK;
+    if (newattrs & TXTBGCOLOUR)
+	txtpendingattrs &= ~TXT_ATTR_BG_MASK;
+}
 
 /*****************************************************************************
  * Utilities dealing with colour and other forms of highlighting.
@@ -1607,7 +1699,7 @@ struct highlight {
 };
 
 static const struct highlight highlights[] = {
-    { "none", 0, TXT_ATTR_ON_MASK },
+    { "none", 0, TXT_ATTR_ALL },
     { "bold", TXTBOLDFACE, 0 },
     { "standout", TXTSTANDOUT, 0 },
     { "underline", TXTUNDERLINE, 0 },
@@ -1645,8 +1737,8 @@ match_named_colour(const char **teststrp)
  * Match just the colour part of a highlight specification.
  * If teststrp is NULL, use the already parsed numeric colour.
  * Return the attributes to set in the attribute variable.
- * Return -1 for out of range.  Does not check the character
- * following the colour specification.
+ * Return TXT_ERROR for out of range.  Does not check the
+ * character following the colour specification.
  */
 
 /**/
@@ -1693,10 +1785,8 @@ match_colour(const char **teststrp, int is_fg, int colour)
 	    }
 	} else if ((named = ialpha(**teststrp))) {
 	    colour = match_named_colour(teststrp);
-	    if (colour == 8) {
-		/* default */
-		return is_fg ? TXTNOFGCOLOUR : TXTNOBGCOLOUR;
-	    }
+	    if (colour == 8) /* default */
+		return 0;
 	    if (colour < 0)
 		return TXT_ERROR;
 	}
@@ -2024,13 +2114,13 @@ set_colour_attribute(zattr atr, int fg_bg, int flags)
     if (fg_bg == COL_SEQ_FG) {
 	colour = txtchangeget(atr, TXT_ATTR_FG_COL);
 	tc = TCFGCOLOUR;
-	def = txtchangeisset(atr, TXTNOFGCOLOUR);
-	use_truecolor = txtchangeisset(atr, TXT_ATTR_FG_24BIT);
+	def = !(atr & TXTFGCOLOUR);
+	use_truecolor = atr & TXT_ATTR_FG_24BIT;
     } else {
 	colour = txtchangeget(atr, TXT_ATTR_BG_COL);
 	tc = TCBGCOLOUR;
-	def = txtchangeisset(atr, TXTNOBGCOLOUR);
-	use_truecolor = txtchangeisset(atr, TXT_ATTR_BG_24BIT);
+	def = !(atr & TXTBGCOLOUR);
+	use_truecolor = atr & TXT_ATTR_BG_24BIT;
     }
 
     /* Test if current zle_highlight settings are customized, or
diff --git a/Src/subst.c b/Src/subst.c
index b8e4023e1..897188862 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -3716,6 +3716,8 @@ colonsubscript:
     if (presc) {
 	int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG];
 	int opp = opts[PROMPTPERCENT];
+	zattr savecurrent = txtcurrentattrs;
+	zattr saveunknown = txtunknownattrs;
 
 	if (presc < 2) {
 	    opts[PROMPTPERCENT] = 1;
@@ -3738,7 +3740,8 @@ colonsubscript:
 	    for (; *ap; ap++) {
 		char *tmps;
 		untokenize(*ap);
-		tmps = promptexpand(*ap, 0, NULL, NULL, NULL);
+		txtunknownattrs = TXT_ATTR_ALL;
+		tmps = promptexpand(*ap, 0, NULL, NULL);
 		*ap = dupstring(tmps);
 		free(tmps);
 	    }
@@ -3747,10 +3750,14 @@ colonsubscript:
 	    if (!copied)
 		val = dupstring(val), copied = 1;
 	    untokenize(val);
-	    tmps = promptexpand(val, 0, NULL, NULL, NULL);
+	    txtunknownattrs = TXT_ATTR_ALL;
+	    tmps = promptexpand(val, 0, NULL, NULL);
 	    val = dupstring(tmps);
 	    free(tmps);
 	}
+
+	txtpendingattrs = txtcurrentattrs = savecurrent;
+	txtunknownattrs = saveunknown;
 	opts[PROMPTSUBST] = ops;
 	opts[PROMPTBANG] = opb;
 	opts[PROMPTPERCENT] = opp;
diff --git a/Src/utils.c b/Src/utils.c
index 32492a93b..55f2d1ab0 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1543,7 +1543,8 @@ preprompt(void)
 	if (!eolmark)
 	    eolmark = "%B%S%#%s%b";
 	opts[PROMPTPERCENT] = 1;
-	str = promptexpand(eolmark, 1, NULL, NULL, NULL);
+	txtunknownattrs = TXT_ATTR_ALL;
+	str = promptexpand(eolmark, 1, NULL, NULL);
 	countprompt(str, &w, 0, -1);
 	opts[PROMPTPERCENT] = percents;
 	zputs(str, shout);
@@ -1713,7 +1714,7 @@ printprompt4(void)
 	opts[XTRACE] = 0;
 	unmetafy(s, &l);
 	s = unmetafy(promptexpand(metafy(s, l, META_NOALLOC),
-				  0, NULL, NULL, NULL), &l);
+				  0, NULL, NULL), &l);
 	opts[XTRACE] = t;
 
 	fprintf(xtrerr, "%s", s);
@@ -3211,7 +3212,7 @@ spckword(char **s, int hist, int cmd, int ask)
 		x = 'n';
 	    } else if (shout) {
 		char *pptbuf;
-		pptbuf = promptexpand(sprompt, 0, best, guess, NULL);
+		pptbuf = promptexpand(sprompt, 0, best, guess);
 		zputs(pptbuf, shout);
 		free(pptbuf);
 		fflush(shout);
diff --git a/Src/zsh.h b/Src/zsh.h
index b035a1184..35ae033e3 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2681,25 +2681,8 @@ struct ttyinfo {
 #define TXTFGCOLOUR   0x0008
 #define TXTBGCOLOUR   0x0010
 
-#define TXT_ATTR_ON_MASK   0x001F
-
-#define txtisset(X)  (txtattrmask & (X))
-#define txtset(X)    (txtattrmask |= (X))
-#define txtunset(X)  (txtattrmask &= ~(X))
-
-#define TXTNOBOLDFACE	0x0020
-#define TXTNOSTANDOUT	0x0040
-#define TXTNOUNDERLINE	0x0080
-#define TXTNOFGCOLOUR	0x0100
-#define TXTNOBGCOLOUR	0x0200
-
-#define TXT_ATTR_OFF_MASK  0x03E0
-/* Bits to shift off right to get on */
-#define TXT_ATTR_OFF_ON_SHIFT 5
-#define TXT_ATTR_OFF_FROM_ON(attr)	\
-    (((attr) & TXT_ATTR_ON_MASK) << TXT_ATTR_OFF_ON_SHIFT)
-#define TXT_ATTR_ON_FROM_OFF(attr)	\
-    (((attr) & TXT_ATTR_OFF_MASK) >> TXT_ATTR_OFF_ON_SHIFT)
+#define TXT_ATTR_ALL  0x001F
+
 /*
  * Indicates to zle_refresh.c that the character entry is an
  * index into the list of multiword symbols.
@@ -2707,7 +2690,7 @@ struct ttyinfo {
 #define TXT_MULTIWORD_MASK  0x0400
 
 /* used when, e.g an invalid colour is specified */
-#define TXT_ERROR 0x0800
+#define TXT_ERROR 0xF00000F000000800
 
 /* Mask for colour to use in foreground */
 #define TXT_ATTR_FG_COL_MASK     0x000000FFFFFF0000
@@ -2723,26 +2706,19 @@ struct ttyinfo {
 /* Flag to indicate that background is a 24-bit colour */
 #define TXT_ATTR_BG_24BIT        0x8000
 
-/* Things to turn on, including values for the colour elements */
-#define TXT_ATTR_ON_VALUES_MASK	\
-    (TXT_ATTR_ON_MASK|TXT_ATTR_FG_COL_MASK|TXT_ATTR_BG_COL_MASK|\
-     TXT_ATTR_FG_24BIT|TXT_ATTR_BG_24BIT)
-
 /* Mask out everything to do with setting a foreground colour */
-#define TXT_ATTR_FG_ON_MASK \
+#define TXT_ATTR_FG_MASK \
     (TXTFGCOLOUR|TXT_ATTR_FG_COL_MASK|TXT_ATTR_FG_24BIT)
 
 /* Mask out everything to do with setting a background colour */
-#define TXT_ATTR_BG_ON_MASK \
+#define TXT_ATTR_BG_MASK \
     (TXTBGCOLOUR|TXT_ATTR_BG_COL_MASK|TXT_ATTR_BG_24BIT)
 
 /* Mask out everything to do with activating colours */
-#define TXT_ATTR_COLOUR_ON_MASK			\
-    (TXT_ATTR_FG_ON_MASK|TXT_ATTR_BG_ON_MASK)
+#define TXT_ATTR_COLOUR_MASK \
+    (TXT_ATTR_FG_MASK|TXT_ATTR_BG_MASK)
 
-#define txtchangeisset(T,X)	((T) & (X))
 #define txtchangeget(T,A)	(((T) & A ## _MASK) >> A ## _SHIFT)
-#define txtchangeset(T, X, Y)	((void)(T && (*T &= ~(Y), *T |= (X))))
 
 /*
  * For outputting sequences to change colour: specify foreground
@@ -2750,7 +2726,6 @@ struct ttyinfo {
  */
 #define COL_SEQ_FG	(0)
 #define COL_SEQ_BG	(1)
-#define COL_SEQ_COUNT	(2)
 
 struct color_rgb {
     unsigned int red, green, blue;
@@ -2766,11 +2741,7 @@ enum {
     /* Raw output: use stdout rather than shout */
     TSC_RAW = 0x0001,
     /* Output to current prompt buffer: only used when assembling prompt */
-    TSC_PROMPT = 0x0002,
-    /* Mask to get the output mode */
-    TSC_OUTPUT_MASK = 0x0003,
-    /* Change needs reset of other attributes */
-    TSC_DIRTY = 0x0004
+    TSC_PROMPT = 0x0002
 };
 
 /****************************************/
diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst
index 6879e6fd1..a0abb7e1d 100644
--- a/Test/D01prompt.ztst
+++ b/Test/D01prompt.ztst
@@ -258,6 +258,19 @@
   fi
 0:Equivalence of terminal colour settings (background colour)
 
+  A1=${(%):-%s}
+  A2=${(%):-%u}
+  A3=${(%):-%s%u%s}
+  [[ $A3 = $A1$A2 && -n $A1 && -n $A2 ]]
+0:Attribute optimisation - preserve initial disabling of attribute but drop useless later one
+
+  : ${(%):-%K{blue}}
+  A1="${(%):-%b}x"
+  : ${(%):-%k}
+  A2="${(%):-%b}x"
+  [[ $A1 = $A2 && -n $A1 && -n $A2 ]]
+0:Don't restore attributes from earlier substitution after disabling bold
+
  (RPS1=foo; echo $RPS1 $RPROMPT)
  (RPS2=bar; echo $RPS2 $RPROMPT2)
 -fD:RPS1 and RPROMPT are aliases (regression from 5.0.6) (workers/49600)
diff --git a/Test/X04zlehighlight.ztst b/Test/X04zlehighlight.ztst
index f84c02505..6d9ca4a48 100644
--- a/Test/X04zlehighlight.ztst
+++ b/Test/X04zlehighlight.ztst
@@ -79,7 +79,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:region highlight - standout overlapping on other region_highlight entry
->0m27m24mtr7mu27me word2 word3
+>0m27m24mtr7mu0me word2 word3
 
   zpty_start
   zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=green" ); }'
@@ -90,7 +90,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:basic region_highlight with 8 colors
->0m27m24mCDE|32|trueCDE|39|
+>0m27m24mCDE|32|true0m
 
   zpty_start
   zpty_input 'rh_widget() { region_highlight+=( "0 4 fg=green memo=someplugin" ); typeset -p region_highlight }'
@@ -145,7 +145,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:basic region_highlight with true-color (hex-triplets)
->0m27m24m38;2;4;8;16mtrueCDE|39|
+>0m27m24m38;2;4;8;16mtrue0m
 
   zpty_start
   zpty_input 'zmodload zsh/nearcolor'
@@ -157,7 +157,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:basic region_highlight with near-color (hex-triplets at input)
->0m27m24mCDE|3232|trueCDE|39|
+>0m27m24mCDE|3232|true0m
 
   zpty_start
   zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=green" ); rh2; }'
@@ -169,7 +169,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:overlapping region_highlight with 8 colors
->0m27m24mCDE|32|tCDE|31|rCDE|39|CDE|32|ueCDE|39|
+>0m27m24mCDE|32|tCDE|31|rCDE|32|ue0m
 
   zpty_start
   zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=#00cc00" ); rh2; }'
@@ -181,7 +181,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:overlapping region_highlight with true-color
->0m27m24m38;2;0;204;0mt38;2;204;0;0mrCDE|39|38;2;0;204;0mueCDE|39|
+>0m27m24m38;2;0;204;0mt38;2;204;0;0mr38;2;0;204;0mue0m
 
   zpty_start
   zpty_input 'zmodload zsh/nearcolor'
@@ -194,7 +194,7 @@
   zpty_line 1 p       # the line of interest, preserving escapes ("p")
   zpty_stop
 0:overlapping region_highlight with near-color (hex-triplets at input)
->0m27m24mCDE|340|tCDE|3160|rCDE|39|CDE|340|ueCDE|39|
+>0m27m24mCDE|340|tCDE|3160|rCDE|340|ue0m
 
   zpty_start
   zpty_input 'f () { zle clear-screen; zle g -f nolast; BUFFER=": ${(q)LASTWIDGET}" }; zle -N f'