xref: /netbsd-src/external/gpl3/gdb.old/dist/gdb/ui-style.c (revision 6881a4007f077b54e5f51159c52b9b25f57deb0d)
17f2ac410Schristos /* Styling for ui_file
2*6881a400Schristos    Copyright (C) 2018-2023 Free Software Foundation, Inc.
37f2ac410Schristos 
47f2ac410Schristos    This file is part of GDB.
57f2ac410Schristos 
67f2ac410Schristos    This program is free software; you can redistribute it and/or modify
77f2ac410Schristos    it under the terms of the GNU General Public License as published by
87f2ac410Schristos    the Free Software Foundation; either version 3 of the License, or
97f2ac410Schristos    (at your option) any later version.
107f2ac410Schristos 
117f2ac410Schristos    This program is distributed in the hope that it will be useful,
127f2ac410Schristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
137f2ac410Schristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
147f2ac410Schristos    GNU General Public License for more details.
157f2ac410Schristos 
167f2ac410Schristos    You should have received a copy of the GNU General Public License
177f2ac410Schristos    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
187f2ac410Schristos 
197f2ac410Schristos #include "defs.h"
207f2ac410Schristos #include "ui-style.h"
21*6881a400Schristos #include "gdbsupport/gdb_regex.h"
227f2ac410Schristos 
237f2ac410Schristos /* A regular expression that is used for matching ANSI terminal escape
247f2ac410Schristos    sequences.  */
257f2ac410Schristos 
26*6881a400Schristos static const char ansi_regex_text[] =
277f2ac410Schristos   /* Introduction.  */
287f2ac410Schristos   "^\033\\["
297f2ac410Schristos #define DATA_SUBEXP 1
307f2ac410Schristos   /* Capture parameter and intermediate bytes.  */
317f2ac410Schristos   "("
327f2ac410Schristos   /* Parameter bytes.  */
337f2ac410Schristos   "[\x30-\x3f]*"
347f2ac410Schristos   /* Intermediate bytes.  */
357f2ac410Schristos   "[\x20-\x2f]*"
367f2ac410Schristos   /* End the first capture.  */
377f2ac410Schristos   ")"
387f2ac410Schristos   /* The final byte.  */
397f2ac410Schristos #define FINAL_SUBEXP 2
407f2ac410Schristos   "([\x40-\x7e])";
417f2ac410Schristos 
427f2ac410Schristos /* The number of subexpressions to allocate space for, including the
437f2ac410Schristos    "0th" whole match subexpression.  */
447f2ac410Schristos #define NUM_SUBEXPRESSIONS 3
457f2ac410Schristos 
467f2ac410Schristos /* The compiled form of ansi_regex_text.  */
477f2ac410Schristos 
487f2ac410Schristos static regex_t ansi_regex;
497f2ac410Schristos 
507f2ac410Schristos /* This maps bright colors to RGB triples.  The index is the bright
517f2ac410Schristos    color index, starting with bright black.  The values come from
527f2ac410Schristos    xterm.  */
537f2ac410Schristos 
547f2ac410Schristos static const uint8_t bright_colors[][3] = {
557f2ac410Schristos   { 127, 127, 127 },		/* Black.  */
567f2ac410Schristos   { 255, 0, 0 },		/* Red.  */
577f2ac410Schristos   { 0, 255, 0 },		/* Green.  */
587f2ac410Schristos   { 255, 255, 0 },		/* Yellow.  */
597f2ac410Schristos   { 92, 92, 255 },		/* Blue.  */
607f2ac410Schristos   { 255, 0, 255 },		/* Magenta.  */
617f2ac410Schristos   { 0, 255, 255 },		/* Cyan.  */
627f2ac410Schristos   { 255, 255, 255 }		/* White.  */
637f2ac410Schristos };
647f2ac410Schristos 
657f2ac410Schristos /* See ui-style.h.  */
667f2ac410Schristos 
677f2ac410Schristos bool
687f2ac410Schristos ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
697f2ac410Schristos {
707f2ac410Schristos   if (m_simple)
717f2ac410Schristos     {
727f2ac410Schristos       if (m_value >= BLACK && m_value <= WHITE)
737f2ac410Schristos 	str->append (std::to_string (m_value + (is_fg ? 30 : 40)));
747f2ac410Schristos       else if (m_value > WHITE && m_value <= WHITE + 8)
757f2ac410Schristos 	str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100)));
767f2ac410Schristos       else if (m_value != -1)
777f2ac410Schristos 	{
787f2ac410Schristos 	  str->append (is_fg ? "38;5;" : "48;5;");
797f2ac410Schristos 	  str->append (std::to_string (m_value));
807f2ac410Schristos 	}
817f2ac410Schristos       else
827f2ac410Schristos 	return false;
837f2ac410Schristos     }
847f2ac410Schristos   else
857f2ac410Schristos     {
867f2ac410Schristos       str->append (is_fg ? "38;2;" : "48;2;");
877f2ac410Schristos       str->append (std::to_string (m_red)
887f2ac410Schristos 		   + ";" + std::to_string (m_green)
897f2ac410Schristos 		   + ";" + std::to_string (m_blue));
907f2ac410Schristos     }
917f2ac410Schristos   return true;
927f2ac410Schristos }
937f2ac410Schristos 
947f2ac410Schristos /* See ui-style.h.  */
957f2ac410Schristos 
967f2ac410Schristos void
977f2ac410Schristos ui_file_style::color::get_rgb (uint8_t *rgb) const
987f2ac410Schristos {
997f2ac410Schristos   if (m_simple)
1007f2ac410Schristos     {
1017f2ac410Schristos       /* Can't call this for a basic color or NONE -- those will end
1027f2ac410Schristos 	 up in the assert below.  */
1037f2ac410Schristos       if (m_value >= 8 && m_value <= 15)
1047f2ac410Schristos 	memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t));
1057f2ac410Schristos       else if (m_value >= 16 && m_value <= 231)
1067f2ac410Schristos 	{
1077f2ac410Schristos 	  int value = m_value;
1087f2ac410Schristos 	  value -= 16;
1097f2ac410Schristos 	  /* This obscure formula seems to be what terminals actually
1107f2ac410Schristos 	     do.  */
1117f2ac410Schristos 	  int component = value / 36;
1127f2ac410Schristos 	  rgb[0] = component == 0 ? 0 : (55 + component * 40);
1137f2ac410Schristos 	  value %= 36;
1147f2ac410Schristos 	  component = value / 6;
1157f2ac410Schristos 	  rgb[1] = component == 0 ? 0 : (55 + component * 40);
1167f2ac410Schristos 	  value %= 6;
1177f2ac410Schristos 	  rgb[2] = value == 0 ? 0 : (55 + value * 40);
1187f2ac410Schristos 	}
1197f2ac410Schristos       else if (m_value >= 232)
1207f2ac410Schristos 	{
1217f2ac410Schristos 	  uint8_t v = (m_value - 232) * 10 + 8;
1227f2ac410Schristos 	  rgb[0] = v;
1237f2ac410Schristos 	  rgb[1] = v;
1247f2ac410Schristos 	  rgb[2] = v;
1257f2ac410Schristos 	}
1267f2ac410Schristos       else
1277f2ac410Schristos 	gdb_assert_not_reached ("get_rgb called on invalid color");
1287f2ac410Schristos     }
1297f2ac410Schristos   else
1307f2ac410Schristos     {
1317f2ac410Schristos       rgb[0] = m_red;
1327f2ac410Schristos       rgb[1] = m_green;
1337f2ac410Schristos       rgb[2] = m_blue;
1347f2ac410Schristos     }
1357f2ac410Schristos }
1367f2ac410Schristos 
1377f2ac410Schristos /* See ui-style.h.  */
1387f2ac410Schristos 
1397f2ac410Schristos std::string
1407f2ac410Schristos ui_file_style::to_ansi () const
1417f2ac410Schristos {
1427f2ac410Schristos   std::string result ("\033[");
1437f2ac410Schristos   bool need_semi = m_foreground.append_ansi (true, &result);
1447f2ac410Schristos   if (!m_background.is_none ())
1457f2ac410Schristos     {
1467f2ac410Schristos       if (need_semi)
1477f2ac410Schristos 	result.push_back (';');
1487f2ac410Schristos       m_background.append_ansi (false, &result);
1497f2ac410Schristos       need_semi = true;
1507f2ac410Schristos     }
1517f2ac410Schristos   if (m_intensity != NORMAL)
1527f2ac410Schristos     {
1537f2ac410Schristos       if (need_semi)
1547f2ac410Schristos 	result.push_back (';');
1557f2ac410Schristos       result.append (std::to_string (m_intensity));
1567f2ac410Schristos       need_semi = true;
1577f2ac410Schristos     }
1587f2ac410Schristos   if (m_reverse)
1597f2ac410Schristos     {
1607f2ac410Schristos       if (need_semi)
1617f2ac410Schristos 	result.push_back (';');
1627f2ac410Schristos       result.push_back ('7');
1637f2ac410Schristos     }
1647f2ac410Schristos   result.push_back ('m');
1657f2ac410Schristos   return result;
1667f2ac410Schristos }
1677f2ac410Schristos 
1687f2ac410Schristos /* Read a ";" and a number from STRING.  Return the number of
1697f2ac410Schristos    characters read and put the number into *NUM.  */
1707f2ac410Schristos 
1717f2ac410Schristos static bool
172*6881a400Schristos read_semi_number (const char *string, regoff_t *idx, long *num)
1737f2ac410Schristos {
1747f2ac410Schristos   if (string[*idx] != ';')
1757f2ac410Schristos     return false;
1767f2ac410Schristos   ++*idx;
1777f2ac410Schristos   if (string[*idx] < '0' || string[*idx] > '9')
1787f2ac410Schristos     return false;
1797f2ac410Schristos   char *tail;
1807f2ac410Schristos   *num = strtol (string + *idx, &tail, 10);
1817f2ac410Schristos   *idx = tail - string;
1827f2ac410Schristos   return true;
1837f2ac410Schristos }
1847f2ac410Schristos 
1857f2ac410Schristos /* A helper for ui_file_style::parse that reads an extended color
1867f2ac410Schristos    sequence; that is, and 8- or 24- bit color.  */
1877f2ac410Schristos 
1887f2ac410Schristos static bool
189*6881a400Schristos extended_color (const char *str, regoff_t *idx, ui_file_style::color *color)
1907f2ac410Schristos {
1917f2ac410Schristos   long value;
1927f2ac410Schristos 
1937f2ac410Schristos   if (!read_semi_number (str, idx, &value))
1947f2ac410Schristos     return false;
1957f2ac410Schristos 
1967f2ac410Schristos   if (value == 5)
1977f2ac410Schristos     {
1987f2ac410Schristos       /* 8-bit color.  */
1997f2ac410Schristos       if (!read_semi_number (str, idx, &value))
2007f2ac410Schristos 	return false;
2017f2ac410Schristos 
2027f2ac410Schristos       if (value >= 0 && value <= 255)
2037f2ac410Schristos 	*color = ui_file_style::color (value);
2047f2ac410Schristos       else
2057f2ac410Schristos 	return false;
2067f2ac410Schristos     }
2077f2ac410Schristos   else if (value == 2)
2087f2ac410Schristos     {
2097f2ac410Schristos       /* 24-bit color.  */
2107f2ac410Schristos       long r, g, b;
2117f2ac410Schristos       if (!read_semi_number (str, idx, &r)
2127f2ac410Schristos 	  || r > 255
2137f2ac410Schristos 	  || !read_semi_number (str, idx, &g)
2147f2ac410Schristos 	  || g > 255
2157f2ac410Schristos 	  || !read_semi_number (str, idx, &b)
2167f2ac410Schristos 	  || b > 255)
2177f2ac410Schristos 	return false;
2187f2ac410Schristos       *color = ui_file_style::color (r, g, b);
2197f2ac410Schristos     }
2207f2ac410Schristos   else
2217f2ac410Schristos     {
2227f2ac410Schristos       /* Unrecognized sequence.  */
2237f2ac410Schristos       return false;
2247f2ac410Schristos     }
2257f2ac410Schristos 
2267f2ac410Schristos   return true;
2277f2ac410Schristos }
2287f2ac410Schristos 
2297f2ac410Schristos /* See ui-style.h.  */
2307f2ac410Schristos 
2317f2ac410Schristos bool
2327f2ac410Schristos ui_file_style::parse (const char *buf, size_t *n_read)
2337f2ac410Schristos {
2347f2ac410Schristos   regmatch_t subexps[NUM_SUBEXPRESSIONS];
2357f2ac410Schristos 
2367f2ac410Schristos   int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
2377f2ac410Schristos   if (match == REG_NOMATCH)
2387f2ac410Schristos     {
2397f2ac410Schristos       *n_read = 0;
2407f2ac410Schristos       return false;
2417f2ac410Schristos     }
2427f2ac410Schristos   /* Other failures mean the regexp is broken.  */
2437f2ac410Schristos   gdb_assert (match == 0);
2447f2ac410Schristos   /* The regexp is anchored.  */
2457f2ac410Schristos   gdb_assert (subexps[0].rm_so == 0);
2467f2ac410Schristos   /* The final character exists.  */
2477f2ac410Schristos   gdb_assert (subexps[FINAL_SUBEXP].rm_eo - subexps[FINAL_SUBEXP].rm_so == 1);
2487f2ac410Schristos 
2497f2ac410Schristos   if (buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
2507f2ac410Schristos     {
2517f2ac410Schristos       /* We don't handle this sequence, so just drop it.  */
2527f2ac410Schristos       *n_read = subexps[0].rm_eo;
2537f2ac410Schristos       return false;
2547f2ac410Schristos     }
2557f2ac410Schristos 
2567f2ac410Schristos   /* Examine each setting in the match and apply it to the result.
2577f2ac410Schristos      See the Select Graphic Rendition section of
2587f2ac410Schristos      https://en.wikipedia.org/wiki/ANSI_escape_code.  In essence each
2597f2ac410Schristos      code is just a number, separated by ";"; there are some more
2607f2ac410Schristos      wrinkles but we don't support them all..  */
2617f2ac410Schristos 
2627f2ac410Schristos   /* "\033[m" means the same thing as "\033[0m", so handle that
2637f2ac410Schristos      specially here.  */
2647f2ac410Schristos   if (subexps[DATA_SUBEXP].rm_so == subexps[DATA_SUBEXP].rm_eo)
2657f2ac410Schristos     *this = ui_file_style ();
2667f2ac410Schristos 
2677f2ac410Schristos   for (regoff_t i = subexps[DATA_SUBEXP].rm_so;
2687f2ac410Schristos        i < subexps[DATA_SUBEXP].rm_eo;
2697f2ac410Schristos        ++i)
2707f2ac410Schristos     {
2717f2ac410Schristos       if (buf[i] == ';')
2727f2ac410Schristos 	{
2737f2ac410Schristos 	  /* Skip.  */
2747f2ac410Schristos 	}
2757f2ac410Schristos       else if (buf[i] >= '0' && buf[i] <= '9')
2767f2ac410Schristos 	{
2777f2ac410Schristos 	  char *tail;
2787f2ac410Schristos 	  long value = strtol (buf + i, &tail, 10);
2797f2ac410Schristos 	  i = tail - buf;
2807f2ac410Schristos 
2817f2ac410Schristos 	  switch (value)
2827f2ac410Schristos 	    {
2837f2ac410Schristos 	    case 0:
2847f2ac410Schristos 	      /* Reset.  */
2857f2ac410Schristos 	      *this = ui_file_style ();
2867f2ac410Schristos 	      break;
2877f2ac410Schristos 	    case 1:
2887f2ac410Schristos 	      /* Bold.  */
2897f2ac410Schristos 	      m_intensity = BOLD;
2907f2ac410Schristos 	      break;
2917f2ac410Schristos 	    case 2:
2927f2ac410Schristos 	      /* Dim.  */
2937f2ac410Schristos 	      m_intensity = DIM;
2947f2ac410Schristos 	      break;
2957f2ac410Schristos 	    case 7:
2967f2ac410Schristos 	      /* Reverse.  */
2977f2ac410Schristos 	      m_reverse = true;
2987f2ac410Schristos 	      break;
2997f2ac410Schristos 	    case 21:
3007f2ac410Schristos 	      m_intensity = NORMAL;
3017f2ac410Schristos 	      break;
3027f2ac410Schristos 	    case 22:
3037f2ac410Schristos 	      /* Normal.  */
3047f2ac410Schristos 	      m_intensity = NORMAL;
3057f2ac410Schristos 	      break;
3067f2ac410Schristos 	    case 27:
3077f2ac410Schristos 	      /* Inverse off.  */
3087f2ac410Schristos 	      m_reverse = false;
3097f2ac410Schristos 	      break;
3107f2ac410Schristos 
3117f2ac410Schristos 	    case 30:
3127f2ac410Schristos 	    case 31:
3137f2ac410Schristos 	    case 32:
3147f2ac410Schristos 	    case 33:
3157f2ac410Schristos 	    case 34:
3167f2ac410Schristos 	    case 35:
3177f2ac410Schristos 	    case 36:
3187f2ac410Schristos 	    case 37:
3197f2ac410Schristos 	      /* Note: not 38.  */
3207f2ac410Schristos 	    case 39:
3217f2ac410Schristos 	      m_foreground = color (value - 30);
3227f2ac410Schristos 	      break;
3237f2ac410Schristos 
3247f2ac410Schristos 	    case 40:
3257f2ac410Schristos 	    case 41:
3267f2ac410Schristos 	    case 42:
3277f2ac410Schristos 	    case 43:
3287f2ac410Schristos 	    case 44:
3297f2ac410Schristos 	    case 45:
3307f2ac410Schristos 	    case 46:
3317f2ac410Schristos 	    case 47:
3327f2ac410Schristos 	      /* Note: not 48.  */
3337f2ac410Schristos 	    case 49:
3347f2ac410Schristos 	      m_background = color (value - 40);
3357f2ac410Schristos 	      break;
3367f2ac410Schristos 
3377f2ac410Schristos 	    case 90:
3387f2ac410Schristos 	    case 91:
3397f2ac410Schristos 	    case 92:
3407f2ac410Schristos 	    case 93:
3417f2ac410Schristos 	    case 94:
3427f2ac410Schristos 	    case 95:
3437f2ac410Schristos 	    case 96:
3447f2ac410Schristos 	    case 97:
3457f2ac410Schristos 	      m_foreground = color (value - 90 + 8);
3467f2ac410Schristos 	      break;
3477f2ac410Schristos 
3487f2ac410Schristos 	    case 100:
3497f2ac410Schristos 	    case 101:
3507f2ac410Schristos 	    case 102:
3517f2ac410Schristos 	    case 103:
3527f2ac410Schristos 	    case 104:
3537f2ac410Schristos 	    case 105:
3547f2ac410Schristos 	    case 106:
3557f2ac410Schristos 	    case 107:
3567f2ac410Schristos 	      m_background = color (value - 100 + 8);
3577f2ac410Schristos 	      break;
3587f2ac410Schristos 
3597f2ac410Schristos 	    case 38:
3607f2ac410Schristos 	      /* If we can't parse the extended color, fail.  */
3617f2ac410Schristos 	      if (!extended_color (buf, &i, &m_foreground))
3627f2ac410Schristos 		{
3637f2ac410Schristos 		  *n_read = subexps[0].rm_eo;
3647f2ac410Schristos 		  return false;
3657f2ac410Schristos 		}
3667f2ac410Schristos 	      break;
3677f2ac410Schristos 
3687f2ac410Schristos 	    case 48:
3697f2ac410Schristos 	      /* If we can't parse the extended color, fail.  */
3707f2ac410Schristos 	      if (!extended_color (buf, &i, &m_background))
3717f2ac410Schristos 		{
3727f2ac410Schristos 		  *n_read = subexps[0].rm_eo;
3737f2ac410Schristos 		  return false;
3747f2ac410Schristos 		}
3757f2ac410Schristos 	      break;
3767f2ac410Schristos 
3777f2ac410Schristos 	    default:
3787f2ac410Schristos 	      /* Ignore everything else.  */
3797f2ac410Schristos 	      break;
3807f2ac410Schristos 	    }
3817f2ac410Schristos 	}
3827f2ac410Schristos       else
3837f2ac410Schristos 	{
3847f2ac410Schristos 	  /* Unknown, let's just ignore.  */
3857f2ac410Schristos 	}
3867f2ac410Schristos     }
3877f2ac410Schristos 
3887f2ac410Schristos   *n_read = subexps[0].rm_eo;
3897f2ac410Schristos   return true;
3907f2ac410Schristos }
3917f2ac410Schristos 
3927f2ac410Schristos /* See ui-style.h.  */
3937f2ac410Schristos 
3947f2ac410Schristos bool
3957f2ac410Schristos skip_ansi_escape (const char *buf, int *n_read)
3967f2ac410Schristos {
3977f2ac410Schristos   regmatch_t subexps[NUM_SUBEXPRESSIONS];
3987f2ac410Schristos 
3997f2ac410Schristos   int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
4007f2ac410Schristos   if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
4017f2ac410Schristos     return false;
4027f2ac410Schristos 
4037f2ac410Schristos   *n_read = subexps[FINAL_SUBEXP].rm_eo;
4047f2ac410Schristos   return true;
4057f2ac410Schristos }
4067f2ac410Schristos 
4077d62b00eSchristos void _initialize_ui_style ();
4087f2ac410Schristos void
4097f2ac410Schristos _initialize_ui_style ()
4107f2ac410Schristos {
4117f2ac410Schristos   int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED);
4127f2ac410Schristos   /* If the regular expression was incorrect, it was a programming
4137f2ac410Schristos      error.  */
4147f2ac410Schristos   gdb_assert (code == 0);
4157f2ac410Schristos }
416