diff --git a/doc/manual/rl-next/clicolor-clarity.md b/doc/manual/rl-next/clicolor-clarity.md
new file mode 100644
index 000000000..8a289e362
--- /dev/null
+++ b/doc/manual/rl-next/clicolor-clarity.md
@@ -0,0 +1,26 @@
+---
+synopsis: "Better usage of colour control environment variables"
+cls: [1699, 1702]
+credits: [jade]
+category: Improvements
+---
+
+Lix now heeds `NO_COLOR`/`NOCOLOR` for more output types, such as that used in `nix search`, `nix flake metadata` and similar.
+
+It also now supports `CLICOLOR_FORCE`/`FORCE_COLOR` to force colours regardless of whether there is a terminal on the other side.
+
+It now follows rules compatible with those described on with `CLICOLOR` defaulted to enabled.
+
+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
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index 53460f729..cbeb7aa36 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -37,7 +37,7 @@ void Logger::warn(const std::string & msg)
void Logger::writeToStdout(std::string_view s)
{
- writeFull(STDOUT_FILENO, s);
+ writeFull(STDOUT_FILENO, filterANSIEscapes(s, !shouldANSI(), std::numeric_limits::max(), false));
writeFull(STDOUT_FILENO, "\n");
}
diff --git a/src/libutil/terminal.cc b/src/libutil/terminal.cc
index 68d358dc5..2ba1cb81b 100644
--- a/src/libutil/terminal.cc
+++ b/src/libutil/terminal.cc
@@ -26,7 +26,8 @@ bool shouldANSI()
return cached;
}
-std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width)
+// FIXME(jade): replace with TerminalCodeEater. wowie this is evil code.
+std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width, bool eatTabs)
{
std::string t, e;
size_t w = 0;
@@ -55,7 +56,7 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w
t += e;
}
- else if (*i == '\t') {
+ else if (*i == '\t' && eatTabs) {
i++; t += ' '; w++;
while (w < (size_t) width && w % 8) {
t += ' '; w++;
diff --git a/src/libutil/terminal.hh b/src/libutil/terminal.hh
index 6b8d59182..2c422ecff 100644
--- a/src/libutil/terminal.hh
+++ b/src/libutil/terminal.hh
@@ -30,7 +30,8 @@ bool shouldANSI();
*/
std::string filterANSIEscapes(std::string_view s,
bool filterAll = false,
- unsigned int width = std::numeric_limits::max());
+ unsigned int width = std::numeric_limits::max(),
+ bool eatTabs = true);
/**
* Recalculate the window size, updating a global variable. Used in the
diff --git a/tests/functional/search.sh b/tests/functional/search.sh
index d9c7a75da..1a2a20089 100644
--- a/tests/functional/search.sh
+++ b/tests/functional/search.sh
@@ -29,6 +29,8 @@ nix search -f search.nix '' ^ | grepQuiet hello
## Tests for multiple regex/match highlighting
+# FIXME: possibly not test this with colour in the future
+export CLICOLOR_FORCE=1
e=$'\x1b' # grep doesn't support \e, \033 or even \x1b
# Multiple overlapping regexes
(( $(nix search -f search.nix '' 'oo' 'foo' 'oo' | grep -c "$e\[32;1mfoo$e\\[0;1m") == 1 ))