live_render.py 3.6 KB

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