[3.13] gh-124096: Enable REPL virtual terminal support on Windows (GH… · python/cpython@a2bf7a0 · GitHub
Skip to content

Commit a2bf7a0

Browse files
miss-islingtony5c4l3encukoupablogsalDHowett
authored
[3.13] gh-124096: Enable REPL virtual terminal support on Windows (GH-124119) (GH-133457)
To support virtual terminal mode in Windows PYREPL, we need a scanner to read over the supported escaped VT sequences. Windows REPL input was using virtual key mode, which does not support terminal escape sequences. This patch calls `SetConsoleMode` properly when initializing and send sequences to enable bracketed-paste modes to support verbatim copy-and-paste. (cherry picked from commit a65366e) Co-authored-by: Y5 <124019959+y5c4l3@users.noreply.github.com> Signed-off-by: y5c4l3 <y5c4l3@proton.me> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> Co-authored-by: Dustin L. Howett <dustin@howett.net> Co-authored-by: wheeheee <104880306+wheeheee@users.noreply.github.com>
1 parent 1105ed3 commit a2bf7a0

7 files changed

Lines changed: 264 additions & 113 deletions

File tree

.github/CODEOWNERS

Lines changed: 0 additions & 1 deletion

Lib/_pyrepl/base_eventqueue.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
2+
# Armin Rigo
3+
#
4+
# All Rights Reserved
5+
#
6+
#
7+
# Permission to use, copy, modify, and distribute this software and
8+
# its documentation for any purpose is hereby granted without fee,
9+
# provided that the above copyright notice appear in all copies and
10+
# that both that copyright notice and this permission notice appear in
11+
# supporting documentation.
12+
#
13+
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
14+
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15+
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
16+
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
17+
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
18+
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19+
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20+
21+
"""
22+
OS-independent base for an event and VT sequence scanner
23+
24+
See unix_eventqueue and windows_eventqueue for subclasses.
25+
"""
26+
27+
from collections import deque
28+
29+
from . import keymap
30+
from .console import Event
31+
from .trace import trace
32+
33+
class BaseEventQueue:
34+
def __init__(self, encoding: str, keymap_dict: dict[bytes, str]) -> None:
35+
self.compiled_keymap = keymap.compile_keymap(keymap_dict)
36+
self.keymap = self.compiled_keymap
37+
trace("keymap {k!r}", k=self.keymap)
38+
self.encoding = encoding
39+
self.events: deque[Event] = deque()
40+
self.buf = bytearray()
41+
42+
def get(self) -> Event | None:
43+
"""
44+
Retrieves the next event from the queue.
45+
"""
46+
if self.events:
47+
return self.events.popleft()
48+
else:
49+
return None
50+
51+
def empty(self) -> bool:
52+
"""
53+
Checks if the queue is empty.
54+
"""
55+
return not self.events
56+
57+
def flush_buf(self) -> bytearray:
58+
"""
59+
Flushes the buffer and returns its contents.
60+
"""
61+
old = self.buf
62+
self.buf = bytearray()
63+
return old
64+
65+
def insert(self, event: Event) -> None:
66+
"""
67+
Inserts an event into the queue.
68+
"""
69+
trace('added event {event}', event=event)
70+
self.events.append(event)
71+
72+
def push(self, char: int | bytes) -> None:
73+
"""
74+
Processes a character by updating the buffer and handling special key mappings.
75+
"""
76+
ord_char = char if isinstance(char, int) else ord(char)
77+
char = bytes(bytearray((ord_char,)))
78+
self.buf.append(ord_char)
79+
if char in self.keymap:
80+
if self.keymap is self.compiled_keymap:
81+
# sanity check, buffer is empty when a special key comes
82+
assert len(self.buf) == 1
83+
k = self.keymap[char]
84+
trace('found map {k!r}', k=k)
85+
if isinstance(k, dict):
86+
self.keymap = k
87+
else:
88+
self.insert(Event('key', k, self.flush_buf()))
89+
self.keymap = self.compiled_keymap
90+
91+
elif self.buf and self.buf[0] == 27: # escape
92+
# escape sequence not recognized by our keymap: propagate it
93+
# outside so that i can be recognized as an M-... key (see also
94+
# the docstring in keymap.py
95+
trace('unrecognized escape sequence, propagating...')
96+
self.keymap = self.compiled_keymap
97+
self.insert(Event('key', '\033', bytearray(b'\033')))
98+
for _c in self.flush_buf()[1:]:
99+
self.push(_c)
100+
101+
else:
102+
try:
103+
decoded = bytes(self.buf).decode(self.encoding)
104+
except UnicodeError:
105+
return
106+
else:
107+
self.insert(Event('key', decoded, self.flush_buf()))
108+
self.keymap = self.compiled_keymap

Lib/_pyrepl/unix_eventqueue.py

Lines changed: 5 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@
1818
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
1919
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2020

21-
from collections import deque
22-
23-
from . import keymap
24-
from .console import Event
2521
from . import curses
2622
from .trace import trace
23+
from .base_eventqueue import BaseEventQueue
2724
from termios import tcgetattr, VERASE
2825
import os
2926

@@ -70,83 +67,10 @@ def get_terminal_keycodes() -> dict[bytes, str]:
7067
keycodes.update(CTRL_ARROW_KEYCODES)
7168
return keycodes
7269

73-
class EventQueue:
70+
class EventQueue(BaseEventQueue):
7471
def __init__(self, fd: int, encoding: str) -> None:
75-
self.keycodes = get_terminal_keycodes()
72+
keycodes = get_terminal_keycodes()
7673
if os.isatty(fd):
7774
backspace = tcgetattr(fd)[6][VERASE]
78-
self.keycodes[backspace] = "backspace"
79-
self.compiled_keymap = keymap.compile_keymap(self.keycodes)
80-
self.keymap = self.compiled_keymap
81-
trace("keymap {k!r}", k=self.keymap)
82-
self.encoding = encoding
83-
self.events: deque[Event] = deque()
84-
self.buf = bytearray()
85-
86-
def get(self) -> Event | None:
87-
"""
88-
Retrieves the next event from the queue.
89-
"""
90-
if self.events:
91-
return self.events.popleft()
92-
else:
93-
return None
94-
95-
def empty(self) -> bool:
96-
"""
97-
Checks if the queue is empty.
98-
"""
99-
return not self.events
100-
101-
def flush_buf(self) -> bytearray:
102-
"""
103-
Flushes the buffer and returns its contents.
104-
"""
105-
old = self.buf
106-
self.buf = bytearray()
107-
return old
108-
109-
def insert(self, event: Event) -> None:
110-
"""
111-
Inserts an event into the queue.
112-
"""
113-
trace('added event {event}', event=event)
114-
self.events.append(event)
115-
116-
def push(self, char: int | bytes) -> None:
117-
"""
118-
Processes a character by updating the buffer and handling special key mappings.
119-
"""
120-
ord_char = char if isinstance(char, int) else ord(char)
121-
char = bytes(bytearray((ord_char,)))
122-
self.buf.append(ord_char)
123-
if char in self.keymap:
124-
if self.keymap is self.compiled_keymap:
125-
#sanity check, buffer is empty when a special key comes
126-
assert len(self.buf) == 1
127-
k = self.keymap[char]
128-
trace('found map {k!r}', k=k)
129-
if isinstance(k, dict):
130-
self.keymap = k
131-
else:
132-
self.insert(Event('key', k, self.flush_buf()))
133-
self.keymap = self.compiled_keymap
134-
135-
elif self.buf and self.buf[0] == 27: # escape
136-
# escape sequence not recognized by our keymap: propagate it
137-
# outside so that i can be recognized as an M-... key (see also
138-
# the docstring in keymap.py
139-
trace('unrecognized escape sequence, propagating...')
140-
self.keymap = self.compiled_keymap
141-
self.insert(Event('key', '\033', bytearray(b'\033')))
142-
for _c in self.flush_buf()[1:]:
143-
self.push(_c)
144-
145-
else:
146-
try:
147-
decoded = bytes(self.buf).decode(self.encoding)
148-
except UnicodeError:
149-
return
150-
else:
151-
self.insert(Event('key', decoded, self.flush_buf()))
152-
self.keymap = self.compiled_keymap
75+
keycodes[backspace] = "backspace"
76+
BaseEventQueue.__init__(self, encoding, keycodes)

Lib/_pyrepl/windows_console.py

Lines changed: 58 additions & 9 deletions

0 commit comments

Comments
 (0)