349 lines
11 KiB
C
349 lines
11 KiB
C
/* Output colorization.
|
|
Copyright (C) 2011-2021 Free Software Foundation, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
|
|
02110-1301, USA. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "diagnostic-color.h"
|
|
#include "diagnostic-url.h"
|
|
|
|
#ifdef __MINGW32__
|
|
# include <windows.h>
|
|
#endif
|
|
|
|
#include "color-macros.h"
|
|
|
|
/* The context and logic for choosing default --color screen attributes
|
|
(foreground and background colors, etc.) are the following.
|
|
-- There are eight basic colors available, each with its own
|
|
nominal luminosity to the human eye and foreground/background
|
|
codes (black [0 %, 30/40], blue [11 %, 34/44], red [30 %, 31/41],
|
|
magenta [41 %, 35/45], green [59 %, 32/42], cyan [70 %, 36/46],
|
|
yellow [89 %, 33/43], and white [100 %, 37/47]).
|
|
-- Sometimes, white as a background is actually implemented using
|
|
a shade of light gray, so that a foreground white can be visible
|
|
on top of it (but most often not).
|
|
-- Sometimes, black as a foreground is actually implemented using
|
|
a shade of dark gray, so that it can be visible on top of a
|
|
background black (but most often not).
|
|
-- Sometimes, more colors are available, as extensions.
|
|
-- Other attributes can be selected/deselected (bold [1/22],
|
|
underline [4/24], standout/inverse [7/27], blink [5/25], and
|
|
invisible/hidden [8/28]). They are sometimes implemented by
|
|
using colors instead of what their names imply; e.g., bold is
|
|
often achieved by using brighter colors. In practice, only bold
|
|
is really available to us, underline sometimes being mapped by
|
|
the terminal to some strange color choice, and standout best
|
|
being left for use by downstream programs such as less(1).
|
|
-- We cannot assume that any of the extensions or special features
|
|
are available for the purpose of choosing defaults for everyone.
|
|
-- The most prevalent default terminal backgrounds are pure black
|
|
and pure white, and are not necessarily the same shades of
|
|
those as if they were selected explicitly with SGR sequences.
|
|
Some terminals use dark or light pictures as default background,
|
|
but those are covered over by an explicit selection of background
|
|
color with an SGR sequence; their users will appreciate their
|
|
background pictures not be covered like this, if possible.
|
|
-- Some uses of colors attributes is to make some output items
|
|
more understated (e.g., context lines); this cannot be achieved
|
|
by changing the background color.
|
|
-- For these reasons, the GCC color defaults should strive not
|
|
to change the background color from its default, unless it's
|
|
for a short item that should be highlighted, not understated.
|
|
-- The GCC foreground color defaults (without an explicitly set
|
|
background) should provide enough contrast to be readable on any
|
|
terminal with either a black (dark) or white (light) background.
|
|
This only leaves red, magenta, green, and cyan (and their bold
|
|
counterparts) and possibly bold blue. */
|
|
/* Default colors. The user can overwrite them using environment
|
|
variable GCC_COLORS. */
|
|
struct color_cap
|
|
{
|
|
const char *name;
|
|
const char *val;
|
|
unsigned char name_len;
|
|
bool free_val;
|
|
};
|
|
|
|
/* For GCC_COLORS. */
|
|
static struct color_cap color_dict[] =
|
|
{
|
|
{ "error", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED), 5, false },
|
|
{ "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA),
|
|
7, false },
|
|
{ "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
|
|
{ "range1", SGR_SEQ (COLOR_FG_GREEN), 6, false },
|
|
{ "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
|
|
{ "locus", SGR_SEQ (COLOR_BOLD), 5, false },
|
|
{ "quote", SGR_SEQ (COLOR_BOLD), 5, false },
|
|
{ "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
|
|
{ "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
|
|
{ "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
|
|
{ "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
|
|
{ "diff-hunk", SGR_SEQ (COLOR_FG_CYAN), 9, false },
|
|
{ "diff-delete", SGR_SEQ (COLOR_FG_RED), 11, false },
|
|
{ "diff-insert", SGR_SEQ (COLOR_FG_GREEN), 11, false },
|
|
{ "type-diff", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), 9, false },
|
|
{ NULL, NULL, 0, false }
|
|
};
|
|
|
|
const char *
|
|
colorize_start (bool show_color, const char *name, size_t name_len)
|
|
{
|
|
struct color_cap const *cap;
|
|
|
|
if (!show_color)
|
|
return "";
|
|
|
|
for (cap = color_dict; cap->name; cap++)
|
|
if (cap->name_len == name_len
|
|
&& memcmp (cap->name, name, name_len) == 0)
|
|
break;
|
|
if (cap->name == NULL)
|
|
return "";
|
|
|
|
return cap->val;
|
|
}
|
|
|
|
const char *
|
|
colorize_stop (bool show_color)
|
|
{
|
|
return show_color ? SGR_RESET : "";
|
|
}
|
|
|
|
/* Parse GCC_COLORS. The default would look like:
|
|
GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
|
|
range1=32:range2=34:locus=01:quote=01:path=01;36:\
|
|
fixit-insert=32:fixit-delete=31:'\
|
|
diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
|
|
type-diff=01;32'
|
|
No character escaping is needed or supported. */
|
|
static bool
|
|
parse_gcc_colors (void)
|
|
{
|
|
const char *p, *q, *name, *val;
|
|
char *b;
|
|
size_t name_len = 0, val_len = 0;
|
|
|
|
p = getenv ("GCC_COLORS"); /* Plural! */
|
|
if (p == NULL)
|
|
return true;
|
|
if (*p == '\0')
|
|
return false;
|
|
|
|
name = q = p;
|
|
val = NULL;
|
|
/* From now on, be well-formed or you're gone. */
|
|
for (;;)
|
|
if (*q == ':' || *q == '\0')
|
|
{
|
|
struct color_cap *cap;
|
|
|
|
if (val)
|
|
val_len = q - val;
|
|
else
|
|
name_len = q - name;
|
|
/* Empty name without val (empty cap)
|
|
won't match and will be ignored. */
|
|
for (cap = color_dict; cap->name; cap++)
|
|
if (cap->name_len == name_len
|
|
&& memcmp (cap->name, name, name_len) == 0)
|
|
break;
|
|
/* If name unknown, go on for forward compatibility. */
|
|
if (cap->val && val)
|
|
{
|
|
if (cap->free_val)
|
|
free (CONST_CAST (char *, cap->val));
|
|
b = XNEWVEC (char, val_len + sizeof (SGR_SEQ ("")));
|
|
memcpy (b, SGR_START, strlen (SGR_START));
|
|
memcpy (b + strlen (SGR_START), val, val_len);
|
|
memcpy (b + strlen (SGR_START) + val_len, SGR_END,
|
|
sizeof (SGR_END));
|
|
cap->val = (const char *) b;
|
|
cap->free_val = true;
|
|
}
|
|
if (*q == '\0')
|
|
return true;
|
|
name = ++q;
|
|
val = NULL;
|
|
}
|
|
else if (*q == '=')
|
|
{
|
|
if (q == name || val)
|
|
return true;
|
|
|
|
name_len = q - name;
|
|
val = ++q; /* Can be the empty string. */
|
|
}
|
|
else if (val == NULL)
|
|
q++; /* Accumulate name. */
|
|
else if (*q == ';' || (*q >= '0' && *q <= '9'))
|
|
q++; /* Accumulate val. Protect the terminal from being sent
|
|
garbage. */
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/* Return true if we should use color when in auto mode, false otherwise. */
|
|
static bool
|
|
should_colorize (void)
|
|
{
|
|
#ifdef __MINGW32__
|
|
/* For consistency reasons, one should check the handle returned by
|
|
_get_osfhandle(_fileno(stderr)) because the function
|
|
pp_write_text_to_stream() in pretty-print.c calls fputs() on
|
|
that stream. However, the code below for non-Windows doesn't seem
|
|
to care about it either... */
|
|
HANDLE h;
|
|
DWORD m;
|
|
|
|
h = GetStdHandle (STD_ERROR_HANDLE);
|
|
return (h != INVALID_HANDLE_VALUE) && (h != NULL)
|
|
&& GetConsoleMode (h, &m);
|
|
#else
|
|
char const *t = getenv ("TERM");
|
|
/* emacs M-x shell sets TERM="dumb". */
|
|
return t && strcmp (t, "dumb") != 0 && isatty (STDERR_FILENO);
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
colorize_init (diagnostic_color_rule_t rule)
|
|
{
|
|
switch (rule)
|
|
{
|
|
case DIAGNOSTICS_COLOR_NO:
|
|
return false;
|
|
case DIAGNOSTICS_COLOR_YES:
|
|
return parse_gcc_colors ();
|
|
case DIAGNOSTICS_COLOR_AUTO:
|
|
if (should_colorize ())
|
|
return parse_gcc_colors ();
|
|
else
|
|
return false;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* Return URL_FORMAT_XXX which tells how we should emit urls
|
|
when in always mode.
|
|
We use GCC_URLS and if that is not defined TERM_URLS.
|
|
If neither is defined the feature is enabled by default. */
|
|
|
|
static diagnostic_url_format
|
|
parse_env_vars_for_urls ()
|
|
{
|
|
const char *p;
|
|
|
|
p = getenv ("GCC_URLS"); /* Plural! */
|
|
if (p == NULL)
|
|
p = getenv ("TERM_URLS");
|
|
|
|
if (p == NULL)
|
|
return URL_FORMAT_DEFAULT;
|
|
|
|
if (*p == '\0')
|
|
return URL_FORMAT_NONE;
|
|
|
|
if (!strcmp (p, "no"))
|
|
return URL_FORMAT_NONE;
|
|
|
|
if (!strcmp (p, "st"))
|
|
return URL_FORMAT_ST;
|
|
|
|
if (!strcmp (p, "bel"))
|
|
return URL_FORMAT_BEL;
|
|
|
|
return URL_FORMAT_DEFAULT;
|
|
}
|
|
|
|
/* Return true if we should use urls when in auto mode, false otherwise. */
|
|
|
|
static bool
|
|
auto_enable_urls ()
|
|
{
|
|
#ifdef __MINGW32__
|
|
return false;
|
|
#else
|
|
const char *term, *colorterm;
|
|
|
|
/* First check the terminal is capable of printing color escapes,
|
|
if not URLs won't work either. */
|
|
if (!should_colorize ())
|
|
return false;
|
|
|
|
/* xfce4-terminal is known to not implement URLs at this time.
|
|
Recently new installations (0.8) will safely ignore the URL escape
|
|
sequences, but a large number of legacy installations (0.6.3) print
|
|
garbage when URLs are printed. Therefore we lose nothing by
|
|
disabling this feature for that specific terminal type. */
|
|
colorterm = getenv ("COLORTERM");
|
|
if (colorterm && !strcmp (colorterm, "xfce4-terminal"))
|
|
return false;
|
|
|
|
/* Old versions of gnome-terminal where URL escapes cause screen
|
|
corruptions set COLORTERM="gnome-terminal", recent versions
|
|
with working URL support set this to "truecolor". */
|
|
if (colorterm && !strcmp (colorterm, "gnome-terminal"))
|
|
return false;
|
|
|
|
/* Since the following checks are less specific than the ones
|
|
above, let GCC_URLS and TERM_URLS override the decision. */
|
|
if (getenv ("GCC_URLS") || getenv ("TERM_URLS"))
|
|
return true;
|
|
|
|
/* In an ssh session the COLORTERM is not there, but TERM=xterm
|
|
can be used as an indication of a incompatible terminal while
|
|
TERM=xterm-256color appears to be a working terminal. */
|
|
term = getenv ("TERM");
|
|
if (!colorterm && term && !strcmp (term, "xterm"))
|
|
return false;
|
|
|
|
/* When logging in a linux over serial line, we see TERM=linux
|
|
and no COLORTERM, it is unlikely that the URL escapes will
|
|
work in that environmen either. */
|
|
if (!colorterm && term && !strcmp (term, "linux"))
|
|
return false;
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/* Determine if URLs should be enabled, based on RULE,
|
|
and, if so, which format to use.
|
|
This reuses the logic for colorization. */
|
|
|
|
diagnostic_url_format
|
|
determine_url_format (diagnostic_url_rule_t rule)
|
|
{
|
|
switch (rule)
|
|
{
|
|
case DIAGNOSTICS_URL_NO:
|
|
return URL_FORMAT_NONE;
|
|
case DIAGNOSTICS_URL_YES:
|
|
return parse_env_vars_for_urls ();
|
|
case DIAGNOSTICS_URL_AUTO:
|
|
if (auto_enable_urls ())
|
|
return parse_env_vars_for_urls ();
|
|
else
|
|
return URL_FORMAT_NONE;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|