FE 0.9.0
Header-only C++ frontend library
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 <ostream>
9#include <string_view>
10
11#ifdef _WIN32
12# ifndef WIN32_LEAN_AND_MEAN
13# define WIN32_LEAN_AND_MEAN
14# endif
15# ifndef NOMINMAX
16# define NOMINMAX
17# endif
18# include <windows.h>
19#else
20# include <unistd.h>
21#endif
22
23#include "fe/assert.h"
24#include "fe/format.h"
25
26/// Lightweight stream-based terminal colors for diagnostics and CLI output.
27///
28/// Include `fe/term.h` and stream a @ref fe::term::FG value into an `std::ostream`:
29/// ```
30/// std::cerr << fe::term::FG::Red << "error: " << fe::term::FG::Reset << "unexpected token\n";
31/// ```
32///
33/// The current behavior is controlled via @ref fe::term::Mode and can be overridden with
34/// @ref fe::term::set_mode. In @ref fe::term::Mode::Auto, colors are emitted only for
35/// `std::cout`, `std::cerr`, `std::clog`, or streams sharing those buffers when they refer to
36/// terminals. FE also respects the common environment conventions `NO_COLOR`, `CLICOLOR=0`, and
37/// `CLICOLOR_FORCE` (unless it is set to `0`).
38namespace fe::term {
39
40/// Controls whether color escape sequences are emitted.
41enum class Mode {
45};
46
47/// Foreground colors that can be streamed into an `std::ostream`.
60
61namespace detail {
62
63enum class Stream {
64 Unknown,
65 Stdout,
66 Stderr,
67};
68
69inline bool env_set(const char* name) noexcept {
70 auto* value = std::getenv(name);
71 return value && *value != '\0';
72}
73
74inline bool env_is(const char* name, const char* expected) noexcept {
75 auto* value = std::getenv(name);
76 return value && std::strcmp(value, expected) == 0;
77}
78
79inline Mode default_mode() noexcept {
80 if (env_set("NO_COLOR")) return Mode::Never;
81 if (env_set("CLICOLOR_FORCE") && !env_is("CLICOLOR_FORCE", "0")) return Mode::Always;
82 if (env_is("CLICOLOR", "0")) return Mode::Never;
83 return Mode::Auto;
84}
85
86inline std::atomic<Mode>& current_mode() noexcept {
87 static std::atomic<Mode> mode(default_mode());
88 return mode;
89}
90
91inline std::streambuf* stdout_rdbuf() noexcept {
92 static std::streambuf* buf = std::cout.rdbuf();
93 return buf;
94}
95
96inline std::streambuf* stderr_rdbuf() noexcept {
97 static std::streambuf* buf = std::cerr.rdbuf();
98 return buf;
99}
100
101inline std::streambuf* clog_rdbuf() noexcept {
102 static std::streambuf* buf = std::clog.rdbuf();
103 return buf;
104}
105
106inline Stream stream(std::ostream& os) noexcept {
107 auto* const buf = os.rdbuf();
108 if (buf == stdout_rdbuf()) return Stream::Stdout;
109 if (buf == stderr_rdbuf() || buf == clog_rdbuf()) return Stream::Stderr;
110 return Stream::Unknown;
111}
112
113#ifdef _WIN32
114inline bool enable_vt(HANDLE handle) noexcept {
115 if (handle == INVALID_HANDLE_VALUE) return false;
116
117 DWORD mode = 0;
118 if (!GetConsoleMode(handle, &mode)) return false;
119 if (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) return true;
120 return SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
121}
122
123inline bool is_terminal(Stream s) noexcept {
124 switch (s) {
125 case Stream::Stdout: {
126 static bool stdout_is_terminal = enable_vt(GetStdHandle(STD_OUTPUT_HANDLE));
127 return stdout_is_terminal;
128 }
129 case Stream::Stderr: {
130 static bool stderr_is_terminal = enable_vt(GetStdHandle(STD_ERROR_HANDLE));
131 return stderr_is_terminal;
132 }
133 default: return false;
134 }
135}
136#else
137inline bool is_terminal(Stream s) noexcept {
138 switch (s) {
139 case Stream::Stdout: {
140 static bool stdout_is_terminal = ::isatty(STDOUT_FILENO) != 0;
141 return stdout_is_terminal;
142 }
143 case Stream::Stderr: {
144 static bool stderr_is_terminal = ::isatty(STDERR_FILENO) != 0;
145 return stderr_is_terminal;
146 }
147 default: return false;
148 }
149}
150#endif
151
152inline bool use_color(std::ostream& os) noexcept {
153 // clang-format off
154 switch (current_mode().load(std::memory_order_relaxed)) {
155 case Mode::Always: return true;
156 case Mode::Never: return false;
157 case Mode::Auto: return is_terminal(stream(os));
158 default: fe::unreachable();
159 }
160 // clang-format on
161}
162
163constexpr std::string_view sgr(FG color) noexcept {
164 // clang-format off
165 switch (color) {
166 case FG::Black: return "\033[30m";
167 case FG::Red: return "\033[31m";
168 case FG::Green: return "\033[32m";
169 case FG::Yellow: return "\033[33m";
170 case FG::Blue: return "\033[34m";
171 case FG::Magenta: return "\033[35m";
172 case FG::Cyan: return "\033[36m";
173 case FG::Gray: return "\033[90m";
174 case FG::Reset: return "\033[39m";
175 default: fe::unreachable();
176 }
177 // clang-format on
178}
179
180} // namespace detail
181
182/// Returns the current terminal color mode.
183inline Mode mode() noexcept { return detail::current_mode().load(std::memory_order_relaxed); }
184
185/// Overrides the current terminal color mode.
186inline void set_mode(Mode m) noexcept { detail::current_mode().store(m, std::memory_order_relaxed); }
187
188/// Streams the ANSI escape sequence for @p color when colors are enabled for @p os.
189inline std::ostream& operator<<(std::ostream& os, FG color) {
190 if (detail::use_color(os)) {
191 auto esc = detail::sgr(color);
192 os.write(esc.data(), esc.size());
193 }
194 return os;
195}
196
197} // namespace fe::term
198
199#ifndef DOXYGEN
200template<>
201struct std::formatter<fe::term::FG> : fe::ostream_formatter {};
202#endif
Lightweight stream-based terminal colors for diagnostics and CLI output.
Definition term.h:38
Mode mode() noexcept
Returns the current terminal color mode.
Definition term.h:183
FG
Foreground colors that can be streamed into an std::ostream.
Definition term.h:48
@ Magenta
Definition term.h:54
Mode
Controls whether color escape sequences are emitted.
Definition term.h:41
std::ostream & operator<<(std::ostream &os, FG color)
Streams the ANSI escape sequence for color when colors are enabled for os.
Definition term.h:189
void set_mode(Mode m) noexcept
Overrides the current terminal color mode.
Definition term.h:186
Definition arena.h:13
basic_ostream_formatter< char > ostream_formatter
Definition format.h:64
void unreachable()
Definition assert.h:19