chiark / gitweb /
@@@ tty mess
[mLib] / ui / ttycolour.c
index 7e70a3b45106049355e4a19255c70546a0c92580..d2658e70cbadcb29f0e7cffd3142063ac0205671 100644 (file)
 
 #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 -------------------------------------------------*/