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