Skip to content

Commit 93fd473

Browse files
Rakete1111vitaut
authored andcommitted
Add support for builtin terminal colors. (fmtlib#974)
1 parent 61ad543 commit 93fd473

File tree

2 files changed

+124
-37
lines changed

2 files changed

+124
-37
lines changed

include/fmt/color.h

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,25 @@ enum class color : uint32_t {
191191
yellow_green = 0x9ACD32 // rgb(154,205,50)
192192
}; // enum class color
193193

194+
enum class terminal_color : uint8_t {
195+
black = 30,
196+
red,
197+
green,
198+
yellow,
199+
blue,
200+
magenta,
201+
cyan,
202+
white,
203+
bright_black = 90,
204+
bright_red,
205+
bright_green,
206+
bright_yellow,
207+
bright_blue,
208+
bright_magenta,
209+
bright_cyan,
210+
bright_white
211+
}; // enum class terminal_color
212+
194213
enum class emphasis : uint8_t {
195214
bold = 1,
196215
italic = 1 << 1,
@@ -215,6 +234,32 @@ struct rgb {
215234
uint8_t b;
216235
};
217236

237+
namespace internal {
238+
239+
// color is a struct of either a rgb color or a terminal color.
240+
struct color_type {
241+
FMT_CONSTEXPR color_type() FMT_NOEXCEPT
242+
: is_rgb(), value{} {}
243+
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT
244+
: is_rgb(true), value{} {
245+
value.rgb_color = static_cast<uint32_t>(rgb_color);
246+
}
247+
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT
248+
: is_rgb(true), value{} {
249+
value.rgb_color = (rgb_color.r << 16) + (rgb_color.g << 8) + rgb_color.b;
250+
}
251+
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT
252+
: is_rgb(), value{} {
253+
value.term_color = static_cast<uint8_t>(term_color);
254+
}
255+
bool is_rgb;
256+
union color_union {
257+
uint8_t term_color;
258+
uint32_t rgb_color;
259+
} value;
260+
};
261+
} // namespace internal
262+
218263
// Experimental text formatting support.
219264
class text_style {
220265
public:
@@ -227,18 +272,18 @@ class text_style {
227272
set_foreground_color = rhs.set_foreground_color;
228273
foreground_color = rhs.foreground_color;
229274
} else if (rhs.set_foreground_color) {
230-
foreground_color.r |= rhs.foreground_color.r;
231-
foreground_color.g |= rhs.foreground_color.g;
232-
foreground_color.b |= rhs.foreground_color.b;
275+
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
276+
throw format_error("can't OR a terminal color");
277+
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
233278
}
234279

235280
if (!set_background_color) {
236281
set_background_color = rhs.set_background_color;
237282
background_color = rhs.background_color;
238283
} else if (rhs.set_background_color) {
239-
background_color.r |= rhs.background_color.r;
240-
background_color.g |= rhs.background_color.g;
241-
background_color.b |= rhs.background_color.b;
284+
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
285+
throw format_error("can't OR a terminal color");
286+
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
242287
}
243288

244289
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
@@ -256,18 +301,18 @@ class text_style {
256301
set_foreground_color = rhs.set_foreground_color;
257302
foreground_color = rhs.foreground_color;
258303
} else if (rhs.set_foreground_color) {
259-
foreground_color.r &= rhs.foreground_color.r;
260-
foreground_color.g &= rhs.foreground_color.g;
261-
foreground_color.b &= rhs.foreground_color.b;
304+
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
305+
throw format_error("can't AND a terminal color");
306+
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
262307
}
263308

264309
if (!set_background_color) {
265310
set_background_color = rhs.set_background_color;
266311
background_color = rhs.background_color;
267312
} else if (rhs.set_background_color) {
268-
background_color.r &= rhs.background_color.r;
269-
background_color.g &= rhs.background_color.g;
270-
background_color.b &= rhs.background_color.b;
313+
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
314+
throw format_error("can't AND a terminal color");
315+
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
271316
}
272317

273318
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
@@ -289,11 +334,11 @@ class text_style {
289334
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
290335
return static_cast<uint8_t>(ems) != 0;
291336
}
292-
FMT_CONSTEXPR rgb get_foreground() const FMT_NOEXCEPT {
337+
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
293338
assert(has_foreground() && "no foreground specified for this style");
294339
return foreground_color;
295340
}
296-
FMT_CONSTEXPR rgb get_background() const FMT_NOEXCEPT {
341+
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
297342
assert(has_background() && "no background specified for this style");
298343
return background_color;
299344
}
@@ -303,32 +348,37 @@ class text_style {
303348
}
304349

305350
private:
306-
FMT_CONSTEXPR text_style(bool is_foreground, rgb text_color) FMT_NOEXCEPT
307-
: set_foreground_color(), set_background_color(), ems() {
308-
if (is_foreground) {
309-
foreground_color = text_color;
310-
set_foreground_color = true;
311-
} else {
312-
background_color = text_color;
313-
set_background_color = true;
314-
}
315-
}
316-
317-
friend FMT_CONSTEXPR_DECL text_style fg(rgb foreground) FMT_NOEXCEPT;
318-
friend FMT_CONSTEXPR_DECL text_style bg(rgb background) FMT_NOEXCEPT;
319-
320-
rgb foreground_color;
321-
rgb background_color;
351+
FMT_CONSTEXPR text_style(bool is_foreground,
352+
internal::color_type text_color) FMT_NOEXCEPT
353+
: set_foreground_color(),
354+
set_background_color(),
355+
ems() {
356+
if (is_foreground) {
357+
foreground_color = text_color;
358+
set_foreground_color = true;
359+
} else {
360+
background_color = text_color;
361+
set_background_color = true;
362+
}
363+
}
364+
365+
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
366+
FMT_NOEXCEPT;
367+
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
368+
FMT_NOEXCEPT;
369+
370+
internal::color_type foreground_color;
371+
internal::color_type background_color;
322372
bool set_foreground_color;
323373
bool set_background_color;
324374
emphasis ems;
325375
};
326376

327-
FMT_CONSTEXPR text_style fg(rgb foreground) FMT_NOEXCEPT {
377+
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
328378
return text_style(/*is_foreground=*/true, foreground);
329379
}
330380

331-
FMT_CONSTEXPR text_style bg(rgb background) FMT_NOEXCEPT {
381+
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
332382
return text_style(/*is_foreground=*/false, background);
333383
}
334384

@@ -340,10 +390,37 @@ namespace internal {
340390

341391
template <typename Char>
342392
struct ansi_color_escape {
343-
FMT_CONSTEXPR ansi_color_escape(rgb color, const char * esc) FMT_NOEXCEPT {
393+
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, const char * esc) FMT_NOEXCEPT {
394+
// If we have a terminal color, we need to output another escape code
395+
// sequence.
396+
if (!text_color.is_rgb) {
397+
bool is_background = esc == internal::data::BACKGROUND_COLOR;
398+
uint8_t value = text_color.value.term_color;
399+
// Background ASCII codes are the same as the foreground ones but with
400+
// 10 more.
401+
if (is_background)
402+
value += 10;
403+
404+
std::size_t index = 0;
405+
buffer[index++] = static_cast<Char>('\x1b');
406+
buffer[index++] = static_cast<Char>('[');
407+
408+
if (value >= 100) {
409+
buffer[index++] = static_cast<Char>('1');
410+
value %= 100;
411+
}
412+
buffer[index++] = static_cast<Char>('0' + value / 10);
413+
buffer[index++] = static_cast<Char>('0' + value % 10);
414+
415+
buffer[index++] = static_cast<Char>('m');
416+
buffer[index++] = static_cast<Char>('\0');
417+
return;
418+
}
419+
344420
for (int i = 0; i < 7; i++) {
345421
buffer[i] = static_cast<Char>(esc[i]);
346422
}
423+
rgb color(text_color.value.rgb_color);
347424
to_esc(color.r, buffer + 7, ';');
348425
to_esc(color.g, buffer + 11, ';');
349426
to_esc(color.b, buffer + 15, 'm');
@@ -388,14 +465,14 @@ struct ansi_color_escape {
388465

389466
template <typename Char>
390467
FMT_CONSTEXPR ansi_color_escape<Char>
391-
make_foreground_color(rgb color) FMT_NOEXCEPT {
392-
return ansi_color_escape<Char>(color, internal::data::FOREGROUND_COLOR);
468+
make_foreground_color(internal::color_type foreground) FMT_NOEXCEPT {
469+
return ansi_color_escape<Char>(foreground, internal::data::FOREGROUND_COLOR);
393470
}
394471

395472
template <typename Char>
396473
FMT_CONSTEXPR ansi_color_escape<Char>
397-
make_background_color(rgb color) FMT_NOEXCEPT {
398-
return ansi_color_escape<Char>(color, internal::data::BACKGROUND_COLOR);
474+
make_background_color(internal::color_type background) FMT_NOEXCEPT {
475+
return ansi_color_escape<Char>(background, internal::data::BACKGROUND_COLOR);
399476
}
400477

401478
template <typename Char>

test/format-impl-test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,14 @@ TEST(ColorsTest, Colors) {
233233
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
234234
"\x1b[38;2;000;000;255mblue log\x1b[0m");
235235
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
236+
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
237+
"\x1b[31mtred\x1b[0m");
238+
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
239+
"\x1b[46mtcyan\x1b[0m");
240+
EXPECT_WRITE(stdout,
241+
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
242+
"\x1b[92mtbgreen\x1b[0m");
243+
EXPECT_WRITE(stdout,
244+
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
245+
"\x1b[105mtbmagenta\x1b[0m");
236246
}

0 commit comments

Comments
 (0)