From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 27047 invoked from network); 9 May 2008 17:31:14 -0000 X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=AWL,BAYES_00 autolearn=ham version=3.2.4 Received: from news.dotsrc.org (HELO a.mx.sunsite.dk) (130.225.247.88) by ns1.primenet.com.au with SMTP; 9 May 2008 17:31:14 -0000 Received-SPF: none (ns1.primenet.com.au: domain at sunsite.dk does not designate permitted sender hosts) Received: (qmail 21737 invoked from network); 9 May 2008 17:31:09 -0000 Received: from sunsite.dk (130.225.247.90) by a.mx.sunsite.dk with SMTP; 9 May 2008 17:31:09 -0000 Received: (qmail 1071 invoked by alias); 9 May 2008 17:31:05 -0000 Mailing-List: contact zsh-workers-help@sunsite.dk; run by ezmlm Precedence: bulk X-No-Archive: yes X-Seq: 24986 Received: (qmail 1049 invoked from network); 9 May 2008 17:31:04 -0000 Received: from bifrost.dotsrc.org (130.225.254.106) by sunsite.dk with SMTP; 9 May 2008 17:31:04 -0000 Received: from cluster-g.mailcontrol.com (cluster-g.mailcontrol.com [85.115.41.190]) by bifrost.dotsrc.org (Postfix) with ESMTP id CD97680ED172 for ; Fri, 9 May 2008 19:30:55 +0200 (CEST) Received: from cameurexb01.EUROPE.ROOT.PRI ([62.189.241.200]) by rly21g.srv.mailcontrol.com (MailControl) with ESMTP id m49HUmWi022482 for ; Fri, 9 May 2008 18:30:51 +0100 Received: from news01.csr.com ([10.103.143.38]) by cameurexb01.EUROPE.ROOT.PRI with Microsoft SMTPSVC(6.0.3790.3959); Fri, 9 May 2008 18:30:41 +0100 Received: from news01.csr.com (localhost.localdomain [127.0.0.1]) by news01.csr.com (8.14.2/8.13.4) with ESMTP id m49HUgD9010338 for ; Fri, 9 May 2008 18:30:42 +0100 Received: from csr.com (pws@localhost) by news01.csr.com (8.14.2/8.14.2/Submit) with ESMTP id m49HUgZx010335 for ; Fri, 9 May 2008 18:30:42 +0100 X-Authentication-Warning: news01.csr.com: pws owned process doing -bs To: zsh-workers@sunsite.dk (Zsh hackers list) Subject: PATCH: colouring in prompts done properly X-Mailer: MH-E 8.0.3; nmh 1.2-20070115cvs; GNU Emacs 22.1.1 Date: Fri, 09 May 2008 18:30:42 +0100 Message-ID: <10334.1210354242@csr.com> From: Peter Stephenson X-OriginalArrivalTime: 09 May 2008 17:30:41.0969 (UTC) FILETIME=[67B19210:01C8B1FA] X-Scanned-By: MailControl A-08-50-01 (www.mailcontrol.com) on 10.71.0.131 X-Virus-Scanned: ClamAV 0.91.2/7081/Fri May 9 17:52:50 2008 on bifrost X-Virus-Status: Clean ...after all these years. Not yet well tested but I'm sure you'll let me know. The best letters are taken, as usual, but I don't think %K for bacKground is so bad. It has the right hard, guttural, background-type feel. It's the last letter of Jackson PollocK, so you expect lots of splatter all over the screen. It makes everything Kolourful. Enough justification. I'm happy to add prompt codes for specifying attributes in one go, but the syntax becomes a little less obvious: with the current set, it's clear that individual attributes are being turned on or off. I think with a %A or something it would have to be clear that this was setting all display attributes in one go, overriding what was there before. Index: Doc/Zsh/prompt.yo =================================================================== RCS file: /cvsroot/zsh/zsh/Doc/Zsh/prompt.yo,v retrieving revision 1.11 diff -u -r1.11 prompt.yo --- Doc/Zsh/prompt.yo 17 Feb 2008 18:15:10 -0000 1.11 +++ Doc/Zsh/prompt.yo 9 May 2008 17:25:14 -0000 @@ -183,6 +183,20 @@ 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 Index: Doc/Zsh/zle.yo =================================================================== RCS file: /cvsroot/zsh/zsh/Doc/Zsh/zle.yo,v retrieving revision 1.71 diff -u -r1.71 zle.yo --- Doc/Zsh/zle.yo 6 May 2008 21:33:10 -0000 1.71 +++ Doc/Zsh/zle.yo 9 May 2008 17:25:17 -0000 @@ -2182,9 +2182,10 @@ 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 Index: Src/init.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/init.c,v retrieving revision 1.83 diff -u -r1.83 init.c --- Src/init.c 29 Apr 2008 17:19:26 -0000 1.83 +++ Src/init.c 9 May 2008 17:25:18 -0000 @@ -909,6 +909,9 @@ 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 */ Index: Src/prompt.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/prompt.c,v retrieving revision 1.47 diff -u -r1.47 prompt.c --- Src/prompt.c 22 Apr 2008 15:08:12 -0000 1.47 +++ Src/prompt.c 9 May 2008 17:25:19 -0000 @@ -423,38 +423,78 @@ 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 @@ stradd(psvar[arg - 1]); break; case 'E': - tsetcap(TCCLEAREOL, 1); + tsetcap(TCCLEAREOL, TSC_PROMPT); break; case '^': if (cmdsp) { @@ -816,18 +856,19 @@ /**/ 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 @@ 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 @@ } 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(); +} Index: Src/watch.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/watch.c,v retrieving revision 1.5 diff -u -r1.5 watch.c --- Src/watch.c 4 Apr 2005 09:59:05 -0000 1.5 +++ Src/watch.c 9 May 2008 17:25:19 -0000 @@ -338,31 +338,27 @@ 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('%'); Index: Src/zsh.h =================================================================== RCS file: /cvsroot/zsh/zsh/Src/zsh.h,v retrieving revision 1.131 diff -u -r1.131 zsh.h --- Src/zsh.h 8 May 2008 12:07:06 -0000 1.131 +++ Src/zsh.h 9 May 2008 17:25:20 -0000 @@ -1975,7 +1975,6 @@ #define TXTUNDERLINE 0x0004 #define TXTFGCOLOUR 0x0008 #define TXTBGCOLOUR 0x0010 -#define TXTDIRTY 0x0020 #define TXT_ATTR_ON_MASK 0x001F @@ -1983,15 +1982,15 @@ #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 @@ * 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 @@ (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 */ /****************************************/ Index: Src/Zle/zle_refresh.c =================================================================== RCS file: /cvsroot/zsh/zsh/Src/Zle/zle_refresh.c,v retrieving revision 1.68 diff -u -r1.68 zle_refresh.c --- Src/Zle/zle_refresh.c 6 May 2008 21:33:10 -0000 1.68 +++ Src/Zle/zle_refresh.c 9 May 2008 17:25:21 -0000 @@ -327,232 +327,6 @@ #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 @@ } 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 @@ if (!isearch_atr_on_set) region_highlights[1].atr = TXTUNDERLINE; - set_colseq_buf(); + allocate_colour_buffer(); } @@ -631,10 +393,7 @@ 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 @@ 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 @@ 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 @@ } -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 @@ 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 @@ 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 @@ void zle_refresh_boot(void) { - set_default_colour_sequences(); } /* Provided for unloading the module in a modular fashion */ @@ -2956,6 +2637,4 @@ if (region_highlights) zfree(region_highlights, sizeof(struct region_highlight) * n_region_highlights); - - free_colour_sequences(); } -- Peter Stephenson Software Engineer CSR PLC, Churchill House, Cambridge Business Park, Cowley Road Cambridge, CB4 0WZ, UK Tel: +44 (0)1223 692070