aboutsummaryrefslogtreecommitdiff
path: root/gcc/text-art/styled-string.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/text-art/styled-string.cc')
-rw-r--r--gcc/text-art/styled-string.cc1108
1 files changed, 1108 insertions, 0 deletions
diff --git a/gcc/text-art/styled-string.cc b/gcc/text-art/styled-string.cc
new file mode 100644
index 0000000..a0cc187
--- /dev/null
+++ b/gcc/text-art/styled-string.cc
@@ -0,0 +1,1108 @@
+/* Implementation of text_art::styled_string.
+ 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_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/types.h"
+#include "color-macros.h"
+
+using namespace text_art;
+
+namespace {
+
+/* Support class for parsing text containing escape codes.
+ See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code
+ We only support the codes that pretty-print.cc can generate. */
+
+class escape_code_parser
+{
+public:
+ escape_code_parser (style_manager &sm,
+ std::vector<styled_unichar> &out)
+ : m_sm (sm),
+ m_out (out),
+ m_cur_style_obj (),
+ m_cur_style_id (style::id_plain),
+ m_state (state::START)
+ {
+ }
+
+ void on_char (cppchar_t ch)
+ {
+ switch (m_state)
+ {
+ default:
+ gcc_unreachable ();
+ case state::START:
+ if (ch == '\033')
+ {
+ /* The start of an escape sequence. */
+ m_state = state::AFTER_ESC;
+ return;
+ }
+ break;
+ case state::AFTER_ESC:
+ if (ch == '[')
+ {
+ /* ESC [ is a Control Sequence Introducer. */
+ m_state = state::CS_PARAMETER_BYTES;
+ return;
+ }
+ else if (ch == ']')
+ {
+ /* ESC ] is an Operating System Command. */
+ m_state = state::WITHIN_OSC;
+ return;
+ }
+ break;
+ case state::CS_PARAMETER_BYTES:
+ if (parameter_byte_p (ch))
+ {
+ m_parameter_bytes.push_back ((char)ch);
+ return;
+ }
+ else if (intermediate_byte_p (ch))
+ {
+ m_intermediate_bytes.push_back ((char)ch);
+ m_state = state::CS_INTERMEDIATE_BYTES;
+ return;
+ }
+ else if (final_byte_p (ch))
+ {
+ on_final_csi_char (ch);
+ return;
+ }
+ break;
+ case state::CS_INTERMEDIATE_BYTES:
+ /* Expect zero or more intermediate bytes. */
+ if (intermediate_byte_p (ch))
+ {
+ m_intermediate_bytes.push_back ((char)ch);
+ return;
+ }
+ else if (final_byte_p (ch))
+ {
+ on_final_csi_char (ch);
+ return;
+ }
+ break;
+ case state::WITHIN_OSC:
+ /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */
+ {
+ /* Check for ESC \, the String Terminator (aka "ST"). */
+ if (ch == '\\'
+ && m_osc_string.size () > 0
+ && m_osc_string.back () == '\033')
+ {
+ m_osc_string.pop_back ();
+ on_final_osc_char ();
+ return;
+ }
+ else if (ch == '\a')
+ {
+ // BEL
+ on_final_osc_char ();
+ return;
+ }
+ m_osc_string.push_back (ch);
+ return;
+ }
+ break;
+ }
+
+ /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation for the previous character. */
+ if (ch == 0xFE0F)
+ {
+ if (m_out.size () > 0)
+ m_out.back ().set_emoji_variant ();
+ return;
+ }
+
+ if (cpp_is_combining_char (ch))
+ {
+ if (m_out.size () > 0)
+ {
+ m_out.back ().add_combining_char (ch);
+ return;
+ }
+ }
+ /* By default, add the char. */
+ m_out.push_back (styled_unichar (ch, false, m_cur_style_id));
+ }
+
+private:
+ void on_final_csi_char (cppchar_t ch)
+ {
+ switch (ch)
+ {
+ default:
+ /* Unrecognized. */
+ break;
+ case 'm':
+ {
+ /* SGR control sequence. */
+ if (m_parameter_bytes.empty ())
+ reset_style ();
+ std::vector<int> params (params_from_decimal ());
+ for (auto iter = params.begin (); iter != params.end (); )
+ {
+ const int param = *iter;
+ switch (param)
+ {
+ default:
+ /* Unrecognized SGR parameter. */
+ break;
+ case 0:
+ reset_style ();
+ break;
+ case 1:
+ set_style_bold ();
+ break;
+ case 4:
+ set_style_underscore ();
+ break;
+ case 5:
+ set_style_blink ();
+ break;
+
+ /* Named foreground colors. */
+ case 30:
+ set_style_fg_color (style::named_color::BLACK);
+ break;
+ case 31:
+ set_style_fg_color (style::named_color::RED);
+ break;
+ case 32:
+ set_style_fg_color (style::named_color::GREEN);
+ break;
+ case 33:
+ set_style_fg_color (style::named_color::YELLOW);
+ break;
+ case 34:
+ set_style_fg_color (style::named_color::BLUE);
+ break;
+ case 35:
+ set_style_fg_color (style::named_color::MAGENTA);
+ break;
+ case 36:
+ set_style_fg_color (style::named_color::CYAN);
+ break;
+ case 37:
+ set_style_fg_color (style::named_color::WHITE);
+ break;
+
+ /* 8-bit and 24-bit color */
+ case 38:
+ case 48:
+ {
+ const bool fg = (param == 38);
+ iter++;
+ if (iter != params.end ())
+ switch (*(iter++))
+ {
+ default:
+ break;
+ case 5:
+ /* 8-bit color. */
+ if (iter != params.end ())
+ {
+ const uint8_t col = *(iter++);
+ if (fg)
+ set_style_fg_color (style::color (col));
+ else
+ set_style_bg_color (style::color (col));
+ }
+ continue;
+ case 2:
+ /* 24-bit color. */
+ if (iter != params.end ())
+ {
+ const uint8_t r = *(iter++);
+ if (iter != params.end ())
+ {
+ const uint8_t g = *(iter++);
+ if (iter != params.end ())
+ {
+ const uint8_t b = *(iter++);
+ if (fg)
+ set_style_fg_color (style::color (r,
+ g,
+ b));
+ else
+ set_style_bg_color (style::color (r,
+ g,
+ b));
+ }
+ }
+ }
+ continue;
+ }
+ continue;
+ }
+ break;
+
+ /* Named background colors. */
+ case 40:
+ set_style_bg_color (style::named_color::BLACK);
+ break;
+ case 41:
+ set_style_bg_color (style::named_color::RED);
+ break;
+ case 42:
+ set_style_bg_color (style::named_color::GREEN);
+ break;
+ case 43:
+ set_style_bg_color (style::named_color::YELLOW);
+ break;
+ case 44:
+ set_style_bg_color (style::named_color::BLUE);
+ break;
+ case 45:
+ set_style_bg_color (style::named_color::MAGENTA);
+ break;
+ case 46:
+ set_style_bg_color (style::named_color::CYAN);
+ break;
+ case 47:
+ set_style_bg_color (style::named_color::WHITE);
+ break;
+
+ /* Named foreground colors, bright. */
+ case 90:
+ set_style_fg_color (style::color (style::named_color::BLACK,
+ true));
+ break;
+ case 91:
+ set_style_fg_color (style::color (style::named_color::RED,
+ true));
+ break;
+ case 92:
+ set_style_fg_color (style::color (style::named_color::GREEN,
+ true));
+ break;
+ case 93:
+ set_style_fg_color (style::color (style::named_color::YELLOW,
+ true));
+ break;
+ case 94:
+ set_style_fg_color (style::color (style::named_color::BLUE,
+ true));
+ break;
+ case 95:
+ set_style_fg_color (style::color (style::named_color::MAGENTA,
+ true));
+ break;
+ case 96:
+ set_style_fg_color (style::color (style::named_color::CYAN,
+ true));
+ break;
+ case 97:
+ set_style_fg_color (style::color (style::named_color::WHITE,
+ true));
+ break;
+
+ /* Named foreground colors, bright. */
+ case 100:
+ set_style_bg_color (style::color (style::named_color::BLACK,
+ true));
+ break;
+ case 101:
+ set_style_bg_color (style::color (style::named_color::RED,
+ true));
+ break;
+ case 102:
+ set_style_bg_color (style::color (style::named_color::GREEN,
+ true));
+ break;
+ case 103:
+ set_style_bg_color (style::color (style::named_color::YELLOW,
+ true));
+ break;
+ case 104:
+ set_style_bg_color (style::color (style::named_color::BLUE,
+ true));
+ break;
+ case 105:
+ set_style_bg_color (style::color (style::named_color::MAGENTA,
+ true));
+ break;
+ case 106:
+ set_style_bg_color (style::color (style::named_color::CYAN,
+ true));
+ break;
+ case 107:
+ set_style_bg_color (style::color (style::named_color::WHITE,
+ true));
+ break;
+ }
+ ++iter;
+ }
+ }
+ break;
+ }
+ m_parameter_bytes.clear ();
+ m_intermediate_bytes.clear ();
+ m_state = state::START;
+ }
+
+ void on_final_osc_char ()
+ {
+ if (!m_osc_string.empty ())
+ {
+ switch (m_osc_string[0])
+ {
+ default:
+ break;
+ case '8':
+ /* Hyperlink support; see:
+ https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
+ We don't support params, so we expect either:
+ (a) "8;;URL" to begin a url (see pp_begin_url), or
+ (b) "8;;" to end a URL (see pp_end_url). */
+ if (m_osc_string.size () >= 3
+ && m_osc_string[1] == ';'
+ && m_osc_string[2] == ';')
+ {
+ set_style_url (m_osc_string.begin () + 3,
+ m_osc_string.end ());
+ }
+ break;
+ }
+ }
+ m_osc_string.clear ();
+ m_state = state::START;
+ }
+
+ std::vector<int> params_from_decimal () const
+ {
+ std::vector<int> result;
+
+ int curr_int = -1;
+ for (auto param_ch : m_parameter_bytes)
+ {
+ if (param_ch >= '0' && param_ch <= '9')
+ {
+ if (curr_int == -1)
+ curr_int = 0;
+ else
+ curr_int *= 10;
+ curr_int += param_ch - '0';
+ }
+ else
+ {
+ if (curr_int != -1)
+ {
+ result.push_back (curr_int);
+ curr_int = -1;
+ }
+ }
+ }
+ if (curr_int != -1)
+ result.push_back (curr_int);
+ return result;
+ }
+
+ void refresh_style_id ()
+ {
+ m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj);
+ }
+ void reset_style ()
+ {
+ m_cur_style_obj = style ();
+ refresh_style_id ();
+ }
+ void set_style_bold ()
+ {
+ m_cur_style_obj.m_bold = true;
+ refresh_style_id ();
+ }
+ void set_style_underscore ()
+ {
+ m_cur_style_obj.m_underscore = true;
+ refresh_style_id ();
+ }
+ void set_style_blink ()
+ {
+ m_cur_style_obj.m_blink = true;
+ refresh_style_id ();
+ }
+ void set_style_fg_color (style::color color)
+ {
+ m_cur_style_obj.m_fg_color = color;
+ refresh_style_id ();
+ }
+ void set_style_bg_color (style::color color)
+ {
+ m_cur_style_obj.m_bg_color = color;
+ refresh_style_id ();
+ }
+ void set_style_url (std::vector<cppchar_t>::iterator begin,
+ std::vector<cppchar_t>::iterator end)
+ {
+ // The empty string means "no URL"
+ m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end);
+ refresh_style_id ();
+ }
+
+ static bool parameter_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x30 && ch <= 0x3F;
+ }
+
+ static bool intermediate_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x20 && ch <= 0x2F;
+ }
+
+ static bool final_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x40 && ch <= 0x7E;
+ }
+
+ style_manager &m_sm;
+ std::vector<styled_unichar> &m_out;
+
+ style m_cur_style_obj;
+ style::id_t m_cur_style_id;
+
+ /* Handling of control sequences. */
+ enum class state
+ {
+ START,
+
+ /* After ESC, expecting '['. */
+ AFTER_ESC,
+
+ /* Expecting zero or more parameter bytes, an
+ intermediate byte, or a final byte. */
+ CS_PARAMETER_BYTES,
+
+ /* Expecting zero or more intermediate bytes, or a final byte. */
+ CS_INTERMEDIATE_BYTES,
+
+ /* Within OSC. */
+ WITHIN_OSC
+
+ } m_state;
+ std::vector<char> m_parameter_bytes;
+ std::vector<char> m_intermediate_bytes;
+ std::vector<cppchar_t> m_osc_string;
+};
+
+} // anon namespace
+
+/* class text_art::styled_string. */
+
+/* Construct a styled_string from STR.
+ STR is assumed to be UTF-8 encoded and 0-terminated.
+
+ Parse SGR formatting chars from being in-band (within in the sequence
+ of chars) to being out-of-band, as style elements.
+ We only support parsing the subset of SGR chars that can be emitted
+ by pretty-print.cc */
+
+styled_string::styled_string (style_manager &sm, const char *str)
+: m_chars ()
+{
+ escape_code_parser parser (sm, m_chars);
+
+ /* We don't actually want the display widths here, but
+ it's an easy way to decode UTF-8. */
+ cpp_char_column_policy policy (8, cpp_wcwidth);
+ cpp_display_width_computation dw (str, strlen (str), policy);
+ while (!dw.done ())
+ {
+ cpp_decoded_char decoded_char;
+ dw.process_next_codepoint (&decoded_char);
+
+ if (!decoded_char.m_valid_ch)
+ /* Skip bytes that aren't valid UTF-8. */
+ continue;
+
+ /* Decode SGR formatting. */
+ cppchar_t ch = decoded_char.m_ch;
+ parser.on_char (ch);
+ }
+}
+
+styled_string::styled_string (cppchar_t cppchar, bool emoji)
+{
+ m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain));
+}
+
+styled_string
+styled_string::from_fmt_va (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt,
+ va_list *args)
+{
+ text_info text;
+ text.err_no = errno;
+ text.args_ptr = args;
+ text.format_spec = fmt;
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_DEFAULT;
+ pp_format_decoder (&pp) = format_decoder;
+ pp_format (&pp, &text);
+ pp_output_formatted_text (&pp);
+ styled_string result (sm, pp_formatted_text (&pp));
+ return result;
+}
+
+styled_string
+styled_string::from_fmt (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap);
+ va_end (ap);
+ return result;
+}
+
+int
+styled_string::calc_canvas_width () const
+{
+ int result = 0;
+ for (auto ch : m_chars)
+ result += ch.get_canvas_width ();
+ return result;
+}
+
+void
+styled_string::append (const styled_string &suffix)
+{
+ m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (),
+ suffix.begin (),
+ suffix.end ());
+}
+
+void
+styled_string::set_url (style_manager &sm, const char *url)
+{
+ for (auto& ch : m_chars)
+ {
+ const style &existing_style = sm.get_style (ch.get_style_id ());
+ style with_url (existing_style);
+ with_url.set_style_url (url);
+ ch.m_style_id = sm.get_or_create_id (with_url);
+ }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_combining_chars ()
+{
+ /* This really ought to be in libcpp, but we don't have
+ selftests there. */
+ ASSERT_FALSE (cpp_is_combining_char (0));
+ ASSERT_FALSE (cpp_is_combining_char ('a'));
+
+ /* COMBINING BREVE (U+0306). */
+ ASSERT_TRUE (cpp_is_combining_char (0x0306));
+
+ /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */
+ ASSERT_FALSE (cpp_is_combining_char (0x5B57));
+
+ /* U+FE0F VARIATION SELECTOR-16. */
+ ASSERT_FALSE (cpp_is_combining_char (0xFE0F));
+}
+
+static void
+test_empty ()
+{
+ style_manager sm;
+ styled_string s (sm, "");
+ ASSERT_EQ (s.size (), 0);
+ ASSERT_EQ (s.calc_canvas_width (), 0);
+}
+
+/* Test of a pure ASCII string with no escape codes. */
+
+static void
+test_simple ()
+{
+ const char *c_str = "hello world!";
+ style_manager sm;
+ styled_string s (sm, c_str);
+ ASSERT_EQ (s.size (), strlen (c_str));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str));
+ for (size_t i = 0; i < strlen (c_str); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]);
+ ASSERT_EQ (s[i].get_style_id (), 0);
+ }
+}
+
+/* Test of decoding UTF-8. */
+
+static void
+test_pi_from_utf8 ()
+{
+ /* U+03C0 "GREEK SMALL LETTER PI". */
+ const char * const pi_utf8 = "\xCF\x80";
+
+ style_manager sm;
+ styled_string s (sm, pi_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x03c0);
+ ASSERT_EQ (s[0].emoji_variant_p (), false);
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+/* Test of double-width character. */
+
+static void
+test_emoji_from_utf8 ()
+{
+ /* U+1F642 "SLIGHTLY SMILING FACE". */
+ const char * const emoji_utf8 = "\xF0\x9F\x99\x82";
+
+ style_manager sm;
+ styled_string s (sm, emoji_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x1f642);
+ ASSERT_EQ (s[0].double_width_p (), true);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation for the previous character. */
+
+static void
+test_emoji_variant_from_utf8 ()
+{
+ const char * const emoji_utf8
+ = (/* U+26A0 WARNING SIGN. */
+ "\xE2\x9A\xA0"
+ /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
+ "\xEF\xB8\x8F");
+
+ style_manager sm;
+ styled_string s (sm, emoji_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x26a0);
+ ASSERT_EQ (s[0].emoji_variant_p (), true);
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+test_emoji_from_codepoint ()
+{
+ styled_string s ((cppchar_t)0x1f642);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x1f642);
+ ASSERT_EQ (s[0].double_width_p (), true);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+test_from_mixed_width_utf8 ()
+{
+ /* This UTF-8 string literal is of the form
+ before mojibake after
+ where the Japanese word "mojibake" is written as the following
+ four unicode code points:
+ U+6587 CJK UNIFIED IDEOGRAPH-6587
+ U+5B57 CJK UNIFIED IDEOGRAPH-5B57
+ U+5316 CJK UNIFIED IDEOGRAPH-5316
+ U+3051 HIRAGANA LETTER KE.
+ Each of these is 3 bytes wide when encoded in UTF-8, whereas the
+ "before" and "after" are 1 byte per unicode character. */
+ const char * const mixed_width_utf8
+ = ("before "
+
+ /* U+6587 CJK UNIFIED IDEOGRAPH-6587
+ UTF-8: 0xE6 0x96 0x87
+ C octal escaped UTF-8: \346\226\207. */
+ "\346\226\207"
+
+ /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57
+ UTF-8: 0xE5 0xAD 0x97
+ C octal escaped UTF-8: \345\255\227. */
+ "\345\255\227"
+
+ /* U+5316 CJK UNIFIED IDEOGRAPH-5316
+ UTF-8: 0xE5 0x8C 0x96
+ C octal escaped UTF-8: \345\214\226. */
+ "\345\214\226"
+
+ /* U+3051 HIRAGANA LETTER KE
+ UTF-8: 0xE3 0x81 0x91
+ C octal escaped UTF-8: \343\201\221. */
+ "\343\201\221"
+
+ " after");
+
+ style_manager sm;
+ styled_string s (sm, mixed_width_utf8);
+ ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ // We expect the Japanese characters to be double width.
+ ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5);
+
+ ASSERT_EQ (s[0].get_code (), 'b');
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[1].get_code (), 'e');
+ ASSERT_EQ (s[2].get_code (), 'f');
+ ASSERT_EQ (s[3].get_code (), 'o');
+ ASSERT_EQ (s[4].get_code (), 'r');
+ ASSERT_EQ (s[5].get_code (), 'e');
+ ASSERT_EQ (s[6].get_code (), ' ');
+ ASSERT_EQ (s[7].get_code (), 0x6587);
+ ASSERT_EQ (s[7].double_width_p (), true);
+ ASSERT_EQ (s[8].get_code (), 0x5B57);
+ ASSERT_EQ (s[9].get_code (), 0x5316);
+ ASSERT_EQ (s[10].get_code (), 0x3051);
+ ASSERT_EQ (s[11].get_code (), ' ');
+ ASSERT_EQ (s[12].get_code (), 'a');
+ ASSERT_EQ (s[13].get_code (), 'f');
+ ASSERT_EQ (s[14].get_code (), 't');
+ ASSERT_EQ (s[15].get_code (), 'e');
+ ASSERT_EQ (s[16].get_code (), 'r');
+
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+assert_style_urleq (const location &loc,
+ const style &s,
+ const char *expected_str)
+{
+ ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str));
+ for (size_t i = 0; i < s.m_url.size (); i++)
+ ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]);
+}
+
+#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \
+ assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR))
+
+static void
+test_url ()
+{
+ // URL_FORMAT_ST
+ {
+ style_manager sm;
+ styled_string s
+ (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\");
+ const char *expected = "This is a link";
+ ASSERT_EQ (s.size (), strlen (expected));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ for (size_t i = 0; i < strlen (expected); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
+ ASSERT_EQ (s[i].get_style_id (), 1);
+ }
+ ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
+ }
+
+ // URL_FORMAT_BEL
+ {
+ style_manager sm;
+ styled_string s
+ (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a");
+ const char *expected = "This is a link";
+ ASSERT_EQ (s.size (), strlen (expected));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ for (size_t i = 0; i < strlen (expected); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
+ ASSERT_EQ (s[i].get_style_id (), 1);
+ }
+ ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
+ }
+}
+
+static void
+test_from_fmt ()
+{
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42));
+ ASSERT_EQ (s[0].get_code (), '%');
+ ASSERT_EQ (s[1].get_code (), 'i');
+ ASSERT_EQ (s[2].get_code (), ':');
+ ASSERT_EQ (s[3].get_code (), ' ');
+ ASSERT_EQ (s[4].get_code (), '4');
+ ASSERT_EQ (s[5].get_code (), '2');
+ ASSERT_EQ (s.size (), 6);
+ ASSERT_EQ (s.calc_canvas_width (), 6);
+}
+
+static void
+test_from_fmt_qs ()
+{
+ auto_fix_quotes fix_quotes;
+ open_quote = "\xe2\x80\x98";
+ close_quote = "\xe2\x80\x99";
+
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg"));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x2018);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'm');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), 's');
+ ASSERT_EQ (s[2].get_style_id (), 1);
+ ASSERT_EQ (s[3].get_code (), 'g');
+ ASSERT_EQ (s[3].get_style_id (), 1);
+ ASSERT_EQ (s[4].get_code (), 0x2019);
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (s.size (), 5);
+}
+
+// Test of parsing SGR codes.
+
+static void
+test_from_str_with_bold ()
+{
+ style_manager sm;
+ /* This is the result of pp_printf (pp, "%qs", "foo")
+ with auto_fix_quotes. */
+ styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'");
+ ASSERT_EQ (s[0].get_code (), '`');
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'f');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), 'o');
+ ASSERT_EQ (s[2].get_style_id (), 1);
+ ASSERT_EQ (s[3].get_code (), 'o');
+ ASSERT_EQ (s[3].get_style_id (), 1);
+ ASSERT_EQ (s[4].get_code (), '\'');
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_TRUE (sm.get_style (1).m_bold);
+}
+
+static void
+test_from_str_with_underscore ()
+{
+ style_manager sm;
+ styled_string s (sm, "\33[04m\33[KA");
+ ASSERT_EQ (s[0].get_code (), 'A');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_TRUE (sm.get_style (1).m_underscore);
+}
+
+static void
+test_from_str_with_blink ()
+{
+ style_manager sm;
+ styled_string s (sm, "\33[05m\33[KA");
+ ASSERT_EQ (s[0].get_code (), 'A');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_TRUE (sm.get_style (1).m_blink);
+}
+
+// Test of parsing SGR codes.
+
+static void
+test_from_str_with_color ()
+{
+ style_manager sm;
+
+ styled_string s (sm,
+ ("0"
+ SGR_SEQ (COLOR_FG_RED)
+ "R"
+ SGR_RESET
+ "2"
+ SGR_SEQ (COLOR_FG_GREEN)
+ "G"
+ SGR_RESET
+ "4"));
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_EQ (s[0].get_code (), '0');
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'R');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), '2');
+ ASSERT_EQ (s[2].get_style_id (), 0);
+ ASSERT_EQ (s[3].get_code (), 'G');
+ ASSERT_EQ (s[3].get_style_id (), 2);
+ ASSERT_EQ (s[4].get_code (), '4');
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED);
+ ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN);
+}
+
+static void
+test_from_str_with_named_color ()
+{
+ style_manager sm;
+ styled_string s (sm,
+ ("F"
+ SGR_SEQ (COLOR_FG_BLACK) "F"
+ SGR_SEQ (COLOR_FG_RED) "F"
+ SGR_SEQ (COLOR_FG_GREEN) "F"
+ SGR_SEQ (COLOR_FG_YELLOW) "F"
+ SGR_SEQ (COLOR_FG_BLUE) "F"
+ SGR_SEQ (COLOR_FG_MAGENTA) "F"
+ SGR_SEQ (COLOR_FG_CYAN) "F"
+ SGR_SEQ (COLOR_FG_WHITE) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_RED) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F"
+ SGR_SEQ (COLOR_BG_BLACK) "B"
+ SGR_SEQ (COLOR_BG_RED) "B"
+ SGR_SEQ (COLOR_BG_GREEN) "B"
+ SGR_SEQ (COLOR_BG_YELLOW) "B"
+ SGR_SEQ (COLOR_BG_BLUE) "B"
+ SGR_SEQ (COLOR_BG_MAGENTA) "B"
+ SGR_SEQ (COLOR_BG_CYAN) "B"
+ SGR_SEQ (COLOR_BG_WHITE) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_RED) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B"));
+ ASSERT_EQ (s.size (), 33);
+ for (size_t i = 0; i < s.size (); i++)
+ ASSERT_EQ (s[i].get_style_id (), i);
+ for (size_t i = 0; i < 17; i++)
+ ASSERT_EQ (s[i].get_code (), 'F');
+ for (size_t i = 17; i < 33; i++)
+ ASSERT_EQ (s[i].get_code (), 'B');
+}
+
+static void
+test_from_str_with_8_bit_color ()
+{
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("F"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'F');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232));
+ }
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("B"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'B');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231));
+ }
+}
+
+static void
+test_from_str_with_24_bit_color ()
+{
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("F"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'F');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242));
+ }
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("B"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'B');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231));
+ }
+}
+
+static void
+test_from_str_combining_characters ()
+{
+ style_manager sm;
+ styled_string s (sm,
+ /* CYRILLIC CAPITAL LETTER U (U+0423). */
+ "\xD0\xA3"
+ /* COMBINING BREVE (U+0306). */
+ "\xCC\x86");
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x423);
+ ASSERT_EQ (s[0].get_combining_chars ().size (), 1);
+ ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306);
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_styled_string_cc_tests ()
+{
+ test_combining_chars ();
+ test_empty ();
+ test_simple ();
+ test_pi_from_utf8 ();
+ test_emoji_from_utf8 ();
+ test_emoji_variant_from_utf8 ();
+ test_emoji_from_codepoint ();
+ test_from_mixed_width_utf8 ();
+ test_url ();
+ test_from_fmt ();
+ test_from_fmt_qs ();
+ test_from_str_with_bold ();
+ test_from_str_with_underscore ();
+ test_from_str_with_blink ();
+ test_from_str_with_color ();
+ test_from_str_with_named_color ();
+ test_from_str_with_8_bit_color ();
+ test_from_str_with_24_bit_color ();
+ test_from_str_combining_characters ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */