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