1 | /* Output colorization. |
2 | Copyright (C) 2011-2024 Free Software Foundation, Inc. |
3 | |
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. |
8 | |
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. |
13 | |
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. */ |
18 | |
19 | #include "config.h" |
20 | #include "system.h" |
21 | #include "diagnostic-color.h" |
22 | #include "diagnostic-url.h" |
23 | |
24 | #ifdef __MINGW32__ |
25 | # define WIN32_LEAN_AND_MEAN |
26 | # include <windows.h> |
27 | #endif |
28 | |
29 | #include "color-macros.h" |
30 | |
31 | /* The context and logic for choosing default --color screen attributes |
32 | (foreground and background colors, etc.) are the following. |
33 | -- There are eight basic colors available, each with its own |
34 | nominal luminosity to the human eye and foreground/background |
35 | codes (black [0 %, 30/40], blue [11 %, 34/44], red [30 %, 31/41], |
36 | magenta [41 %, 35/45], green [59 %, 32/42], cyan [70 %, 36/46], |
37 | yellow [89 %, 33/43], and white [100 %, 37/47]). |
38 | -- Sometimes, white as a background is actually implemented using |
39 | a shade of light gray, so that a foreground white can be visible |
40 | on top of it (but most often not). |
41 | -- Sometimes, black as a foreground is actually implemented using |
42 | a shade of dark gray, so that it can be visible on top of a |
43 | background black (but most often not). |
44 | -- Sometimes, more colors are available, as extensions. |
45 | -- Other attributes can be selected/deselected (bold [1/22], |
46 | underline [4/24], standout/inverse [7/27], blink [5/25], and |
47 | invisible/hidden [8/28]). They are sometimes implemented by |
48 | using colors instead of what their names imply; e.g., bold is |
49 | often achieved by using brighter colors. In practice, only bold |
50 | is really available to us, underline sometimes being mapped by |
51 | the terminal to some strange color choice, and standout best |
52 | being left for use by downstream programs such as less(1). |
53 | -- We cannot assume that any of the extensions or special features |
54 | are available for the purpose of choosing defaults for everyone. |
55 | -- The most prevalent default terminal backgrounds are pure black |
56 | and pure white, and are not necessarily the same shades of |
57 | those as if they were selected explicitly with SGR sequences. |
58 | Some terminals use dark or light pictures as default background, |
59 | but those are covered over by an explicit selection of background |
60 | color with an SGR sequence; their users will appreciate their |
61 | background pictures not be covered like this, if possible. |
62 | -- Some uses of colors attributes is to make some output items |
63 | more understated (e.g., context lines); this cannot be achieved |
64 | by changing the background color. |
65 | -- For these reasons, the GCC color defaults should strive not |
66 | to change the background color from its default, unless it's |
67 | for a short item that should be highlighted, not understated. |
68 | -- The GCC foreground color defaults (without an explicitly set |
69 | background) should provide enough contrast to be readable on any |
70 | terminal with either a black (dark) or white (light) background. |
71 | This only leaves red, magenta, green, and cyan (and their bold |
72 | counterparts) and possibly bold blue. */ |
73 | /* Default colors. The user can overwrite them using environment |
74 | variable GCC_COLORS. */ |
75 | struct color_cap |
76 | { |
77 | const char *name; |
78 | const char *val; |
79 | unsigned char name_len; |
80 | bool free_val; |
81 | }; |
82 | |
83 | /* For GCC_COLORS. */ |
84 | static struct color_cap color_dict[] = |
85 | { |
86 | { .name: "error" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED), .name_len: 5, .free_val: false }, |
87 | { .name: "warning" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA), |
88 | .name_len: 7, .free_val: false }, |
89 | { .name: "note" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), .name_len: 4, .free_val: false }, |
90 | { .name: "range1" , SGR_SEQ (COLOR_FG_GREEN), .name_len: 6, .free_val: false }, |
91 | { .name: "range2" , SGR_SEQ (COLOR_FG_BLUE), .name_len: 6, .free_val: false }, |
92 | { .name: "locus" , SGR_SEQ (COLOR_BOLD), .name_len: 5, .free_val: false }, |
93 | { .name: "quote" , SGR_SEQ (COLOR_BOLD), .name_len: 5, .free_val: false }, |
94 | { .name: "path" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), .name_len: 4, .free_val: false }, |
95 | { .name: "fnname" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), .name_len: 6, .free_val: false }, |
96 | { .name: "targs" , SGR_SEQ (COLOR_FG_MAGENTA), .name_len: 5, .free_val: false }, |
97 | { .name: "fixit-insert" , SGR_SEQ (COLOR_FG_GREEN), .name_len: 12, .free_val: false }, |
98 | { .name: "fixit-delete" , SGR_SEQ (COLOR_FG_RED), .name_len: 12, .free_val: false }, |
99 | { .name: "diff-filename" , SGR_SEQ (COLOR_BOLD), .name_len: 13, .free_val: false }, |
100 | { .name: "diff-hunk" , SGR_SEQ (COLOR_FG_CYAN), .name_len: 9, .free_val: false }, |
101 | { .name: "diff-delete" , SGR_SEQ (COLOR_FG_RED), .name_len: 11, .free_val: false }, |
102 | { .name: "diff-insert" , SGR_SEQ (COLOR_FG_GREEN), .name_len: 11, .free_val: false }, |
103 | { .name: "type-diff" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), .name_len: 9, .free_val: false }, |
104 | { .name: "valid" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), .name_len: 5, .free_val: false }, |
105 | { .name: "invalid" , SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED), .name_len: 7, .free_val: false }, |
106 | { NULL, NULL, .name_len: 0, .free_val: false } |
107 | }; |
108 | |
109 | const char * |
110 | colorize_start (bool show_color, const char *name, size_t name_len) |
111 | { |
112 | struct color_cap const *cap; |
113 | |
114 | if (!show_color) |
115 | return "" ; |
116 | |
117 | for (cap = color_dict; cap->name; cap++) |
118 | if (cap->name_len == name_len |
119 | && memcmp (s1: cap->name, s2: name, n: name_len) == 0) |
120 | break; |
121 | if (cap->name == NULL) |
122 | return "" ; |
123 | |
124 | return cap->val; |
125 | } |
126 | |
127 | const char * |
128 | colorize_stop (bool show_color) |
129 | { |
130 | return show_color ? SGR_RESET : "" ; |
131 | } |
132 | |
133 | /* Parse GCC_COLORS. The default would look like: |
134 | GCC_COLORS='error=01;31:warning=01;35:note=01;36:\ |
135 | range1=32:range2=34:locus=01:quote=01:path=01;36:\ |
136 | fixit-insert=32:fixit-delete=31:'\ |
137 | diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\ |
138 | type-diff=01;32' |
139 | No character escaping is needed or supported. */ |
140 | static bool |
141 | parse_gcc_colors (void) |
142 | { |
143 | const char *p, *q, *name, *val; |
144 | char *b; |
145 | size_t name_len = 0, val_len = 0; |
146 | |
147 | p = getenv (name: "GCC_COLORS" ); /* Plural! */ |
148 | if (p == NULL) |
149 | return true; |
150 | if (*p == '\0') |
151 | return false; |
152 | |
153 | name = q = p; |
154 | val = NULL; |
155 | /* From now on, be well-formed or you're gone. */ |
156 | for (;;) |
157 | if (*q == ':' || *q == '\0') |
158 | { |
159 | struct color_cap *cap; |
160 | |
161 | if (val) |
162 | val_len = q - val; |
163 | else |
164 | name_len = q - name; |
165 | /* Empty name without val (empty cap) |
166 | won't match and will be ignored. */ |
167 | for (cap = color_dict; cap->name; cap++) |
168 | if (cap->name_len == name_len |
169 | && memcmp (s1: cap->name, s2: name, n: name_len) == 0) |
170 | break; |
171 | /* If name unknown, go on for forward compatibility. */ |
172 | if (cap->val && val) |
173 | { |
174 | if (cap->free_val) |
175 | free (CONST_CAST (char *, cap->val)); |
176 | b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("" ))); |
177 | memcpy (dest: b, SGR_START, n: strlen (SGR_START)); |
178 | memcpy (dest: b + strlen (SGR_START), src: val, n: val_len); |
179 | memcpy (dest: b + strlen (SGR_START) + val_len, SGR_END, |
180 | n: sizeof (SGR_END)); |
181 | cap->val = (const char *) b; |
182 | cap->free_val = true; |
183 | } |
184 | if (*q == '\0') |
185 | return true; |
186 | name = ++q; |
187 | val = NULL; |
188 | } |
189 | else if (*q == '=') |
190 | { |
191 | if (q == name || val) |
192 | return true; |
193 | |
194 | name_len = q - name; |
195 | val = ++q; /* Can be the empty string. */ |
196 | } |
197 | else if (val == NULL) |
198 | q++; /* Accumulate name. */ |
199 | else if (*q == ';' || (*q >= '0' && *q <= '9')) |
200 | q++; /* Accumulate val. Protect the terminal from being sent |
201 | garbage. */ |
202 | else |
203 | return true; |
204 | } |
205 | |
206 | /* Return true if we should use color when in auto mode, false otherwise. */ |
207 | static bool |
208 | should_colorize (void) |
209 | { |
210 | #ifdef __MINGW32__ |
211 | /* For consistency reasons, one should check the handle returned by |
212 | _get_osfhandle(_fileno(stderr)) because the function |
213 | pp_write_text_to_stream() in pretty-print.cc calls fputs() on |
214 | that stream. However, the code below for non-Windows doesn't seem |
215 | to care about it either... */ |
216 | HANDLE h; |
217 | DWORD m; |
218 | |
219 | h = GetStdHandle (STD_ERROR_HANDLE); |
220 | return (h != INVALID_HANDLE_VALUE) && (h != NULL) |
221 | && GetConsoleMode (h, &m); |
222 | #else |
223 | char const *t = getenv (name: "TERM" ); |
224 | /* emacs M-x shell sets TERM="dumb". */ |
225 | return t && strcmp (s1: t, s2: "dumb" ) != 0 && isatty (STDERR_FILENO); |
226 | #endif |
227 | } |
228 | |
229 | bool |
230 | colorize_init (diagnostic_color_rule_t rule) |
231 | { |
232 | switch (rule) |
233 | { |
234 | case DIAGNOSTICS_COLOR_NO: |
235 | return false; |
236 | case DIAGNOSTICS_COLOR_YES: |
237 | return parse_gcc_colors (); |
238 | case DIAGNOSTICS_COLOR_AUTO: |
239 | if (should_colorize ()) |
240 | return parse_gcc_colors (); |
241 | else |
242 | return false; |
243 | default: |
244 | gcc_unreachable (); |
245 | } |
246 | } |
247 | |
248 | /* Return URL_FORMAT_XXX which tells how we should emit urls |
249 | when in always mode. |
250 | We use GCC_URLS and if that is not defined TERM_URLS. |
251 | If neither is defined the feature is enabled by default. */ |
252 | |
253 | static diagnostic_url_format |
254 | parse_env_vars_for_urls () |
255 | { |
256 | const char *p; |
257 | |
258 | p = getenv (name: "GCC_URLS" ); /* Plural! */ |
259 | if (p == NULL) |
260 | p = getenv (name: "TERM_URLS" ); |
261 | |
262 | if (p == NULL) |
263 | return URL_FORMAT_DEFAULT; |
264 | |
265 | if (*p == '\0') |
266 | return URL_FORMAT_NONE; |
267 | |
268 | if (!strcmp (s1: p, s2: "no" )) |
269 | return URL_FORMAT_NONE; |
270 | |
271 | if (!strcmp (s1: p, s2: "st" )) |
272 | return URL_FORMAT_ST; |
273 | |
274 | if (!strcmp (s1: p, s2: "bel" )) |
275 | return URL_FORMAT_BEL; |
276 | |
277 | return URL_FORMAT_DEFAULT; |
278 | } |
279 | |
280 | /* Return true if we should use urls when in auto mode, false otherwise. */ |
281 | |
282 | static bool |
283 | auto_enable_urls () |
284 | { |
285 | #ifdef __MINGW32__ |
286 | return false; |
287 | #else |
288 | const char *term, *colorterm; |
289 | |
290 | /* First check the terminal is capable of printing color escapes, |
291 | if not URLs won't work either. */ |
292 | if (!should_colorize ()) |
293 | return false; |
294 | |
295 | /* xfce4-terminal is known to not implement URLs at this time. |
296 | Recently new installations (0.8) will safely ignore the URL escape |
297 | sequences, but a large number of legacy installations (0.6.3) print |
298 | garbage when URLs are printed. Therefore we lose nothing by |
299 | disabling this feature for that specific terminal type. */ |
300 | colorterm = getenv (name: "COLORTERM" ); |
301 | if (colorterm && !strcmp (s1: colorterm, s2: "xfce4-terminal" )) |
302 | return false; |
303 | |
304 | /* Old versions of gnome-terminal where URL escapes cause screen |
305 | corruptions set COLORTERM="gnome-terminal", recent versions |
306 | with working URL support set this to "truecolor". */ |
307 | if (colorterm && !strcmp (s1: colorterm, s2: "gnome-terminal" )) |
308 | return false; |
309 | |
310 | /* Since the following checks are less specific than the ones |
311 | above, let GCC_URLS and TERM_URLS override the decision. */ |
312 | if (getenv (name: "GCC_URLS" ) || getenv (name: "TERM_URLS" )) |
313 | return true; |
314 | |
315 | /* In an ssh session the COLORTERM is not there, but TERM=xterm |
316 | can be used as an indication of a incompatible terminal while |
317 | TERM=xterm-256color appears to be a working terminal. */ |
318 | term = getenv (name: "TERM" ); |
319 | if (!colorterm && term && !strcmp (s1: term, s2: "xterm" )) |
320 | return false; |
321 | |
322 | /* When logging in a linux over serial line, we see TERM=linux |
323 | and no COLORTERM, it is unlikely that the URL escapes will |
324 | work in that environmen either. */ |
325 | if (!colorterm && term && !strcmp (s1: term, s2: "linux" )) |
326 | return false; |
327 | |
328 | return true; |
329 | #endif |
330 | } |
331 | |
332 | /* Determine if URLs should be enabled, based on RULE, |
333 | and, if so, which format to use. |
334 | This reuses the logic for colorization. */ |
335 | |
336 | diagnostic_url_format |
337 | determine_url_format (diagnostic_url_rule_t rule) |
338 | { |
339 | switch (rule) |
340 | { |
341 | case DIAGNOSTICS_URL_NO: |
342 | return URL_FORMAT_NONE; |
343 | case DIAGNOSTICS_URL_YES: |
344 | return parse_env_vars_for_urls (); |
345 | case DIAGNOSTICS_URL_AUTO: |
346 | if (auto_enable_urls ()) |
347 | return parse_env_vars_for_urls (); |
348 | else |
349 | return URL_FORMAT_NONE; |
350 | default: |
351 | gcc_unreachable (); |
352 | } |
353 | } |
354 | |