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