Coding style.

- Add xterm colors, and func for finding closest color, to
  termstr.py.
- Remove termstr's dependencies on pygments and ColorMap.py.
This commit is contained in:
Andrew Hamilton 2021-11-10 23:58:59 +10:00
parent b62afb5e69
commit 27fc9a433c
5 changed files with 35 additions and 150 deletions

View file

@ -174,9 +174,9 @@ def highlight_str(line, highlight_color, transparency):
@functools.lru_cache()
def blend_style(style):
fg_color = (style.fg_color if type(style.fg_color) == tuple
else termstr.xterm_color_to_rgb(style.fg_color))
else termstr.XTERM_COLORS[style.fg_color])
bg_color = (style.bg_color if type(style.bg_color) == tuple
else termstr.xterm_color_to_rgb(style.bg_color))
else termstr.XTERM_COLORS[style.bg_color])
return termstr.CharStyle(
blend_color(fg_color, highlight_color, transparency),
blend_color(bg_color, highlight_color, transparency),

View file

@ -1,2 +1 @@
pygments==2.10.0
cwcwidth==0.1.5

View file

@ -1,128 +0,0 @@
# This is from https://github.com/broadinstitute/xtermcolor
# Copyright (C) 2012 The Broad Institute
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
class TerminalColorMapException(Exception):
pass
def _rgb(color):
return ((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff)
def _diff(color1, color2):
(r1, g1, b1) = _rgb(color1)
(r2, g2, b2) = _rgb(color2)
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
class TerminalColorMap:
def getColors(self, order='rgb'):
return self.colors
def convert(self, hexcolor):
diffs = {}
for xterm, rgb in self.colors.items():
diffs[_diff(rgb, hexcolor)] = xterm
minDiffAnsi = diffs[min(diffs.keys())]
return (minDiffAnsi, self.colors[minDiffAnsi])
def colorize(self, string, rgb=None, ansi=None, bg=None, ansi_bg=None):
'''Returns the colored string'''
if not isinstance(string, str):
string = str(string)
if rgb is None and ansi is None:
raise TerminalColorMapException(
'colorize: must specify one named parameter: rgb or ansi')
if rgb is not None and ansi is not None:
raise TerminalColorMapException(
'colorize: must specify only one named parameter: rgb or ansi')
if bg is not None and ansi_bg is not None:
raise TerminalColorMapException(
'colorize: must specify only one named parameter: bg or ansi_bg')
if rgb is not None:
(closestAnsi, closestRgb) = self.convert(rgb)
elif ansi is not None:
(closestAnsi, closestRgb) = (ansi, self.colors[ansi])
if bg is None and ansi_bg is None:
return "\033[38;5;{ansiCode:d}m{string:s}\033[0m".format(ansiCode=closestAnsi, string=string)
if bg is not None:
(closestBgAnsi, unused) = self.convert(bg)
elif ansi_bg is not None:
(closestBgAnsi, unused) = (ansi_bg, self.colors[ansi_bg])
return "\033[38;5;{ansiCode:d}m\033[48;5;{bf:d}m{string:s}\033[0m".format(ansiCode=closestAnsi, bf=closestBgAnsi, string=string)
class VT100ColorMap(TerminalColorMap):
primary = [
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0
]
bright = [
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
]
def __init__(self):
self.colors = dict()
self._compute()
def _compute(self):
for index, color in enumerate(self.primary + self.bright):
self.colors[index] = color
class XTermColorMap(VT100ColorMap):
grayscale_start = 0x08
grayscale_end = 0xf8
grayscale_step = 10
intensities = [
0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
]
def _compute(self):
for index, color in enumerate(self.primary + self.bright):
self.colors[index] = color
c = 16
for i in self.intensities:
color = i << 16
for j in self.intensities:
color &= ~(0xff << 8)
color |= j << 8
for k in self.intensities:
color &= ~0xff
color |= k
self.colors[c] = color
c += 1
c = 232
for hex in list(range(self.grayscale_start, self.grayscale_end, self.grayscale_step)):
color = (hex << 16) | (hex << 8) | hex
self.colors[c] = color
c += 1

View file

@ -7,11 +7,8 @@ import itertools
import os
import weakref
import pygments.formatters.terminal256
import cwcwidth
import termstr.ColorMap
ESC = "\x1b"
@ -21,9 +18,6 @@ ITALIC = "[3m"
UNDERLINE = "[4m"
xterm_colormap = termstr.ColorMap.XTermColorMap()
def color(color_number, is_foreground):
return f"[{'38' if is_foreground else '48'};5;{color_number:d}m"
@ -32,11 +26,6 @@ def rgb_color(rgb, is_foreground):
return f"[{'38' if is_foreground else '48'};2;" + "%i;%i;%im" % rgb
@functools.lru_cache()
def xterm_color_to_rgb(color_index):
return termstr.ColorMap._rgb(xterm_colormap.colors[color_index])
class Color:
# https://en.wikipedia.org/wiki/Natural_Color_System
@ -60,11 +49,38 @@ class Color:
orange = (255, 153, 0)
def _xterm_colors():
result = [
(0x00, 0x00, 0x00), (0xcd, 0x00, 0x00), (0x00, 0xcd, 0x00),
(0xcd, 0xcd, 0x00), (0x00, 0x00, 0xee), (0xcd, 0x00, 0xcd),
(0x00, 0xcd, 0xcd), (0xe5, 0xe5, 0xe5), (0x7f, 0x7f, 0x7f),
(0xff, 0x00, 0x00), (0x00, 0xff, 0x00), (0xff, 0xff, 0x00),
(0x5c, 0x5c, 0xff), (0xff, 0x00, 0xff), (0x00, 0xff, 0xff),
(0xff, 0xff, 0xff)]
intensities = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
result.extend([(intensities[(i // 36) % 6], intensities[(i // 6) % 6],
intensities[i % 6]) for i in range(216)])
result.extend([(8 + i * 10, 8 + i * 10, 8 + i * 10) for i in range(24)])
return result
XTERM_COLORS = _xterm_colors()
def closest_color_index(color, colors):
r, g, b = color
closest_distance = 3 * (256*256) + 1
for index, (r_, g_, b_) in enumerate(colors):
distance = (r_ - r) ** 2 + (g_ - g) ** 2 + (b_ - b) ** 2
if distance < closest_distance:
closest_distance = distance
color_index = index
return color_index
class CharStyle:
_POOL = weakref.WeakValueDictionary()
_TERMINAL256_FORMATTER = \
pygments.formatters.terminal256.Terminal256Formatter()
def __new__(cls, fg_color=None, bg_color=None, is_bold=False,
is_italic=False, is_underlined=False):
@ -110,9 +126,8 @@ class CharStyle:
return color(color_, is_foreground)
else: # true color
if os.environ.get("TERM", None) == "xterm":
closest_color = self._TERMINAL256_FORMATTER._closest_color(
*color_)
return color(closest_color, is_foreground)
color_index = closest_color_index(color_, XTERM_COLORS)
return color(color_index, is_foreground)
else:
return rgb_color(color_, is_foreground)
@ -132,9 +147,9 @@ class CharStyle:
underline_code = ("text-decoration:underline; "
if self.is_underlined else "")
fg_color = (self.fg_color if type(self.fg_color) == tuple
else xterm_color_to_rgb(self.fg_color))
else xterm_colors[self.fg_color])
bg_color = (self.bg_color if type(self.bg_color) == tuple
else xterm_color_to_rgb(self.bg_color))
else xterm_colors[self.bg_color])
return (f"<style>.S{id(self)} {{font-size:80%%; color:rgb{fg_color!r};"
f" background-color:rgb{bg_color!r}; "
f"{bold_code}{italic_code}{underline_code}}}</style>")

View file

@ -5,7 +5,6 @@ import os
import pickle
import unittest
os.environ["TERM"] = "xterm-256color"
import fill3.terminal as terminal
import termstr