libcpp, c, middle-end: Optimize initializers using #embed in C
[official-gcc.git] / gcc / diagnostic-color.cc
blob2ad708c06e6e886c8ce2479e5d6aea360eba48e6
1 /* Output colorization.
2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
17 02110-1301, USA. */
19 #include "config.h"
20 #define INCLUDE_VECTOR
21 #include "system.h"
22 #include "diagnostic-color.h"
23 #include "diagnostic-url.h"
24 #include "label-text.h"
26 #ifdef __MINGW32__
27 # define WIN32_LEAN_AND_MEAN
28 # include <windows.h>
29 #endif
31 #include "color-macros.h"
32 #include "selftest.h"
34 /* The context and logic for choosing default --color screen attributes
35 (foreground and background colors, etc.) are the following.
36 -- There are eight basic colors available, each with its own
37 nominal luminosity to the human eye and foreground/background
38 codes (black [0 %, 30/40], blue [11 %, 34/44], red [30 %, 31/41],
39 magenta [41 %, 35/45], green [59 %, 32/42], cyan [70 %, 36/46],
40 yellow [89 %, 33/43], and white [100 %, 37/47]).
41 -- Sometimes, white as a background is actually implemented using
42 a shade of light gray, so that a foreground white can be visible
43 on top of it (but most often not).
44 -- Sometimes, black as a foreground is actually implemented using
45 a shade of dark gray, so that it can be visible on top of a
46 background black (but most often not).
47 -- Sometimes, more colors are available, as extensions.
48 -- Other attributes can be selected/deselected (bold [1/22],
49 underline [4/24], standout/inverse [7/27], blink [5/25], and
50 invisible/hidden [8/28]). They are sometimes implemented by
51 using colors instead of what their names imply; e.g., bold is
52 often achieved by using brighter colors. In practice, only bold
53 is really available to us, underline sometimes being mapped by
54 the terminal to some strange color choice, and standout best
55 being left for use by downstream programs such as less(1).
56 -- We cannot assume that any of the extensions or special features
57 are available for the purpose of choosing defaults for everyone.
58 -- The most prevalent default terminal backgrounds are pure black
59 and pure white, and are not necessarily the same shades of
60 those as if they were selected explicitly with SGR sequences.
61 Some terminals use dark or light pictures as default background,
62 but those are covered over by an explicit selection of background
63 color with an SGR sequence; their users will appreciate their
64 background pictures not be covered like this, if possible.
65 -- Some uses of colors attributes is to make some output items
66 more understated (e.g., context lines); this cannot be achieved
67 by changing the background color.
68 -- For these reasons, the GCC color defaults should strive not
69 to change the background color from its default, unless it's
70 for a short item that should be highlighted, not understated.
71 -- The GCC foreground color defaults (without an explicitly set
72 background) should provide enough contrast to be readable on any
73 terminal with either a black (dark) or white (light) background.
74 This only leaves red, magenta, green, and cyan (and their bold
75 counterparts) and possibly bold blue. */
76 /* Default colors. The user can overwrite them using environment
77 variable GCC_COLORS. */
78 struct color_default
80 const char *m_name;
81 const char *m_val;
84 /* For GCC_COLORS. */
85 static const color_default gcc_color_defaults[] =
87 { "error", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) },
88 { "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA) },
89 { "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN) },
90 { "range1", SGR_SEQ (COLOR_FG_GREEN) },
91 { "range2", SGR_SEQ (COLOR_FG_BLUE) },
92 { "locus", SGR_SEQ (COLOR_BOLD) },
93 { "quote", SGR_SEQ (COLOR_BOLD) },
94 { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN) },
95 { "fnname", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
96 { "targs", SGR_SEQ (COLOR_FG_MAGENTA) },
97 { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN) },
98 { "fixit-delete", SGR_SEQ (COLOR_FG_RED) },
99 { "diff-filename", SGR_SEQ (COLOR_BOLD) },
100 { "diff-hunk", SGR_SEQ (COLOR_FG_CYAN) },
101 { "diff-delete", SGR_SEQ (COLOR_FG_RED) },
102 { "diff-insert", SGR_SEQ (COLOR_FG_GREEN) },
103 { "type-diff", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
104 { "valid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
105 { "invalid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) },
106 { "highlight-a", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
107 { "highlight-b", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_BLUE) }
110 class diagnostic_color_dict
112 public:
113 diagnostic_color_dict (const color_default *default_values,
114 size_t num_default_values);
116 bool parse_envvar_value (const char *const envvar_value);
118 const char *get_start_by_name (const char *name, size_t name_len) const;
119 const char *get_start_by_name (const char *name) const
121 return get_start_by_name (name, strlen (name));
124 private:
125 struct entry
127 entry (const color_default &d)
128 : m_name (d.m_name),
129 m_name_len (strlen (d.m_name)),
130 m_val (label_text::borrow (d.m_val))
134 const char *m_name;
135 size_t m_name_len;
136 label_text m_val;
139 const entry *get_entry_by_name (const char *name, size_t name_len) const;
140 entry *get_entry_by_name (const char *name, size_t name_len);
142 std::vector<entry> m_entries;
145 static diagnostic_color_dict *g_color_dict;
147 const char *
148 colorize_start (bool show_color, const char *name, size_t name_len)
150 if (!show_color)
151 return "";
153 if (!g_color_dict)
154 return "";
156 return g_color_dict->get_start_by_name (name, name_len);
159 /* Look for an entry named NAME of length NAME_LEN within this
160 diagnostic_color_dict, or nullptr if there isn't one. */
162 const diagnostic_color_dict::entry *
163 diagnostic_color_dict::get_entry_by_name (const char *name,
164 size_t name_len) const
166 for (auto &iter : m_entries)
167 if (iter.m_name_len == name_len
168 && memcmp (iter.m_name, name, name_len) == 0)
169 return &iter;
170 return nullptr;
173 /* Non-const version of the above. */
175 diagnostic_color_dict::entry *
176 diagnostic_color_dict::get_entry_by_name (const char *name,
177 size_t name_len)
179 for (auto &iter : m_entries)
180 if (iter.m_name_len == name_len
181 && memcmp (iter.m_name, name, name_len) == 0)
182 return &iter;
183 return nullptr;
186 /* Return the SGR codes to start a color entry named NAME of length
187 NAME_LEN within this diagnostic_color_dict, or the empty string if
188 there isn't one. */
190 const char *
191 diagnostic_color_dict::get_start_by_name (const char *name,
192 size_t name_len) const
194 if (const entry *e = get_entry_by_name (name, name_len))
195 return e->m_val.get ();
197 return "";
200 const char *
201 colorize_stop (bool show_color)
203 return show_color ? SGR_RESET : "";
206 /* diagnostic_color_dict's ctor. Initialize it from the given array
207 of color_default values. */
209 diagnostic_color_dict::
210 diagnostic_color_dict (const color_default *default_values,
211 size_t num_default_values)
213 m_entries.reserve (num_default_values);
214 for (size_t idx = 0; idx < num_default_values; idx++)
215 m_entries.push_back (entry (default_values[idx]));
218 /* Parse a list of color definitions from an environment variable
219 value (such as that of GCC_COLORS).
220 No character escaping is needed or supported. */
222 bool
223 diagnostic_color_dict::parse_envvar_value (const char *const envvar_value)
225 /* envvar not set: use the default colors. */
226 if (envvar_value == nullptr)
227 return true;
229 /* envvar set to empty string: disable colorization. */
230 if (*envvar_value == '\0')
231 return false;
233 const char *q, *name, *val;
234 size_t name_len = 0, val_len = 0;
236 name = q = envvar_value;
237 val = NULL;
238 /* From now on, be well-formed or you're gone. */
239 for (;;)
240 if (*q == ':' || *q == '\0')
242 if (val)
243 val_len = q - val;
244 else
245 name_len = q - name;
246 /* Empty name without val (empty cap)
247 won't match and will be ignored. */
248 entry *e = get_entry_by_name (name, name_len);
249 /* If name unknown, go on for forward compatibility. */
250 if (e && val)
252 char *b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("")));
253 memcpy (b, SGR_START, strlen (SGR_START));
254 memcpy (b + strlen (SGR_START), val, val_len);
255 memcpy (b + strlen (SGR_START) + val_len, SGR_END,
256 sizeof (SGR_END));
257 e->m_val = label_text::take (b);
259 if (*q == '\0')
260 return true;
261 name = ++q;
262 val = NULL;
264 else if (*q == '=')
266 if (q == name || val)
267 return true;
269 name_len = q - name;
270 val = ++q; /* Can be the empty string. */
272 else if (val == NULL)
273 q++; /* Accumulate name. */
274 else if (*q == ';' || (*q >= '0' && *q <= '9'))
275 q++; /* Accumulate val. Protect the terminal from being sent
276 garbage. */
277 else
278 return true;
281 /* Parse GCC_COLORS. The default would look like:
282 GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
283 range1=32:range2=34:locus=01:quote=01:path=01;36:\
284 fixit-insert=32:fixit-delete=31:'\
285 diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
286 type-diff=01;32'. */
287 static bool
288 parse_gcc_colors ()
290 if (!g_color_dict)
291 return false;
292 return g_color_dict->parse_envvar_value (getenv ("GCC_COLORS")); /* Plural! */
295 /* Return true if we should use color when in auto mode, false otherwise. */
296 static bool
297 should_colorize (void)
299 #ifdef __MINGW32__
300 /* For consistency reasons, one should check the handle returned by
301 _get_osfhandle(_fileno(stderr)) because the function
302 pp_write_text_to_stream() in pretty-print.cc calls fputs() on
303 that stream. However, the code below for non-Windows doesn't seem
304 to care about it either... */
305 HANDLE handle;
306 DWORD mode;
307 BOOL isconsole = false;
309 handle = GetStdHandle (STD_ERROR_HANDLE);
311 if ((handle != INVALID_HANDLE_VALUE) && (handle != NULL))
312 isconsole = GetConsoleMode (handle, &mode);
314 #ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
315 if (isconsole)
317 /* Try to enable processing of VT100 escape sequences */
318 mode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
319 SetConsoleMode (handle, mode);
321 #endif
323 return isconsole;
324 #else
325 char const *t = getenv ("TERM");
326 /* emacs M-x shell sets TERM="dumb". */
327 return t && strcmp (t, "dumb") != 0 && isatty (STDERR_FILENO);
328 #endif
331 bool
332 colorize_init (diagnostic_color_rule_t rule)
334 if (!g_color_dict)
335 g_color_dict = new diagnostic_color_dict (gcc_color_defaults,
336 ARRAY_SIZE (gcc_color_defaults));
338 switch (rule)
340 case DIAGNOSTICS_COLOR_NO:
341 return false;
342 case DIAGNOSTICS_COLOR_YES:
343 return parse_gcc_colors ();
344 case DIAGNOSTICS_COLOR_AUTO:
345 if (should_colorize ())
346 return parse_gcc_colors ();
347 else
348 return false;
349 default:
350 gcc_unreachable ();
354 /* Return URL_FORMAT_XXX which tells how we should emit urls
355 when in always mode.
356 We use GCC_URLS and if that is not defined TERM_URLS.
357 If neither is defined the feature is enabled by default. */
359 static diagnostic_url_format
360 parse_env_vars_for_urls ()
362 const char *p;
364 p = getenv ("GCC_URLS"); /* Plural! */
365 if (p == NULL)
366 p = getenv ("TERM_URLS");
368 if (p == NULL)
369 return URL_FORMAT_DEFAULT;
371 if (*p == '\0')
372 return URL_FORMAT_NONE;
374 if (!strcmp (p, "no"))
375 return URL_FORMAT_NONE;
377 if (!strcmp (p, "st"))
378 return URL_FORMAT_ST;
380 if (!strcmp (p, "bel"))
381 return URL_FORMAT_BEL;
383 return URL_FORMAT_DEFAULT;
386 /* Return true if we should use urls when in auto mode, false otherwise. */
388 static bool
389 auto_enable_urls ()
391 const char *term, *colorterm;
393 /* First check the terminal is capable of printing color escapes,
394 if not URLs won't work either. */
395 if (!should_colorize ())
396 return false;
398 #ifdef __MINGW32__
399 HANDLE handle;
400 DWORD mode;
402 handle = GetStdHandle (STD_ERROR_HANDLE);
403 if ((handle == INVALID_HANDLE_VALUE) || (handle == NULL))
404 return false;
406 /* If ansi escape sequences aren't supported by the console, then URLs will
407 print mangled from mingw_ansi_fputs's console API translation. It wouldn't
408 be useful even if this weren't the case. */
409 if (GetConsoleMode (handle, &mode)
410 #ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
411 && !(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
412 #endif
414 return false;
415 #endif
417 /* xfce4-terminal is known to not implement URLs at this time.
418 Recently new installations (0.8) will safely ignore the URL escape
419 sequences, but a large number of legacy installations (0.6.3) print
420 garbage when URLs are printed. Therefore we lose nothing by
421 disabling this feature for that specific terminal type. */
422 colorterm = getenv ("COLORTERM");
423 if (colorterm && !strcmp (colorterm, "xfce4-terminal"))
424 return false;
426 /* Old versions of gnome-terminal where URL escapes cause screen
427 corruptions set COLORTERM="gnome-terminal", recent versions
428 with working URL support set this to "truecolor". */
429 if (colorterm && !strcmp (colorterm, "gnome-terminal"))
430 return false;
432 /* Since the following checks are less specific than the ones
433 above, let GCC_URLS and TERM_URLS override the decision. */
434 if (getenv ("GCC_URLS") || getenv ("TERM_URLS"))
435 return true;
437 /* In an ssh session the COLORTERM is not there, but TERM=xterm
438 can be used as an indication of a incompatible terminal while
439 TERM=xterm-256color appears to be a working terminal. */
440 term = getenv ("TERM");
441 if (!colorterm && term && !strcmp (term, "xterm"))
442 return false;
444 /* When logging in a linux over serial line, we see TERM=linux
445 and no COLORTERM, it is unlikely that the URL escapes will
446 work in that environmen either. */
447 if (!colorterm && term && !strcmp (term, "linux"))
448 return false;
450 return true;
453 /* Determine if URLs should be enabled, based on RULE,
454 and, if so, which format to use.
455 This reuses the logic for colorization. */
457 diagnostic_url_format
458 determine_url_format (diagnostic_url_rule_t rule)
460 switch (rule)
462 case DIAGNOSTICS_URL_NO:
463 return URL_FORMAT_NONE;
464 case DIAGNOSTICS_URL_YES:
465 return parse_env_vars_for_urls ();
466 case DIAGNOSTICS_URL_AUTO:
467 if (auto_enable_urls ())
468 return parse_env_vars_for_urls ();
469 else
470 return URL_FORMAT_NONE;
471 default:
472 gcc_unreachable ();
476 #if CHECKING_P
478 namespace selftest {
480 /* Test of an empty diagnostic_color_dict. */
482 static void
483 test_empty_color_dict ()
485 diagnostic_color_dict d (nullptr, 0);
486 ASSERT_STREQ (d.get_start_by_name ("warning"), "");
487 ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
490 /* Test of a diagnostic_color_dict with GCC's defaults. */
492 static void
493 test_default_color_dict ()
495 diagnostic_color_dict d (gcc_color_defaults,
496 ARRAY_SIZE (gcc_color_defaults));
497 ASSERT_STREQ (d.get_start_by_name ("warning"),
498 SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA));
499 ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
502 /* Test of a diagnostic_color_dict with GCC's defaults plus overrides from
503 an environment variable. */
505 static void
506 test_color_dict_envvar_parsing ()
508 diagnostic_color_dict d (gcc_color_defaults,
509 ARRAY_SIZE (gcc_color_defaults));
511 d.parse_envvar_value ("error=01;37:warning=01;42:unknown-value=01;36");
513 ASSERT_STREQ (d.get_start_by_name ("error"),
514 SGR_SEQ ("01;37"));
515 ASSERT_STREQ (d.get_start_by_name ("warning"),
516 SGR_SEQ ("01;42"));
517 ASSERT_STREQ (d.get_start_by_name ("unknown-value"), "");
518 ASSERT_STREQ (d.get_start_by_name ("should-not-be-found"), "");
522 /* Run all of the selftests within this file. */
524 void
525 diagnostic_color_cc_tests ()
527 test_empty_color_dict ();
528 test_default_color_dict ();
529 test_color_dict_envvar_parsing ();
532 } // namespace selftest
534 #endif /* #if CHECKING_P */