Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

220 wiersze
6.3 KiB

  1. import time
  2. from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union, Final
  3. from .segment import ControlCode, ControlType, Segment
  4. if TYPE_CHECKING:
  5. from .console import Console, ConsoleOptions, RenderResult
  6. STRIP_CONTROL_CODES: Final = [
  7. 7, # Bell
  8. 8, # Backspace
  9. 11, # Vertical tab
  10. 12, # Form feed
  11. 13, # Carriage return
  12. ]
  13. _CONTROL_STRIP_TRANSLATE: Final = {
  14. _codepoint: None for _codepoint in STRIP_CONTROL_CODES
  15. }
  16. CONTROL_ESCAPE: Final = {
  17. 7: "\\a",
  18. 8: "\\b",
  19. 11: "\\v",
  20. 12: "\\f",
  21. 13: "\\r",
  22. }
  23. CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
  24. ControlType.BELL: lambda: "\x07",
  25. ControlType.CARRIAGE_RETURN: lambda: "\r",
  26. ControlType.HOME: lambda: "\x1b[H",
  27. ControlType.CLEAR: lambda: "\x1b[2J",
  28. ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
  29. ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
  30. ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
  31. ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
  32. ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
  33. ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
  34. ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
  35. ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
  36. ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
  37. ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
  38. ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
  39. ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
  40. }
  41. class Control:
  42. """A renderable that inserts a control code (non printable but may move cursor).
  43. Args:
  44. *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
  45. tuple of ControlType and an integer parameter
  46. """
  47. __slots__ = ["segment"]
  48. def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
  49. control_codes: List[ControlCode] = [
  50. (code,) if isinstance(code, ControlType) else code for code in codes
  51. ]
  52. _format_map = CONTROL_CODES_FORMAT
  53. rendered_codes = "".join(
  54. _format_map[code](*parameters) for code, *parameters in control_codes
  55. )
  56. self.segment = Segment(rendered_codes, None, control_codes)
  57. @classmethod
  58. def bell(cls) -> "Control":
  59. """Ring the 'bell'."""
  60. return cls(ControlType.BELL)
  61. @classmethod
  62. def home(cls) -> "Control":
  63. """Move cursor to 'home' position."""
  64. return cls(ControlType.HOME)
  65. @classmethod
  66. def move(cls, x: int = 0, y: int = 0) -> "Control":
  67. """Move cursor relative to current position.
  68. Args:
  69. x (int): X offset.
  70. y (int): Y offset.
  71. Returns:
  72. ~Control: Control object.
  73. """
  74. def get_codes() -> Iterable[ControlCode]:
  75. control = ControlType
  76. if x:
  77. yield (
  78. control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
  79. abs(x),
  80. )
  81. if y:
  82. yield (
  83. control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
  84. abs(y),
  85. )
  86. control = cls(*get_codes())
  87. return control
  88. @classmethod
  89. def move_to_column(cls, x: int, y: int = 0) -> "Control":
  90. """Move to the given column, optionally add offset to row.
  91. Returns:
  92. x (int): absolute x (column)
  93. y (int): optional y offset (row)
  94. Returns:
  95. ~Control: Control object.
  96. """
  97. return (
  98. cls(
  99. (ControlType.CURSOR_MOVE_TO_COLUMN, x),
  100. (
  101. ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
  102. abs(y),
  103. ),
  104. )
  105. if y
  106. else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
  107. )
  108. @classmethod
  109. def move_to(cls, x: int, y: int) -> "Control":
  110. """Move cursor to absolute position.
  111. Args:
  112. x (int): x offset (column)
  113. y (int): y offset (row)
  114. Returns:
  115. ~Control: Control object.
  116. """
  117. return cls((ControlType.CURSOR_MOVE_TO, x, y))
  118. @classmethod
  119. def clear(cls) -> "Control":
  120. """Clear the screen."""
  121. return cls(ControlType.CLEAR)
  122. @classmethod
  123. def show_cursor(cls, show: bool) -> "Control":
  124. """Show or hide the cursor."""
  125. return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
  126. @classmethod
  127. def alt_screen(cls, enable: bool) -> "Control":
  128. """Enable or disable alt screen."""
  129. if enable:
  130. return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
  131. else:
  132. return cls(ControlType.DISABLE_ALT_SCREEN)
  133. @classmethod
  134. def title(cls, title: str) -> "Control":
  135. """Set the terminal window title
  136. Args:
  137. title (str): The new terminal window title
  138. """
  139. return cls((ControlType.SET_WINDOW_TITLE, title))
  140. def __str__(self) -> str:
  141. return self.segment.text
  142. def __rich_console__(
  143. self, console: "Console", options: "ConsoleOptions"
  144. ) -> "RenderResult":
  145. if self.segment.text:
  146. yield self.segment
  147. def strip_control_codes(
  148. text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE
  149. ) -> str:
  150. """Remove control codes from text.
  151. Args:
  152. text (str): A string possibly contain control codes.
  153. Returns:
  154. str: String with control codes removed.
  155. """
  156. return text.translate(_translate_table)
  157. def escape_control_codes(
  158. text: str,
  159. _translate_table: Dict[int, str] = CONTROL_ESCAPE,
  160. ) -> str:
  161. """Replace control codes with their "escaped" equivalent in the given text.
  162. (e.g. "\b" becomes "\\b")
  163. Args:
  164. text (str): A string possibly containing control codes.
  165. Returns:
  166. str: String with control codes replaced with their escaped version.
  167. """
  168. return text.translate(_translate_table)
  169. if __name__ == "__main__": # pragma: no cover
  170. from rich.console import Console
  171. console = Console()
  172. console.print("Look at the title of your terminal window ^")
  173. # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
  174. for i in range(10):
  175. console.set_window_title("🚀 Loading" + "." * i)
  176. time.sleep(0.5)