FE
0.13.1
Header-only C++ frontend library
Toggle main menu visibility
Loading...
Searching...
No Matches
term.h
Go to the documentation of this file.
1
#pragma once
2
3
#include <cstdlib>
4
#include <cstring>
5
6
#include <atomic>
7
#include <iostream>
8
#include <optional>
9
#include <ostream>
10
#include <string_view>
11
12
#ifdef _WIN32
13
# ifndef WIN32_LEAN_AND_MEAN
14
# define WIN32_LEAN_AND_MEAN
15
# endif
16
# ifndef NOMINMAX
17
# define NOMINMAX
18
# endif
19
# include <windows.h>
20
#else
21
# include <sys/ioctl.h>
22
# include <unistd.h>
23
#endif
24
25
#include "
fe/assert.h
"
26
#include "
fe/format.h
"
27
#include "
fe/restore.h
"
28
29
/// Lightweight stream-based terminal colors for diagnostics and CLI output.
30
///
31
/// Include `fe/term.h` and stream a @ref fe::term::FG value into an `std::ostream`:
32
/// ```
33
/// std::cerr << fe::term::FG::Red << "error: " << fe::term::FG::Reset << "unexpected token\n";
34
/// ```
35
///
36
/// The current behavior is controlled via @ref fe::term::Mode and can be overridden with
37
/// @ref fe::term::set_mode. In @ref fe::term::Mode::Auto, colors are emitted only for
38
/// `std::cout`, `std::cerr`, `std::clog`, or streams sharing those buffers when they refer to
39
/// terminals. FE also respects the common environment conventions `NO_COLOR`, `CLICOLOR=0`, and
40
/// `CLICOLOR_FORCE` (unless it is set to `0`).
41
///
42
/// Note that a `std::formatter` never sees the destination stream - it formats into a detached buffer.
43
/// Hence, embedding a @ref fe::term::FG value in a `std::format`/`std::print` format string resolves
44
/// @ref fe::term::Mode::Auto to "no color".
45
/// If you emit colors this way, call @ref fe::term::resolve_mode once at startup to decide
46
/// @ref fe::term::Mode::Auto up front based on a representative stream.
47
///
48
/// Use @ref fe::term::use_color to branch on whether color will actually be emitted, e.g. to keep a
49
/// plain-text fallback in sync with the colored rendering.
50
namespace
fe::term
{
51
52
/// Controls whether color escape sequences are emitted.
53
enum class
Mode
{
54
Auto
,
55
Never
,
56
Always
,
57
};
58
59
/// Foreground colors that can be streamed into an `std::ostream`.
60
enum class
FG
{
61
Black
,
62
Red
,
63
Green
,
64
Yellow
,
65
Blue
,
66
Magenta
,
67
Cyan
,
68
Gray
,
69
Grey
=
Gray
,
70
Reset
,
71
};
72
73
namespace
detail {
74
75
enum class
Stream {
76
Unknown,
77
Stdout,
78
Stderr,
79
};
80
81
inline
bool
env_set(
const
char
* name)
noexcept
{
82
auto
* value = std::getenv(name);
83
return
value && *value !=
'\0'
;
84
}
85
86
inline
bool
env_is(
const
char
* name,
const
char
* expected)
noexcept
{
87
auto
* value = std::getenv(name);
88
return
value && std::strcmp(value, expected) == 0;
89
}
90
91
inline
Mode
default_mode() noexcept {
92
if
(env_set(
"NO_COLOR"
))
return
Mode::Never
;
93
if
(env_set(
"CLICOLOR_FORCE"
) && !env_is(
"CLICOLOR_FORCE"
,
"0"
))
return
Mode::Always
;
94
if
(env_is(
"CLICOLOR"
,
"0"
))
return
Mode::Never
;
95
return
Mode::Auto
;
96
}
97
98
inline
std::atomic<Mode>& current_mode() noexcept {
99
static
std::atomic<Mode>
mode
(default_mode());
100
return
mode
;
101
}
102
103
inline
std::streambuf* stdout_rdbuf() noexcept {
104
static
std::streambuf* buf = std::cout.rdbuf();
105
return
buf;
106
}
107
108
inline
std::streambuf* stderr_rdbuf() noexcept {
109
static
std::streambuf* buf = std::cerr.rdbuf();
110
return
buf;
111
}
112
113
inline
std::streambuf* clog_rdbuf() noexcept {
114
static
std::streambuf* buf = std::clog.rdbuf();
115
return
buf;
116
}
117
118
inline
Stream stream(std::ostream& os)
noexcept
{
119
auto
*
const
buf = os.rdbuf();
120
if
(buf == stdout_rdbuf())
return
Stream::Stdout;
121
if
(buf == stderr_rdbuf() || buf == clog_rdbuf())
return
Stream::Stderr;
122
return
Stream::Unknown;
123
}
124
125
#ifdef _WIN32
126
inline
bool
enable_vt(HANDLE handle)
noexcept
{
127
if
(handle == INVALID_HANDLE_VALUE)
return
false
;
128
129
DWORD
mode
= 0;
130
if
(!GetConsoleMode(handle, &
mode
))
return
false
;
131
if
(
mode
& ENABLE_VIRTUAL_TERMINAL_PROCESSING)
return
true
;
132
return
SetConsoleMode(handle,
mode
| ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
133
}
134
135
inline
bool
is_terminal(Stream s)
noexcept
{
136
switch
(s) {
137
case
Stream::Stdout: {
138
static
bool
stdout_is_terminal = enable_vt(GetStdHandle(STD_OUTPUT_HANDLE));
139
return
stdout_is_terminal;
140
}
141
case
Stream::Stderr: {
142
static
bool
stderr_is_terminal = enable_vt(GetStdHandle(STD_ERROR_HANDLE));
143
return
stderr_is_terminal;
144
}
145
default
:
return
false
;
146
}
147
}
148
#else
149
inline
bool
is_terminal(Stream s)
noexcept
{
150
switch
(s) {
151
case
Stream::Stdout: {
152
static
bool
stdout_is_terminal = ::isatty(STDOUT_FILENO) != 0;
153
return
stdout_is_terminal;
154
}
155
case
Stream::Stderr: {
156
static
bool
stderr_is_terminal = ::isatty(STDERR_FILENO) != 0;
157
return
stderr_is_terminal;
158
}
159
default
:
return
false
;
160
}
161
}
162
#endif
163
164
constexpr
std::string_view sgr(
FG
color)
noexcept
{
165
// clang-format off
166
switch
(color) {
167
case
FG::Black
:
return
"\033[30m"
;
168
case
FG::Red
:
return
"\033[31m"
;
169
case
FG::Green
:
return
"\033[32m"
;
170
case
FG::Yellow
:
return
"\033[33m"
;
171
case
FG::Blue
:
return
"\033[34m"
;
172
case
FG::Magenta
:
return
"\033[35m"
;
173
case
FG::Cyan
:
return
"\033[36m"
;
174
case
FG::Gray
:
return
"\033[90m"
;
175
case
FG::Reset
:
return
"\033[39m"
;
176
default
:
fe::unreachable
();
177
}
178
// clang-format on
179
}
180
181
}
// namespace detail
182
183
/// Returns the current terminal color mode.
184
inline
Mode
mode
() noexcept {
return
detail::current_mode().load(std::memory_order_relaxed); }
185
186
/// Whether color escape sequences are emitted for @p os right now.
187
/// In Mode::Auto this is decided by whether @p os refers to a terminal, so a detached buffer
188
/// (anything a `std::formatter` writes into) yields `false`; see @ref resolve_mode.
189
/// Use this to keep a plain-text fallback in sync with what @ref operator<<(std::ostream&, FG) will emit,
190
/// e.g. to spell out a marker only when it cannot be conveyed by color.
191
inline
bool
use_color
(std::ostream& os)
noexcept
{
192
// clang-format off
193
switch
(
mode
()) {
194
case
Mode::Always
:
return
true
;
195
case
Mode::Never
:
return
false
;
196
case
Mode::Auto
:
return
detail::is_terminal(detail::stream(os));
197
default
:
fe::unreachable
();
198
}
199
// clang-format on
200
}
201
202
/// Number of columns of the terminal @p os refers to.
203
/// Unlike @ref use_color, this ignores @ref Mode and always asks the actual stream.
204
/// @returns `std::nullopt` if @p os is not a terminal or its size cannot be determined.
205
inline
std::optional<size_t>
width
(std::ostream& os)
noexcept
{
206
auto
s = detail::stream(os);
207
if
(!detail::is_terminal(s))
return
{};
208
209
#ifdef _WIN32
210
auto
handle = GetStdHandle(s == detail::Stream::Stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
211
if
(CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(handle, &info)) {
212
if
(
auto
cols = info.srWindow.Right - info.srWindow.Left + 1; cols > 0)
return
size_t(cols);
213
}
214
#else
215
auto
fd = s == detail::Stream::Stdout ? STDOUT_FILENO : STDERR_FILENO;
216
if
(winsize ws; ::ioctl(fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
return
size_t
(ws.ws_col);
217
#endif
218
return
{};
219
}
220
221
/// Overrides the current terminal color mode.
222
inline
void
set_mode
(
Mode
m)
noexcept
{ detail::current_mode().store(m, std::memory_order_relaxed); }
223
224
/// Overrides the color mode for the duration of the scope.
225
/// @warning The mode is global, so this affects every stream - and every thread - while it is alive.
226
using
ScopedMode
=
Restore<Mode, &mode, &set_mode>
;
227
228
/// Resolves Mode::Auto to Mode::Always or Mode::Never, depending on whether @p os refers to a terminal.
229
/// A `std::formatter` cannot see its destination stream, so FG values embedded in a
230
/// `std::format`/`std::print` format string never detect a terminal in Mode::Auto.
231
/// Call this once at startup to make formatted output colored as well; explicit modes are left untouched:
232
/// ```
233
/// fe::term::resolve_mode(); // decide based on stderr
234
/// std::print(std::cerr, "{}error:{} ...", fe::term::FG::Red, fe::term::FG::Reset);
235
/// ```
236
inline
void
resolve_mode
(std::ostream& os = std::cerr)
noexcept
{
237
if
(
mode
() ==
Mode::Auto
)
set_mode
(detail::is_terminal(detail::stream(os)) ?
Mode::Always
:
Mode::Never
);
238
}
239
240
/// Streams the ANSI escape sequence for @p color when colors are enabled for @p os.
241
inline
std::ostream&
operator<<
(std::ostream& os,
FG
color) {
242
if
(
use_color
(os)) {
243
auto
esc = detail::sgr(color);
244
os.write(esc.data(), esc.size());
245
}
246
return
os;
247
}
248
249
}
// namespace fe::term
250
251
#ifndef DOXYGEN
252
template
<>
253
struct
std::formatter<
fe
::term::FG> :
fe::ostream_formatter
{};
254
#endif
assert.h
fe::Restore
RAII guard that restores a value at the end of the scope.
Definition
restore.h:10
format.h
fe::term
Lightweight stream-based terminal colors for diagnostics and CLI output.
Definition
term.h:50
fe::term::mode
Mode mode() noexcept
Returns the current terminal color mode.
Definition
term.h:184
fe::term::ScopedMode
Restore< Mode, &mode, &set_mode > ScopedMode
Overrides the color mode for the duration of the scope.
Definition
term.h:226
fe::term::FG
FG
Foreground colors that can be streamed into an std::ostream.
Definition
term.h:60
fe::term::FG::Cyan
@ Cyan
Definition
term.h:67
fe::term::FG::Yellow
@ Yellow
Definition
term.h:64
fe::term::FG::Reset
@ Reset
Definition
term.h:70
fe::term::FG::Blue
@ Blue
Definition
term.h:65
fe::term::FG::Gray
@ Gray
Definition
term.h:68
fe::term::FG::Magenta
@ Magenta
Definition
term.h:66
fe::term::FG::Grey
@ Grey
Definition
term.h:69
fe::term::FG::Green
@ Green
Definition
term.h:63
fe::term::FG::Black
@ Black
Definition
term.h:61
fe::term::FG::Red
@ Red
Definition
term.h:62
fe::term::Mode
Mode
Controls whether color escape sequences are emitted.
Definition
term.h:53
fe::term::Mode::Auto
@ Auto
Definition
term.h:54
fe::term::Mode::Always
@ Always
Definition
term.h:56
fe::term::Mode::Never
@ Never
Definition
term.h:55
fe::term::width
std::optional< size_t > width(std::ostream &os) noexcept
Number of columns of the terminal os refers to.
Definition
term.h:205
fe::term::resolve_mode
void resolve_mode(std::ostream &os=std::cerr) noexcept
Resolves Mode::Auto to Mode::Always or Mode::Never, depending on whether os refers to a terminal.
Definition
term.h:236
fe::term::operator<<
std::ostream & operator<<(std::ostream &os, FG color)
Streams the ANSI escape sequence for color when colors are enabled for os.
Definition
term.h:241
fe::term::use_color
bool use_color(std::ostream &os) noexcept
Whether color escape sequences are emitted for os right now.
Definition
term.h:191
fe::term::set_mode
void set_mode(Mode m) noexcept
Overrides the current terminal color mode.
Definition
term.h:222
fe
Definition
algo.h:17
fe::ostream_formatter
basic_ostream_formatter< char > ostream_formatter
Definition
format.h:64
fe::unreachable
void unreachable()
Definition
assert.h:31
restore.h
fe
term.h
Generated by
1.18.0