Llama-3.1-8B-DALv0.1
/
venv
/lib
/python3.12
/site-packages
/prompt_toolkit
/styles
/style_transformation.py
""" | |
Collection of style transformations. | |
Think of it as a kind of color post processing after the rendering is done. | |
This could be used for instance to change the contrast/saturation; swap light | |
and dark colors or even change certain colors for other colors. | |
When the UI is rendered, these transformations can be applied right after the | |
style strings are turned into `Attrs` objects that represent the actual | |
formatting. | |
""" | |
from __future__ import annotations | |
from abc import ABCMeta, abstractmethod | |
from colorsys import hls_to_rgb, rgb_to_hls | |
from typing import Callable, Hashable, Sequence | |
from prompt_toolkit.cache import memoized | |
from prompt_toolkit.filters import FilterOrBool, to_filter | |
from prompt_toolkit.utils import AnyFloat, to_float, to_str | |
from .base import ANSI_COLOR_NAMES, Attrs | |
from .style import parse_color | |
__all__ = [ | |
"StyleTransformation", | |
"SwapLightAndDarkStyleTransformation", | |
"ReverseStyleTransformation", | |
"SetDefaultColorStyleTransformation", | |
"AdjustBrightnessStyleTransformation", | |
"DummyStyleTransformation", | |
"ConditionalStyleTransformation", | |
"DynamicStyleTransformation", | |
"merge_style_transformations", | |
] | |
class StyleTransformation(metaclass=ABCMeta): | |
""" | |
Base class for any style transformation. | |
""" | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
""" | |
Take an `Attrs` object and return a new `Attrs` object. | |
Remember that the color formats can be either "ansi..." or a 6 digit | |
lowercase hexadecimal color (without '#' prefix). | |
""" | |
def invalidation_hash(self) -> Hashable: | |
""" | |
When this changes, the cache should be invalidated. | |
""" | |
return f"{self.__class__.__name__}-{id(self)}" | |
class SwapLightAndDarkStyleTransformation(StyleTransformation): | |
""" | |
Turn dark colors into light colors and the other way around. | |
This is meant to make color schemes that work on a dark background usable | |
on a light background (and the other way around). | |
Notice that this doesn't swap foreground and background like "reverse" | |
does. It turns light green into dark green and the other way around. | |
Foreground and background colors are considered individually. | |
Also notice that when <reverse> is used somewhere and no colors are given | |
in particular (like what is the default for the bottom toolbar), then this | |
doesn't change anything. This is what makes sense, because when the | |
'default' color is chosen, it's what works best for the terminal, and | |
reverse works good with that. | |
""" | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
""" | |
Return the `Attrs` used when opposite luminosity should be used. | |
""" | |
# Reverse colors. | |
attrs = attrs._replace(color=get_opposite_color(attrs.color)) | |
attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor)) | |
return attrs | |
class ReverseStyleTransformation(StyleTransformation): | |
""" | |
Swap the 'reverse' attribute. | |
(This is still experimental.) | |
""" | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
return attrs._replace(reverse=not attrs.reverse) | |
class SetDefaultColorStyleTransformation(StyleTransformation): | |
""" | |
Set default foreground/background color for output that doesn't specify | |
anything. This is useful for overriding the terminal default colors. | |
:param fg: Color string or callable that returns a color string for the | |
foreground. | |
:param bg: Like `fg`, but for the background. | |
""" | |
def __init__( | |
self, fg: str | Callable[[], str], bg: str | Callable[[], str] | |
) -> None: | |
self.fg = fg | |
self.bg = bg | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
if attrs.bgcolor in ("", "default"): | |
attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg))) | |
if attrs.color in ("", "default"): | |
attrs = attrs._replace(color=parse_color(to_str(self.fg))) | |
return attrs | |
def invalidation_hash(self) -> Hashable: | |
return ( | |
"set-default-color", | |
to_str(self.fg), | |
to_str(self.bg), | |
) | |
class AdjustBrightnessStyleTransformation(StyleTransformation): | |
""" | |
Adjust the brightness to improve the rendering on either dark or light | |
backgrounds. | |
For dark backgrounds, it's best to increase `min_brightness`. For light | |
backgrounds it's best to decrease `max_brightness`. Usually, only one | |
setting is adjusted. | |
This will only change the brightness for text that has a foreground color | |
defined, but no background color. It works best for 256 or true color | |
output. | |
.. note:: Notice that there is no universal way to detect whether the | |
application is running in a light or dark terminal. As a | |
developer of an command line application, you'll have to make | |
this configurable for the user. | |
:param min_brightness: Float between 0.0 and 1.0 or a callable that returns | |
a float. | |
:param max_brightness: Float between 0.0 and 1.0 or a callable that returns | |
a float. | |
""" | |
def __init__( | |
self, min_brightness: AnyFloat = 0.0, max_brightness: AnyFloat = 1.0 | |
) -> None: | |
self.min_brightness = min_brightness | |
self.max_brightness = max_brightness | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
min_brightness = to_float(self.min_brightness) | |
max_brightness = to_float(self.max_brightness) | |
assert 0 <= min_brightness <= 1 | |
assert 0 <= max_brightness <= 1 | |
# Don't do anything if the whole brightness range is acceptable. | |
# This also avoids turning ansi colors into RGB sequences. | |
if min_brightness == 0.0 and max_brightness == 1.0: | |
return attrs | |
# If a foreground color is given without a background color. | |
no_background = not attrs.bgcolor or attrs.bgcolor == "default" | |
has_fgcolor = attrs.color and attrs.color != "ansidefault" | |
if has_fgcolor and no_background: | |
# Calculate new RGB values. | |
r, g, b = self._color_to_rgb(attrs.color or "") | |
hue, brightness, saturation = rgb_to_hls(r, g, b) | |
brightness = self._interpolate_brightness( | |
brightness, min_brightness, max_brightness | |
) | |
r, g, b = hls_to_rgb(hue, brightness, saturation) | |
new_color = f"{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}" | |
attrs = attrs._replace(color=new_color) | |
return attrs | |
def _color_to_rgb(self, color: str) -> tuple[float, float, float]: | |
""" | |
Parse `style.Attrs` color into RGB tuple. | |
""" | |
# Do RGB lookup for ANSI colors. | |
try: | |
from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB | |
r, g, b = ANSI_COLORS_TO_RGB[color] | |
return r / 255.0, g / 255.0, b / 255.0 | |
except KeyError: | |
pass | |
# Parse RRGGBB format. | |
return ( | |
int(color[0:2], 16) / 255.0, | |
int(color[2:4], 16) / 255.0, | |
int(color[4:6], 16) / 255.0, | |
) | |
# NOTE: we don't have to support named colors here. They are already | |
# transformed into RGB values in `style.parse_color`. | |
def _interpolate_brightness( | |
self, value: float, min_brightness: float, max_brightness: float | |
) -> float: | |
""" | |
Map the brightness to the (min_brightness..max_brightness) range. | |
""" | |
return min_brightness + (max_brightness - min_brightness) * value | |
def invalidation_hash(self) -> Hashable: | |
return ( | |
"adjust-brightness", | |
to_float(self.min_brightness), | |
to_float(self.max_brightness), | |
) | |
class DummyStyleTransformation(StyleTransformation): | |
""" | |
Don't transform anything at all. | |
""" | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
return attrs | |
def invalidation_hash(self) -> Hashable: | |
# Always return the same hash for these dummy instances. | |
return "dummy-style-transformation" | |
class DynamicStyleTransformation(StyleTransformation): | |
""" | |
StyleTransformation class that can dynamically returns any | |
`StyleTransformation`. | |
:param get_style_transformation: Callable that returns a | |
:class:`.StyleTransformation` instance. | |
""" | |
def __init__( | |
self, get_style_transformation: Callable[[], StyleTransformation | None] | |
) -> None: | |
self.get_style_transformation = get_style_transformation | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
style_transformation = ( | |
self.get_style_transformation() or DummyStyleTransformation() | |
) | |
return style_transformation.transform_attrs(attrs) | |
def invalidation_hash(self) -> Hashable: | |
style_transformation = ( | |
self.get_style_transformation() or DummyStyleTransformation() | |
) | |
return style_transformation.invalidation_hash() | |
class ConditionalStyleTransformation(StyleTransformation): | |
""" | |
Apply the style transformation depending on a condition. | |
""" | |
def __init__( | |
self, style_transformation: StyleTransformation, filter: FilterOrBool | |
) -> None: | |
self.style_transformation = style_transformation | |
self.filter = to_filter(filter) | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
if self.filter(): | |
return self.style_transformation.transform_attrs(attrs) | |
return attrs | |
def invalidation_hash(self) -> Hashable: | |
return (self.filter(), self.style_transformation.invalidation_hash()) | |
class _MergedStyleTransformation(StyleTransformation): | |
def __init__(self, style_transformations: Sequence[StyleTransformation]) -> None: | |
self.style_transformations = style_transformations | |
def transform_attrs(self, attrs: Attrs) -> Attrs: | |
for transformation in self.style_transformations: | |
attrs = transformation.transform_attrs(attrs) | |
return attrs | |
def invalidation_hash(self) -> Hashable: | |
return tuple(t.invalidation_hash() for t in self.style_transformations) | |
def merge_style_transformations( | |
style_transformations: Sequence[StyleTransformation], | |
) -> StyleTransformation: | |
""" | |
Merge multiple transformations together. | |
""" | |
return _MergedStyleTransformation(style_transformations) | |
# Dictionary that maps ANSI color names to their opposite. This is useful for | |
# turning color schemes that are optimized for a black background usable for a | |
# white background. | |
OPPOSITE_ANSI_COLOR_NAMES = { | |
"ansidefault": "ansidefault", | |
"ansiblack": "ansiwhite", | |
"ansired": "ansibrightred", | |
"ansigreen": "ansibrightgreen", | |
"ansiyellow": "ansibrightyellow", | |
"ansiblue": "ansibrightblue", | |
"ansimagenta": "ansibrightmagenta", | |
"ansicyan": "ansibrightcyan", | |
"ansigray": "ansibrightblack", | |
"ansiwhite": "ansiblack", | |
"ansibrightred": "ansired", | |
"ansibrightgreen": "ansigreen", | |
"ansibrightyellow": "ansiyellow", | |
"ansibrightblue": "ansiblue", | |
"ansibrightmagenta": "ansimagenta", | |
"ansibrightcyan": "ansicyan", | |
"ansibrightblack": "ansigray", | |
} | |
assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES) | |
assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES) | |
def get_opposite_color(colorname: str | None) -> str | None: | |
""" | |
Take a color name in either 'ansi...' format or 6 digit RGB, return the | |
color of opposite luminosity (same hue/saturation). | |
This is used for turning color schemes that work on a light background | |
usable on a dark background. | |
""" | |
if colorname is None: # Because color/bgcolor can be None in `Attrs`. | |
return None | |
# Special values. | |
if colorname in ("", "default"): | |
return colorname | |
# Try ANSI color names. | |
try: | |
return OPPOSITE_ANSI_COLOR_NAMES[colorname] | |
except KeyError: | |
# Try 6 digit RGB colors. | |
r = int(colorname[:2], 16) / 255.0 | |
g = int(colorname[2:4], 16) / 255.0 | |
b = int(colorname[4:6], 16) / 255.0 | |
h, l, s = rgb_to_hls(r, g, b) | |
l = 1 - l | |
r, g, b = hls_to_rgb(h, l, s) | |
r = int(r * 255) | |
g = int(g * 255) | |
b = int(b * 255) | |
return f"{r:02x}{g:02x}{b:02x}" | |