1 | /* Output colorization. |
2 | Copyright (C) 2011-2023 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 | { NULL, NULL, .name_len: 0, .free_val: false } |
105 | }; |
106 | |
107 | const char * |
108 | colorize_start (bool show_color, const char *name, size_t name_len) |
109 | { |
110 | struct color_cap const *cap; |
111 | |
112 | if (!show_color) |
113 | return "" ; |
114 | |
115 | for (cap = color_dict; cap->name; cap++) |
116 | if (cap->name_len == name_len |
117 | && memcmp (s1: cap->name, s2: name, n: name_len) == 0) |
118 | break; |
119 | if (cap->name == NULL) |
120 | return "" ; |
121 | |
122 | return cap->val; |
123 | } |
124 | |
125 | const char * |
126 | colorize_stop (bool show_color) |
127 | { |
128 | return show_color ? SGR_RESET : "" ; |
129 | } |
130 | |
131 | /* Parse GCC_COLORS. The default would look like: |
132 | GCC_COLORS='error=01;31:warning=01;35:note=01;36:\ |
133 | range1=32:range2=34:locus=01:quote=01:path=01;36:\ |
134 | fixit-insert=32:fixit-delete=31:'\ |
135 | diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\ |
136 | type-diff=01;32' |
137 | No character escaping is needed or supported. */ |
138 | static bool |
139 | parse_gcc_colors (void) |
140 | { |
141 | const char *p, *q, *name, *val; |
142 | char *b; |
143 | size_t name_len = 0, val_len = 0; |
144 | |
145 | p = getenv (name: "GCC_COLORS" ); /* Plural! */ |
146 | if (p == NULL) |
147 | return true; |
148 | if (*p == '\0') |
149 | return false; |
150 | |
151 | name = q = p; |
152 | val = NULL; |
153 | /* From now on, be well-formed or you're gone. */ |
154 | for (;;) |
155 | if (*q == ':' || *q == '\0') |
156 | { |
157 | struct color_cap *cap; |
158 | |
159 | if (val) |
160 | val_len = q - val; |
161 | else |
162 | name_len = q - name; |
163 | /* Empty name without val (empty cap) |
164 | won't match and will be ignored. */ |
165 | for (cap = color_dict; cap->name; cap++) |
166 | if (cap->name_len == name_len |
167 | && memcmp (s1: cap->name, s2: name, n: name_len) == 0) |
168 | break; |
169 | /* If name unknown, go on for forward compatibility. */ |
170 | if (cap->val && val) |
171 | { |
172 | if (cap->free_val) |
173 | free (CONST_CAST (char *, cap->val)); |
174 | b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("" ))); |
175 | memcpy (dest: b, SGR_START, n: strlen (SGR_START)); |
176 | memcpy (dest: b + strlen (SGR_START), src: val, n: val_len); |
177 | memcpy (dest: b + strlen (SGR_START) + val_len, SGR_END, |
178 | n: sizeof (SGR_END)); |
179 | cap->val = (const char *) b; |
180 | cap->free_val = true; |
181 | } |
182 | if (*q == '\0') |
183 | return true; |
184 | name = ++q; |
185 | val = NULL; |
186 | } |
187 | else if (*q == '=') |
188 | { |
189 | if (q == name || val) |
190 | return true; |
191 | |
192 | name_len = q - name; |
193 | val = ++q; /* Can be the empty string. */ |
194 | } |
195 | else if (val == NULL) |
196 | q++; /* Accumulate name. */ |
197 | else if (*q == ';' || (*q >= '0' && *q <= '9')) |
198 | q++; /* Accumulate val. Protect the terminal from being sent |
199 | garbage. */ |
200 | else |
201 | return true; |
202 | } |
203 | |
204 | /* Return true if we should use color when in auto mode, false otherwise. */ |
205 | static bool |
206 | should_colorize (void) |
207 | { |
208 | #ifdef __MINGW32__ |
209 | /* For consistency reasons, one should check the handle returned by |
210 | _get_osfhandle(_fileno(stderr)) because the function |
211 | pp_write_text_to_stream() in pretty-print.cc calls fputs() on |
212 | that stream. However, the code below for non-Windows doesn't seem |
213 | to care about it either... */ |
214 | HANDLE h; |
215 | DWORD m; |
216 | |
217 | h = GetStdHandle (STD_ERROR_HANDLE); |
218 | return (h != INVALID_HANDLE_VALUE) && (h != NULL) |
219 | && GetConsoleMode (h, &m); |
220 | #else |
221 | char const *t = getenv (name: "TERM" ); |
222 | /* emacs M-x shell sets TERM="dumb". */ |
223 | return t && strcmp (s1: t, s2: "dumb" ) != 0 && isatty (STDERR_FILENO); |
224 | #endif |
225 | } |
226 | |
227 | bool |
228 | colorize_init (diagnostic_color_rule_t rule) |
229 | { |
230 | switch (rule) |
231 | { |
232 | case DIAGNOSTICS_COLOR_NO: |
233 | return false; |
234 | case DIAGNOSTICS_COLOR_YES: |
235 | return parse_gcc_colors (); |
236 | case DIAGNOSTICS_COLOR_AUTO: |
237 | if (should_colorize ()) |
238 | return parse_gcc_colors (); |
239 | else |
240 | return false; |
241 | default: |
242 | gcc_unreachable (); |
243 | } |
244 | } |
245 | |
246 | /* Return URL_FORMAT_XXX which tells how we should emit urls |
247 | when in always mode. |
248 | We use GCC_URLS and if that is not defined TERM_URLS. |
249 | If neither is defined the feature is enabled by default. */ |
250 | |
251 | static diagnostic_url_format |
252 | parse_env_vars_for_urls () |
253 | { |
254 | const char *p; |
255 | |
256 | p = getenv (name: "GCC_URLS" ); /* Plural! */ |
257 | if (p == NULL) |
258 | p = getenv (name: "TERM_URLS" ); |
259 | |
260 | if (p == NULL) |
261 | return URL_FORMAT_DEFAULT; |
262 | |
263 | if (*p == '\0') |
264 | return URL_FORMAT_NONE; |
265 | |
266 | if (!strcmp (s1: p, s2: "no" )) |
267 | return URL_FORMAT_NONE; |
268 | |
269 | if (!strcmp (s1: p, s2: "st" )) |
270 | return URL_FORMAT_ST; |
271 | |
272 | if (!strcmp (s1: p, s2: "bel" )) |
273 | return URL_FORMAT_BEL; |
274 | |
275 | return URL_FORMAT_DEFAULT; |
276 | } |
277 | |
278 | /* Return true if we should use urls when in auto mode, false otherwise. */ |
279 | |
280 | static bool |
281 | auto_enable_urls () |
282 | { |
283 | #ifdef __MINGW32__ |
284 | return false; |
285 | #else |
286 | const char *term, *colorterm; |
287 | |
288 | /* First check the terminal is capable of printing color escapes, |
289 | if not URLs won't work either. */ |
290 | if (!should_colorize ()) |
291 | return false; |
292 | |
293 | /* xfce4-terminal is known to not implement URLs at this time. |
294 | Recently new installations (0.8) will safely ignore the URL escape |
295 | sequences, but a large number of legacy installations (0.6.3) print |
296 | garbage when URLs are printed. Therefore we lose nothing by |
297 | disabling this feature for that specific terminal type. */ |
298 | colorterm = getenv (name: "COLORTERM" ); |
299 | if (colorterm && !strcmp (s1: colorterm, s2: "xfce4-terminal" )) |
300 | return false; |
301 | |
302 | /* Old versions of gnome-terminal where URL escapes cause screen |
303 | corruptions set COLORTERM="gnome-terminal", recent versions |
304 | with working URL support set this to "truecolor". */ |
305 | if (colorterm && !strcmp (s1: colorterm, s2: "gnome-terminal" )) |
306 | return false; |
307 | |
308 | /* Since the following checks are less specific than the ones |
309 | above, let GCC_URLS and TERM_URLS override the decision. */ |
310 | if (getenv (name: "GCC_URLS" ) || getenv (name: "TERM_URLS" )) |
311 | return true; |
312 | |
313 | /* In an ssh session the COLORTERM is not there, but TERM=xterm |
314 | can be used as an indication of a incompatible terminal while |
315 | TERM=xterm-256color appears to be a working terminal. */ |
316 | term = getenv (name: "TERM" ); |
317 | if (!colorterm && term && !strcmp (s1: term, s2: "xterm" )) |
318 | return false; |
319 | |
320 | /* When logging in a linux over serial line, we see TERM=linux |
321 | and no COLORTERM, it is unlikely that the URL escapes will |
322 | work in that environmen either. */ |
323 | if (!colorterm && term && !strcmp (s1: term, s2: "linux" )) |
324 | return false; |
325 | |
326 | return true; |
327 | #endif |
328 | } |
329 | |
330 | /* Determine if URLs should be enabled, based on RULE, |
331 | and, if so, which format to use. |
332 | This reuses the logic for colorization. */ |
333 | |
334 | diagnostic_url_format |
335 | determine_url_format (diagnostic_url_rule_t rule) |
336 | { |
337 | switch (rule) |
338 | { |
339 | case DIAGNOSTICS_URL_NO: |
340 | return URL_FORMAT_NONE; |
341 | case DIAGNOSTICS_URL_YES: |
342 | return parse_env_vars_for_urls (); |
343 | case DIAGNOSTICS_URL_AUTO: |
344 | if (auto_enable_urls ()) |
345 | return parse_env_vars_for_urls (); |
346 | else |
347 | return URL_FORMAT_NONE; |
348 | default: |
349 | gcc_unreachable (); |
350 | } |
351 | } |
352 | |