diff options
author | Jonathan Slenders <jonathan@slenders.be> | 2016-08-15 17:52:21 +0200 |
---|---|---|
committer | Jonathan Slenders <jonathan@slenders.be> | 2016-08-15 17:52:21 +0200 |
commit | b51512d71fa30bf612daa412a155022e186a40da (patch) | |
tree | ca4a2906ae151baa642b3725d82f893c3deeb7f8 | |
parent | fbc2bd62d1208f37447c97c923c1e333aff8dd00 (diff) |
Better support for conversion from #ffffff values to ANSI colors in Vt100_Output.
- Prefer colors with some saturation, instead of gray colors, if the given color
was not gray.
- Prefer a different foreground and background color if they were originally not the same.
-rw-r--r-- | prompt_toolkit/terminal/vt100_output.py | 135 |
1 files changed, 92 insertions, 43 deletions
diff --git a/prompt_toolkit/terminal/vt100_output.py b/prompt_toolkit/terminal/vt100_output.py index b9cd05c5..52757985 100644 --- a/prompt_toolkit/terminal/vt100_output.py +++ b/prompt_toolkit/terminal/vt100_output.py @@ -88,7 +88,6 @@ ANSI_COLORS_TO_RGB = { 'ansiturquoise': (0x00, 0xcd, 0xcd), 'ansilightgray': (0xe5, 0xe5, 0xe5), - # High intensity. 'ansidarkgray': (0x7f, 0x7f, 0x7f), # Bright black. 'ansidarkred': (0xff, 0x00, 0x00), @@ -105,6 +104,40 @@ assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) +def _get_closest_ansi_color(r, g, b, exclude=()): + """ + Find closest ANSI color. Return it by name. + + :param r: Red (Between 0 and 255.) + :param g: Green (Between 0 and 255.) + :param b: Blue (Between 0 and 255.) + :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) + """ + assert isinstance(exclude, tuple) + + # When we have a bit of saturation, avoid the gray-like colors, otherwise, + # too often the distance to the gray color is less. + saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 + + if saturation > 30: + exclude += ('ansilightgray', 'ansidarkgray', 'ansiwhite', 'ansiblack') + + # Take the closest color. + # (Thanks to Pygments for this part.) + distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) + match = 'ansidefault' + + for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): + if name != 'ansidefault' and name not in exclude: + d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 + + if d < distance: + match = name + distance = d + + return match + + class _16ColorCache(dict): """ Cache which maps (r, g, b) tuples to 16 ansi colors. @@ -115,21 +148,19 @@ class _16ColorCache(dict): assert isinstance(bg, bool) self.bg = bg - def __missing__(self, value): - r, g, b = value - - # Find closest color. - # (Thanks to Pygments for this!) - distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) - match = 'default' - - for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): - if name != 'default': - d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 + def get_code(self, value, exclude=()): + """ + Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for + a given (r,g,b) value. + """ + key = (value, exclude) + if key not in self: + self[key] = self._get(value, exclude) + return self[key] - if d < distance: - match = name - distance = d + def _get(self, value, exclude=()): + r, g, b = value + match = _get_closest_ansi_color(r, g, b, exclude=exclude) # Turn color name into code. if self.bg: @@ -138,7 +169,7 @@ class _16ColorCache(dict): code = FG_ANSI_COLORS[match] self[value] = code - return code + return code, match class _256ColorCache(dict): @@ -224,10 +255,8 @@ class _EscapeCodeCache(dict): fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs parts = [] - if fgcolor: - parts.extend(self._color_to_code(fgcolor)) - if bgcolor: - parts.extend(self._color_to_code(bgcolor, True)) + parts.extend(self._colors_to_code(fgcolor, bgcolor)) + if bold: parts.append('1') if italic: @@ -259,36 +288,56 @@ class _EscapeCodeCache(dict): b = rgb & 0xff return r, g, b - def _color_to_code(self, color, bg=False): + def _colors_to_code(self, fg_color, bg_color): " Return a tuple with the vt100 values that represent this color. " - table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS + # When requesting ANSI colors only, and both fg/bg color were converted + # to ANSI, ensure that the foreground and background color are not the + # same. (Unless they were explicitely defined to be the same color.) + fg_ansi = [()] - # 16 ANSI colors. (Given by name.) - if color in table: - result = (table[color], ) + def get(color, bg): + table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS - # RGB colors. (Defined as 'ffffff'.) - else: - try: - rgb = self._color_name_to_rgb(color) - except ValueError: + if color is None: return () - # When only 16 colors are supported, use that. - if self.ansi_colors_only(): - if bg: - result = (_16_bg_colors[rgb], ) - else: - result = (_16_fg_colors[rgb], ) + # 16 ANSI colors. (Given by name.) + elif color in table: + return (table[color], ) - # True colors. (Only when this feature is enabled.) - elif self.true_color: - r, g, b = rgb - result = (48 if bg else 38, 2, r, g, b) - - # 256 RGB colors. + # RGB colors. (Defined as 'ffffff'.) else: - result = (48 if bg else 38, 5, _256_colors[rgb]) + try: + rgb = self._color_name_to_rgb(color) + except ValueError: + return () + + # When only 16 colors are supported, use that. + if self.ansi_colors_only(): + if bg: # Background. + if fg_color != bg_color: + exclude = (fg_ansi[0], ) + else: + exclude = () + code, name = _16_bg_colors.get_code(rgb, exclude=exclude) + return (code, ) + else: # Foreground. + code, name = _16_fg_colors.get_code(rgb) + fg_ansi[0] = name + return (code, ) + + # True colors. (Only when this feature is enabled.) + elif self.true_color: + r, g, b = rgb + return (48 if bg else 38, 2, r, g, b) + + # 256 RGB colors. + else: + return (48 if bg else 38, 5, _256_colors[rgb]) + + result = [] + result.extend(get(fg_color, False)) + result.extend(get(bg_color, True)) return map(six.text_type, result) |