Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for builtin terminal colors. #974

Merged
merged 13 commits into from
Dec 15, 2018
151 changes: 114 additions & 37 deletions include/fmt/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,25 @@ enum class color : uint32_t {
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color

enum class terminal_color : uint8_t {
Rakete1111 marked this conversation as resolved.
Show resolved Hide resolved
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
}; // enum class terminal_color

enum class emphasis : uint8_t {
bold = 1,
italic = 1 << 1,
Expand All @@ -215,6 +234,32 @@ struct rgb {
uint8_t b;
};

namespace internal {

// color is a struct of either a rgb color or a terminal color.
struct color_type {
Rakete1111 marked this conversation as resolved.
Show resolved Hide resolved
FMT_CONSTEXPR color_type() FMT_NOEXCEPT
: is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT
: is_rgb(true), value{} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why call value{} if it is initialized below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was some another attempt to fix gcc 4.4 that we don't need anymore :). I'll remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitaut Nevermind my previous comment. They are initialized because they have to be for constexpr and it seems like VS2013 doesn't implement directly initializing the union in a member initializer list. So I have to leave it like it is :(

value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT
: is_rgb(true), value{} {
value.rgb_color = (rgb_color.r << 16) + (rgb_color.g << 8) + rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace internal

// Experimental text formatting support.
class text_style {
public:
Expand All @@ -227,18 +272,18 @@ class text_style {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
foreground_color.r |= rhs.foreground_color.r;
foreground_color.g |= rhs.foreground_color.g;
foreground_color.b |= rhs.foreground_color.b;
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
throw format_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}

if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
background_color.r |= rhs.background_color.r;
background_color.g |= rhs.background_color.g;
background_color.b |= rhs.background_color.b;
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
throw format_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}

ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
Expand All @@ -256,18 +301,18 @@ class text_style {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
foreground_color.r &= rhs.foreground_color.r;
foreground_color.g &= rhs.foreground_color.g;
foreground_color.b &= rhs.foreground_color.b;
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
throw format_error("can't AND a terminal color");
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}

if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
background_color.r &= rhs.background_color.r;
background_color.g &= rhs.background_color.g;
background_color.b &= rhs.background_color.b;
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
throw format_error("can't AND a terminal color");
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}

ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
Expand All @@ -289,11 +334,11 @@ class text_style {
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR rgb get_foreground() const FMT_NOEXCEPT {
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
assert(has_foreground() && "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR rgb get_background() const FMT_NOEXCEPT {
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
assert(has_background() && "no background specified for this style");
return background_color;
}
Expand All @@ -303,32 +348,37 @@ class text_style {
}

private:
FMT_CONSTEXPR text_style(bool is_foreground, rgb text_color) FMT_NOEXCEPT
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}

friend FMT_CONSTEXPR_DECL text_style fg(rgb foreground) FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(rgb background) FMT_NOEXCEPT;

rgb foreground_color;
rgb background_color;
FMT_CONSTEXPR text_style(bool is_foreground,
internal::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}

friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
FMT_NOEXCEPT;

internal::color_type foreground_color;
internal::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};

FMT_CONSTEXPR text_style fg(rgb foreground) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}

FMT_CONSTEXPR text_style bg(rgb background) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}

Expand All @@ -340,10 +390,37 @@ namespace internal {

template <typename Char>
struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(rgb color, const char * esc) FMT_NOEXCEPT {
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, const char * esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == internal::data::BACKGROUND_COLOR;
uint8_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background)
value += 10;

std::size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');

if (value >= 100) {
buffer[index++] = static_cast<Char>('1');
value %= 100;
}
buffer[index++] = static_cast<Char>('0' + value / 10);
buffer[index++] = static_cast<Char>('0' + value % 10);

buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}

for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
Expand Down Expand Up @@ -388,14 +465,14 @@ struct ansi_color_escape {

template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char>
make_foreground_color(rgb color) FMT_NOEXCEPT {
return ansi_color_escape<Char>(color, internal::data::FOREGROUND_COLOR);
make_foreground_color(internal::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, internal::data::FOREGROUND_COLOR);
}

template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char>
make_background_color(rgb color) FMT_NOEXCEPT {
return ansi_color_escape<Char>(color, internal::data::BACKGROUND_COLOR);
make_background_color(internal::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, internal::data::BACKGROUND_COLOR);
}

template <typename Char>
Expand Down
10 changes: 10 additions & 0 deletions test/format-impl-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,14 @@ TEST(ColorsTest, Colors) {
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
"\x1b[38;2;000;000;255mblue log\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
"\x1b[31mtred\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
"\x1b[46mtcyan\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
"\x1b[92mtbgreen\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
"\x1b[105mtbmagenta\x1b[0m");
}