Skip to content

Commit f932bec

Browse files
committed
Add Windows REPL virtual terminal queue
To support virtual terminal mode in Windows PYREPL, we need a scanner to read over the supported escaped VT sequences. Signed-off-by: y5c4l3 <[email protected]>
1 parent 166587a commit f932bec

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

Lib/_pyrepl/windows_eventqueue.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Windows event and VT sequence scanner, similar to `unix_eventqueue.py`
3+
"""
4+
5+
from collections import deque
6+
7+
from . import keymap
8+
from .console import Event
9+
from .trace import trace
10+
import os
11+
12+
# Reference: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#input-sequences
13+
VT_MAP: dict[bytes, str] = {
14+
b'\x1b[A': 'up',
15+
b'\x1b[B': 'down',
16+
b'\x1b[C': 'right',
17+
b'\x1b[D': 'left',
18+
19+
b'\x1b[H': 'home',
20+
b'\x1b[F': 'end',
21+
22+
b'\x7f': 'backspace',
23+
b'\x1b[2~': 'insert',
24+
b'\x1b[3~': 'delete',
25+
b'\x1b[5~': 'page up',
26+
b'\x1b[6~': 'page down',
27+
28+
b'\x1bOP': 'f1',
29+
b'\x1bOQ': 'f2',
30+
b'\x1bOR': 'f3',
31+
b'\x1bOS': 'f4',
32+
b'\x1b[15~': 'f5',
33+
b'\x1b[17~]': 'f6',
34+
b'\x1b[18~]': 'f7',
35+
b'\x1b[19~]': 'f8',
36+
b'\x1b[20~]': 'f9',
37+
b'\x1b[21~]': 'f10',
38+
b'\x1b[23~]': 'f11',
39+
b'\x1b[24~]': 'f12',
40+
}
41+
42+
class EventQueue:
43+
def __init__(self, encoding: str) -> None:
44+
self.compiled_keymap = keymap.compile_keymap(VT_MAP)
45+
self.keymap = self.compiled_keymap
46+
trace("keymap {k!r}", k=self.keymap)
47+
self.encoding = encoding
48+
self.events: deque[Event] = deque()
49+
self.buf = bytearray()
50+
51+
def get(self) -> Event | None:
52+
"""
53+
Retrieves the next event from the queue.
54+
"""
55+
if self.events:
56+
return self.events.popleft()
57+
else:
58+
return None
59+
60+
def empty(self) -> bool:
61+
"""
62+
Checks if the queue is empty.
63+
"""
64+
return not self.events
65+
66+
def flush_buf(self) -> bytearray:
67+
"""
68+
Flushes the buffer and returns its contents.
69+
"""
70+
old = self.buf
71+
self.buf = bytearray()
72+
return old
73+
74+
def insert(self, event: Event) -> None:
75+
"""
76+
Inserts an event into the queue.
77+
"""
78+
trace('added event {event}', event=event)
79+
self.events.append(event)
80+
81+
def push(self, char: int | bytes) -> None:
82+
"""
83+
Processes a character by updating the buffer and handling special key mappings.
84+
"""
85+
ord_char = char if isinstance(char, int) else ord(char)
86+
char = bytes(bytearray((ord_char,)))
87+
self.buf.append(ord_char)
88+
if char in self.keymap:
89+
if self.keymap is self.compiled_keymap:
90+
#sanity check, buffer is empty when a special key comes
91+
assert len(self.buf) == 1
92+
k = self.keymap[char]
93+
trace('found map {k!r}', k=k)
94+
if isinstance(k, dict):
95+
self.keymap = k
96+
else:
97+
self.insert(Event('key', k, self.flush_buf()))
98+
self.keymap = self.compiled_keymap
99+
100+
elif self.buf and self.buf[0] == 27: # escape
101+
# escape sequence not recognized by our keymap: propagate it
102+
# outside so that i can be recognized as an M-... key (see also
103+
# the docstring in keymap.py
104+
trace('unrecognized escape sequence, propagating...')
105+
self.keymap = self.compiled_keymap
106+
self.insert(Event('key', '\033', bytearray(b'\033')))
107+
for _c in self.flush_buf()[1:]:
108+
self.push(_c)
109+
110+
else:
111+
try:
112+
decoded = bytes(self.buf).decode(self.encoding)
113+
except UnicodeError:
114+
return
115+
else:
116+
self.insert(Event('key', decoded, self.flush_buf()))
117+
self.keymap = self.compiled_keymap

0 commit comments

Comments
 (0)