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