]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/rich/layout.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / rich / layout.py
1 from abc import ABC, abstractmethod
2 from itertools import islice
3 from operator import itemgetter
4 from threading import RLock
5 from typing import (
6 TYPE_CHECKING,
7 Dict,
8 Iterable,
9 List,
10 NamedTuple,
11 Optional,
12 Sequence,
13 Tuple,
14 Union,
15 )
16
17 from ._ratio import ratio_resolve
18 from .align import Align
19 from .console import Console, ConsoleOptions, RenderableType, RenderResult
20 from .highlighter import ReprHighlighter
21 from .panel import Panel
22 from .pretty import Pretty
23 from .region import Region
24 from .repr import Result, rich_repr
25 from .segment import Segment
26 from .style import StyleType
27
28 if TYPE_CHECKING:
29 from pip._vendor.rich.tree import Tree
30
31
32 class LayoutRender(NamedTuple):
33 """An individual layout render."""
34
35 region: Region
36 render: List[List[Segment]]
37
38
39 RegionMap = Dict["Layout", Region]
40 RenderMap = Dict["Layout", LayoutRender]
41
42
43 class LayoutError(Exception):
44 """Layout related error."""
45
46
47 class NoSplitter(LayoutError):
48 """Requested splitter does not exist."""
49
50
51 class _Placeholder:
52 """An internal renderable used as a Layout placeholder."""
53
54 highlighter = ReprHighlighter()
55
56 def __init__(self, layout: "Layout", style: StyleType = "") -> None:
57 self.layout = layout
58 self.style = style
59
60 def __rich_console__(
61 self, console: Console, options: ConsoleOptions
62 ) -> RenderResult:
63 width = options.max_width
64 height = options.height or options.size.height
65 layout = self.layout
66 title = (
67 f"{layout.name!r} ({width} x {height})"
68 if layout.name
69 else f"({width} x {height})"
70 )
71 yield Panel(
72 Align.center(Pretty(layout), vertical="middle"),
73 style=self.style,
74 title=self.highlighter(title),
75 border_style="blue",
76 height=height,
77 )
78
79
80 class Splitter(ABC):
81 """Base class for a splitter."""
82
83 name: str = ""
84
85 @abstractmethod
86 def get_tree_icon(self) -> str:
87 """Get the icon (emoji) used in layout.tree"""
88
89 @abstractmethod
90 def divide(
91 self, children: Sequence["Layout"], region: Region
92 ) -> Iterable[Tuple["Layout", Region]]:
93 """Divide a region amongst several child layouts.
94
95 Args:
96 children (Sequence(Layout)): A number of child layouts.
97 region (Region): A rectangular region to divide.
98 """
99
100
101 class RowSplitter(Splitter):
102 """Split a layout region in to rows."""
103
104 name = "row"
105
106 def get_tree_icon(self) -> str:
107 return "[layout.tree.row]⬌"
108
109 def divide(
110 self, children: Sequence["Layout"], region: Region
111 ) -> Iterable[Tuple["Layout", Region]]:
112 x, y, width, height = region
113 render_widths = ratio_resolve(width, children)
114 offset = 0
115 _Region = Region
116 for child, child_width in zip(children, render_widths):
117 yield child, _Region(x + offset, y, child_width, height)
118 offset += child_width
119
120
121 class ColumnSplitter(Splitter):
122 """Split a layout region in to columns."""
123
124 name = "column"
125
126 def get_tree_icon(self) -> str:
127 return "[layout.tree.column]⬍"
128
129 def divide(
130 self, children: Sequence["Layout"], region: Region
131 ) -> Iterable[Tuple["Layout", Region]]:
132 x, y, width, height = region
133 render_heights = ratio_resolve(height, children)
134 offset = 0
135 _Region = Region
136 for child, child_height in zip(children, render_heights):
137 yield child, _Region(x, y + offset, width, child_height)
138 offset += child_height
139
140
141 @rich_repr
142 class Layout:
143 """A renderable to divide a fixed height in to rows or columns.
144
145 Args:
146 renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None.
147 name (str, optional): Optional identifier for Layout. Defaults to None.
148 size (int, optional): Optional fixed size of layout. Defaults to None.
149 minimum_size (int, optional): Minimum size of layout. Defaults to 1.
150 ratio (int, optional): Optional ratio for flexible layout. Defaults to 1.
151 visible (bool, optional): Visibility of layout. Defaults to True.
152 """
153
154 splitters = {"row": RowSplitter, "column": ColumnSplitter}
155
156 def __init__(
157 self,
158 renderable: Optional[RenderableType] = None,
159 *,
160 name: Optional[str] = None,
161 size: Optional[int] = None,
162 minimum_size: int = 1,
163 ratio: int = 1,
164 visible: bool = True,
165 ) -> None:
166 self._renderable = renderable or _Placeholder(self)
167 self.size = size
168 self.minimum_size = minimum_size
169 self.ratio = ratio
170 self.name = name
171 self.visible = visible
172 self.splitter: Splitter = self.splitters["column"]()
173 self._children: List[Layout] = []
174 self._render_map: RenderMap = {}
175 self._lock = RLock()
176
177 def __rich_repr__(self) -> Result:
178 yield "name", self.name, None
179 yield "size", self.size, None
180 yield "minimum_size", self.minimum_size, 1
181 yield "ratio", self.ratio, 1
182
183 @property
184 def renderable(self) -> RenderableType:
185 """Layout renderable."""
186 return self if self._children else self._renderable
187
188 @property
189 def children(self) -> List["Layout"]:
190 """Gets (visible) layout children."""
191 return [child for child in self._children if child.visible]
192
193 @property
194 def map(self) -> RenderMap:
195 """Get a map of the last render."""
196 return self._render_map
197
198 def get(self, name: str) -> Optional["Layout"]:
199 """Get a named layout, or None if it doesn't exist.
200
201 Args:
202 name (str): Name of layout.
203
204 Returns:
205 Optional[Layout]: Layout instance or None if no layout was found.
206 """
207 if self.name == name:
208 return self
209 else:
210 for child in self._children:
211 named_layout = child.get(name)
212 if named_layout is not None:
213 return named_layout
214 return None
215
216 def __getitem__(self, name: str) -> "Layout":
217 layout = self.get(name)
218 if layout is None:
219 raise KeyError(f"No layout with name {name!r}")
220 return layout
221
222 @property
223 def tree(self) -> "Tree":
224 """Get a tree renderable to show layout structure."""
225 from pip._vendor.rich.styled import Styled
226 from pip._vendor.rich.table import Table
227 from pip._vendor.rich.tree import Tree
228
229 def summary(layout: "Layout") -> Table:
230
231 icon = layout.splitter.get_tree_icon()
232
233 table = Table.grid(padding=(0, 1, 0, 0))
234
235 text: RenderableType = (
236 Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim")
237 )
238 table.add_row(icon, text)
239 _summary = table
240 return _summary
241
242 layout = self
243 tree = Tree(
244 summary(layout),
245 guide_style=f"layout.tree.{layout.splitter.name}",
246 highlight=True,
247 )
248
249 def recurse(tree: "Tree", layout: "Layout") -> None:
250 for child in layout._children:
251 recurse(
252 tree.add(
253 summary(child),
254 guide_style=f"layout.tree.{child.splitter.name}",
255 ),
256 child,
257 )
258
259 recurse(tree, self)
260 return tree
261
262 def split(
263 self,
264 *layouts: Union["Layout", RenderableType],
265 splitter: Union[Splitter, str] = "column",
266 ) -> None:
267 """Split the layout in to multiple sub-layouts.
268
269 Args:
270 *layouts (Layout): Positional arguments should be (sub) Layout instances.
271 splitter (Union[Splitter, str]): Splitter instance or name of splitter.
272 """
273 _layouts = [
274 layout if isinstance(layout, Layout) else Layout(layout)
275 for layout in layouts
276 ]
277 try:
278 self.splitter = (
279 splitter
280 if isinstance(splitter, Splitter)
281 else self.splitters[splitter]()
282 )
283 except KeyError:
284 raise NoSplitter(f"No splitter called {splitter!r}")
285 self._children[:] = _layouts
286
287 def add_split(self, *layouts: Union["Layout", RenderableType]) -> None:
288 """Add a new layout(s) to existing split.
289
290 Args:
291 *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances.
292
293 """
294 _layouts = (
295 layout if isinstance(layout, Layout) else Layout(layout)
296 for layout in layouts
297 )
298 self._children.extend(_layouts)
299
300 def split_row(self, *layouts: Union["Layout", RenderableType]) -> None:
301 """Split the layout in to a row (layouts side by side).
302
303 Args:
304 *layouts (Layout): Positional arguments should be (sub) Layout instances.
305 """
306 self.split(*layouts, splitter="row")
307
308 def split_column(self, *layouts: Union["Layout", RenderableType]) -> None:
309 """Split the layout in to a column (layouts stacked on top of each other).
310
311 Args:
312 *layouts (Layout): Positional arguments should be (sub) Layout instances.
313 """
314 self.split(*layouts, splitter="column")
315
316 def unsplit(self) -> None:
317 """Reset splits to initial state."""
318 del self._children[:]
319
320 def update(self, renderable: RenderableType) -> None:
321 """Update renderable.
322
323 Args:
324 renderable (RenderableType): New renderable object.
325 """
326 with self._lock:
327 self._renderable = renderable
328
329 def refresh_screen(self, console: "Console", layout_name: str) -> None:
330 """Refresh a sub-layout.
331
332 Args:
333 console (Console): Console instance where Layout is to be rendered.
334 layout_name (str): Name of layout.
335 """
336 with self._lock:
337 layout = self[layout_name]
338 region, _lines = self._render_map[layout]
339 (x, y, width, height) = region
340 lines = console.render_lines(
341 layout, console.options.update_dimensions(width, height)
342 )
343 self._render_map[layout] = LayoutRender(region, lines)
344 console.update_screen_lines(lines, x, y)
345
346 def _make_region_map(self, width: int, height: int) -> RegionMap:
347 """Create a dict that maps layout on to Region."""
348 stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))]
349 push = stack.append
350 pop = stack.pop
351 layout_regions: List[Tuple[Layout, Region]] = []
352 append_layout_region = layout_regions.append
353 while stack:
354 append_layout_region(pop())
355 layout, region = layout_regions[-1]
356 children = layout.children
357 if children:
358 for child_and_region in layout.splitter.divide(children, region):
359 push(child_and_region)
360
361 region_map = {
362 layout: region
363 for layout, region in sorted(layout_regions, key=itemgetter(1))
364 }
365 return region_map
366
367 def render(self, console: Console, options: ConsoleOptions) -> RenderMap:
368 """Render the sub_layouts.
369
370 Args:
371 console (Console): Console instance.
372 options (ConsoleOptions): Console options.
373
374 Returns:
375 RenderMap: A dict that maps Layout on to a tuple of Region, lines
376 """
377 render_width = options.max_width
378 render_height = options.height or console.height
379 region_map = self._make_region_map(render_width, render_height)
380 layout_regions = [
381 (layout, region)
382 for layout, region in region_map.items()
383 if not layout.children
384 ]
385 render_map: Dict["Layout", "LayoutRender"] = {}
386 render_lines = console.render_lines
387 update_dimensions = options.update_dimensions
388
389 for layout, region in layout_regions:
390 lines = render_lines(
391 layout.renderable, update_dimensions(region.width, region.height)
392 )
393 render_map[layout] = LayoutRender(region, lines)
394 return render_map
395
396 def __rich_console__(
397 self, console: Console, options: ConsoleOptions
398 ) -> RenderResult:
399 with self._lock:
400 width = options.max_width or console.width
401 height = options.height or console.height
402 render_map = self.render(console, options.update_dimensions(width, height))
403 self._render_map = render_map
404 layout_lines: List[List[Segment]] = [[] for _ in range(height)]
405 _islice = islice
406 for (region, lines) in render_map.values():
407 _x, y, _layout_width, layout_height = region
408 for row, line in zip(
409 _islice(layout_lines, y, y + layout_height), lines
410 ):
411 row.extend(line)
412
413 new_line = Segment.line()
414 for layout_row in layout_lines:
415 yield from layout_row
416 yield new_line
417
418
419 if __name__ == "__main__":
420 from pip._vendor.rich.console import Console
421
422 console = Console()
423 layout = Layout()
424
425 layout.split_column(
426 Layout(name="header", size=3),
427 Layout(ratio=1, name="main"),
428 Layout(size=10, name="footer"),
429 )
430
431 layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2))
432
433 layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2"))
434
435 layout["s2"].split_column(
436 Layout(name="top"), Layout(name="middle"), Layout(name="bottom")
437 )
438
439 layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2"))
440
441 layout["content"].update("foo")
442
443 console.print(layout)