Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/layout/screen.py: 33%
107 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1from __future__ import annotations
3from collections import defaultdict
4from typing import TYPE_CHECKING, Callable
6from prompt_toolkit.cache import FastDictCache
7from prompt_toolkit.data_structures import Point
8from prompt_toolkit.utils import get_cwidth
10if TYPE_CHECKING:
11 from .containers import Window
14__all__ = [
15 "Screen",
16 "Char",
17]
20class Char:
21 """
22 Represent a single character in a :class:`.Screen`.
24 This should be considered immutable.
26 :param char: A single character (can be a double-width character).
27 :param style: A style string. (Can contain classnames.)
28 """
30 __slots__ = ("char", "style", "width")
32 # If we end up having one of these special control sequences in the input string,
33 # we should display them as follows:
34 # Usually this happens after a "quoted insert".
35 display_mappings: dict[str, str] = {
36 "\x00": "^@", # Control space
37 "\x01": "^A",
38 "\x02": "^B",
39 "\x03": "^C",
40 "\x04": "^D",
41 "\x05": "^E",
42 "\x06": "^F",
43 "\x07": "^G",
44 "\x08": "^H",
45 "\x09": "^I",
46 "\x0a": "^J",
47 "\x0b": "^K",
48 "\x0c": "^L",
49 "\x0d": "^M",
50 "\x0e": "^N",
51 "\x0f": "^O",
52 "\x10": "^P",
53 "\x11": "^Q",
54 "\x12": "^R",
55 "\x13": "^S",
56 "\x14": "^T",
57 "\x15": "^U",
58 "\x16": "^V",
59 "\x17": "^W",
60 "\x18": "^X",
61 "\x19": "^Y",
62 "\x1a": "^Z",
63 "\x1b": "^[", # Escape
64 "\x1c": "^\\",
65 "\x1d": "^]",
66 "\x1e": "^^",
67 "\x1f": "^_",
68 "\x7f": "^?", # ASCII Delete (backspace).
69 # Special characters. All visualized like Vim does.
70 "\x80": "<80>",
71 "\x81": "<81>",
72 "\x82": "<82>",
73 "\x83": "<83>",
74 "\x84": "<84>",
75 "\x85": "<85>",
76 "\x86": "<86>",
77 "\x87": "<87>",
78 "\x88": "<88>",
79 "\x89": "<89>",
80 "\x8a": "<8a>",
81 "\x8b": "<8b>",
82 "\x8c": "<8c>",
83 "\x8d": "<8d>",
84 "\x8e": "<8e>",
85 "\x8f": "<8f>",
86 "\x90": "<90>",
87 "\x91": "<91>",
88 "\x92": "<92>",
89 "\x93": "<93>",
90 "\x94": "<94>",
91 "\x95": "<95>",
92 "\x96": "<96>",
93 "\x97": "<97>",
94 "\x98": "<98>",
95 "\x99": "<99>",
96 "\x9a": "<9a>",
97 "\x9b": "<9b>",
98 "\x9c": "<9c>",
99 "\x9d": "<9d>",
100 "\x9e": "<9e>",
101 "\x9f": "<9f>",
102 # For the non-breaking space: visualize like Emacs does by default.
103 # (Print a space, but attach the 'nbsp' class that applies the
104 # underline style.)
105 "\xa0": " ",
106 }
108 def __init__(self, char: str = " ", style: str = "") -> None:
109 # If this character has to be displayed otherwise, take that one.
110 if char in self.display_mappings:
111 if char == "\xa0":
112 style += " class:nbsp " # Will be underlined.
113 else:
114 style += " class:control-character "
116 char = self.display_mappings[char]
118 self.char = char
119 self.style = style
121 # Calculate width. (We always need this, so better to store it directly
122 # as a member for performance.)
123 self.width = get_cwidth(char)
125 # In theory, `other` can be any type of object, but because of performance
126 # we don't want to do an `isinstance` check every time. We assume "other"
127 # is always a "Char".
128 def _equal(self, other: Char) -> bool:
129 return self.char == other.char and self.style == other.style
131 def _not_equal(self, other: Char) -> bool:
132 # Not equal: We don't do `not char.__eq__` here, because of the
133 # performance of calling yet another function.
134 return self.char != other.char or self.style != other.style
136 if not TYPE_CHECKING:
137 __eq__ = _equal
138 __ne__ = _not_equal
140 def __repr__(self) -> str:
141 return f"{self.__class__.__name__}({self.char!r}, {self.style!r})"
144_CHAR_CACHE: FastDictCache[tuple[str, str], Char] = FastDictCache(
145 Char, size=1000 * 1000
146)
147Transparent = "[transparent]"
150class Screen:
151 """
152 Two dimensional buffer of :class:`.Char` instances.
153 """
155 def __init__(
156 self,
157 default_char: Char | None = None,
158 initial_width: int = 0,
159 initial_height: int = 0,
160 ) -> None:
161 if default_char is None:
162 default_char2 = _CHAR_CACHE[" ", Transparent]
163 else:
164 default_char2 = default_char
166 self.data_buffer: defaultdict[int, defaultdict[int, Char]] = defaultdict(
167 lambda: defaultdict(lambda: default_char2)
168 )
170 #: Escape sequences to be injected.
171 self.zero_width_escapes: defaultdict[int, defaultdict[int, str]] = defaultdict(
172 lambda: defaultdict(lambda: "")
173 )
175 #: Position of the cursor.
176 self.cursor_positions: dict[
177 Window, Point
178 ] = {} # Map `Window` objects to `Point` objects.
180 #: Visibility of the cursor.
181 self.show_cursor = True
183 #: (Optional) Where to position the menu. E.g. at the start of a completion.
184 #: (We can't use the cursor position, because we don't want the
185 #: completion menu to change its position when we browse through all the
186 #: completions.)
187 self.menu_positions: dict[
188 Window, Point
189 ] = {} # Map `Window` objects to `Point` objects.
191 #: Currently used width/height of the screen. This will increase when
192 #: data is written to the screen.
193 self.width = initial_width or 0
194 self.height = initial_height or 0
196 # Windows that have been drawn. (Each `Window` class will add itself to
197 # this list.)
198 self.visible_windows_to_write_positions: dict[Window, WritePosition] = {}
200 # List of (z_index, draw_func)
201 self._draw_float_functions: list[tuple[int, Callable[[], None]]] = []
203 @property
204 def visible_windows(self) -> list[Window]:
205 return list(self.visible_windows_to_write_positions.keys())
207 def set_cursor_position(self, window: Window, position: Point) -> None:
208 """
209 Set the cursor position for a given window.
210 """
211 self.cursor_positions[window] = position
213 def set_menu_position(self, window: Window, position: Point) -> None:
214 """
215 Set the cursor position for a given window.
216 """
217 self.menu_positions[window] = position
219 def get_cursor_position(self, window: Window) -> Point:
220 """
221 Get the cursor position for a given window.
222 Returns a `Point`.
223 """
224 try:
225 return self.cursor_positions[window]
226 except KeyError:
227 return Point(x=0, y=0)
229 def get_menu_position(self, window: Window) -> Point:
230 """
231 Get the menu position for a given window.
232 (This falls back to the cursor position if no menu position was set.)
233 """
234 try:
235 return self.menu_positions[window]
236 except KeyError:
237 try:
238 return self.cursor_positions[window]
239 except KeyError:
240 return Point(x=0, y=0)
242 def draw_with_z_index(self, z_index: int, draw_func: Callable[[], None]) -> None:
243 """
244 Add a draw-function for a `Window` which has a >= 0 z_index.
245 This will be postponed until `draw_all_floats` is called.
246 """
247 self._draw_float_functions.append((z_index, draw_func))
249 def draw_all_floats(self) -> None:
250 """
251 Draw all float functions in order of z-index.
252 """
253 # We keep looping because some draw functions could add new functions
254 # to this list. See `FloatContainer`.
255 while self._draw_float_functions:
256 # Sort the floats that we have so far by z_index.
257 functions = sorted(self._draw_float_functions, key=lambda item: item[0])
259 # Draw only one at a time, then sort everything again. Now floats
260 # might have been added.
261 self._draw_float_functions = functions[1:]
262 functions[0][1]()
264 def append_style_to_content(self, style_str: str) -> None:
265 """
266 For all the characters in the screen.
267 Set the style string to the given `style_str`.
268 """
269 b = self.data_buffer
270 char_cache = _CHAR_CACHE
272 append_style = " " + style_str
274 for y, row in b.items():
275 for x, char in row.items():
276 row[x] = char_cache[char.char, char.style + append_style]
278 def fill_area(
279 self, write_position: WritePosition, style: str = "", after: bool = False
280 ) -> None:
281 """
282 Fill the content of this area, using the given `style`.
283 The style is prepended before whatever was here before.
284 """
285 if not style.strip():
286 return
288 xmin = write_position.xpos
289 xmax = write_position.xpos + write_position.width
290 char_cache = _CHAR_CACHE
291 data_buffer = self.data_buffer
293 if after:
294 append_style = " " + style
295 prepend_style = ""
296 else:
297 append_style = ""
298 prepend_style = style + " "
300 for y in range(
301 write_position.ypos, write_position.ypos + write_position.height
302 ):
303 row = data_buffer[y]
304 for x in range(xmin, xmax):
305 cell = row[x]
306 row[x] = char_cache[
307 cell.char, prepend_style + cell.style + append_style
308 ]
311class WritePosition:
312 def __init__(self, xpos: int, ypos: int, width: int, height: int) -> None:
313 assert height >= 0
314 assert width >= 0
315 # xpos and ypos can be negative. (A float can be partially visible.)
317 self.xpos = xpos
318 self.ypos = ypos
319 self.width = width
320 self.height = height
322 def __repr__(self) -> str:
323 return "{}(x={!r}, y={!r}, width={!r}, height={!r})".format(
324 self.__class__.__name__,
325 self.xpos,
326 self.ypos,
327 self.width,
328 self.height,
329 )