1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-15 20:46:37 +08:00

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
This commit is contained in:
Brad King
2025-05-06 13:33:22 -04:00
parent 03c97133e8
commit 329d755dbd
5 changed files with 279 additions and 0 deletions

View File

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

170
Source/cmStdIoTerminal.cxx Normal file
View File

@@ -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
Source/cmStdIoTerminal.h Normal file
View File

@@ -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);
}
}

View File

@@ -9,6 +9,7 @@
#include "cmStdIoConsole.h" #include "cmStdIoConsole.h"
#include "cmStdIoInit.h" #include "cmStdIoInit.h"
#include "cmStdIoStream.h" #include "cmStdIoStream.h"
#include "cmStdIoTerminal.h"
#include "testCommon.h" #include "testCommon.h"
@@ -70,6 +71,54 @@ bool testConsole()
return true; 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; cm::string_view const kUsage = "usage: CMakeLibTests testStdIo [--stdin]"_s;
} }
@@ -91,5 +140,6 @@ int testStdIo(int argc, char* argv[])
return runTests({ return runTests({
testStream, testStream,
testConsole, testConsole,
testTerminal,
}); });
} }

View File

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