Home | History | Annotate | Line # | Download | only in gdb
      1      1.1  christos /* Styling for ui_file
      2  1.1.1.4  christos    Copyright (C) 2018-2024 Free Software Foundation, Inc.
      3      1.1  christos 
      4      1.1  christos    This file is part of GDB.
      5      1.1  christos 
      6      1.1  christos    This program is free software; you can redistribute it and/or modify
      7      1.1  christos    it under the terms of the GNU General Public License as published by
      8      1.1  christos    the Free Software Foundation; either version 3 of the License, or
      9      1.1  christos    (at your option) any later version.
     10      1.1  christos 
     11      1.1  christos    This program is distributed in the hope that it will be useful,
     12      1.1  christos    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13      1.1  christos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14      1.1  christos    GNU General Public License for more details.
     15      1.1  christos 
     16      1.1  christos    You should have received a copy of the GNU General Public License
     17      1.1  christos    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     18      1.1  christos 
     19      1.1  christos #include "ui-style.h"
     20  1.1.1.3  christos #include "gdbsupport/gdb_regex.h"
     21      1.1  christos 
     22      1.1  christos /* A regular expression that is used for matching ANSI terminal escape
     23      1.1  christos    sequences.  */
     24      1.1  christos 
     25  1.1.1.3  christos static const char ansi_regex_text[] =
     26      1.1  christos   /* Introduction.  */
     27      1.1  christos   "^\033\\["
     28      1.1  christos #define DATA_SUBEXP 1
     29      1.1  christos   /* Capture parameter and intermediate bytes.  */
     30      1.1  christos   "("
     31      1.1  christos   /* Parameter bytes.  */
     32      1.1  christos   "[\x30-\x3f]*"
     33      1.1  christos   /* Intermediate bytes.  */
     34      1.1  christos   "[\x20-\x2f]*"
     35      1.1  christos   /* End the first capture.  */
     36      1.1  christos   ")"
     37      1.1  christos   /* The final byte.  */
     38      1.1  christos #define FINAL_SUBEXP 2
     39      1.1  christos   "([\x40-\x7e])";
     40      1.1  christos 
     41      1.1  christos /* The number of subexpressions to allocate space for, including the
     42      1.1  christos    "0th" whole match subexpression.  */
     43      1.1  christos #define NUM_SUBEXPRESSIONS 3
     44      1.1  christos 
     45      1.1  christos /* The compiled form of ansi_regex_text.  */
     46      1.1  christos 
     47      1.1  christos static regex_t ansi_regex;
     48      1.1  christos 
     49      1.1  christos /* This maps bright colors to RGB triples.  The index is the bright
     50      1.1  christos    color index, starting with bright black.  The values come from
     51      1.1  christos    xterm.  */
     52      1.1  christos 
     53      1.1  christos static const uint8_t bright_colors[][3] = {
     54      1.1  christos   { 127, 127, 127 },		/* Black.  */
     55      1.1  christos   { 255, 0, 0 },		/* Red.  */
     56      1.1  christos   { 0, 255, 0 },		/* Green.  */
     57      1.1  christos   { 255, 255, 0 },		/* Yellow.  */
     58      1.1  christos   { 92, 92, 255 },		/* Blue.  */
     59      1.1  christos   { 255, 0, 255 },		/* Magenta.  */
     60      1.1  christos   { 0, 255, 255 },		/* Cyan.  */
     61      1.1  christos   { 255, 255, 255 }		/* White.  */
     62      1.1  christos };
     63      1.1  christos 
     64      1.1  christos /* See ui-style.h.  */
     65      1.1  christos 
     66      1.1  christos bool
     67      1.1  christos ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
     68      1.1  christos {
     69      1.1  christos   if (m_simple)
     70      1.1  christos     {
     71      1.1  christos       if (m_value >= BLACK && m_value <= WHITE)
     72      1.1  christos 	str->append (std::to_string (m_value + (is_fg ? 30 : 40)));
     73      1.1  christos       else if (m_value > WHITE && m_value <= WHITE + 8)
     74      1.1  christos 	str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100)));
     75      1.1  christos       else if (m_value != -1)
     76      1.1  christos 	{
     77      1.1  christos 	  str->append (is_fg ? "38;5;" : "48;5;");
     78      1.1  christos 	  str->append (std::to_string (m_value));
     79      1.1  christos 	}
     80      1.1  christos       else
     81      1.1  christos 	return false;
     82      1.1  christos     }
     83      1.1  christos   else
     84      1.1  christos     {
     85      1.1  christos       str->append (is_fg ? "38;2;" : "48;2;");
     86      1.1  christos       str->append (std::to_string (m_red)
     87      1.1  christos 		   + ";" + std::to_string (m_green)
     88      1.1  christos 		   + ";" + std::to_string (m_blue));
     89      1.1  christos     }
     90      1.1  christos   return true;
     91      1.1  christos }
     92      1.1  christos 
     93      1.1  christos /* See ui-style.h.  */
     94      1.1  christos 
     95      1.1  christos void
     96      1.1  christos ui_file_style::color::get_rgb (uint8_t *rgb) const
     97      1.1  christos {
     98      1.1  christos   if (m_simple)
     99      1.1  christos     {
    100      1.1  christos       /* Can't call this for a basic color or NONE -- those will end
    101      1.1  christos 	 up in the assert below.  */
    102      1.1  christos       if (m_value >= 8 && m_value <= 15)
    103      1.1  christos 	memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t));
    104      1.1  christos       else if (m_value >= 16 && m_value <= 231)
    105      1.1  christos 	{
    106      1.1  christos 	  int value = m_value;
    107      1.1  christos 	  value -= 16;
    108      1.1  christos 	  /* This obscure formula seems to be what terminals actually
    109      1.1  christos 	     do.  */
    110      1.1  christos 	  int component = value / 36;
    111      1.1  christos 	  rgb[0] = component == 0 ? 0 : (55 + component * 40);
    112      1.1  christos 	  value %= 36;
    113      1.1  christos 	  component = value / 6;
    114      1.1  christos 	  rgb[1] = component == 0 ? 0 : (55 + component * 40);
    115      1.1  christos 	  value %= 6;
    116      1.1  christos 	  rgb[2] = value == 0 ? 0 : (55 + value * 40);
    117      1.1  christos 	}
    118      1.1  christos       else if (m_value >= 232)
    119      1.1  christos 	{
    120      1.1  christos 	  uint8_t v = (m_value - 232) * 10 + 8;
    121      1.1  christos 	  rgb[0] = v;
    122      1.1  christos 	  rgb[1] = v;
    123      1.1  christos 	  rgb[2] = v;
    124      1.1  christos 	}
    125      1.1  christos       else
    126      1.1  christos 	gdb_assert_not_reached ("get_rgb called on invalid color");
    127      1.1  christos     }
    128      1.1  christos   else
    129      1.1  christos     {
    130      1.1  christos       rgb[0] = m_red;
    131      1.1  christos       rgb[1] = m_green;
    132      1.1  christos       rgb[2] = m_blue;
    133      1.1  christos     }
    134      1.1  christos }
    135      1.1  christos 
    136      1.1  christos /* See ui-style.h.  */
    137      1.1  christos 
    138      1.1  christos std::string
    139      1.1  christos ui_file_style::to_ansi () const
    140      1.1  christos {
    141      1.1  christos   std::string result ("\033[");
    142      1.1  christos   bool need_semi = m_foreground.append_ansi (true, &result);
    143      1.1  christos   if (!m_background.is_none ())
    144      1.1  christos     {
    145      1.1  christos       if (need_semi)
    146      1.1  christos 	result.push_back (';');
    147      1.1  christos       m_background.append_ansi (false, &result);
    148      1.1  christos       need_semi = true;
    149      1.1  christos     }
    150      1.1  christos   if (m_intensity != NORMAL)
    151      1.1  christos     {
    152      1.1  christos       if (need_semi)
    153      1.1  christos 	result.push_back (';');
    154      1.1  christos       result.append (std::to_string (m_intensity));
    155      1.1  christos       need_semi = true;
    156      1.1  christos     }
    157      1.1  christos   if (m_reverse)
    158      1.1  christos     {
    159      1.1  christos       if (need_semi)
    160      1.1  christos 	result.push_back (';');
    161      1.1  christos       result.push_back ('7');
    162      1.1  christos     }
    163      1.1  christos   result.push_back ('m');
    164      1.1  christos   return result;
    165      1.1  christos }
    166      1.1  christos 
    167      1.1  christos /* Read a ";" and a number from STRING.  Return the number of
    168      1.1  christos    characters read and put the number into *NUM.  */
    169      1.1  christos 
    170      1.1  christos static bool
    171  1.1.1.3  christos read_semi_number (const char *string, regoff_t *idx, long *num)
    172      1.1  christos {
    173      1.1  christos   if (string[*idx] != ';')
    174      1.1  christos     return false;
    175      1.1  christos   ++*idx;
    176      1.1  christos   if (string[*idx] < '0' || string[*idx] > '9')
    177      1.1  christos     return false;
    178      1.1  christos   char *tail;
    179      1.1  christos   *num = strtol (string + *idx, &tail, 10);
    180      1.1  christos   *idx = tail - string;
    181      1.1  christos   return true;
    182      1.1  christos }
    183      1.1  christos 
    184      1.1  christos /* A helper for ui_file_style::parse that reads an extended color
    185      1.1  christos    sequence; that is, and 8- or 24- bit color.  */
    186      1.1  christos 
    187      1.1  christos static bool
    188  1.1.1.3  christos extended_color (const char *str, regoff_t *idx, ui_file_style::color *color)
    189      1.1  christos {
    190      1.1  christos   long value;
    191      1.1  christos 
    192      1.1  christos   if (!read_semi_number (str, idx, &value))
    193      1.1  christos     return false;
    194      1.1  christos 
    195      1.1  christos   if (value == 5)
    196      1.1  christos     {
    197      1.1  christos       /* 8-bit color.  */
    198      1.1  christos       if (!read_semi_number (str, idx, &value))
    199      1.1  christos 	return false;
    200      1.1  christos 
    201      1.1  christos       if (value >= 0 && value <= 255)
    202      1.1  christos 	*color = ui_file_style::color (value);
    203      1.1  christos       else
    204      1.1  christos 	return false;
    205      1.1  christos     }
    206      1.1  christos   else if (value == 2)
    207      1.1  christos     {
    208      1.1  christos       /* 24-bit color.  */
    209      1.1  christos       long r, g, b;
    210      1.1  christos       if (!read_semi_number (str, idx, &r)
    211      1.1  christos 	  || r > 255
    212      1.1  christos 	  || !read_semi_number (str, idx, &g)
    213      1.1  christos 	  || g > 255
    214      1.1  christos 	  || !read_semi_number (str, idx, &b)
    215      1.1  christos 	  || b > 255)
    216      1.1  christos 	return false;
    217      1.1  christos       *color = ui_file_style::color (r, g, b);
    218      1.1  christos     }
    219      1.1  christos   else
    220      1.1  christos     {
    221      1.1  christos       /* Unrecognized sequence.  */
    222      1.1  christos       return false;
    223      1.1  christos     }
    224      1.1  christos 
    225      1.1  christos   return true;
    226      1.1  christos }
    227      1.1  christos 
    228      1.1  christos /* See ui-style.h.  */
    229      1.1  christos 
    230      1.1  christos bool
    231      1.1  christos ui_file_style::parse (const char *buf, size_t *n_read)
    232      1.1  christos {
    233      1.1  christos   regmatch_t subexps[NUM_SUBEXPRESSIONS];
    234      1.1  christos 
    235      1.1  christos   int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
    236      1.1  christos   if (match == REG_NOMATCH)
    237      1.1  christos     {
    238      1.1  christos       *n_read = 0;
    239      1.1  christos       return false;
    240      1.1  christos     }
    241      1.1  christos   /* Other failures mean the regexp is broken.  */
    242      1.1  christos   gdb_assert (match == 0);
    243      1.1  christos   /* The regexp is anchored.  */
    244      1.1  christos   gdb_assert (subexps[0].rm_so == 0);
    245      1.1  christos   /* The final character exists.  */
    246      1.1  christos   gdb_assert (subexps[FINAL_SUBEXP].rm_eo - subexps[FINAL_SUBEXP].rm_so == 1);
    247      1.1  christos 
    248      1.1  christos   if (buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
    249      1.1  christos     {
    250      1.1  christos       /* We don't handle this sequence, so just drop it.  */
    251      1.1  christos       *n_read = subexps[0].rm_eo;
    252      1.1  christos       return false;
    253      1.1  christos     }
    254      1.1  christos 
    255      1.1  christos   /* Examine each setting in the match and apply it to the result.
    256      1.1  christos      See the Select Graphic Rendition section of
    257      1.1  christos      https://en.wikipedia.org/wiki/ANSI_escape_code.  In essence each
    258      1.1  christos      code is just a number, separated by ";"; there are some more
    259      1.1  christos      wrinkles but we don't support them all..  */
    260      1.1  christos 
    261      1.1  christos   /* "\033[m" means the same thing as "\033[0m", so handle that
    262      1.1  christos      specially here.  */
    263      1.1  christos   if (subexps[DATA_SUBEXP].rm_so == subexps[DATA_SUBEXP].rm_eo)
    264      1.1  christos     *this = ui_file_style ();
    265      1.1  christos 
    266      1.1  christos   for (regoff_t i = subexps[DATA_SUBEXP].rm_so;
    267      1.1  christos        i < subexps[DATA_SUBEXP].rm_eo;
    268      1.1  christos        ++i)
    269      1.1  christos     {
    270      1.1  christos       if (buf[i] == ';')
    271      1.1  christos 	{
    272      1.1  christos 	  /* Skip.  */
    273      1.1  christos 	}
    274      1.1  christos       else if (buf[i] >= '0' && buf[i] <= '9')
    275      1.1  christos 	{
    276      1.1  christos 	  char *tail;
    277      1.1  christos 	  long value = strtol (buf + i, &tail, 10);
    278      1.1  christos 	  i = tail - buf;
    279      1.1  christos 
    280      1.1  christos 	  switch (value)
    281      1.1  christos 	    {
    282      1.1  christos 	    case 0:
    283      1.1  christos 	      /* Reset.  */
    284      1.1  christos 	      *this = ui_file_style ();
    285      1.1  christos 	      break;
    286      1.1  christos 	    case 1:
    287      1.1  christos 	      /* Bold.  */
    288      1.1  christos 	      m_intensity = BOLD;
    289      1.1  christos 	      break;
    290      1.1  christos 	    case 2:
    291      1.1  christos 	      /* Dim.  */
    292      1.1  christos 	      m_intensity = DIM;
    293      1.1  christos 	      break;
    294      1.1  christos 	    case 7:
    295      1.1  christos 	      /* Reverse.  */
    296      1.1  christos 	      m_reverse = true;
    297      1.1  christos 	      break;
    298      1.1  christos 	    case 21:
    299      1.1  christos 	      m_intensity = NORMAL;
    300      1.1  christos 	      break;
    301      1.1  christos 	    case 22:
    302      1.1  christos 	      /* Normal.  */
    303      1.1  christos 	      m_intensity = NORMAL;
    304      1.1  christos 	      break;
    305      1.1  christos 	    case 27:
    306      1.1  christos 	      /* Inverse off.  */
    307      1.1  christos 	      m_reverse = false;
    308      1.1  christos 	      break;
    309      1.1  christos 
    310      1.1  christos 	    case 30:
    311      1.1  christos 	    case 31:
    312      1.1  christos 	    case 32:
    313      1.1  christos 	    case 33:
    314      1.1  christos 	    case 34:
    315      1.1  christos 	    case 35:
    316      1.1  christos 	    case 36:
    317      1.1  christos 	    case 37:
    318  1.1.1.5  christos 	      m_foreground = color (value - 30);
    319  1.1.1.5  christos 	      break;
    320      1.1  christos 	      /* Note: not 38.  */
    321      1.1  christos 	    case 39:
    322  1.1.1.5  christos 	      m_foreground = NONE;
    323      1.1  christos 	      break;
    324      1.1  christos 
    325      1.1  christos 	    case 40:
    326      1.1  christos 	    case 41:
    327      1.1  christos 	    case 42:
    328      1.1  christos 	    case 43:
    329      1.1  christos 	    case 44:
    330      1.1  christos 	    case 45:
    331      1.1  christos 	    case 46:
    332      1.1  christos 	    case 47:
    333  1.1.1.5  christos 	      m_background = color (value - 40);
    334  1.1.1.5  christos 	      break;
    335      1.1  christos 	      /* Note: not 48.  */
    336      1.1  christos 	    case 49:
    337  1.1.1.5  christos 	      m_background = NONE;
    338      1.1  christos 	      break;
    339      1.1  christos 
    340      1.1  christos 	    case 90:
    341      1.1  christos 	    case 91:
    342      1.1  christos 	    case 92:
    343      1.1  christos 	    case 93:
    344      1.1  christos 	    case 94:
    345      1.1  christos 	    case 95:
    346      1.1  christos 	    case 96:
    347      1.1  christos 	    case 97:
    348      1.1  christos 	      m_foreground = color (value - 90 + 8);
    349      1.1  christos 	      break;
    350      1.1  christos 
    351      1.1  christos 	    case 100:
    352      1.1  christos 	    case 101:
    353      1.1  christos 	    case 102:
    354      1.1  christos 	    case 103:
    355      1.1  christos 	    case 104:
    356      1.1  christos 	    case 105:
    357      1.1  christos 	    case 106:
    358      1.1  christos 	    case 107:
    359      1.1  christos 	      m_background = color (value - 100 + 8);
    360      1.1  christos 	      break;
    361      1.1  christos 
    362      1.1  christos 	    case 38:
    363      1.1  christos 	      /* If we can't parse the extended color, fail.  */
    364      1.1  christos 	      if (!extended_color (buf, &i, &m_foreground))
    365      1.1  christos 		{
    366      1.1  christos 		  *n_read = subexps[0].rm_eo;
    367      1.1  christos 		  return false;
    368      1.1  christos 		}
    369      1.1  christos 	      break;
    370      1.1  christos 
    371      1.1  christos 	    case 48:
    372      1.1  christos 	      /* If we can't parse the extended color, fail.  */
    373      1.1  christos 	      if (!extended_color (buf, &i, &m_background))
    374      1.1  christos 		{
    375      1.1  christos 		  *n_read = subexps[0].rm_eo;
    376      1.1  christos 		  return false;
    377      1.1  christos 		}
    378      1.1  christos 	      break;
    379      1.1  christos 
    380      1.1  christos 	    default:
    381      1.1  christos 	      /* Ignore everything else.  */
    382      1.1  christos 	      break;
    383      1.1  christos 	    }
    384      1.1  christos 	}
    385      1.1  christos       else
    386      1.1  christos 	{
    387      1.1  christos 	  /* Unknown, let's just ignore.  */
    388      1.1  christos 	}
    389      1.1  christos     }
    390      1.1  christos 
    391      1.1  christos   *n_read = subexps[0].rm_eo;
    392      1.1  christos   return true;
    393      1.1  christos }
    394      1.1  christos 
    395      1.1  christos /* See ui-style.h.  */
    396      1.1  christos 
    397      1.1  christos bool
    398      1.1  christos skip_ansi_escape (const char *buf, int *n_read)
    399      1.1  christos {
    400      1.1  christos   regmatch_t subexps[NUM_SUBEXPRESSIONS];
    401      1.1  christos 
    402      1.1  christos   int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
    403      1.1  christos   if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
    404      1.1  christos     return false;
    405      1.1  christos 
    406      1.1  christos   *n_read = subexps[FINAL_SUBEXP].rm_eo;
    407      1.1  christos   return true;
    408      1.1  christos }
    409      1.1  christos 
    410  1.1.1.2  christos void _initialize_ui_style ();
    411      1.1  christos void
    412      1.1  christos _initialize_ui_style ()
    413      1.1  christos {
    414      1.1  christos   int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED);
    415      1.1  christos   /* If the regular expression was incorrect, it was a programming
    416      1.1  christos      error.  */
    417      1.1  christos   gdb_assert (code == 0);
    418      1.1  christos }
    419