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