[clang] [llvm] [clang][Diagnostics] Add CLANG_HIGHLIGHT_COLORS= to configure syntax highlighting in diagnostics (#82945) (PR #83060)

via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 26 13:45:09 PST 2024


https://github.com/nabijaczleweli updated https://github.com/llvm/llvm-project/pull/83060

>From a786f5c27ce7d25553a224bcc92185755b3875d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= <nabijaczleweli at nabijaczleweli.xyz>
Date: Mon, 26 Feb 2024 21:58:24 +0100
Subject: [PATCH] [clang][Diagnostics] Add CLANG_HIGHLIGHT_COLORS= to configure
 syntax highlighting in diagnostics (#82945)

CLANG_HIGHLIGHT_COLORS=on is equivalent to unset,
and enables the default.

CLANG_HIGHLIGHT_COLORS=off (or =no, or anything to that effect)
disables highlighting altogether.

CLANG_HIGHLIGHT_COLORS=comment=red highlights comments in red,
and the rest as-default.

CLANG_HIGHLIGHT_COLORS=comment=blue,literal=bright-magenta,keyword=no
highlights comments in blue, literals in bright magenta,
and doesn't highlight keywords.

Fixes: 718aac9f7a19227b5c5ec85819a3a5ae24ce32e1
---
 clang/docs/UsersManual.rst              | 11 +++++++
 clang/lib/Frontend/TextDiagnostic.cpp   | 43 ++++++++++++++++++++-----
 llvm/include/llvm/Support/raw_ostream.h |  3 ++
 llvm/lib/Support/raw_ostream.cpp        | 40 +++++++++++++++++++++++
 4 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 7391e4cf3a9aeb..e268fb73159f12 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -256,6 +256,17 @@ output format of the diagnostics that it generates.
    defined and ``-fcolor-diagnostics`` is passed on the command line, Clang
    will honor the command line argument.
 
+   Additionally, if enabled, Clang will semantically highlight the code
+   the diagnostics pertain to. The ``CLANG_HIGHLIGHT_COLORS`` environment
+   variable controls this behaviour:
+   if unset or set to "on", code is highlighted normally.
+   If set to "off" or "no", highlighting is disabled.
+   Otherwise, a comma-separated, `token=color` list may be given,
+   where `token`s may be ``comment``, ``literal``, or ``keyword``,
+   and `color`s may be ``{bright-,}black,red,green,yellow,blue,magenta,cyan,white``.
+   If an unknown `color` is given, it's disabled. For example, a valid spec may be
+   ``CLANG_HIGHLIGHT_COLORS=comment=blue,literal=bright-magenta,keyword=no``.
+
 .. option:: -fansi-escape-codes
 
    Controls whether ANSI escape codes are used instead of the Windows Console
diff --git a/clang/lib/Frontend/TextDiagnostic.cpp b/clang/lib/Frontend/TextDiagnostic.cpp
index 10240d7ee6f2e6..0da6fbca27978b 100644
--- a/clang/lib/Frontend/TextDiagnostic.cpp
+++ b/clang/lib/Frontend/TextDiagnostic.cpp
@@ -24,6 +24,7 @@
 #include <optional>
 
 using namespace clang;
+using namespace std::literals;
 
 static const enum raw_ostream::Colors noteColor = raw_ostream::CYAN;
 static const enum raw_ostream::Colors remarkColor =
@@ -46,9 +47,31 @@ static const enum raw_ostream::Colors savedColor =
 // is already taken for 'note'. Green is already used to underline
 // source ranges. White and black are bad because of the usual
 // terminal backgrounds. Which leaves us only with TWO options.
-static constexpr raw_ostream::Colors CommentColor = raw_ostream::YELLOW;
-static constexpr raw_ostream::Colors LiteralColor = raw_ostream::GREEN;
-static constexpr raw_ostream::Colors KeywordColor = raw_ostream::BLUE;
+static std::optional<raw_ostream::Colors> CommentColor = raw_ostream::YELLOW;
+static std::optional<raw_ostream::Colors> LiteralColor = raw_ostream::GREEN;
+static std::optional<raw_ostream::Colors> KeywordColor = raw_ostream::BLUE;
+
+static __attribute__((constructor)) void loadHighlightColors() {
+  auto cfg = std::getenv("CLANG_HIGHLIGHT_COLORS");
+  if (!cfg || cfg == "on"sv)
+    return;
+
+  if (!std::strchr(cfg, '=')) {
+    CommentColor = LiteralColor = KeywordColor = {};
+    return;
+  }
+
+  const char *const tokens[]{"comment", "literal", "keyword", nullptr};
+  std::optional<raw_ostream::Colors> *const colors[]{
+      &CommentColor, &LiteralColor, &KeywordColor};
+  for (char *value; *cfg;) {
+    auto idx = getsubopt(&cfg, const_cast<char *const *>(tokens), &value);
+    if (idx == -1 || !value)
+      continue;
+
+    *colors[idx] = raw_ostream::parse_color(value);
+  }
+}
 
 /// Add highlights to differences in template strings.
 static void applyTemplateHighlighting(raw_ostream &OS, StringRef Str,
@@ -1138,7 +1161,7 @@ highlightLines(StringRef FileData, unsigned StartLineNumber,
       std::make_unique<SmallVector<TextDiagnostic::StyleRange>[]>(
           EndLineNumber - StartLineNumber + 1);
 
-  if (!PP || !ShowColors)
+  if (!PP || !ShowColors || (!CommentColor && !LiteralColor && !KeywordColor))
     return SnippetRanges;
 
   // Might cause emission of another diagnostic.
@@ -1164,6 +1187,10 @@ highlightLines(StringRef FileData, unsigned StartLineNumber,
   auto appendStyle =
       [PP, &LangOpts](SmallVector<TextDiagnostic::StyleRange> &Vec,
                       const Token &T, unsigned Start, unsigned Length) -> void {
+    auto setColor = [&](auto &&color) {
+      if (color)
+        Vec.emplace_back(Start, Start + Length, *color);
+    };
     if (T.is(tok::raw_identifier)) {
       StringRef RawIdent = T.getRawIdentifier();
       // Special case true/false/nullptr/... literals, since they will otherwise
@@ -1183,18 +1210,18 @@ highlightLines(StringRef FileData, unsigned StartLineNumber,
               .Case("__FUNCTION__", true)
               .Case("__FUNCSIG__", true)
               .Default(false)) {
-        Vec.emplace_back(Start, Start + Length, LiteralColor);
+        setColor(LiteralColor);
       } else {
         const IdentifierInfo *II = PP->getIdentifierInfo(RawIdent);
         assert(II);
         if (II->isKeyword(LangOpts))
-          Vec.emplace_back(Start, Start + Length, KeywordColor);
+          setColor(KeywordColor);
       }
     } else if (tok::isLiteral(T.getKind())) {
-      Vec.emplace_back(Start, Start + Length, LiteralColor);
+      setColor(LiteralColor);
     } else {
       assert(T.is(tok::comment));
-      Vec.emplace_back(Start, Start + Length, CommentColor);
+      setColor(CommentColor);
     }
   };
 
diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index 696290a5d99cf5..c46a00e9304ccd 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -133,6 +133,9 @@ class raw_ostream {
   static constexpr Colors SAVEDCOLOR = Colors::SAVEDCOLOR;
   static constexpr Colors RESET = Colors::RESET;
 
+  static std::optional<Colors>
+  parse_color(const std::string_view &name) noexcept;
+
   explicit raw_ostream(bool unbuffered = false,
                        OStreamKind K = OStreamKind::OK_OStream)
       : Kind(K), BufferMode(unbuffered ? BufferKind::Unbuffered
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index c7064d2dfedc56..db57500255a1a8 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -27,6 +27,7 @@
 #include <algorithm>
 #include <cerrno>
 #include <cstdio>
+#include <string_view>
 #include <sys/stat.h>
 
 // <fcntl.h> may provide O_BINARY.
@@ -62,6 +63,7 @@
 #endif
 
 using namespace llvm;
+using namespace std::literals;
 
 constexpr raw_ostream::Colors raw_ostream::BLACK;
 constexpr raw_ostream::Colors raw_ostream::RED;
@@ -74,6 +76,44 @@ constexpr raw_ostream::Colors raw_ostream::WHITE;
 constexpr raw_ostream::Colors raw_ostream::SAVEDCOLOR;
 constexpr raw_ostream::Colors raw_ostream::RESET;
 
+std::optional<raw_ostream::Colors>
+raw_ostream::parse_color(const std::string_view &name) noexcept {
+  if (name == "black"sv)
+    return Colors::BLACK;
+  else if (name == "red"sv)
+    return Colors::RED;
+  else if (name == "green"sv)
+    return Colors::GREEN;
+  else if (name == "yellow"sv)
+    return Colors::YELLOW;
+  else if (name == "blue"sv)
+    return Colors::BLUE;
+  else if (name == "magenta"sv)
+    return Colors::MAGENTA;
+  else if (name == "cyan"sv)
+    return Colors::CYAN;
+  else if (name == "white"sv)
+    return Colors::WHITE;
+  else if (name == "bright-black"sv)
+    return Colors::BRIGHT_BLACK;
+  else if (name == "bright-red"sv)
+    return Colors::BRIGHT_RED;
+  else if (name == "bright-green"sv)
+    return Colors::BRIGHT_GREEN;
+  else if (name == "bright-yellow"sv)
+    return Colors::BRIGHT_YELLOW;
+  else if (name == "bright-blue"sv)
+    return Colors::BRIGHT_BLUE;
+  else if (name == "bright-magenta"sv)
+    return Colors::BRIGHT_MAGENTA;
+  else if (name == "bright-cyan"sv)
+    return Colors::BRIGHT_CYAN;
+  else if (name == "bright-white"sv)
+    return Colors::BRIGHT_WHITE;
+  else
+    return std::nullopt;
+}
+
 raw_ostream::~raw_ostream() {
   // raw_ostream's subclasses should take care to flush the buffer
   // in their destructors.



More information about the cfe-commits mailing list