Another fix for GDB styling
[external/binutils.git] / gdb / ui-style.c
1 /* Styling for ui_file
2    Copyright (C) 2018-2019 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 "defs.h"
20 #include "ui-style.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
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
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
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
171 read_semi_number (const char *string, int *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
188 extended_color (const char *str, int *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
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
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
407 _initialize_ui_style ()
408 {
409   int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED);
410   /* If the regular expression was incorrect, it was a programming
411      error.  */
412   gdb_assert (code == 0);
413 }