1"""
2The base classes for the styling.
3"""
4
5from __future__ import annotations
6
7from abc import ABCMeta, abstractmethod, abstractproperty
8from typing import Callable, Hashable, NamedTuple
9
10__all__ = [
11 "Attrs",
12 "DEFAULT_ATTRS",
13 "ANSI_COLOR_NAMES",
14 "ANSI_COLOR_NAMES_ALIASES",
15 "BaseStyle",
16 "DummyStyle",
17 "DynamicStyle",
18]
19
20
21#: Style attributes.
22class Attrs(NamedTuple):
23 color: str | None
24 bgcolor: str | None
25 bold: bool | None
26 underline: bool | None
27 strike: bool | None
28 italic: bool | None
29 blink: bool | None
30 reverse: bool | None
31 hidden: bool | None
32
33
34"""
35:param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue'
36:param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired'
37:param bold: Boolean
38:param underline: Boolean
39:param strike: Boolean
40:param italic: Boolean
41:param blink: Boolean
42:param reverse: Boolean
43:param hidden: Boolean
44"""
45
46#: The default `Attrs`.
47DEFAULT_ATTRS = Attrs(
48 color="",
49 bgcolor="",
50 bold=False,
51 underline=False,
52 strike=False,
53 italic=False,
54 blink=False,
55 reverse=False,
56 hidden=False,
57)
58
59
60#: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of
61#: the following in case we want to take colors from the 8/16 color palette.
62#: Usually, in that case, the terminal application allows to configure the RGB
63#: values for these names.
64#: ISO 6429 colors
65ANSI_COLOR_NAMES = [
66 "ansidefault",
67 # Low intensity, dark. (One or two components 0x80, the other 0x00.)
68 "ansiblack",
69 "ansired",
70 "ansigreen",
71 "ansiyellow",
72 "ansiblue",
73 "ansimagenta",
74 "ansicyan",
75 "ansigray",
76 # High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.)
77 "ansibrightblack",
78 "ansibrightred",
79 "ansibrightgreen",
80 "ansibrightyellow",
81 "ansibrightblue",
82 "ansibrightmagenta",
83 "ansibrightcyan",
84 "ansiwhite",
85]
86
87
88# People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0
89# we used some unconventional names (which were contributed like that to
90# Pygments). This is fixed now, but we still support the old names.
91
92# The table below maps the old aliases to the current names.
93ANSI_COLOR_NAMES_ALIASES: dict[str, str] = {
94 "ansidarkgray": "ansibrightblack",
95 "ansiteal": "ansicyan",
96 "ansiturquoise": "ansibrightcyan",
97 "ansibrown": "ansiyellow",
98 "ansipurple": "ansimagenta",
99 "ansifuchsia": "ansibrightmagenta",
100 "ansilightgray": "ansigray",
101 "ansidarkred": "ansired",
102 "ansidarkgreen": "ansigreen",
103 "ansidarkblue": "ansiblue",
104}
105assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES))
106assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES))
107
108
109class BaseStyle(metaclass=ABCMeta):
110 """
111 Abstract base class for prompt_toolkit styles.
112 """
113
114 @abstractmethod
115 def get_attrs_for_style_str(
116 self, style_str: str, default: Attrs = DEFAULT_ATTRS
117 ) -> Attrs:
118 """
119 Return :class:`.Attrs` for the given style string.
120
121 :param style_str: The style string. This can contain inline styling as
122 well as classnames (e.g. "class:title").
123 :param default: `Attrs` to be used if no styling was defined.
124 """
125
126 @abstractproperty
127 def style_rules(self) -> list[tuple[str, str]]:
128 """
129 The list of style rules, used to create this style.
130 (Required for `DynamicStyle` and `_MergedStyle` to work.)
131 """
132 return []
133
134 @abstractmethod
135 def invalidation_hash(self) -> Hashable:
136 """
137 Invalidation hash for the style. When this changes over time, the
138 renderer knows that something in the style changed, and that everything
139 has to be redrawn.
140 """
141
142
143class DummyStyle(BaseStyle):
144 """
145 A style that doesn't style anything.
146 """
147
148 def get_attrs_for_style_str(
149 self, style_str: str, default: Attrs = DEFAULT_ATTRS
150 ) -> Attrs:
151 return default
152
153 def invalidation_hash(self) -> Hashable:
154 return 1 # Always the same value.
155
156 @property
157 def style_rules(self) -> list[tuple[str, str]]:
158 return []
159
160
161class DynamicStyle(BaseStyle):
162 """
163 Style class that can dynamically returns an other Style.
164
165 :param get_style: Callable that returns a :class:`.Style` instance.
166 """
167
168 def __init__(self, get_style: Callable[[], BaseStyle | None]):
169 self.get_style = get_style
170 self._dummy = DummyStyle()
171
172 def get_attrs_for_style_str(
173 self, style_str: str, default: Attrs = DEFAULT_ATTRS
174 ) -> Attrs:
175 style = self.get_style() or self._dummy
176
177 return style.get_attrs_for_style_str(style_str, default)
178
179 def invalidation_hash(self) -> Hashable:
180 return (self.get_style() or self._dummy).invalidation_hash()
181
182 @property
183 def style_rules(self) -> list[tuple[str, str]]:
184 return (self.get_style() or self._dummy).style_rules