about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-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
5 files changed, 598 insertions, 373 deletions
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 */
 /****************************************/