/* Classes for styling text cells (color, URLs). Copyright (C) 2023-2024 Free Software Foundation, Inc. Contributed by David Malcolm . 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 . */ #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" #include "diagnostic-color.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; } } } } /* Look up the current SGR codes for a color capability NAME (from GCC_COLORS or the defaults), and convert them to a text_art::style. */ style text_art::get_style_from_color_cap_name (const char *name) { const char *sgr_codes = colorize_start (true, name); gcc_assert (sgr_codes); /* Parse the sgr codes. We expect the resulting styled_string to be empty; we're interested in the final style created during parsing. */ style_manager sm; styled_string styled_str (sm, sgr_codes); return sm.get_style (sm.get_num_styles () - 1); } /* 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