Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/control.py: 58%
64 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1import sys
2import time
3from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
5if sys.version_info >= (3, 8):
6 from typing import Final
7else:
8 from typing_extensions import Final # pragma: no cover
10from .segment import ControlCode, ControlType, Segment
12if TYPE_CHECKING:
13 from .console import Console, ConsoleOptions, RenderResult
15STRIP_CONTROL_CODES: Final = [
16 7, # Bell
17 8, # Backspace
18 11, # Vertical tab
19 12, # Form feed
20 13, # Carriage return
21]
22_CONTROL_STRIP_TRANSLATE: Final = {
23 _codepoint: None for _codepoint in STRIP_CONTROL_CODES
24}
26CONTROL_ESCAPE: Final = {
27 7: "\\a",
28 8: "\\b",
29 11: "\\v",
30 12: "\\f",
31 13: "\\r",
32}
34CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
35 ControlType.BELL: lambda: "\x07",
36 ControlType.CARRIAGE_RETURN: lambda: "\r",
37 ControlType.HOME: lambda: "\x1b[H",
38 ControlType.CLEAR: lambda: "\x1b[2J",
39 ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
40 ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
41 ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
42 ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
43 ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
44 ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
45 ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
46 ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
47 ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
48 ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
49 ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
50 ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
51}
54class Control:
55 """A renderable that inserts a control code (non printable but may move cursor).
57 Args:
58 *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
59 tuple of ControlType and an integer parameter
60 """
62 __slots__ = ["segment"]
64 def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
65 control_codes: List[ControlCode] = [
66 (code,) if isinstance(code, ControlType) else code for code in codes
67 ]
68 _format_map = CONTROL_CODES_FORMAT
69 rendered_codes = "".join(
70 _format_map[code](*parameters) for code, *parameters in control_codes
71 )
72 self.segment = Segment(rendered_codes, None, control_codes)
74 @classmethod
75 def bell(cls) -> "Control":
76 """Ring the 'bell'."""
77 return cls(ControlType.BELL)
79 @classmethod
80 def home(cls) -> "Control":
81 """Move cursor to 'home' position."""
82 return cls(ControlType.HOME)
84 @classmethod
85 def move(cls, x: int = 0, y: int = 0) -> "Control":
86 """Move cursor relative to current position.
88 Args:
89 x (int): X offset.
90 y (int): Y offset.
92 Returns:
93 ~Control: Control object.
95 """
97 def get_codes() -> Iterable[ControlCode]:
98 control = ControlType
99 if x:
100 yield (
101 control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
102 abs(x),
103 )
104 if y:
105 yield (
106 control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
107 abs(y),
108 )
110 control = cls(*get_codes())
111 return control
113 @classmethod
114 def move_to_column(cls, x: int, y: int = 0) -> "Control":
115 """Move to the given column, optionally add offset to row.
117 Returns:
118 x (int): absolute x (column)
119 y (int): optional y offset (row)
121 Returns:
122 ~Control: Control object.
123 """
125 return (
126 cls(
127 (ControlType.CURSOR_MOVE_TO_COLUMN, x),
128 (
129 ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
130 abs(y),
131 ),
132 )
133 if y
134 else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
135 )
137 @classmethod
138 def move_to(cls, x: int, y: int) -> "Control":
139 """Move cursor to absolute position.
141 Args:
142 x (int): x offset (column)
143 y (int): y offset (row)
145 Returns:
146 ~Control: Control object.
147 """
148 return cls((ControlType.CURSOR_MOVE_TO, x, y))
150 @classmethod
151 def clear(cls) -> "Control":
152 """Clear the screen."""
153 return cls(ControlType.CLEAR)
155 @classmethod
156 def show_cursor(cls, show: bool) -> "Control":
157 """Show or hide the cursor."""
158 return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
160 @classmethod
161 def alt_screen(cls, enable: bool) -> "Control":
162 """Enable or disable alt screen."""
163 if enable:
164 return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
165 else:
166 return cls(ControlType.DISABLE_ALT_SCREEN)
168 @classmethod
169 def title(cls, title: str) -> "Control":
170 """Set the terminal window title
172 Args:
173 title (str): The new terminal window title
174 """
175 return cls((ControlType.SET_WINDOW_TITLE, title))
177 def __str__(self) -> str:
178 return self.segment.text
180 def __rich_console__(
181 self, console: "Console", options: "ConsoleOptions"
182 ) -> "RenderResult":
183 if self.segment.text:
184 yield self.segment
187def strip_control_codes(
188 text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE
189) -> str:
190 """Remove control codes from text.
192 Args:
193 text (str): A string possibly contain control codes.
195 Returns:
196 str: String with control codes removed.
197 """
198 return text.translate(_translate_table)
201def escape_control_codes(
202 text: str,
203 _translate_table: Dict[int, str] = CONTROL_ESCAPE,
204) -> str:
205 """Replace control codes with their "escaped" equivalent in the given text.
206 (e.g. "\b" becomes "\\b")
208 Args:
209 text (str): A string possibly containing control codes.
211 Returns:
212 str: String with control codes replaced with their escaped version.
213 """
214 return text.translate(_translate_table)
217if __name__ == "__main__": # pragma: no cover
218 from rich.console import Console
220 console = Console()
221 console.print("Look at the title of your terminal window ^")
222 # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
223 for i in range(10):
224 console.set_window_title("🚀 Loading" + "." * i)
225 time.sleep(0.5)