1from typing import Optional, Tuple, Literal
2
3
4from ._loop import loop_last
5from .console import Console, ConsoleOptions, RenderableType, RenderResult
6from .control import Control
7from .segment import ControlType, Segment
8from .style import StyleType
9from .text import Text
10
11VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"]
12
13
14class LiveRender:
15 """Creates a renderable that may be updated.
16
17 Args:
18 renderable (RenderableType): Any renderable object.
19 style (StyleType, optional): An optional style to apply to the renderable. Defaults to "".
20 """
21
22 def __init__(
23 self,
24 renderable: RenderableType,
25 style: StyleType = "",
26 vertical_overflow: VerticalOverflowMethod = "ellipsis",
27 ) -> None:
28 self.renderable = renderable
29 self.style = style
30 self.vertical_overflow = vertical_overflow
31 self._shape: Optional[Tuple[int, int]] = None
32
33 def set_renderable(self, renderable: RenderableType) -> None:
34 """Set a new renderable.
35
36 Args:
37 renderable (RenderableType): Any renderable object, including str.
38 """
39 self.renderable = renderable
40
41 def position_cursor(self) -> Control:
42 """Get control codes to move cursor to beginning of live render.
43
44 Returns:
45 Control: A control instance that may be printed.
46 """
47 if self._shape is not None:
48 _, height = self._shape
49 return Control(
50 ControlType.CARRIAGE_RETURN,
51 (ControlType.ERASE_IN_LINE, 2),
52 *(
53 (
54 (ControlType.CURSOR_UP, 1),
55 (ControlType.ERASE_IN_LINE, 2),
56 )
57 * (height - 1)
58 )
59 )
60 return Control()
61
62 def restore_cursor(self) -> Control:
63 """Get control codes to clear the render and restore the cursor to its previous position.
64
65 Returns:
66 Control: A Control instance that may be printed.
67 """
68 if self._shape is not None:
69 _, height = self._shape
70 return Control(
71 ControlType.CARRIAGE_RETURN,
72 *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height
73 )
74 return Control()
75
76 def __rich_console__(
77 self, console: Console, options: ConsoleOptions
78 ) -> RenderResult:
79 renderable = self.renderable
80 style = console.get_style(self.style)
81 lines = console.render_lines(renderable, options, style=style, pad=False)
82 shape = Segment.get_shape(lines)
83
84 _, height = shape
85 if height > options.size.height:
86 if self.vertical_overflow == "crop":
87 lines = lines[: options.size.height]
88 shape = Segment.get_shape(lines)
89 elif self.vertical_overflow == "ellipsis":
90 lines = lines[: (options.size.height - 1)]
91 overflow_text = Text(
92 "...",
93 overflow="crop",
94 justify="center",
95 end="",
96 style="live.ellipsis",
97 )
98 lines.append(list(console.render(overflow_text)))
99 shape = Segment.get_shape(lines)
100 self._shape = shape
101
102 new_line = Segment.line()
103 for last, line in loop_last(lines):
104 yield from line
105 if not last:
106 yield new_line