Improve compatibility with 0-16 color terminals.

Fish assumed that it could use tparm to emit escapes to set colors
as long as the color was under 16 or max_colors from terminfo was 256::

 if (idx < 16 || term256_support_is_native()) {
    // Use tparm to emit color escape
    writembs(tparm(todo, idx);

If a terminal has max_colors = 8, here is what happenened, except
inside fish:

 > env TERM=xterm tput setaf 7 | xxd
   00000000: 1b5b 3337 6d                             .[37m
 > env TERM=xterm tput setaf 9 | xxd
   00000000: 1b5b 3338 6d                             .[39m

The first escape is good, that second escape is not valid.
Bright colors should start at \e[90m:

 > env TERM=xterm-16color tput setaf 9 | xxd
   00000000: 1b5b 3931 6d                             .[91m

This is what caused "white" not to work in #3176 in Terminal.app, and
obviously isn't good for real low-color terminals either.

So we replace the term256_support_is_native(), which just checked if
max_colors is 256 or not, with a function that takes an argument and
checks terminfo for that to see if tparm can handle it. We only use this
test, because otherwise, tparm should be expected to output garbage:

 /// Returns true if we think tparm can handle outputting a color index
 static bool term_supports_color_natively(unsigned int c) { return max_colors >= c; }
...

 if (term_supports_color_natively(idx) {

And if terminfo can't do it, the "forced" escapes no longer use the fancy
format when handling colors under 16, as this is not going to be compatible with
low color terminals. The code before used:

 else {
     char buff[16] = "";
     snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx);

I added an intermediate format for colors 0-15:

 else {
     // We are attempting to bypass the term here. Generate the ANSI escape sequence ourself.
     char buff[16] = "";
     if (idx < 16) {
         snprintf(buff, sizeof buff, "\x1b[%dm", ((idx > 7) ? 82 : 30) + idx + !is_fg * 10);
     } else {
         snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx);
     }

Restores harmony to white, brwhite, brblack, black color names.
We don't want "white" to refer to color color #16, but to the
standard color #8. #16 is "brwhite".

Move comments from output.h to output.cpp

Nuke the config.fish set_color hack for linux VTs.

Sync up our various incomplete color lists and fix all color values.
Colors 0-8 are assumed to be brights - e.g. red was FF0000. Perplexing!

Using this table:
 <http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html>

Fixes #3176
This commit is contained in:
Aaron Gyes 2016-07-21 10:55:28 -07:00
parent 88688d02b2
commit 3669805627
5 changed files with 120 additions and 120 deletions

View file

@ -303,26 +303,6 @@ function __fish_config_interactive -d "Initializations that should be performed
set -g fish_pager_color_description yellow set -g fish_pager_color_description yellow
set -g fish_pager_color_progress cyan set -g fish_pager_color_progress cyan
# Don't allow setting color other than what linux offers (see #2001)
functions -e set_color
function set_color --shadow-builtin
set -l term_colors black red green yellow blue magenta cyan white normal
for a in $argv
if not contains -- $a $term_colors
switch $a
# Also allow options
case "-*"
continue
case "*"
echo "Color not valid in TERM = linux: $a"
return 1
end
end
end
builtin set_color $argv
return $status
end
# Set fish_prompt to a VT-friendly version # Set fish_prompt to a VT-friendly version
# without color or unicode # without color or unicode
function fish_prompt function fish_prompt

View file

@ -65,23 +65,27 @@ def escape_fish_cmd(text):
named_colors = { named_colors = {
'black' : '000000', 'black' : '000000',
'red' : 'FF0000', 'red' : '800000',
'green' : '00FF00', 'green' : '008000',
'brown' : '725000', 'brown' : '725000',
'yellow' : 'FFFF00', 'yellow' : '808000',
'blue' : '0000FF', 'blue' : '000080',
'magenta' : 'FF00FF', 'magenta' : '800080',
'purple' : 'FF00FF', 'purple' : '800080',
'cyan' : '00FFFF', 'cyan' : '008080',
'grey' : 'E5E5E5', 'grey' : 'e5e5e5',
'brgrey' : '555555', 'brgrey' : '555555',
'brbrown' : 'FFFF55', 'white' : 'c0c0c0',
'bryellow' : 'FFFF55', 'brblack' : '808080',
'brblue' : '5555FF', 'brred' : 'ff0000',
'brmagenta' : 'FF55FF', 'brgreen' : '00ff00',
'brpurple' : 'FF55FF', 'brbrown' : 'ffff00',
'brcyan' : '55FFFF', 'bryellow' : 'ffff00',
'white' : 'FFFFFF' 'brblue' : '0000ff',
'brmagenta' : 'ff00ff',
'brpurple' : 'ff00ff',
'brcyan' : '00ffff',
'brwhite' : 'ffffff'
} }
def parse_one_color(comp): def parse_one_color(comp):

View file

@ -149,29 +149,32 @@ struct named_color_t {
const wchar_t *name; const wchar_t *name;
unsigned char idx; unsigned char idx;
unsigned char rgb[3]; unsigned char rgb[3];
bool hidden;
}; };
static const named_color_t named_colors[] = { static const named_color_t named_colors[] = {
{L"black", 0, {0, 0, 0}}, {L"black", 0, {0, 0, 0}, false},
{L"red", 1, {0xFF, 0, 0}}, {L"red", 1, {0x80, 0, 0}, false},
{L"green", 2, {0, 0xFF, 0}}, {L"green", 2, {0, 0x80, 0}, false},
{L"brown", 3, {0x72, 0x50, 0}}, {L"brown", 3, {0x72, 0x50, 0}, true},
{L"yellow", 3, {0xFF, 0xFF, 0}}, {L"yellow", 3, {0x80, 0x80, 0}, false},
{L"blue", 4, {0, 0, 0xFF}}, {L"blue", 4, {0, 0, 0x80}, false},
{L"magenta", 5, {0xFF, 0, 0xFF}}, {L"magenta", 5, {0x80, 0, 0x80}, false},
{L"purple", 5, {0xFF, 0, 0xFF}}, {L"purple", 5, {0x80, 0, 0x80}, true},
{L"cyan", 6, {0, 0xFF, 0xFF}}, {L"cyan", 6, {0, 0x80, 0x80}, false},
{L"grey", 7, {0xE5, 0xE5, 0xE5}}, {L"white", 7, {0xC0, 0xC0, 0xC0}, false},
{L"brgrey", 8, {0x55, 0x55, 0x55}}, {L"grey", 7, {0xe5, 0xe5, 0xe5}, true},
{L"brred", 9, {0xFF, 0x55, 0x55}}, {L"brblack", 8, {0x80, 0x80, 0x80}, false},
{L"brgreen", 10, {0x55, 0xFF, 0x55}}, {L"brgrey", 8, {0055, 0x55, 0x55}, true},
{L"brbrown", 11, {0xFF, 0xFF, 0x55}}, {L"brred", 9, {0xFF, 0x00, 0x00}, false},
{L"bryellow", 11, {0xFF, 0xFF, 0x55}}, {L"brgreen", 10, {0x00, 0xFF, 0x00}, false},
{L"brblue", 12, {0x55, 0x55, 0xFF}}, {L"brbrown", 11, {0xFF, 0xFF, 0x00}, true},
{L"brmagenta", 13, {0xFF, 0x55, 0xFF}}, {L"bryellow", 11, {0xFF, 0xFF, 0x00}, false},
{L"brpurple", 13, {0xFF, 0x55, 0xFF}}, {L"brblue", 12, {0x00, 0, 0xFF}, false},
{L"brcyan", 14, {0x55, 0xFF, 0xFF}}, {L"brmagenta", 13, {0xFF, 0, 0xFF}, false},
{L"white", 15, {0xFF, 0xFF, 0xFF}}, {L"brpurple", 13, {0xFF, 0, 0xFF}, true},
{L"brcyan", 14, {0x00, 0xFF, 0xFF}, false},
{L"brwhite", 15, {0xFF, 0xFF, 0xFF}, false},
}; };
wcstring_list_t rgb_color_t::named_color_names(void) { wcstring_list_t rgb_color_t::named_color_names(void) {
@ -179,8 +182,10 @@ wcstring_list_t rgb_color_t::named_color_names(void) {
wcstring_list_t result; wcstring_list_t result;
result.reserve(1 + count); result.reserve(1 + count);
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
if (named_colors[i].hidden == false) {
result.push_back(named_colors[i].name); result.push_back(named_colors[i].name);
} }
}
// "normal" isn't really a color and does not have a color palette index or // "normal" isn't really a color and does not have a color palette index or
// RGB value. Therefore, it does not appear in the named_colors table. // RGB value. Therefore, it does not appear in the named_colors table.
// However, it is a legitimate color name for the "set_color" command so // However, it is a legitimate color name for the "set_color" command so
@ -227,16 +232,24 @@ rgb_color_t rgb_color_t::white() { return rgb_color_t(type_named, 7); }
rgb_color_t rgb_color_t::black() { return rgb_color_t(type_named, 0); } rgb_color_t rgb_color_t::black() { return rgb_color_t(type_named, 0); }
static unsigned char term8_color_for_rgb(const unsigned char rgb[3]) { static unsigned char term16_color_for_rgb(const unsigned char rgb[3]) {
const uint32_t kColors[] = { const uint32_t kColors[] = {
0x000000, // Black 0x000000, // Black
0xFF0000, // Red 0x800000, // Red
0x00FF00, // Green 0x008000, // Green
0xFFFF00, // Yellow 0x808000, // Yellow
0x0000FF, // Blue 0x000080, // Blue
0xFF00FF, // Magenta 0x800080, // Magenta
0x00FFFF, // Cyan 0x008080, // Cyan
0xFFFFFF, // White 0xc0c0c0, // White
0x808080, // Bright Black
0xFF0000, // Bright Red
0x00FF00, // Bright Green
0xFFFF00, // Bright Yellow
0x0000FF, // Bright Blue
0xFF00FF, // Bright Magenta
0x00FFFF, // Bright Cyan
0xFFFFFF // Bright White
}; };
return convert_color(rgb, kColors, sizeof kColors / sizeof *kColors); return convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
} }
@ -284,9 +297,10 @@ color24_t rgb_color_t::to_color24() const {
} }
unsigned char rgb_color_t::to_name_index() const { unsigned char rgb_color_t::to_name_index() const {
// XXX this should look for the nearest color
assert(type == type_named || type == type_rgb); assert(type == type_named || type == type_rgb);
if (type == type_named) return data.name_idx; if (type == type_named) return data.name_idx;
if (type == type_rgb) return term8_color_for_rgb(data.color.rgb); if (type == type_rgb) return term16_color_for_rgb(data.color.rgb);
return (unsigned char)-1; // this is an error return (unsigned char)-1; // this is an error
} }

View file

@ -37,15 +37,19 @@ static int (*out)(char c) = writeb_internal;
/// Whether term256 and term24bit are supported. /// Whether term256 and term24bit are supported.
static color_support_t color_support = 0; static color_support_t color_support = 0;
/// Set the function used for writing in move_cursor, writespace and set_color and all other output
/// functions in this library. By default, the write call is used to give completely unbuffered
/// output to stdout.
void output_set_writer(int (*writer)(char)) { void output_set_writer(int (*writer)(char)) {
CHECK(writer, ); CHECK(writer, );
out = writer; out = writer;
} }
/// Return the current output writer.
int (*output_get_writer())(char) { return out; } int (*output_get_writer())(char) { return out; }
// Returns true if we think the term256 support is "native" as opposed to forced. /// Returns true if we think tparm can handle outputting a color index
static bool term256_support_is_native(void) { return max_colors >= 256; } static bool term_supports_color_natively(unsigned int c) { return max_colors >= c; }
color_support_t output_get_color_support(void) { return color_support; } color_support_t output_get_color_support(void) { return color_support; }
@ -59,15 +63,18 @@ unsigned char index_for_color(rgb_color_t c) {
} }
static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) { static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) {
bool result = false; if (term_supports_color_natively(idx)) {
if (idx < 16 || term256_support_is_native()) {
// Use tparm to emit color escape. // Use tparm to emit color escape.
writembs(tparm(todo, idx)); writembs(tparm(todo, idx));
result = true; return true;
} else { } else {
// We are attempting to bypass the term here. Generate the ANSI escape sequence ourself. // We are attempting to bypass the term here. Generate the ANSI escape sequence ourself.
char buff[128] = ""; char buff[16] = "";
if (idx < 16) {
snprintf(buff, sizeof buff, "\x1b[%dm", ((idx > 7) ? 82 : 30) + idx + !is_fg * 10);
} else {
snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx); snprintf(buff, sizeof buff, "\x1b[%d;5;%dm", is_fg ? 38 : 48, idx);
}
int (*writer)(char) = output_get_writer(); int (*writer)(char) = output_get_writer();
if (writer) { if (writer) {
@ -76,9 +83,8 @@ static bool write_color_escape(char *todo, unsigned char idx, bool is_fg) {
} }
} }
result = true; return true;
} }
return result;
} }
static bool write_foreground_color(unsigned char idx) { static bool write_foreground_color(unsigned char idx) {
@ -99,6 +105,7 @@ static bool write_background_color(unsigned char idx) {
return false; return false;
} }
// Exported for builtin_set_color's usage only.
void write_color(rgb_color_t color, bool is_fg) { void write_color(rgb_color_t color, bool is_fg) {
bool supports_term24bit = !!(output_get_color_support() & color_support_term24bit); bool supports_term24bit = !!(output_get_color_support() & color_support_term24bit);
if (!supports_term24bit || !color.is_rgb()) { if (!supports_term24bit || !color.is_rgb()) {
@ -122,6 +129,27 @@ void write_color(rgb_color_t color, bool is_fg) {
} }
} }
/// Sets the fg and bg color. May be called as often as you like, since if the new color is the same
/// as the previous, nothing will be written. Negative values for set_color will also be ignored.
/// Since the terminfo string this function emits can potentially cause the screen to flicker, the
/// function takes care to write as little as possible.
///
/// Possible values for color are any form the FISH_COLOR_* enum and FISH_COLOR_RESET.
/// FISH_COLOR_RESET will perform an exit_attribute_mode, even if set_color thinks it is already in
/// FISH_COLOR_NORMAL mode.
///
/// In order to set the color to normal, three terminfo strings may have to be written.
///
/// - First a string to set the color, such as set_a_foreground. This is needed because otherwise
/// the previous strings colors might be removed as well.
///
/// - After that we write the exit_attribute_mode string to reset all color attributes.
///
/// - Lastly we may need to write set_a_background or set_a_foreground to set the other half of the
/// color pair to what it should be.
///
/// \param c Foreground color.
/// \param c2 Background color.
void set_color(rgb_color_t c, rgb_color_t c2) { void set_color(rgb_color_t c, rgb_color_t c2) {
#if 0 #if 0
wcstring tmp = c.description(); wcstring tmp = c.description();
@ -260,11 +288,15 @@ static int writeb_internal(char c) { // cppcheck
return 0; return 0;
} }
/// This is for writing process notification messages. Has to write to stdout, so clr_eol and such
/// functions will work correctly. Not an issue since this function is only used in interactive mode
/// anyway.
int writeb(tputs_arg_t b) { int writeb(tputs_arg_t b) {
out(b); out(b);
return 0; return 0;
} }
/// Write a wide character using the output method specified using output_set_writer().
int writech(wint_t ch) { int writech(wint_t ch) {
char buff[MB_LEN_MAX + 1]; char buff[MB_LEN_MAX + 1];
size_t len; size_t len;
@ -294,6 +326,7 @@ int writech(wint_t ch) {
return 0; return 0;
} }
/// Write a wide character string to FD 1.
void writestr(const wchar_t *str) { void writestr(const wchar_t *str) {
CHECK(str, ); CHECK(str, );
@ -329,6 +362,8 @@ void writestr(const wchar_t *str) {
if (buffer != static_buffer) delete[] buffer; if (buffer != static_buffer) delete[] buffer;
} }
/// Given a list of rgb_color_t, pick the "best" one, as determined by the color support. Returns
/// rgb_color_t::none() if empty.
rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support_t support) { rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support_t support) {
if (candidates.empty()) { if (candidates.empty()) {
return rgb_color_t::none(); return rgb_color_t::none();
@ -358,7 +393,8 @@ rgb_color_t best_color(const std::vector<rgb_color_t> &candidates, color_support
return result; return result;
} }
// This code should be refactored to enable sharing with builtin_set_color. /// Return the internal color code representing the specified color.
/// XXX This code should be refactored to enable sharing with builtin_set_color.
rgb_color_t parse_color(const wcstring &val, bool is_background) { rgb_color_t parse_color(const wcstring &val, bool is_background) {
int is_bold = 0; int is_bold = 0;
int is_underline = 0; int is_underline = 0;
@ -408,6 +444,7 @@ rgb_color_t parse_color(const wcstring &val, bool is_background) {
return result; return result;
} }
/// Write specified multibyte string.
void writembs_check(char *mbs, const char *mbs_name, const char *file, long line) { void writembs_check(char *mbs, const char *mbs_name, const char *file, long line) {
if (mbs != NULL) { if (mbs != NULL) {
tputs(mbs, 1, &writeb); tputs(mbs, 1, &writeb);

View file

@ -14,65 +14,33 @@
/// Constants for various colors as used by the set_color function. /// Constants for various colors as used by the set_color function.
enum { enum {
FISH_COLOR_BLACK, FISH_COLOR_BLACK, // 0
FISH_COLOR_RED, FISH_COLOR_RED, // 1
FISH_COLOR_GREEN, FISH_COLOR_GREEN, // 2
FISH_COLOR_YELLOW, FISH_COLOR_YELLOW, // 3
FISH_COLOR_BLUE, FISH_COLOR_BLUE, // 4
FISH_COLOR_MAGENTA, FISH_COLOR_MAGENTA, // 5
FISH_COLOR_CYAN, FISH_COLOR_CYAN, // 6
FISH_COLOR_WHITE, FISH_COLOR_WHITE, // 7
FISH_COLOR_NORMAL, // the default fg color of the terminal FISH_COLOR_NORMAL, // 8 terminal default
FISH_COLOR_RESET FISH_COLOR_RESET // 9
}; };
/// Sets the fg and bg color. May be called as often as you like, since if the new color is the same
/// as the previous, nothing will be written. Negative values for set_color will also be ignored.
/// Since the terminfo string this function emits can potentially cause the screen to flicker, the
/// function takes care to write as little as possible.
///
/// Possible values for color are any form the FISH_COLOR_* enum and FISH_COLOR_RESET.
/// FISH_COLOR_RESET will perform an exit_attribute_mode, even if set_color thinks it is already in
/// FISH_COLOR_NORMAL mode.
///
/// In order to set the color to normal, three terminfo strings may have to be written.
///
/// - First a string to set the color, such as set_a_foreground. This is needed because otherwise
/// the previous strings colors might be removed as well.
///
/// - After that we write the exit_attribute_mode string to reset all color attributes.
///
/// - Lastly we may need to write set_a_background or set_a_foreground to set the other half of the
/// color pair to what it should be.
///
/// \param c Foreground color.
/// \param c2 Background color.
void set_color(rgb_color_t c, rgb_color_t c2); void set_color(rgb_color_t c, rgb_color_t c2);
/// Write specified multibyte string.
void writembs_check(char *mbs, const char *mbs_name, const char *file, long line); void writembs_check(char *mbs, const char *mbs_name, const char *file, long line);
#define writembs(mbs) writembs_check((mbs), #mbs, __FILE__, __LINE__) #define writembs(mbs) writembs_check((mbs), #mbs, __FILE__, __LINE__)
/// Write a wide character using the output method specified using output_set_writer().
int writech(wint_t ch); int writech(wint_t ch);
/// Write a wide character string to FD 1.
void writestr(const wchar_t *str); void writestr(const wchar_t *str);
/// Return the internal color code representing the specified color.
rgb_color_t parse_color(const wcstring &val, bool is_background); rgb_color_t parse_color(const wcstring &val, bool is_background);
/// This is for writing process notification messages. Has to write to stdout, so clr_eol and such
/// functions will work correctly. Not an issue since this function is only used in interactive mode
/// anyway.
int writeb(tputs_arg_t b); int writeb(tputs_arg_t b);
/// Set the function used for writing in move_cursor, writespace and set_color and all other output
/// functions in this library. By default, the write call is used to give completely unbuffered
/// output to stdout.
void output_set_writer(int (*writer)(char)); void output_set_writer(int (*writer)(char));
/// Return the current output writer.
int (*output_get_writer())(char); int (*output_get_writer())(char);
/// Sets what colors are supported. /// Sets what colors are supported.
@ -81,11 +49,8 @@ typedef unsigned int color_support_t;
color_support_t output_get_color_support(); color_support_t output_get_color_support();
void output_set_color_support(color_support_t support); void output_set_color_support(color_support_t support);
/// Given a list of rgb_color_t, pick the "best" one, as determined by the color support. Returns
/// rgb_color_t::none() if empty.
rgb_color_t best_color(const std::vector<rgb_color_t> &colors, color_support_t support); rgb_color_t best_color(const std::vector<rgb_color_t> &colors, color_support_t support);
// Exported for builtin_set_color's usage only.
void write_color(rgb_color_t color, bool is_fg); void write_color(rgb_color_t color, bool is_fg);
unsigned char index_for_color(rgb_color_t c); unsigned char index_for_color(rgb_color_t c);