#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;
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;
}
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 -------------------------------------------------*/