aboutsummaryrefslogtreecommitdiff
path: root/gcc/text-art/style.cc
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2023-06-26 09:57:21 -0700
committerIan Lance Taylor <iant@golang.org>2023-06-26 09:57:21 -0700
commitaa1e672b5d99102b03eb5fb9c51609c45f62bff7 (patch)
tree886212591b1c9d127eaaf234a4a2e22452ea384a /gcc/text-art/style.cc
parent97e31a0a2a2d2273687fcdb4e5416aab1a2186e1 (diff)
parent3a39a31b8ae9c6465434aefa657f7fcc86f905c0 (diff)
downloadgcc-devel/gccgo.zip
gcc-devel/gccgo.tar.gz
gcc-devel/gccgo.tar.bz2
Merge from trunk revision 3a39a31b8ae9c6465434aefa657f7fcc86f905c0.devel/gccgo
Diffstat (limited to 'gcc/text-art/style.cc')
-rw-r--r--gcc/text-art/style.cc633
1 files changed, 633 insertions, 0 deletions
diff --git a/gcc/text-art/style.cc b/gcc/text-art/style.cc
new file mode 100644
index 0000000..85ad49e
--- /dev/null
+++ b/gcc/text-art/style.cc
@@ -0,0 +1,633 @@
+/* Classes for styling text cells (color, URLs).
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/types.h"
+#include "color-macros.h"
+
+using namespace text_art;
+
+/* class text_art::style. */
+
+style &
+style::set_style_url (const char *url)
+{
+ m_url.clear ();
+ while (*url)
+ m_url.push_back (*(url++));
+ return *this;
+}
+
+/* class text_art::style::color. */
+
+bool
+style::color::operator== (const style::color &other) const
+{
+ if (m_kind != other.m_kind)
+ return false;
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::NAMED:
+ return (u.m_named.m_name == other.u.m_named.m_name
+ && u.m_named.m_bright == other.u.m_named.m_bright);
+ case kind::BITS_8:
+ return u.m_8bit == other.u.m_8bit;
+ case kind::BITS_24:
+ return (u.m_24bit.r == other.u.m_24bit.r
+ && u.m_24bit.g == other.u.m_24bit.g
+ && u.m_24bit.b == other.u.m_24bit.b);
+ }
+}
+
+static void
+ensure_separator (pretty_printer *pp, bool &need_separator)
+{
+ if (need_separator)
+ pp_string (pp, COLOR_SEPARATOR);
+ need_separator = true;
+}
+
+void
+style::color::print_sgr (pretty_printer *pp,
+ bool fg,
+ bool &need_separator) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::NAMED:
+ {
+ static const char * const fg_normal[] = {"", // reset, for DEFAULT
+ COLOR_FG_BLACK,
+ COLOR_FG_RED,
+ COLOR_FG_GREEN,
+ COLOR_FG_YELLOW,
+ COLOR_FG_BLUE,
+ COLOR_FG_MAGENTA,
+ COLOR_FG_CYAN,
+ COLOR_FG_WHITE};
+ static const char * const fg_bright[] = {"", // reset, for DEFAULT
+ COLOR_FG_BRIGHT_BLACK,
+ COLOR_FG_BRIGHT_RED,
+ COLOR_FG_BRIGHT_GREEN,
+ COLOR_FG_BRIGHT_YELLOW,
+ COLOR_FG_BRIGHT_BLUE,
+ COLOR_FG_BRIGHT_MAGENTA,
+ COLOR_FG_BRIGHT_CYAN,
+ COLOR_FG_BRIGHT_WHITE};
+ static const char * const bg_normal[] = {"", // reset, for DEFAULT
+ COLOR_BG_BLACK,
+ COLOR_BG_RED,
+ COLOR_BG_GREEN,
+ COLOR_BG_YELLOW,
+ COLOR_BG_BLUE,
+ COLOR_BG_MAGENTA,
+ COLOR_BG_CYAN,
+ COLOR_BG_WHITE};
+ static const char * const bg_bright[] = {"", // reset, for DEFAULT
+ COLOR_BG_BRIGHT_BLACK,
+ COLOR_BG_BRIGHT_RED,
+ COLOR_BG_BRIGHT_GREEN,
+ COLOR_BG_BRIGHT_YELLOW,
+ COLOR_BG_BRIGHT_BLUE,
+ COLOR_BG_BRIGHT_MAGENTA,
+ COLOR_BG_BRIGHT_CYAN,
+ COLOR_BG_BRIGHT_WHITE};
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright));
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal));
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright));
+ gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal));
+ const char *const *arr;
+ if (fg)
+ arr = u.m_named.m_bright ? fg_bright : fg_normal;
+ else
+ arr = u.m_named.m_bright ? bg_bright : bg_normal;
+ const char *str = arr[(size_t)u.m_named.m_name];
+ if (strlen (str) > 0)
+ {
+ ensure_separator (pp, need_separator);
+ pp_string (pp, str);
+ }
+ }
+ break;
+ case kind::BITS_8:
+ {
+ ensure_separator (pp, need_separator);
+ if (fg)
+ pp_string (pp, "38");
+ else
+ pp_string (pp, "48");
+ pp_printf (pp, ";5;%i", (int)u.m_8bit);
+ }
+ break;
+ case kind::BITS_24:
+ {
+ ensure_separator (pp, need_separator);
+ if (fg)
+ pp_string (pp, "38");
+ else
+ pp_string (pp, "48");
+ pp_printf (pp, ";2;%i;%i;%i",
+ (int)u.m_24bit.r,
+ (int)u.m_24bit.g,
+ (int)u.m_24bit.b);
+ }
+ break;
+ }
+}
+
+/* class text_art::style. */
+
+/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
+ GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or
+ "CUMULATIVE", which affects whether we need to respecify all attributes
+ at each SGR, or can accumulate them. Looks like we can't rely on the value
+ of this, so we have to emit a single SGR for all changes, with a "0" reset
+ at the front, forcing it to be effectively replacing. */
+
+void
+style::print_changes (pretty_printer *pp,
+ const style &old_style,
+ const style &new_style)
+{
+ if (pp_show_color (pp))
+ {
+ bool needs_sgr = ((old_style.m_bold != new_style.m_bold)
+ || (old_style.m_underscore != new_style.m_underscore)
+ || (old_style.m_blink != new_style.m_blink)
+ || (old_style.m_fg_color != new_style.m_fg_color)
+ || (old_style.m_bg_color != new_style.m_bg_color));
+ if (needs_sgr)
+ {
+ bool emit_reset = (old_style.m_bold
+ || new_style.m_bold
+ || old_style.m_underscore
+ || new_style.m_underscore
+ || old_style.m_blink
+ || new_style.m_blink);
+ bool need_separator = false;
+
+ pp_string (pp, SGR_START);
+ if (emit_reset)
+ {
+ pp_string (pp, COLOR_NONE);
+ need_separator = true;
+ }
+ if (new_style.m_bold)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_BOLD);
+ }
+ if (new_style.m_underscore)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_UNDERSCORE);
+ }
+ if (new_style.m_blink)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_BLINK);
+ }
+ new_style.m_fg_color.print_sgr (pp, true, need_separator);
+ new_style.m_bg_color.print_sgr (pp, false, need_separator);
+ pp_string (pp, SGR_END);
+ }
+ }
+
+ if (old_style.m_url != new_style.m_url)
+ {
+ if (!old_style.m_url.empty ())
+ pp_end_url (pp);
+ if (pp->url_format != URL_FORMAT_NONE
+ && !new_style.m_url.empty ())
+ {
+ /* Adapted from pp_begin_url, but encoding the
+ chars to UTF-8 on the fly, rather than converting
+ to a buffer. */
+ pp_string (pp, "\33]8;;");
+ for (auto ch : new_style.m_url)
+ pp_unicode_character (pp, ch);
+ switch (pp->url_format)
+ {
+ default:
+ case URL_FORMAT_NONE:
+ gcc_unreachable ();
+ case URL_FORMAT_ST:
+ pp_string (pp, "\33\\");
+ break;
+ case URL_FORMAT_BEL:
+ pp_string (pp, "\a");
+ break;
+ }
+ }
+ }
+}
+
+/* class text_art::style_manager. */
+
+style_manager::style_manager ()
+{
+ // index 0 will be the default style
+ m_styles.push_back (style ());
+}
+
+style::id_t
+style_manager::get_or_create_id (const style &s)
+{
+ // For now, linear search
+ std::vector<style>::iterator existing
+ (std::find (m_styles.begin (), m_styles.end (), s));
+
+ /* If found, return index of slot. */
+ if (existing != m_styles.end ())
+ return std::distance (m_styles.begin (), existing);
+
+ /* Not found. */
+
+ /* styled_str uses 7 bits for style information, so we can only support
+ up to 128 different style combinations.
+ Gracefully fail by turning off styling when this limit is reached. */
+ if (m_styles.size () >= 127)
+ return 0;
+
+ m_styles.push_back (s);
+ return m_styles.size () - 1;
+}
+
+void
+style_manager::print_any_style_changes (pretty_printer *pp,
+ style::id_t old_id,
+ style::id_t new_id) const
+{
+ gcc_assert (pp);
+ if (old_id == new_id)
+ return;
+
+ const style &old_style = m_styles[old_id];
+ const style &new_style = m_styles[new_id];
+ gcc_assert (!(old_style == new_style));
+ style::print_changes (pp, old_style, new_style);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+void
+assert_style_change_streq (const location &loc,
+ const style &old_style,
+ const style &new_style,
+ const char *expected_str)
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ style::print_changes (&pp, old_style, new_style);
+ ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str);
+}
+
+#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ (OLD_STYLE), \
+ (NEW_STYLE), \
+ (EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+static void
+test_bold ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style bold;
+ bold.m_bold = true;
+
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K");
+}
+
+static void
+test_underscore ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style underscore;
+ underscore.m_underscore = true;
+
+ ASSERT_EQ (sm.get_or_create_id (underscore), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (underscore), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K");
+}
+
+static void
+test_blink ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style blink;
+ blink.m_blink = true;
+
+ ASSERT_EQ (sm.get_or_create_id (blink), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (blink), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K");
+}
+
+#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
+ else \
+ s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_named_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ {
+ const bool bright = false;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "");
+ }
+ {
+ const bool bright = true;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "");
+ }
+ }
+
+ /* Background colors. */
+ {
+ const bool fg = false;
+ {
+ const bool bright = false;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "");
+ }
+ {
+ const bool bright = true;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "");
+ }
+ }
+}
+
+#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color (COL_VAL); \
+ else \
+ s.m_bg_color = style::color (COL_VAL); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_8_bit_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ /* 0-15: standard and high-intensity standard colors. */
+ ASSERT_8_BIT_COL_STREQ (0, fg, "");
+ ASSERT_8_BIT_COL_STREQ (15, fg, "");
+ /* 16-231: 6x6x6 color cube. */
+ ASSERT_8_BIT_COL_STREQ (16, fg, "");
+ ASSERT_8_BIT_COL_STREQ (231, fg, "");
+ /* 232-255: grayscale. */
+ ASSERT_8_BIT_COL_STREQ (232, fg, "");
+ ASSERT_8_BIT_COL_STREQ (255, fg, "");
+ }
+ /* Background colors. */
+ {
+ const bool fg = false;
+ /* 0-15: standard and high-intensity standard colors. */
+ ASSERT_8_BIT_COL_STREQ (0, fg, "");
+ ASSERT_8_BIT_COL_STREQ (15, fg, "");
+ /* 16-231: 6x6x6 color cube. */
+ ASSERT_8_BIT_COL_STREQ (16, fg, "");
+ ASSERT_8_BIT_COL_STREQ (231, fg, "");
+ /* 232-255: grayscale. */
+ ASSERT_8_BIT_COL_STREQ (232, fg, "");
+ ASSERT_8_BIT_COL_STREQ (255, fg, "");
+ }
+}
+
+#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color ((R), (G), (B)); \
+ else \
+ s.m_bg_color = style::color ((R), (G), (B)); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_24_bit_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ // #F3FAF2:
+ ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg,
+ "");
+ }
+ /* Background colors. */
+ {
+ const bool fg = false;
+ // #FDF7E7
+ ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
+ "");
+ }
+}
+
+static void
+test_style_combinations ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style bold;
+ bold.m_bold = true;
+
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ style magenta_on_blue;
+ magenta_on_blue.m_fg_color = style::named_color::MAGENTA;
+ magenta_on_blue.m_bg_color = style::named_color::BLUE;
+ ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_style_cc_tests ()
+{
+ test_bold ();
+ test_underscore ();
+ test_blink ();
+ test_named_colors ();
+ test_8_bit_colors ();
+ test_24_bit_colors ();
+ test_style_combinations ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */