Просмотр исходного кода

StdIo: Add a Terminal abstraction to print color text

Abstract over VT100 sequences and Windows Console text attributes.

This will replace KWSys Terminal, which does not integrate with C++
streams.

Issue: #26924
Brad King 5 месяцев назад
Родитель
Сommit
329d755dbd
5 измененных файлов с 279 добавлено и 0 удалено
  1. 2 0
      Source/CMakeLists.txt
  2. 170 0
      Source/cmStdIoTerminal.cxx
  3. 56 0
      Source/cmStdIoTerminal.h
  4. 50 0
      Tests/CMakeLib/testStdIo.cxx
  5. 1 0
      bootstrap

+ 2 - 0
Source/CMakeLists.txt

@@ -472,6 +472,8 @@ add_library(
   cmStdIoInit.cxx
   cmStdIoStream.h
   cmStdIoStream.cxx
+  cmStdIoTerminal.h
+  cmStdIoTerminal.cxx
   cmStringAlgorithms.cxx
   cmStringAlgorithms.h
   cmSyntheticTargetCache.h

+ 170 - 0
Source/cmStdIoTerminal.cxx

@@ -0,0 +1,170 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmStdIoTerminal.h"
+
+#include <array>
+#include <functional>
+#include <iosfwd>
+#include <string>
+#include <type_traits>
+
+#include <cm/string_view>
+#include <cmext/string_view>
+
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+#include <cm/optional>
+
+#include "cmStdIoStream.h"
+#include "cmSystemTools.h"
+
+namespace cm {
+namespace StdIo {
+
+namespace {
+
+#ifdef _WIN32
+WORD const kConsoleAttrMask = FOREGROUND_RED | FOREGROUND_GREEN |
+  FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN |
+  BACKGROUND_BLUE | BACKGROUND_INTENSITY;
+std::array<WORD, kTermAttrCount> const kConsoleAttrs{ {
+  0,                                                   // Normal
+  FOREGROUND_INTENSITY,                                // ForegroundBold
+  0,                                                   // ForegroundBlack
+  FOREGROUND_BLUE,                                     // ForegroundBlue
+  FOREGROUND_GREEN | FOREGROUND_BLUE,                  // ForegroundCyan
+  FOREGROUND_GREEN,                                    // ForegroundGreen
+  FOREGROUND_RED | FOREGROUND_BLUE,                    // ForegroundMagenta
+  FOREGROUND_RED,                                      // ForegroundRed
+  FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // ForegroundWhite
+  FOREGROUND_RED | FOREGROUND_GREEN,                   // ForegroundYellow
+  BACKGROUND_INTENSITY,                                // BackgroundBold
+  0,                                                   // BackgroundBlack
+  BACKGROUND_BLUE,                                     // BackgroundBlue
+  BACKGROUND_GREEN | BACKGROUND_BLUE,                  // BackgroundCyan
+  BACKGROUND_GREEN,                                    // BackgroundGreen
+  BACKGROUND_RED | BACKGROUND_BLUE,                    // BackgroundMagenta
+  BACKGROUND_RED,                                      // BackgroundRed
+  BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE, // BackgroundWhite
+  BACKGROUND_RED | BACKGROUND_GREEN,                   // BackgroundYellow
+} };
+
+WORD ConsoleAttrs(WORD consoleAttrs, TermAttrSet const& attrs)
+{
+  consoleAttrs =
+    attrs.contains(TermAttr::Normal) ? consoleAttrs & kConsoleAttrMask : 0;
+  for (TermAttr attr : attrs) {
+    auto index = static_cast<std::underlying_type<TermAttr>::type>(attr);
+    consoleAttrs |= kConsoleAttrs[index];
+  }
+  return consoleAttrs;
+}
+#endif
+
+// VT100 escape sequence strings.
+#if defined(__MVS__) // z/OS: assume EBCDIC
+#  define ESC "\47"
+#else
+#  define ESC "\33"
+#endif
+
+std::array<cm::string_view, kTermAttrCount> const kVT100Codes{ {
+  ESC "[0m"_s,  // Normal
+  ESC "[1m"_s,  // ForegroundBold
+  ESC "[30m"_s, // ForegroundBlack
+  ESC "[34m"_s, // ForegroundBlue
+  ESC "[36m"_s, // ForegroundCyan
+  ESC "[32m"_s, // ForegroundGreen
+  ESC "[35m"_s, // ForegroundMagenta
+  ESC "[31m"_s, // ForegroundRed
+  ESC "[37m"_s, // ForegroundWhite
+  ESC "[33m"_s, // ForegroundYellow
+  ""_s,         // BackgroundBold
+  ESC "[40m"_s, // BackgroundBlack
+  ESC "[44m"_s, // BackgroundBlue
+  ESC "[46m"_s, // BackgroundCyan
+  ESC "[42m"_s, // BackgroundGreen
+  ESC "[45m"_s, // BackgroundMagenta
+  ESC "[41m"_s, // BackgroundRed
+  ESC "[47m"_s, // BackgroundWhite
+  ESC "[43m"_s, // BackgroundYellow
+} };
+
+void SetVT100Attrs(std::ostream& os, TermAttrSet const& attrs)
+{
+  for (TermAttr attr : attrs) {
+    auto index = static_cast<std::underlying_type<TermAttr>::type>(attr);
+    os << kVT100Codes[index];
+  }
+}
+
+auto const TermEnv = []() -> cm::optional<TermKind> {
+  /* Force color according to https://bixense.com/clicolors/ convention.  */
+  if (cm::optional<std::string> cliColorForce =
+        cmSystemTools::GetEnvVar("CLICOLOR_FORCE")) {
+    if (!cliColorForce->empty() && *cliColorForce != "0"_s) {
+      return TermKind::VT100;
+    }
+  }
+  /* Disable color according to https://bixense.com/clicolors/ convention. */
+  if (cm::optional<std::string> cliColor =
+        cmSystemTools::GetEnvVar("CLICOLOR")) {
+    if (*cliColor == "0"_s) {
+      return TermKind::None;
+    }
+  }
+  /* GNU make 4.1+ may tell us that its output is destined for a TTY. */
+  if (cm::optional<std::string> makeTermOut =
+        cmSystemTools::GetEnvVar("MAKE_TERMOUT")) {
+    if (!makeTermOut->empty()) {
+      return TermKind::VT100;
+    }
+  }
+  return cm::nullopt;
+}();
+
+void Print(OStream& os, TermAttrSet const& attrs,
+           std::function<void(std::ostream&)> const& f)
+{
+  TermKind kind = TermEnv ? *TermEnv : os.Kind();
+  switch (kind) {
+    case TermKind::None:
+      f(os.IOS());
+      break;
+    case TermKind::VT100:
+      SetVT100Attrs(os.IOS(), attrs);
+      f(os.IOS());
+      SetVT100Attrs(os.IOS(), TermAttr::Normal);
+      break;
+#ifdef _WIN32
+    case TermKind::Console: {
+      HANDLE console = os.Console();
+      CONSOLE_SCREEN_BUFFER_INFO sbi;
+      if (!attrs.empty() && GetConsoleScreenBufferInfo(console, &sbi)) {
+        Out().IOS().flush();
+        Err().IOS().flush();
+        SetConsoleTextAttribute(console, ConsoleAttrs(sbi.wAttributes, attrs));
+        f(os.IOS());
+        Out().IOS().flush();
+        Err().IOS().flush();
+        SetConsoleTextAttribute(
+          console, ConsoleAttrs(sbi.wAttributes, TermAttr::Normal));
+      } else {
+        f(os.IOS());
+      }
+    } break;
+#endif
+  };
+}
+
+} // anonymous namespace
+
+void Print(OStream& os, TermAttrSet const& attrs, cm::string_view s)
+{
+  Print(os, attrs, [s](std::ostream& o) { o << s; });
+}
+
+}
+}

+ 56 - 0
Source/cmStdIoTerminal.h

@@ -0,0 +1,56 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <cstdint>
+
+#include <cm/string_view>
+#include <cmext/enum_set>
+
+namespace cm {
+namespace StdIo {
+
+class OStream;
+
+/**
+ * Represent a text attribute.
+ */
+enum class TermAttr : std::uint8_t
+{
+  Normal,
+  ForegroundBold,
+  ForegroundBlack,
+  ForegroundBlue,
+  ForegroundCyan,
+  ForegroundGreen,
+  ForegroundMagenta,
+  ForegroundRed,
+  ForegroundWhite,
+  ForegroundYellow,
+  BackgroundBold,
+  BackgroundBlack,
+  BackgroundBlue,
+  BackgroundCyan,
+  BackgroundGreen,
+  BackgroundMagenta,
+  BackgroundRed,
+  BackgroundWhite,
+  BackgroundYellow,
+};
+static constexpr std::size_t kTermAttrCount = 19;
+
+/**
+ * Represent a set of text attributes.
+ */
+using TermAttrSet = cm::enum_set<TermAttr, kTermAttrCount>;
+
+/**
+ * Print text to an output stream using a given set of color attributes.
+ */
+void Print(OStream& os, TermAttrSet const& attrs, cm::string_view text);
+
+}
+}

+ 50 - 0
Tests/CMakeLib/testStdIo.cxx

@@ -9,6 +9,7 @@
 #include "cmStdIoConsole.h"
 #include "cmStdIoInit.h"
 #include "cmStdIoStream.h"
+#include "cmStdIoTerminal.h"
 
 #include "testCommon.h"
 
@@ -70,6 +71,54 @@ bool testConsole()
   return true;
 }
 
+void testTerminalPrint(cm::StdIo::TermAttrSet const& attrs,
+                       cm::string_view text)
+{
+  using namespace cm::StdIo;
+  std::cout << "  ";
+  Print(Out(), attrs, text);
+#ifdef _WIN32
+  if (Out().Kind() == TermKind::Console) {
+    std::cout << " : ";
+    Print(Out(), attrs | TermAttr::BackgroundBold, text);
+  }
+#endif
+  std::cout << std::endl;
+}
+
+bool testTerminal()
+{
+  std::cout << "testTerminal()\n";
+  using cm::StdIo::TermAttr;
+  testTerminalPrint(TermAttr::Normal, "Normal"_s);
+  testTerminalPrint(TermAttr::ForegroundBold, "Bold"_s);
+  testTerminalPrint(TermAttr::ForegroundBlack, "Black"_s);
+  testTerminalPrint(TermAttr::ForegroundBlue, "Blue"_s);
+  testTerminalPrint(TermAttr::ForegroundCyan, "Cyan"_s);
+  testTerminalPrint(TermAttr::ForegroundGreen, "Green"_s);
+  testTerminalPrint(TermAttr::ForegroundMagenta, "Magenta"_s);
+  testTerminalPrint(TermAttr::ForegroundRed, "Red"_s);
+  testTerminalPrint(TermAttr::ForegroundWhite, "White"_s);
+  testTerminalPrint(TermAttr::ForegroundYellow, "Yellow"_s);
+  testTerminalPrint({ TermAttr::ForegroundBold, TermAttr::BackgroundBlack },
+                    "Bold on Black"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundBlue },
+                    "Black on Blue"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundCyan },
+                    "Black on Cyan"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundGreen },
+                    "Black on Green"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundMagenta },
+                    "Black on Magenta"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundRed },
+                    "Black on Red"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundWhite },
+                    "Black on White"_s);
+  testTerminalPrint({ TermAttr::ForegroundBlack, TermAttr::BackgroundYellow },
+                    "Black on Yellow"_s);
+  return true;
+}
+
 cm::string_view const kUsage = "usage: CMakeLibTests testStdIo [--stdin]"_s;
 
 }
@@ -91,5 +140,6 @@ int testStdIo(int argc, char* argv[])
   return runTests({
     testStream,
     testConsole,
+    testTerminal,
   });
 }

+ 1 - 0
bootstrap

@@ -491,6 +491,7 @@ CMAKE_CXX_SOURCES="\
   cmStdIoConsole \
   cmStdIoInit \
   cmStdIoStream \
+  cmStdIoTerminal \
   cmString \
   cmStringAlgorithms \
   cmStringReplaceHelper \