Skip to content

Commit aa9eaf3

Browse files
authored
Merge pull request #72 from lysnikolaou/pyrepl-refactor-term-unix-console
Refactor termios stuff in unix console
2 parents 7919bae + 2a7e81d commit aa9eaf3

File tree

3 files changed

+375
-219
lines changed

3 files changed

+375
-219
lines changed

Lib/_pyrepl/_minimal_curses.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

Lib/_pyrepl/fancy_termios.py

Lines changed: 210 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
1818
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1919

20+
import curses
2021
import termios
2122

2223

24+
class InvalidTerminal(RuntimeError):
25+
pass
26+
27+
2328
class TermState:
2429
def __init__(self, tuples):
2530
(
@@ -55,20 +60,212 @@ def tcsetattr(fd, when, attrs):
5560
termios.tcsetattr(fd, when, attrs.as_list())
5661

5762

58-
class Term(TermState):
59-
TS__init__ = TermState.__init__
63+
class TermCapability:
64+
"""Base class for all terminal capabilities we need"""
65+
66+
def __init__(self, buffer):
67+
self._buffer = buffer
68+
self._args = []
69+
70+
self._bytes = curses.tigetstr(self.name)
71+
if self._bytes is None and not self.optional:
72+
raise InvalidTerminal(
73+
f"terminal doesn't have the required {self.name!r} capability"
74+
)
75+
76+
self.supported = self._bytes is not None
77+
78+
def __call__(self, *args):
79+
if self.supported:
80+
self._args = args
81+
self._buffer.push(self)
82+
83+
def text(self):
84+
return curses.tparm(self._bytes, *self._args)
85+
86+
87+
class Bell(TermCapability):
88+
"""Audible signal (bell)"""
89+
90+
name = "bel"
91+
optional = False
92+
93+
94+
class CursorInvisible(TermCapability):
95+
"""Make cursor invisible"""
96+
97+
name = "civis"
98+
optional = True
99+
100+
101+
class ClearScreen(TermCapability):
102+
"""Clear screen and home cursor"""
103+
104+
name = "clear"
105+
optional = False
106+
107+
108+
class CursorNormal(TermCapability):
109+
"""Make cursor appear normal (undo civis/cvvis)"""
110+
111+
name = "cnorm"
112+
optional = True
113+
114+
115+
class ParmLeftCursor(TermCapability):
116+
"""Move #1 characters to the left"""
117+
118+
name = "cub"
119+
optional = True
120+
121+
122+
class CursorLeft(TermCapability):
123+
"""Move left one space"""
124+
125+
name = "cub1"
126+
optional = True
127+
128+
def text(self):
129+
assert len(self._args) == 1 # cursor_down needs to have been called with dx
130+
return curses.tparm(self._args[0] * self._bytes)
131+
132+
133+
class ParmDownCursor(TermCapability):
134+
"""Down #1 lines"""
135+
136+
name = "cud"
137+
optional = True
138+
139+
140+
class CursorDown(TermCapability):
141+
"""Down one line"""
142+
143+
name = "cud1"
144+
optional = True
145+
146+
def text(self):
147+
assert len(self._args) == 1 # cursor_down needs to have been called with dy
148+
return curses.tparm(self._args[0] * self._bytes)
149+
150+
151+
class ParmRightCursor(TermCapability):
152+
"""Move #1 characters to the right"""
153+
154+
name = "cuf"
155+
optional = True
156+
157+
158+
class CursorRight(TermCapability):
159+
"""Non-destructive space (move right one space)"""
160+
161+
name = "cuf1"
162+
optional = True
163+
164+
def text(self):
165+
assert len(self._args) == 1 # cursor_down needs to have been called with dx
166+
return curses.tparm(self._args[0] * self._bytes)
167+
168+
169+
class CursorAddress(TermCapability):
170+
"""Move to row #1 columns #2"""
171+
172+
name = "cup"
173+
optional = False
174+
175+
176+
class ParmUpCursor(TermCapability):
177+
"""Up #1 lines"""
178+
179+
name = "cuu"
180+
optional = True
181+
182+
183+
class CursorUp(TermCapability):
184+
"""Up 1 line"""
185+
186+
name = "cuu1"
187+
optional = True
188+
189+
def text(self):
190+
assert len(self._args) == 1 # cursor_down needs to have been called with dy
191+
return curses.tparm(self._args[0] * self._bytes)
192+
193+
194+
class ParmDeleteCharacter(TermCapability):
195+
"""Delete #1 characters"""
196+
197+
name = "dch"
198+
optional = True
199+
200+
201+
class DeleteCharacter(TermCapability):
202+
"""Delete character"""
203+
204+
name = "dch1"
205+
optional = True
206+
207+
208+
class ClearEol(TermCapability):
209+
"""Clear to end of line"""
210+
211+
name = "el"
212+
optional = False
213+
214+
215+
class ColumnAddress(TermCapability):
216+
"""Horizontal position #1, absolute"""
217+
218+
name = "hpa"
219+
optional = True
220+
221+
222+
class ParmInsertCharacter(TermCapability):
223+
"""Insert #1 characters"""
224+
225+
name = "ich"
226+
optional = True
227+
228+
229+
class InsertCharacter(TermCapability):
230+
"""Insert character"""
231+
232+
name = "ich1"
233+
optional = True
234+
235+
236+
class ScrollForward(TermCapability):
237+
"""Scroll text up"""
238+
239+
name = "ind"
240+
optional = True
241+
242+
243+
class PadChar(TermCapability):
244+
"""Padding char (instead of null)"""
245+
246+
name = "pad"
247+
optional = True
248+
249+
def text(self, nchars):
250+
return self._bytes * nchars
251+
252+
253+
class ScrollReverse(TermCapability):
254+
"""Scroll text down"""
255+
256+
name = "ri"
257+
optional = True
258+
259+
260+
class KeypadLocal(TermCapability):
261+
"""Leave 'keyboard_transmit' mode"""
60262

61-
def __init__(self, fd=0):
62-
self.TS__init__(termios.tcgetattr(fd))
63-
self.fd = fd
64-
self.stack = []
263+
name = "rmkx"
264+
optional = True
65265

66-
def save(self):
67-
self.stack.append(self.as_list())
68266

69-
def set(self, when=termios.TCSANOW):
70-
termios.tcsetattr(self.fd, when, self.as_list())
267+
class KeypadXmit(TermCapability):
268+
"""Enter 'keyboard_transmit' mode"""
71269

72-
def restore(self):
73-
self.TS__init__(self.stack.pop())
74-
self.set()
270+
name = "smkx"
271+
optional = True

0 commit comments

Comments
 (0)