about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--Doc/Zsh/prompt.yo14
-rw-r--r--Doc/Zsh/zle.yo7
-rw-r--r--Src/Zle/zle_refresh.c341
-rw-r--r--Src/init.c3
-rw-r--r--Src/prompt.c559
-rw-r--r--Src/watch.c16
-rw-r--r--Src/zsh.h52
8 files changed, 622 insertions, 376 deletions
diff --git a/ChangeLog b/ChangeLog
index fd8579397..db709af34 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2008-05-09  Peter Stephenson  <pws@csr.com>
+
+	* 24986: Doc/Zsh/prompt.yo, Doc/Zsh/zle.yo, Src/init.c,
+	Src/prompt.c, Src/watch.c, Src/zsh.h, Src/Zle/zle_refresh.c:
+	prompt escapes for colouring.
+
 2008-05-08  Peter Stephenson  <pws@csr.com>
 
 	* unposted: Src/modules.c: bad arguments to error message.
diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo
index 29761140e..8aaa4d4f4 100644
--- a/Doc/Zsh/prompt.yo
+++ b/Doc/Zsh/prompt.yo
@@ -183,6 +183,20 @@ Start (stop) underline mode.
 item(tt(%S) LPAR()tt(%s)RPAR())(
 Start (stop) standout mode.
 )
+item(tt(%F) LPAR()tt(%f)RPAR())(
+Start (stop) using a different foreground colour, if supported
+by the terminal.  The colour may be specified two ways: either
+as a numeric argument, as normal, or by a sequence in braces
+following the tt(%F), for example tt(%F{red}).  In the latter case
+the values allowed are as described for the tt(fg) tt(zle_highlight)
+attribute;
+ifzman(see em(Character Highlighting) in zmanref(zshzle))\
+ifnzman(noderef(Character Highlighting)).
+)
+item(tt(%K) LPAR()tt(%k)RPAR())(
+Start (stop) using a different bacKground colour.  The syntax is
+identical to that for tt(%F) and tt(%f).
+)
 item(tt(%{)...tt(%}))(
 Include a string as a literal escape sequence.
 The string within the braces should not change the cursor
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index b7b4676a7..9f2eec388 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -2182,9 +2182,10 @@ Not all terminals support this and, of those that do, not all provide
 facilities to test the support, hence the user should decide based on the
 terminal type.  Most terminals support the colours tt(black), tt(red),
 tt(green), tt(yellow), tt(blue), tt(magenta), tt(cyan) and tt(white),
-which can be set by name.  Abbreviations are allowed; tt(b) or tt(bl)
-selects black.  Some terminals may generate additional colours if the
-tt(bold) attribute is also present.
+which can be set by name.  In addition. tt(default) may be used to
+set the terminal's default foreground colour.  Abbreviations are allowed;
+tt(b) or tt(bl) selects black.  Some terminals may generate additional
+colours if the tt(bold) attribute is also present.
 
 On recent terminals and on systems with an up-to-date terminal database the
 number of colours supported may be tested by with the command `tt(echotc
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index 6127c1248..dd8c26079 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -327,232 +327,6 @@ static const REFRESH_ELEMENT zr_start_ellipsis[] = {
 #define ZR_START_ELLIPSIS_SIZE	\
     ((int)(sizeof(zr_start_ellipsis)/sizeof(zr_start_ellipsis[0])))
 
-/* Defines standard ANSI colour names in index order */
-static const char *ansi_colours[] = {
-    "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
-    NULL
-};
-
-/* Defines the available types of highlighting */
-struct highlight {
-    const char *name;
-    int mask_on;
-    int mask_off;
-};
-
-static const struct highlight highlights[] = {
-    { "none", 0, TXT_ATTR_ON_MASK },
-    { "bold", TXTBOLDFACE, 0 },
-    { "standout", TXTSTANDOUT, 0 },
-    { "underline", TXTUNDERLINE, 0 },
-    { NULL, 0, 0 }
-};
-
-/* Structure and array for holding special colour terminal sequences */
-
-/* Start of escape sequence for foreground colour */
-#define TC_COL_FG_START	"\033[3"
-/* End of escape sequence for foreground colour */
-#define TC_COL_FG_END	"m"
-/* Code to reset foreground colour */
-#define TC_COL_FG_DEFAULT	"9"
-
-/* Start of escape sequence for background colour */
-#define TC_COL_BG_START	"\033[4"
-/* End of escape sequence for background colour */
-#define TC_COL_BG_END	"m"
-/* Code to reset background colour */
-#define TC_COL_BG_DEFAULT	"9"
-
-struct colour_sequences {
-    char *start;		/* Escape sequence start */
-    char *end;			/* Escape sequence terminator */
-    char *def;			/* Code to reset default colour */
-};
-struct colour_sequences fg_bg_sequences[2];
-
-#define COL_SEQ_FG	(0)
-#define COL_SEQ_BG	(1)
-#define COL_SEQ_COUNT	(2)
-
-/*
- * We need a buffer for colour sequence compostion.  It may
- * vary depending on the sequences set.  However, it's inefficient
- * allocating it separately every time we send a colour sequence,
- * so do it once per refresh.
- */
-static char *colseq_buf;
-
-static void
-set_default_colour_sequences(void)
-{
-    fg_bg_sequences[COL_SEQ_FG].start = ztrdup(TC_COL_FG_START);
-    fg_bg_sequences[COL_SEQ_FG].end = ztrdup(TC_COL_FG_END);
-    fg_bg_sequences[COL_SEQ_FG].def = ztrdup(TC_COL_FG_DEFAULT);
-
-    fg_bg_sequences[COL_SEQ_BG].start = ztrdup(TC_COL_BG_START);
-    fg_bg_sequences[COL_SEQ_BG].end = ztrdup(TC_COL_BG_END);
-    fg_bg_sequences[COL_SEQ_BG].def = ztrdup(TC_COL_BG_DEFAULT);
-}
-
-static void
-free_colour_sequences(void)
-{
-    int i;
-
-    for (i = 0; i < COL_SEQ_COUNT; i++) {
-	zsfree(fg_bg_sequences[i].start);
-	zsfree(fg_bg_sequences[i].end);
-	zsfree(fg_bg_sequences[i].def);
-    }
-}
-
-/*
- * Return index of ANSI colour for which *teststrp is an abbreviation.
- * Any non-alphabetic character ends the abbreviation.
- */
-
-static int
-match_colour(const char **teststrp)
-{
-    const char *teststr = *teststrp, *end, **cptr;
-    int len;
-
-    for (end = teststr; ialpha(*end); end++)
-	;
-    len = end - teststr;
-    *teststrp = end;
-
-    for (cptr = ansi_colours; *cptr; cptr++) {
-	if (!strncmp(teststr, *cptr, len))
-	    return cptr - ansi_colours;
-    }
-
-    return -1;
-}
-
-static void
-set_colour_code(char *str, char **var)
-{
-    char *keyseq;
-    int len;
-
-    zsfree(*var);
-    keyseq = getkeystring(str, &len, GETKEYS_BINDKEY, NULL);
-    *var = metafy(keyseq, len, META_DUP);
-}
-
-
-/*
- * Match a set of highlights in the given teststr.
- * Set *on_var to reflect the values found.
- */
-
-static void
-match_highlight(const char *teststr, int *on_var)
-{
-    int found = 1;
-
-    *on_var = 0;
-    while (found && *teststr) {
-	const struct highlight *hl;
-
-	found = 0;
-	if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
-	    int is_fg = (teststr[0] == 'f');
-	    int colour, shft, on, named, tc;
-
-	    teststr += 3;
-	    if ((named = ialpha(*teststr)))
-		colour = match_colour(&teststr);
-	    else
-		colour = (int)zstrtol(teststr, (char **)&teststr, 10);
-	    if (*teststr == ',')
-		teststr++;
-	    else if (*teststr)
-		break;
-	    found = 1;
-	    /* skip out of range colours but keep scanning attributes */
-	    if (colour < 0 || colour >= 256)
-		continue;
-	    if (is_fg) {
-		shft = TXT_ATTR_FG_COL_SHIFT;
-		on = TXTFGCOLOUR;
-		tc = TCFGCOLOUR;
-	    } else {
-		shft = TXT_ATTR_BG_COL_SHIFT;
-		on = TXTBGCOLOUR;
-		tc = TCBGCOLOUR;
-	    }
-	    /*
-	     * Try termcap for numbered characters if posible.
-	     * Don't for named characters, since our best bet
-	     * of getting the names right is with ANSI sequences.
-	     */
-	    if (!named && tccan(tc)) {
-		if (tccolours >= 0 && colour >= tccolours) {
-		    /*
-		     * Out of range of termcap colours.
-		     * Can we assume ANSI colours work?
-		     */
-		    if (colour > 7)
-			continue; /* No. */
-		} else {
-		    /*
-		     * We can handle termcap colours and the number
-		     * is in range, so use termcap.
-		     */
-		    *on_var |= is_fg ? TXT_ATTR_FG_TERMCAP :
-			TXT_ATTR_BG_TERMCAP;
-		}
-	    }
-	    *on_var |= on | (colour << shft);
-	} else {
-	    for (hl = highlights; hl->name; hl++) {
-		if (strpfx(hl->name, teststr)) {
-		    const char *val = teststr + strlen(hl->name);
-
-		    if (*val == ',')
-			val++;
-		    else if (*val)
-			break;
-
-		    *on_var |= hl->mask_on;
-		    *on_var &= ~hl->mask_off;
-		    teststr = val;
-		    found = 1;
-		}
-	    }
-	}
-    }
-}
-
-
-/* Allocate buffer for colour code composition */
-
-static void
-set_colseq_buf(void)
-{
-    int lenfg, lenbg, len;
-
-    lenfg = strlen(fg_bg_sequences[COL_SEQ_FG].def);
-    /* always need 1 character for non-default code */
-    if (lenfg < 1)
-	lenfg = 1;
-    lenfg += strlen(fg_bg_sequences[COL_SEQ_FG].start) +
-	strlen(fg_bg_sequences[COL_SEQ_FG].end);
-
-    lenbg = strlen(fg_bg_sequences[COL_SEQ_BG].def);
-    /* always need 1 character for non-default code */
-    if (lenbg < 1)
-	lenbg = 1;
-    lenbg += strlen(fg_bg_sequences[COL_SEQ_BG].start) +
-	strlen(fg_bg_sequences[COL_SEQ_BG].end);
-
-    len = lenfg > lenbg ? lenfg : lenbg;
-    colseq_buf = (char *)zalloc(len+1);
-}
-
 /*
  * Parse the variable zle_highlight to decide how to highlight characters
  * and regions.  Set defaults for anything not explicitly covered.
@@ -599,18 +373,6 @@ zle_set_highlight(void)
 	    } else if (strpfx("isearch:", *atrs)) {
 		match_highlight(*atrs + 8, &(region_highlights[1].atr));
 		isearch_atr_on_set = 1;
-	    } else if (strpfx("fg_start_code:", *atrs)) {
-		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_FG].start);
-	    } else if (strpfx("fg_default_code:", *atrs)) {
-		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_FG].def);
-	    } else if (strpfx("fg_end_code:", *atrs)) {
-		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_FG].end);
-	    } else if (strpfx("bg_start_code:", *atrs)) {
-		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_BG].start);
-	    } else if (strpfx("bg_default_code:", *atrs)) {
-		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_BG].def);
-	    } else if (strpfx("bg_end_code:", *atrs)) {
-		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_BG].end);
 	    }
 	}
     }
@@ -623,7 +385,7 @@ zle_set_highlight(void)
     if (!isearch_atr_on_set)
 	region_highlights[1].atr = TXTUNDERLINE;
 
-    set_colseq_buf();
+    allocate_colour_buffer();
 }
 
 
@@ -631,10 +393,7 @@ zle_set_highlight(void)
 static void
 zle_free_highlight(void)
 {
-    DPUTS(!colseq_buf, "Freeing colour sequence buffer without alloc");
-    /* Free buffer for colour code composition */
-    free(colseq_buf);
-    colseq_buf = NULL;
+    free_colour_buffer();
 }
 
 /*
@@ -663,21 +422,12 @@ get_region_highlight(UNUSED(Param pm))
 	 arrsize--;
 	 rhp++, arrp++) {
 	char digbuf1[DIGBUFSIZE], digbuf2[DIGBUFSIZE];
-	int atrlen = 0, alloclen, done1;
-	const struct highlight *hp;
+	int atrlen = 0, alloclen;
 
 	sprintf(digbuf1, "%d", rhp->start);
 	sprintf(digbuf2, "%d", rhp->end);
 
-	for (hp = highlights; hp->name; hp++) {
-	    if (hp->mask_on & rhp->atr) {
-		if (atrlen)
-		    atrlen++; /* comma */
-		atrlen += strlen(hp->name);
-	    }
-	}
-	if (atrlen == 0)
-	    atrlen = 4; /* none */
+	atrlen = output_highlight(rhp->atr, NULL);
 	alloclen = atrlen + strlen(digbuf1) + strlen(digbuf2) +
 	    3; /* 2 spaces, 1 0 */
 	if (rhp->flags & ZRH_PREDISPLAY)
@@ -693,17 +443,7 @@ get_region_highlight(UNUSED(Param pm))
 	sprintf(*arrp, "%s%s %s ",
 		(rhp->flags & ZRH_PREDISPLAY) ? "P" : "",
 		digbuf1, digbuf2);
-	if (atrlen) {
-	    for (hp = highlights, done1 = 0; hp->name; hp++) {
-		if (hp->mask_on & rhp->atr) {
-		    if (done1)
-			strcat(*arrp, ",");
-		    strcat(*arrp, hp->name);
-		    done1 = 1;
-		}
-	    }
-	} else
-	    strcat(*arrp, "none");
+	(void)output_highlight(rhp->atr, *arrp + strlen(*arrp));
     }
     *arrp = '\0';
     return retarr;
@@ -1147,56 +887,6 @@ snextline(Rparams rpms)
 }
 
 
-static void
-setcolourattribute(int colour, int fg_bg, int tc, int def,
-		   int use_termcap)
-{
-    char *ptr;
-    int do_free;
-
-    if ((do_free = (colseq_buf == NULL))) {
-	/* This can happen when moving the cursor in trashzle() */
-	set_colseq_buf();
-    }
-    /*
-     * If we're not restoring the default, and either have a
-     * colour value that is too large for ANSI, or have been told
-     * to use the termcap sequence, try to use the termcap sequence.
-     *
-     * We have already sanitised the values we allow from the
-     * highlighting variables, so much of this shouldn't be
-     * necessary at this point, but we might as well be safe.
-     */
-    if (!def && (colour > 7 || use_termcap)) {
-	/*
-	 * We can if it's available, and either we couldn't get
-	 * the maximum number of colours, or the colour is in range.
-	 */
-	if (tccan(tc) && (tccolours < 0 || colour < tccolours))
-	    tcoutarg(tc, colour);
-	/* for 0 to 7 assume standard ANSI works, otherwise it won't. */
-	if (colour > 7)
-	    return;
-    }
-
-    strcpy(colseq_buf, fg_bg_sequences[fg_bg].start);
-
-    ptr = colseq_buf + strlen(colseq_buf);
-    if (def) {
-	strcpy(ptr, fg_bg_sequences[fg_bg].def);
-	while (*ptr)
-	    ptr++;
-    } else
-	*ptr++ = colour + '0';
-    strcpy(ptr, fg_bg_sequences[fg_bg].end);
-    tputs(colseq_buf, 1, putshout);
-
-    if (do_free) {
-	free(colseq_buf);
-	colseq_buf = NULL;
-    }
-}
-
 /**/
 static void
 settextattributes(int atr)
@@ -1213,18 +903,10 @@ settextattributes(int atr)
 	tsetcap(TCSTANDOUTBEG, 0);
     if (txtchangeisset(atr, TXTUNDERLINE))
 	tsetcap(TCUNDERLINEBEG, 0);
-    if (txtchangeisset(atr, TXTFGCOLOUR|TXTNOFGCOLOUR)) {
-	setcolourattribute(txtchangeget(atr, TXT_ATTR_FG_COL),
-			   COL_SEQ_FG, TCFGCOLOUR,
-			   txtchangeisset(atr, TXTNOFGCOLOUR),
-			   txtchangeisset(atr, TXT_ATTR_FG_TERMCAP));
-    }
-    if (txtchangeisset(atr, TXTBGCOLOUR|TXTNOBGCOLOUR)) {
-	setcolourattribute(txtchangeget(atr, TXT_ATTR_BG_COL),
-			   COL_SEQ_BG, TCBGCOLOUR,
-			   txtchangeisset(atr, TXTNOBGCOLOUR),
-			   txtchangeisset(atr, TXT_ATTR_BG_TERMCAP));
-    }
+    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
@@ -1433,7 +1115,7 @@ zrefresh(void)
 	tsetcap(TCSTANDOUTEND, 0);
 	tsetcap(TCUNDERLINEEND, 0);
 	/* cheat on attribute unset */
-	txtunset(TXTBOLDFACE|TXTSTANDOUT|TXTUNDERLINE|TXTDIRTY);
+	txtunset(TXTBOLDFACE|TXTSTANDOUT|TXTUNDERLINE);
 
 	if (trashedzle)
 	    reexpandprompt(); 
@@ -2942,7 +2624,6 @@ singmoveto(int pos)
 void
 zle_refresh_boot(void)
 {
-    set_default_colour_sequences();
 }
 
 /* Provided for unloading the module in a modular fashion */
@@ -2956,6 +2637,4 @@ zle_refresh_finish(void)
     if (region_highlights)
 	zfree(region_highlights,
 	      sizeof(struct region_highlight) * n_region_highlights);
-
-    free_colour_sequences();
 }
diff --git a/Src/init.c b/Src/init.c
index 253a689b6..7707b825e 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -909,6 +909,9 @@ setupvals(void)
     for (i = 0; i < 10; i++)
 	if (close_fds[i])
 	    close(i);
+
+    /* Colour sequences for outputting colours in prompts and zle */
+    set_default_colour_sequences();
 }
 
 /* Initialize signal handling */
diff --git a/Src/prompt.c b/Src/prompt.c
index 319759874..568c2e5ca 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -423,38 +423,78 @@ putpromptchar(int doprint, int endchar)
 	    case 'S':
 		txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT);
 		txtset(TXTSTANDOUT);
-		tsetcap(TCSTANDOUTBEG, 1);
+		tsetcap(TCSTANDOUTBEG, TSC_PROMPT);
 		break;
 	    case 's':
 		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
-		txtset(TXTDIRTY);
 		txtunset(TXTSTANDOUT);
-		tsetcap(TCSTANDOUTEND, 1);
+		tsetcap(TCSTANDOUTEND, TSC_PROMPT|TSC_DIRTY);
 		break;
 	    case 'B':
 		txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE);
-		txtset(TXTDIRTY);
 		txtset(TXTBOLDFACE);
-		tsetcap(TCBOLDFACEBEG, 1);
+		tsetcap(TCBOLDFACEBEG, TSC_PROMPT|TSC_DIRTY);
 		break;
 	    case 'b':
 		txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE);
 		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
 		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
-		txtset(TXTDIRTY);
 		txtunset(TXTBOLDFACE);
-		tsetcap(TCALLATTRSOFF, 1);
+		tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
 		break;
 	    case 'U':
 		txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE);
 		txtset(TXTUNDERLINE);
-		tsetcap(TCUNDERLINEBEG, 1);
+		tsetcap(TCUNDERLINEBEG, TSC_PROMPT);
 		break;
 	    case 'u':
 		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
-		txtset(TXTDIRTY);
 		txtunset(TXTUNDERLINE);
-		tsetcap(TCUNDERLINEEND, 1);
+		tsetcap(TCUNDERLINEEND, TSC_PROMPT|TSC_DIRTY);
+		break;
+	    case 'F':
+		if (fm[1] == '{') {
+		    fm += 2;
+		    arg = match_colour((const char **)&fm, 1, 0);
+		    if (*fm != '}')
+			fm--;
+		} else
+		    arg = match_colour(NULL, 1, arg);
+		if (arg >= 0 && !(arg & TXTNOFGCOLOUR)) {
+		    txtchangeset(arg & TXT_ATTR_FG_ON_MASK,
+				 TXTNOFGCOLOUR);
+		    txtset(arg & TXT_ATTR_FG_ON_MASK);
+		    set_colour_attribute(arg, COL_SEQ_FG, TSC_PROMPT);
+		    break;
+		}
+		/* else FALLTHROUGH */
+		break;
+	    case 'f':
+		txtchangeset(TXTNOFGCOLOUR, TXT_ATTR_FG_ON_MASK);
+		txtunset(TXT_ATTR_FG_ON_MASK);
+		set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_PROMPT);
+		break;
+	    case 'K':
+		if (fm[1] == '{') {
+		    fm += 2;
+		    arg = match_colour((const char **)&fm, 0, 0);
+		    if (*fm != '}')
+			fm--;
+		} else
+		    arg = match_colour(NULL, 0, arg);
+		if (arg >= 0 && !(arg & TXTNOBGCOLOUR)) {
+		    txtchangeset(arg & TXT_ATTR_BG_ON_MASK,
+				 TXTNOBGCOLOUR);
+		    txtset(arg & TXT_ATTR_BG_ON_MASK);
+		    set_colour_attribute(arg, COL_SEQ_BG, TSC_PROMPT);
+		    break;
+		}
+		/* else FALLTHROUGH */
+		break;
+	    case 'k':
+		txtchangeset(TXTNOBGCOLOUR, TXT_ATTR_BG_ON_MASK);
+		txtunset(TXT_ATTR_BG_ON_MASK);
+		set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_PROMPT);
 		break;
 	    case '[':
 		if (idigit(*++fm))
@@ -610,7 +650,7 @@ putpromptchar(int doprint, int endchar)
 		    stradd(psvar[arg - 1]);
 		break;
 	    case 'E':
-                tsetcap(TCCLEAREOL, 1);
+                tsetcap(TCCLEAREOL, TSC_PROMPT);
 		break;
 	    case '^':
 		if (cmdsp) {
@@ -816,18 +856,19 @@ stradd(char *d)
 
 /**/
 mod_export void
-tsetcap(int cap, int flag)
+tsetcap(int cap, int flags)
 {
     if (tccan(cap) && !isset(SINGLELINEZLE) &&
         !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
-	switch(flag) {
-	case -1:
+	switch (flags & TSC_OUTPUT_MASK) {
+	case TSC_RAW:
 	    tputs(tcstr[cap], 1, putraw);
 	    break;
 	case 0:
+	default:
 	    tputs(tcstr[cap], 1, putshout);
 	    break;
-	case 1:
+	case TSC_PROMPT:
 	    if (!dontcount) {
 		addbufspc(1);
 		*bp++ = Inpar;
@@ -850,14 +891,14 @@ tsetcap(int cap, int flag)
 	    break;
 	}
 
-	if (txtisset(TXTDIRTY)) {
-	    txtunset(TXTDIRTY);
+	if (flags & TSC_DIRTY) {
+	    flags &= ~TSC_DIRTY;
 	    if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
-		tsetcap(TCBOLDFACEBEG, flag);
+		tsetcap(TCBOLDFACEBEG, flags);
 	    if (txtisset(TXTSTANDOUT))
-		tsetcap(TCSTANDOUTBEG, flag);
+		tsetcap(TCSTANDOUTBEG, flags);
 	    if (txtisset(TXTUNDERLINE))
-		tsetcap(TCUNDERLINEBEG, flag);
+		tsetcap(TCUNDERLINEBEG, flags);
 	}
     }
 }
@@ -1361,3 +1402,481 @@ cmdpop(void)
     } else
 	cmdsp--;
 }
+
+
+/*****************************************************************************
+ * Utilities dealing with colour and other forms of highlighting.
+ *
+ * These are shared by prompts and by zle, so it's easiest to have them
+ * in the main shell.
+ *****************************************************************************/
+
+/* Defines standard ANSI colour names in index order */
+static const char *ansi_colours[] = {
+    "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
+    "default", NULL
+};
+
+/* Defines the available types of highlighting */
+struct highlight {
+    const char *name;
+    int mask_on;
+    int mask_off;
+};
+
+static const struct highlight highlights[] = {
+    { "none", 0, TXT_ATTR_ON_MASK },
+    { "bold", TXTBOLDFACE, 0 },
+    { "standout", TXTSTANDOUT, 0 },
+    { "underline", TXTUNDERLINE, 0 },
+    { NULL, 0, 0 }
+};
+
+/*
+ * Return index of ANSI colour for which *teststrp is an abbreviation.
+ * Any non-alphabetic character ends the abbreviation.
+ * 8 is the special value for default (note this is *not* the
+ * right sequence for default which is typically 9).
+ * -1 is failure.
+ */
+
+static int
+match_named_colour(const char **teststrp)
+{
+    const char *teststr = *teststrp, *end, **cptr;
+    int len;
+
+    for (end = teststr; ialpha(*end); end++)
+	;
+    len = end - teststr;
+    *teststrp = end;
+
+    for (cptr = ansi_colours; *cptr; cptr++) {
+	if (!strncmp(teststr, *cptr, len))
+	    return cptr - ansi_colours;
+    }
+
+    return -1;
+}
+
+/*
+ * 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.
+ */
+
+/**/
+static int
+match_colour(const char **teststrp, int is_fg, int colour)
+{
+    int shft, on, named, tc;
+
+    if (teststrp) {
+	if ((named = ialpha(**teststrp))) {
+	    colour = match_named_colour(teststrp);
+	    if (colour == 8) {
+		/* default */
+		return is_fg ? TXTNOFGCOLOUR : TXTNOBGCOLOUR;
+	    }
+	}
+	else
+	    colour = (int)zstrtol(*teststrp, (char **)teststrp, 10);
+    }
+    if (colour < 0 || colour >= 256)
+	return -1;
+    if (is_fg) {
+	shft = TXT_ATTR_FG_COL_SHIFT;
+	on = TXTFGCOLOUR;
+	tc = TCFGCOLOUR;
+    } else {
+	shft = TXT_ATTR_BG_COL_SHIFT;
+	on = TXTBGCOLOUR;
+	tc = TCBGCOLOUR;
+    }
+    /*
+     * Try termcap for numbered characters if posible.
+     * Don't for named characters, since our best bet
+     * of getting the names right is with ANSI sequences.
+     */
+    if (!named && tccan(tc)) {
+	if (tccolours >= 0 && colour >= tccolours) {
+	    /*
+	     * Out of range of termcap colours.
+	     * Can we assume ANSI colours work?
+	     */
+	    if (colour > 7)
+		return -1; /* No. */
+	} else {
+	    /*
+	     * We can handle termcap colours and the number
+	     * is in range, so use termcap.
+	     */
+	    on |= is_fg ? TXT_ATTR_FG_TERMCAP :
+		TXT_ATTR_BG_TERMCAP;
+	}
+    }
+    return on | (colour << shft);
+}
+
+/*
+ * Match a set of highlights in the given teststr.
+ * Set *on_var to reflect the values found.
+ */
+
+/**/
+mod_export void
+match_highlight(const char *teststr, int *on_var)
+{
+    int found = 1;
+
+    *on_var = 0;
+    while (found && *teststr) {
+	const struct highlight *hl;
+
+	found = 0;
+	if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
+	    int is_fg = (teststr[0] == 'f'), atr;
+
+	    teststr += 3;
+	    atr = match_colour(&teststr, is_fg, 0);
+	    if (*teststr == ',')
+		teststr++;
+	    else if (*teststr)
+		break;
+	    found = 1;
+	    /* skip out of range colours but keep scanning attributes */
+	    if (atr >= 0)
+		*on_var |= atr;
+	} else {
+	    for (hl = highlights; hl->name; hl++) {
+		if (strpfx(hl->name, teststr)) {
+		    const char *val = teststr + strlen(hl->name);
+
+		    if (*val == ',')
+			val++;
+		    else if (*val)
+			break;
+
+		    *on_var |= hl->mask_on;
+		    *on_var &= ~hl->mask_off;
+		    teststr = val;
+		    found = 1;
+		}
+	    }
+	}
+    }
+}
+
+/*
+ * Count or output a string for colour information: used
+ * by output_highlight().
+ */
+
+static int
+output_colour(int colour, int fg_bg, int use_tc, char *buf)
+{
+    int atrlen = 3, len;
+    char *ptr = buf;
+    if (buf) {
+	strcpy(ptr, fg_bg == COL_SEQ_FG ? "fg=" : "bg=");
+	ptr += 3;
+    }
+    /* colour should only be > 7 if using termcap but let's be safe */
+    if (use_tc || colour > 7) {
+	char digbuf[DIGBUFSIZE];
+	sprintf(digbuf, "%d", colour);
+	len = strlen(digbuf);
+	atrlen += len;
+	if (buf)
+	    strcpy(ptr, digbuf);
+    } else {
+	len = strlen(ansi_colours[colour]);
+	atrlen += len;
+	if (buf)
+	    strcpy(ptr, ansi_colours[colour]);
+    }
+
+    return atrlen;
+}
+
+/*
+ * Count the length needed for outputting highlighting information
+ * as a string based on the bits for the attributes.
+ *
+ * If buf is not NULL, output the strings into the buffer, too.
+ * As conventional with strings, the allocated length should be
+ * at least the returned value plus 1 for the NUL byte.
+ */
+
+/**/
+mod_export int
+output_highlight(int atr, char *buf)
+{
+    const struct highlight *hp;
+    int atrlen = 0, len;
+    char *ptr = buf;
+
+    if (atr & TXTFGCOLOUR) {
+	len = output_colour(txtchangeget(atr, TXT_ATTR_FG_COL),
+			    COL_SEQ_FG,
+			    (atr & TXT_ATTR_FG_TERMCAP),
+			    ptr);
+	atrlen += len;
+	if (buf)
+	    ptr += len;
+    }
+    if (atr & TXTBGCOLOUR) {
+	if (atrlen) {
+	    atrlen++;
+	    if (buf) {
+		strcpy(ptr, ",");
+		ptr++;
+	    }
+	}
+	len = output_colour(txtchangeget(atr, TXT_ATTR_BG_COL),
+			    COL_SEQ_BG,
+			    (atr & TXT_ATTR_BG_TERMCAP),
+			    ptr);
+	atrlen += len;
+	if (buf)
+	    ptr += len;
+    }
+    for (hp = highlights; hp->name; hp++) {
+	if (hp->mask_on & atr) {
+	    if (atrlen) {
+		atrlen++;
+		if (buf) {
+		    strcpy(ptr, ",");
+		    ptr++;
+		}
+	    }
+	    len = strlen(hp->name);
+	    atrlen += len;
+	    if (buf) {
+		strcpy(ptr, hp->name);
+		ptr += len;
+	    }
+	}
+    }
+
+    if (atrlen == 0) {
+	if (buf)
+	    strcpy(ptr, "none");
+	return 4;
+    }
+    return atrlen;
+}
+
+/* Structure and array for holding special colour terminal sequences */
+
+/* Start of escape sequence for foreground colour */
+#define TC_COL_FG_START	"\033[3"
+/* End of escape sequence for foreground colour */
+#define TC_COL_FG_END	"m"
+/* Code to reset foreground colour */
+#define TC_COL_FG_DEFAULT	"9"
+
+/* Start of escape sequence for background colour */
+#define TC_COL_BG_START	"\033[4"
+/* End of escape sequence for background colour */
+#define TC_COL_BG_END	"m"
+/* Code to reset background colour */
+#define TC_COL_BG_DEFAULT	"9"
+
+struct colour_sequences {
+    char *start;		/* Escape sequence start */
+    char *end;			/* Escape sequence terminator */
+    char *def;			/* Code to reset default colour */
+};
+struct colour_sequences fg_bg_sequences[2];
+
+/*
+ * We need a buffer for colour sequence compostion.  It may
+ * vary depending on the sequences set.  However, it's inefficient
+ * allocating it separately every time we send a colour sequence,
+ * so do it once per refresh.
+ */
+static char *colseq_buf;
+
+/**/
+void
+set_default_colour_sequences(void)
+{
+    fg_bg_sequences[COL_SEQ_FG].start = ztrdup(TC_COL_FG_START);
+    fg_bg_sequences[COL_SEQ_FG].end = ztrdup(TC_COL_FG_END);
+    fg_bg_sequences[COL_SEQ_FG].def = ztrdup(TC_COL_FG_DEFAULT);
+
+    fg_bg_sequences[COL_SEQ_BG].start = ztrdup(TC_COL_BG_START);
+    fg_bg_sequences[COL_SEQ_BG].end = ztrdup(TC_COL_BG_END);
+    fg_bg_sequences[COL_SEQ_BG].def = ztrdup(TC_COL_BG_DEFAULT);
+}
+
+static void
+set_colour_code(char *str, char **var)
+{
+    char *keyseq;
+    int len;
+
+    zsfree(*var);
+    keyseq = getkeystring(str, &len, GETKEYS_BINDKEY, NULL);
+    *var = metafy(keyseq, len, META_DUP);
+}
+
+/* Allocate buffer for colour code composition */
+
+/**/
+mod_export void
+allocate_colour_buffer(void)
+{
+    char **atrs = getaparam("zle_highlight");
+    int lenfg, lenbg, len;
+
+    if (atrs) {
+	for (; *atrs; atrs++) {
+	    if (strpfx("fg_start_code:", *atrs)) {
+		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_FG].start);
+	    } else if (strpfx("fg_default_code:", *atrs)) {
+		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_FG].def);
+	    } else if (strpfx("fg_end_code:", *atrs)) {
+		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_FG].end);
+	    } else if (strpfx("bg_start_code:", *atrs)) {
+		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_BG].start);
+	    } else if (strpfx("bg_default_code:", *atrs)) {
+		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_BG].def);
+	    } else if (strpfx("bg_end_code:", *atrs)) {
+		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_BG].end);
+	    }
+	}
+    }
+
+    lenfg = strlen(fg_bg_sequences[COL_SEQ_FG].def);
+    /* always need 1 character for non-default code */
+    if (lenfg < 1)
+	lenfg = 1;
+    lenfg += strlen(fg_bg_sequences[COL_SEQ_FG].start) +
+	strlen(fg_bg_sequences[COL_SEQ_FG].end);
+
+    lenbg = strlen(fg_bg_sequences[COL_SEQ_BG].def);
+    /* always need 1 character for non-default code */
+    if (lenbg < 1)
+	lenbg = 1;
+    lenbg += strlen(fg_bg_sequences[COL_SEQ_BG].start) +
+	strlen(fg_bg_sequences[COL_SEQ_BG].end);
+
+    len = lenfg > lenbg ? lenfg : lenbg;
+    colseq_buf = (char *)zalloc(len+1);
+}
+
+/* Free the colour buffer previously allocated. */
+
+/**/
+mod_export void
+free_colour_buffer(void)
+{
+    DPUTS(!colseq_buf, "Freeing colour sequence buffer without alloc");
+    /* Free buffer for colour code composition */
+    free(colseq_buf);
+    colseq_buf = NULL;
+}
+
+/*
+ * Handle outputting of a colour for prompts or zle.
+ * colour is the numeric colour, 0 to 255 (or less if termcap
+ * says fewer are supported).
+ * fg_bg indicates if we're changing the foreground or background.
+ * tc indicates the termcap code to use, if appropriate.
+ * def indicates if we're resetting the default colour.
+ * use_termcap indicates if we should use termcap to output colours.
+ * flags is either 0 or TSC_PROMPT.
+ */
+
+/**/
+mod_export void
+set_colour_attribute(int atr, int fg_bg, int flags)
+{
+    char *ptr;
+    int do_free, is_prompt = (flags & TSC_PROMPT) ? 1 : 0;
+    int colour, tc, def, use_termcap;
+
+    if (fg_bg == COL_SEQ_FG) {
+	colour = txtchangeget(atr, TXT_ATTR_FG_COL);
+	tc = TCFGCOLOUR;
+	def = txtchangeisset(atr, TXTNOFGCOLOUR);
+	use_termcap = txtchangeisset(atr, TXT_ATTR_FG_TERMCAP);
+    } else {
+	colour = txtchangeget(atr, TXT_ATTR_BG_COL);
+	tc = TCBGCOLOUR;
+	def = txtchangeisset(atr, TXTNOBGCOLOUR);
+	use_termcap = txtchangeisset(atr, TXT_ATTR_BG_TERMCAP);
+    }
+
+    /*
+     * If we're not restoring the default, and either have a
+     * colour value that is too large for ANSI, or have been told
+     * to use the termcap sequence, try to use the termcap sequence.
+     *
+     * We have already sanitised the values we allow from the
+     * highlighting variables, so much of this shouldn't be
+     * necessary at this point, but we might as well be safe.
+     */
+    if (!def && (colour > 7 || use_termcap)) {
+	/*
+	 * We can if it's available, and either we couldn't get
+	 * the maximum number of colours, or the colour is in range.
+	 */
+	if (tccan(tc) && (tccolours < 0 || colour < tccolours))
+	{
+	    if (is_prompt)
+	    {
+		if (!dontcount) {
+		    addbufspc(1);
+		    *bp++ = Inpar;
+		}
+		tputs(tgoto(tcstr[tc], colour, colour), 1, putstr);
+		if (!dontcount) {
+		    addbufspc(1);
+		    *bp++ = Outpar;
+		}
+	    } else {
+		tputs(tgoto(tcstr[tc], colour, colour), 1, putshout);
+	    }
+	}
+	/* for 0 to 7 assume standard ANSI works, otherwise it won't. */
+	if (colour > 7)
+	    return;
+    }
+
+    if ((do_free = (colseq_buf == NULL))) {
+	/* This can happen when moving the cursor in trashzle() */
+	allocate_colour_buffer();
+    }
+
+    strcpy(colseq_buf, fg_bg_sequences[fg_bg].start);
+
+    ptr = colseq_buf + strlen(colseq_buf);
+    if (def) {
+	strcpy(ptr, fg_bg_sequences[fg_bg].def);
+	while (*ptr)
+	    ptr++;
+    } else
+	*ptr++ = colour + '0';
+    strcpy(ptr, fg_bg_sequences[fg_bg].end);
+
+    if (is_prompt) {
+	if (!dontcount) {
+	    addbufspc(1);
+	    *bp++ = Inpar;
+	}
+	tputs(colseq_buf, 1, putstr);
+	if (!dontcount) {
+	    addbufspc(1);
+	    *bp++ = Outpar;
+	}
+    } else
+	tputs(colseq_buf, 1, putshout);
+
+    if (do_free)
+	free_colour_buffer();
+}
diff --git a/Src/watch.c b/Src/watch.c
index 402b160fe..5231579f8 100644
--- a/Src/watch.c
+++ b/Src/watch.c
@@ -338,31 +338,27 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
 		    break;
 		case 'S':
 		    txtset(TXTSTANDOUT);
-		    tsetcap(TCSTANDOUTBEG, -1);
+		    tsetcap(TCSTANDOUTBEG, TSC_RAW);
 		    break;
 		case 's':
-		    txtset(TXTDIRTY);
 		    txtunset(TXTSTANDOUT);
-		    tsetcap(TCSTANDOUTEND, -1);
+		    tsetcap(TCSTANDOUTEND, TSC_RAW|TSC_DIRTY);
 		    break;
 		case 'B':
-		    txtset(TXTDIRTY);
 		    txtset(TXTBOLDFACE);
-		    tsetcap(TCBOLDFACEBEG, -1);
+		    tsetcap(TCBOLDFACEBEG, TSC_RAW|TSC_DIRTY);
 		    break;
 		case 'b':
-		    txtset(TXTDIRTY);
 		    txtunset(TXTBOLDFACE);
-		    tsetcap(TCALLATTRSOFF, -1);
+		    tsetcap(TCALLATTRSOFF, TSC_RAW|TSC_DIRTY);
 		    break;
 		case 'U':
 		    txtset(TXTUNDERLINE);
-		    tsetcap(TCUNDERLINEBEG, -1);
+		    tsetcap(TCUNDERLINEBEG, TSC_RAW);
 		    break;
 		case 'u':
-		    txtset(TXTDIRTY);
 		    txtunset(TXTUNDERLINE);
-		    tsetcap(TCUNDERLINEEND, -1);
+		    tsetcap(TCUNDERLINEEND, TSC_RAW|TSC_DIRTY);
 		    break;
 		default:
 		    putchar('%');
diff --git a/Src/zsh.h b/Src/zsh.h
index 2f7c4fefe..13bae0585 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1975,7 +1975,6 @@ struct ttyinfo {
 #define TXTUNDERLINE  0x0004
 #define TXTFGCOLOUR   0x0008
 #define TXTBGCOLOUR   0x0010
-#define TXTDIRTY      0x0020
 
 #define TXT_ATTR_ON_MASK   0x001F
 
@@ -1983,15 +1982,15 @@ struct ttyinfo {
 #define txtset(X)    (txtattrmask |= (X))
 #define txtunset(X)  (txtattrmask &= ~(X))
 
-#define TXTNOBOLDFACE	0x0040
-#define TXTNOSTANDOUT	0x0080
-#define TXTNOUNDERLINE	0x0100
-#define TXTNOFGCOLOUR	0x0200
-#define TXTNOBGCOLOUR	0x0400
+#define TXTNOBOLDFACE	0x0020
+#define TXTNOSTANDOUT	0x0040
+#define TXTNOUNDERLINE	0x0080
+#define TXTNOFGCOLOUR	0x0100
+#define TXTNOBGCOLOUR	0x0200
 
-#define TXT_ATTR_OFF_MASK  0x07C0
+#define TXT_ATTR_OFF_MASK  0x03E0
 /* Bits to shift off right to get on */
-#define TXT_ATTR_OFF_ON_SHIFT 6
+#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)	\
@@ -2000,7 +1999,7 @@ struct ttyinfo {
  * Indicates to zle_refresh.c that the character entry is an
  * index into the list of multiword symbols.
  */
-#define TXT_MULTIWORD_MASK  0x0800
+#define TXT_MULTIWORD_MASK  0x0400
 
 /* Mask for colour to use in foreground */
 #define TXT_ATTR_FG_COL_MASK     0x000FF000
@@ -2021,16 +2020,45 @@ struct ttyinfo {
     (TXT_ATTR_ON_MASK|TXT_ATTR_FG_COL_MASK|TXT_ATTR_BG_COL_MASK|\
      TXT_ATTR_FG_TERMCAP|TXT_ATTR_BG_TERMCAP)
 
+/* Mask out everything to do with setting a foreground colour */
+#define TXT_ATTR_FG_ON_MASK \
+    (TXTFGCOLOUR|TXT_ATTR_FG_COL_MASK|TXT_ATTR_FG_TERMCAP)
+
+/* Mask out everything to do with setting a background colour */
+#define TXT_ATTR_BG_ON_MASK \
+    (TXTBGCOLOUR|TXT_ATTR_BG_COL_MASK|TXT_ATTR_BG_TERMCAP)
+
 /* Mask out everything to do with activating colours */
 #define TXT_ATTR_COLOUR_ON_MASK			\
-    (TXTFGCOLOUR|TXTBGCOLOUR|			\
-     TXT_ATTR_FG_COL_MASK|TXT_ATTR_BG_COL_MASK| \
-     TXT_ATTR_FG_TERMCAP|TXT_ATTR_BG_TERMCAP)
+    (TXT_ATTR_FG_ON_MASK|TXT_ATTR_BG_ON_MASK)
 
 #define txtchangeisset(T,X)	((T) & (X))
 #define txtchangeget(T,A)	(((T) & A ## _MASK) >> A ## _SHIFT)
 #define txtchangeset(X, Y)	(txtchange |= (X), txtchange &= ~(Y))
 
+/*
+ * For outputting sequences to change colour: specify foreground
+ * or background.
+ */
+#define COL_SEQ_FG	(0)
+#define COL_SEQ_BG	(1)
+#define COL_SEQ_COUNT	(2)
+
+/*
+ * Flags to testcap() and set_colour_attribute (which currently only
+ * handles TSC_PROMPT).
+ */
+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
+};
+
 /****************************************/
 /* Definitions for the %_ prompt escape */
 /****************************************/