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:
@@ -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
170
Source/cmStdIoTerminal.cxx
Normal 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
56
Source/cmStdIoTerminal.h
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user