From 378ec5fb0611e314511a7afc806664205846fc2e Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Thu, 1 Aug 2024 12:26:16 -0700 Subject: [PATCH] Implement forcing CLI colour on, and document it better This is necessary to make some old tests work when testing colour against non-interactive outputs. Change-Id: Id89f8a1f45c587fede35a69db85f7a52f2c0a981 --- doc/manual/src/contributing/testing.md | 1 + src/libutil/terminal.cc | 18 +++++++++++++++--- src/libutil/terminal.hh | 9 +++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/contributing/testing.md b/doc/manual/src/contributing/testing.md index cea6ee3bf..33197b0ba 100644 --- a/doc/manual/src/contributing/testing.md +++ b/doc/manual/src/contributing/testing.md @@ -427,6 +427,7 @@ I grepped `src/` for `get[eE]nv\("` to find the mentions in Lix code. - `NIX_SHOW_STATS_PATH` - Writes those statistics into a file at the given path instead of stdout. Undocumented. - `NIX_SHOW_SYMBOLS` - Dumps the symbol table into the show-stats json output. - `TERM` - If `dumb` or unset, disables ANSI colour output. +- `FORCE_COLOR`, `CLICOLOR_FORCE` - Enables ANSI colour output if `NO_COLOR`/`NOCOLOR` not set. - `NO_COLOR`, `NOCOLOR` - Disables ANSI colour output. - `_NIX_DEVELOPER_SHOW_UNKNOWN_LOCATIONS` - Highlights unknown locations in errors. - `NIX_PROFILE` - Selects which profile `nix-env` will operate on. Documented elsewhere. diff --git a/src/libutil/terminal.cc b/src/libutil/terminal.cc index b58331d04..68d358dc5 100644 --- a/src/libutil/terminal.cc +++ b/src/libutil/terminal.cc @@ -9,9 +9,21 @@ namespace nix { bool shouldANSI() { - return isatty(STDERR_FILENO) - && getEnv("TERM").value_or("dumb") != "dumb" - && !(getEnv("NO_COLOR").has_value() || getEnv("NOCOLOR").has_value()); + // Implements the behaviour described by https://bixense.com/clicolors/ + // As well as https://force-color.org/ for compatibility, since it fits in the same shape. + // NO_COLOR CLICOLOR CLICOLOR_FORCE Colours? + // set x x No + // unset x set Yes + // unset x unset If attached to a terminal + // [we choose the "modern" approach of colour-by-default] + auto compute = []() -> bool { + bool mustNotColour = getEnv("NO_COLOR").has_value() || getEnv("NOCOLOR").has_value(); + bool shouldForce = getEnv("CLICOLOR_FORCE").has_value() || getEnv("FORCE_COLOR").has_value(); + bool isTerminal = isatty(STDERR_FILENO) && getEnv("TERM").value_or("dumb") != "dumb"; + return !mustNotColour && (shouldForce || isTerminal); + }; + static bool cached = compute(); + return cached; } std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width) diff --git a/src/libutil/terminal.hh b/src/libutil/terminal.hh index 43df5bd70..6b8d59182 100644 --- a/src/libutil/terminal.hh +++ b/src/libutil/terminal.hh @@ -9,6 +9,15 @@ namespace nix { /** * Determine whether ANSI escape sequences are appropriate for the * present output. + * + * This follows the rules described on https://bixense.com/clicolors/ + * with CLICOLOR defaulted to enabled (and thus ignored). + * + * That is to say, the following procedure is followed in order: + * - NO_COLOR or NOCOLOR set -> always disable colour + * - CLICOLOR_FORCE or FORCE_COLOR set -> enable colour + * - The output is a tty; TERM != "dumb" -> enable colour + * - Otherwise -> disable colour */ bool shouldANSI();