| import sys |
| from typing import Optional, Tuple |
| |
| if sys.version_info >= (3, 8): |
| from typing import Literal |
| else: |
| from pip._vendor.typing_extensions import Literal # pragma: no cover |
| |
| |
| from ._loop import loop_last |
| from .console import Console, ConsoleOptions, RenderableType, RenderResult |
| from .control import Control |
| from .segment import ControlType, Segment |
| from .style import StyleType |
| from .text import Text |
| |
| VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] |
| |
| |
| class LiveRender: |
| """Creates a renderable that may be updated. |
| |
| Args: |
| renderable (RenderableType): Any renderable object. |
| style (StyleType, optional): An optional style to apply to the renderable. Defaults to "". |
| """ |
| |
| def __init__( |
| self, |
| renderable: RenderableType, |
| style: StyleType = "", |
| vertical_overflow: VerticalOverflowMethod = "ellipsis", |
| ) -> None: |
| self.renderable = renderable |
| self.style = style |
| self.vertical_overflow = vertical_overflow |
| self._shape: Optional[Tuple[int, int]] = None |
| |
| def set_renderable(self, renderable: RenderableType) -> None: |
| """Set a new renderable. |
| |
| Args: |
| renderable (RenderableType): Any renderable object, including str. |
| """ |
| self.renderable = renderable |
| |
| def position_cursor(self) -> Control: |
| """Get control codes to move cursor to beginning of live render. |
| |
| Returns: |
| Control: A control instance that may be printed. |
| """ |
| if self._shape is not None: |
| _, height = self._shape |
| return Control( |
| ControlType.CARRIAGE_RETURN, |
| (ControlType.ERASE_IN_LINE, 2), |
| *( |
| ( |
| (ControlType.CURSOR_UP, 1), |
| (ControlType.ERASE_IN_LINE, 2), |
| ) |
| * (height - 1) |
| ) |
| ) |
| return Control() |
| |
| def restore_cursor(self) -> Control: |
| """Get control codes to clear the render and restore the cursor to its previous position. |
| |
| Returns: |
| Control: A Control instance that may be printed. |
| """ |
| if self._shape is not None: |
| _, height = self._shape |
| return Control( |
| ControlType.CARRIAGE_RETURN, |
| *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height |
| ) |
| return Control() |
| |
| def __rich_console__( |
| self, console: Console, options: ConsoleOptions |
| ) -> RenderResult: |
| |
| renderable = self.renderable |
| style = console.get_style(self.style) |
| lines = console.render_lines(renderable, options, style=style, pad=False) |
| shape = Segment.get_shape(lines) |
| |
| _, height = shape |
| if height > options.size.height: |
| if self.vertical_overflow == "crop": |
| lines = lines[: options.size.height] |
| shape = Segment.get_shape(lines) |
| elif self.vertical_overflow == "ellipsis": |
| lines = lines[: (options.size.height - 1)] |
| overflow_text = Text( |
| "...", |
| overflow="crop", |
| justify="center", |
| end="", |
| style="live.ellipsis", |
| ) |
| lines.append(list(console.render(overflow_text))) |
| shape = Segment.get_shape(lines) |
| self._shape = shape |
| |
| new_line = Segment.line() |
| for last, line in loop_last(lines): |
| yield from line |
| if not last: |
| yield new_line |