X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/d23763dae4f649cc29a923298e0232ffdf608744..98ff9295493ed2b990f30768e11b18b6bc65eaa4:/ui/ttycolour.c?ds=sidebyside diff --git a/ui/ttycolour.c b/ui/ttycolour.c index 7e70a3b..d2658e7 100644 --- a/ui/ttycolour.c +++ b/ui/ttycolour.c @@ -32,22 +32,122 @@ #include "macros.h" #include "report.h" +#include "tty.h" #include "ttycolour.h" /*----- Main code ---------------------------------------------------------*/ -int ttycolour_config(ttycolour_attr *attr, const char *user, unsigned f, - const struct ttycolour_style *tab) +/* --- @env_setting_p@ --- * + * + * Arguments: @const char *var@ = environment variable name + * + * Returns: Nonzero if the variable is set to a non-empty value, + * otherwise zero. + * + * Use: This is the recommended way to check the `NO_COLOR', + * `CLICOLOR' and `CLICOLOR_FORCE' variables. + */ + +static int env_setting_p(const char *var) + { const char *p; p = getenv(var); return (p && *p); } + +/* --- @ttycolour_enablep@ --- * + * + * Arguments: @unsigned f@ = flags + * + * Returns: Nonzero if colours should be applied to output, otherwise + * zero. + * + * Use: This function determines whether it's generally a good idea + * to produce output in pretty colours. Set @TCEF_TTY@ if the + * output stream is -- or should be considered to be -- + * interactive (e.g., according to @isatty@); set @TCEF_DFLT@ if + * the application prefers to produce coloured output if + * possible. + * + * The detailed behaviour is as follows. (Since the purpose of + * this function is to abide by common conventions and to be + * convenient for users, these details may change in future.) + * + * * If the `NO_COLOR' environment variable is non-empty, then + * colour is disabled (%%\url{https://no-color.org/}%%). + * + * * If the `TERM' variable is set to `dumb', then colour is + * disabled (Emacs). + * + * * If the `FORCE_COLOR' environment variable is non-empty, + * then colour is enabled, unless the value is 0, in which + * case colour is disabled (apparently from the Node + * community, %%\url{%%https://force-color.org/}%% and + * %%\url{https://nodejs.org/api/tty.html#writestreamgetcolordepthenv}%%). + * + * * If the `CLICOLOR_FORCE' environment variable is + * non-empty, then colour is enabled (apparently from + * Mac OS, (%%\url{http://bixense.com/clicolors/}%%). + * + * * If the @TCEF_TTY@ flag is clear, then colour is disabled. + * + * * If the @TCEF_DFLT@ flag is set, then colour is enabled. + * + * * If the `CLICOLOR' environment variable is non-empty, then + * colour is enabled (again, apparently from Mac OS, + * (%%\url{http://bixense.com/clicolors/}%%). + * + * * Otherwise, colour is disabled. + */ + +int ttycolour_enablep(unsigned f) +{ + const char *t; + + if (env_setting_p("NO_COLOR")) return (0); + else if (t = getenv("TERM"), !t || STRCMP(t, ==, "dumb")) return (0); + else if (t = getenv("FORCE_COLOR"), t && *t) return (*t == '0' ? 0 : 1); + else if (env_setting_p("CLICOLOR_FORCE")) return (1); + else if (!(f&TCEF_TTY)) return (0); + else if ((f&TCEF_DFLT) || env_setting_p("CLICOLOR")) return (1); + else return (0); +} + + + +int ttycolour_config(struct tty_attr *attr, const char *user, unsigned f, + struct tty *tty, const struct ttycolour_style *tab) { const char *p, *q; + const struct tty_attrlist *aa; size_t n; - unsigned i, arg, a; + unsigned i, arg, a, fg, bg, st, spc, clr; int rc = 0; - for (i = 0; tab[i].tok; i++) attr[i] = tab[i].dflt; +#define ST_BASE 0u +#define ST_EXT 1u +#define ST_CLR 2u +#define ST_RED 3u +#define ST_GREEN 4u +#define ST_BLUE 5u +#define ST_MASK 0x7fu +#define ST_FG 0x00u +#define ST_BG 0x80u + +#define SETAFIELD(f, v) (a = (a&~TTAF_##f##MASK) | ((v) << TTAF_##f##SHIFT)) + + for (i = 0; tab[i].tok; i++) { + aa = tab[i].dflt; + if (aa) { + for (;; aa++) + if ((tty->acaps&aa->cap_mask) == aa->cap_eq) + { attr[i] = aa->attr; goto next_token; } + else if (!aa->cap_mask) + break; + } + attr[i].f = attr[i]._res0 = attr[i].fg = attr[i].bg = 0; + next_token:; + } if (f&TCIF_GETENV) p = getenv(user); else p = user; + if (p) while (*p) { n = strcspn(p, "=:"); q = p + n; @@ -63,40 +163,134 @@ int ttycolour_config(ttycolour_attr *attr, const char *user, unsigned f, rc = -1; q = p + strcspn(p, ":"); p = *q ? q + 1 : q; continue; found_tok: - a = 0; arg = 0; q++; + a = fg = bg = 0; st = ST_BASE; arg = 0; clr = 0; q++; for (;;) { if (ISDIGIT(*q)) { arg = 10*arg + (*q++ - '0'); continue; } - switch (arg) { - case 0: a = 0; break; - case 1: a |= TCAF_BOLD; break; - case 39: a &= ~(TCAF_FG | TCAF_FGMASK); break; - case 49: a &= ~(TCAF_BG | TCAF_BGMASK); break; - case 30: case 31: case 32: case 33: - case 34: case 35: case 36: case 37: - a = (a&~TCAF_FGMASK) | TCAF_FG | ((arg - 30) << TCAF_FGSHIFT); + switch (st&ST_MASK) { + case ST_BASE: + switch (arg) { + case 0: a = fg = bg = 0; break; + case 1: SETAFIELD(WT, TTWT_BOLD); break; + case 2: SETAFIELD(WT, TTWT_DIM); break; + case 3: a |= TTAF_ITAL; break; + case 4: SETAFIELD(LN, TTLN_ULINE); break; + case 7: a |= TTAF_INVV; break; + case 21: SETAFIELD(LN, TTLN_UULINE); break; + case 22: a &= ~TTAF_WTMASK; break; + case 23: a &= ~TTAF_ITAL; break; + case 24: a &= ~TTAF_LNMASK; break; + case 27: a &= ~TTAF_INVV; break; + + case 30: case 31: case 32: case 33: + case 34: case 35: case 36: case 37: + SETAFIELD(FGSPC, TTCSPC_1BPC); + fg = arg - 30; + break; + case 90: case 91: case 92: case 93: + case 94: case 95: case 96: case 97: + SETAFIELD(FGSPC, TTCSPC_1BPCBR); + fg = (arg - 90) | TT1BPC_BRI; + break; + case 38: st = ST_EXT | ST_FG; break; + case 39: SETAFIELD(FGSPC, TTCSPC_NONE); break; + + case 40: case 41: case 42: case 43: + case 44: case 45: case 46: case 47: + SETAFIELD(BGSPC, TTCSPC_1BPC); + bg = arg - 40; + break; + case 100: case 101: case 102: case 103: + case 104: case 105: case 106: case 107: + SETAFIELD(BGSPC, TTCSPC_1BPCBR); + fg = (arg - 100) | TT1BPC_BRI; + break; + case 48: st = ST_EXT | ST_BG; break; + case 49: SETAFIELD(BGSPC, TTCSPC_NONE); break; + + default: + if (f&TCIF_REPORT) + moan("unknown colour code %u in `%.*s' string in `%s'", + arg, (int)n, p, user); + rc = -1; break; + } break; - case 40: case 41: case 42: case 43: - case 44: case 45: case 46: case 47: - a = (a&~TCAF_BGMASK) | TCAF_BG | ((arg - 40) << TCAF_BGSHIFT); + + case ST_EXT: + switch (arg) { + case 2: st = (st&~ST_MASK) | ST_RED; break; + case 5: st = (st&~ST_MASK) | ST_CLR; break; + default: + if (f&TCIF_REPORT) + moan("unknown extended colour space %u " + "in `%.*s' string in `%s'", + arg, (int)n, p, user); + st = ST_BASE; rc = -1; break; + } break; - case 90: case 91: case 92: case 93: - case 94: case 95: case 96: case 97: - a = (a&~TCAF_FGMASK) | TCAF_FG | - (((arg - 90) | TCCF_BRIGHT) << TCAF_FGSHIFT); + + case ST_CLR: + if (arg < 8) + { spc = TTCSPC_1BPC; clr = arg; } + else if (arg < 16) + { spc = TTCSPC_1BPCBR; clr = (arg - 8) | TT1BPC_BRI; } + else if (arg < 232) + { spc = TTCSPC_6LPC; clr = arg - 16; } + else if (arg < 256) + { spc = TTCSPC_24LGS; clr = arg - 232; } + else { + if (f&TCIF_REPORT) + moan("indexed colour %u out of range " + "in `%.*s' string in `%s'", + arg, (int)n, p, user); + st = ST_BASE; rc = -1; break; + } + if (st&ST_BG) { SETAFIELD(BGSPC, spc); bg = clr; } + else { SETAFIELD(FGSPC, spc); fg = clr; } + st = ST_BASE; break; + + case ST_RED: + if (arg < 256) + { clr = U32(arg) << 16; st = ST_GREEN; } + else { + if (f&TCIF_REPORT) + moan("red channel %u out of range " + "in `%.*s' string in `%s'", + arg, (int)n, p, user); + st = ST_BASE; rc = -1; + } break; - case 100: case 101: case 102: case 103: - case 104: case 105: case 106: case 107: - a = (a&~TCAF_BGMASK) | TCAF_BG | - (((arg - 100) | TCCF_BRIGHT) << TCAF_BGSHIFT); + + case ST_GREEN: + if (arg < 256) + { clr |= U32(arg) << 8; st = ST_BLUE; } + else { + if (f&TCIF_REPORT) + moan("green channel %u out of range " + "in `%.*s' string in `%s'", + arg, (int)n, p, user); + st = ST_BASE; rc = -1; break; + } break; + + case ST_BLUE: + if (arg < 256) { + clr |= U32(arg) << 0; + if (st&ST_BG) { SETAFIELD(BGSPC, TTCSPC_8BPC); bg = clr; } + else { SETAFIELD(FGSPC, TTCSPC_8BPC); fg = clr; } + } else { + if (f&TCIF_REPORT) + moan("blue channel %u out of range " + "in `%.*s' string in `%s'", + arg, (int)n, p, user); + rc = -1; break; + } + st = ST_BASE; break; + default: - if (f&TCIF_REPORT) - moan("unknown colour code %u in `%.*s' string in `%s'", - arg, (int)n, p, user); - rc = -1; break; + assert(0); } arg = 0; if (!*q || *q == ':') break; @@ -108,89 +302,11 @@ int ttycolour_config(ttycolour_attr *attr, const char *user, unsigned f, } q++; } - attr[i] = a; p = *q ? q + 1 : q; + attr[i].f = a; attr[i]._res0 = 0; attr[i].fg = fg; attr[i].bg = bg; + p = *q ? q + 1 : q; } return (rc); } -void ttycolour_init(struct ttycolour_state *tc) - { tc->attr = 0; } - -/* --- @ttycolour_setattr@ --- * - * - * Arguments: @const struct gprintf_ops *ops@ = formatting operations - * @void *go@ = output sink - * @struct ttycolour_state *tc - * @ttycolour_attr attr@ = attribute code to set - * - * Returns: Zero on success, %$-1$% on error. - * - * Use: Send a control sequence to the output stream so that - * subsequent text is printed with the given attributes. - * - * Some effort is taken to avoid unnecessary control sequences. - * In particular, if @attr@ matches the current terminal - * settings already, then nothing is written. - */ - -int ttycolour_setattr(const struct gprintf_ops *gops, void *go, - struct ttycolour_state *tc, ttycolour_attr attr) -{ - unsigned diff = tc->attr ^ attr; - unsigned f = 0; - -#define f_semi 1u - -#define PUT_SEMI do { \ - if (!(f&f_semi)) f |= f_semi; \ - else if (gops->putch(go, ';') < 0) return (-1); \ -} while (0) - -#define SET_COLOUR(norm, bright, colour) do { \ - unsigned char _col = (colour); \ - \ - PUT_SEMI; \ - gprintf(gops, go, "%d", \ - (_col&TCCF_BRIGHT ? (bright) : (norm)) + (_col&TCCF_RGBMASK)); \ -} while (0) - - /* If there's nothing to do, we might as well stop now. */ - if (!diff) return (0); - - /* Start on the control command. */ - if (gops->putm(go, "\x1b[", 2) < 0) return (-1); - - /* Change the boldness if necessary. */ - if (diff&TCAF_BOLD) { - PUT_SEMI; - if (attr&TCAF_BOLD) { if (gops->putch(go, '1') < 0) return (-1); } - else { diff = tc->attr; if (gops->putch(go, '0') < 0) return (-1); } - } - - /* Change the foreground colour if necessary. */ - if (diff&(TCAF_FG | TCAF_FGMASK)) { - if (attr&TCAF_FG) - SET_COLOUR(30, 90, (attr&TCAF_FGMASK) >> TCAF_FGSHIFT); - else - { PUT_SEMI; if (gops->putm(go, "39", 2) < 0) return (-1); } - } - - /* Change the background colour if necessary. */ - if (diff&(TCAF_BG | TCAF_BGMASK)) { - if (attr&TCAF_BG) - SET_COLOUR(40, 100, (attr&TCAF_BGMASK) >> TCAF_BGSHIFT); - else - { PUT_SEMI; if (gops->putm(go, "49", 2) < 0) return (-1); } - } - - /* Terminate the control command and save the new attributes. */ - if (gops->putch(go, 'm') < 0) return (-1); - tc->attr = attr; return (0); - -#undef f_semi -#undef PUT_SEMI -#undef SET_COLOUR -} - /*----- That's all, folks -------------------------------------------------*/