-
-
Notifications
You must be signed in to change notification settings - Fork 31.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-124096: Enable REPL virtual terminal support on Windows #124119
Merged
Merged
Changes from 3 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3db0eb7
NEWS for Windows REPL virtual terminal support
y5c4l3 6d96558
Add Windows REPL virtual terminal queue
y5c4l3 0fed3f7
Enable virtual terminal mode in Windows REPL
y5c4l3 f897500
Merge in the main branch
encukou d3a1698
In VM mode, always request bracketed paste
encukou 89ba4f2
event_queue is no longer a deque
encukou 290aa5e
Update Lib/_pyrepl/windows_eventqueue.py
pablogsal 2b90017
Apply suggestions from code review
encukou 74f8560
Merge branch 'main' into windows-vt
encukou cfc66eb
Deduplicate common EventQueue code; run the tests on both OSes
encukou 0ccf330
Merge in the main branch
encukou 2867297
Adjust the test
encukou b15261b
Annotation, not default...
encukou File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
""" | ||
Windows event and VT sequence scanner, similar to `unix_eventqueue.py` | ||
""" | ||
|
||
from collections import deque | ||
|
||
from . import keymap | ||
from .console import Event | ||
from .trace import trace | ||
import os | ||
|
||
# Reference: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#input-sequences | ||
VT_MAP: dict[bytes, str] = { | ||
b'\x1b[A': 'up', | ||
b'\x1b[B': 'down', | ||
b'\x1b[C': 'right', | ||
b'\x1b[D': 'left', | ||
b'\x1b[1;5D': 'ctrl left', | ||
b'\x1b[1;5C': 'ctrl right', | ||
|
||
b'\x1b[H': 'home', | ||
b'\x1b[F': 'end', | ||
|
||
b'\x7f': 'backspace', | ||
b'\x1b[2~': 'insert', | ||
b'\x1b[3~': 'delete', | ||
b'\x1b[5~': 'page up', | ||
b'\x1b[6~': 'page down', | ||
|
||
b'\x1bOP': 'f1', | ||
b'\x1bOQ': 'f2', | ||
b'\x1bOR': 'f3', | ||
b'\x1bOS': 'f4', | ||
b'\x1b[15~': 'f5', | ||
encukou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
b'\x1b[17~]': 'f6', | ||
b'\x1b[18~]': 'f7', | ||
b'\x1b[19~]': 'f8', | ||
b'\x1b[20~]': 'f9', | ||
b'\x1b[21~]': 'f10', | ||
b'\x1b[23~]': 'f11', | ||
b'\x1b[24~]': 'f12', | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
class EventQueue: | ||
def __init__(self, encoding: str) -> None: | ||
self.compiled_keymap = keymap.compile_keymap(VT_MAP) | ||
self.keymap = self.compiled_keymap | ||
trace("keymap {k!r}", k=self.keymap) | ||
self.encoding = encoding | ||
self.events: deque[Event] = deque() | ||
self.buf = bytearray() | ||
|
||
def get(self) -> Event | None: | ||
""" | ||
Retrieves the next event from the queue. | ||
""" | ||
if self.events: | ||
return self.events.popleft() | ||
else: | ||
return None | ||
|
||
def empty(self) -> bool: | ||
""" | ||
Checks if the queue is empty. | ||
""" | ||
return not self.events | ||
|
||
def flush_buf(self) -> bytearray: | ||
""" | ||
Flushes the buffer and returns its contents. | ||
""" | ||
old = self.buf | ||
self.buf = bytearray() | ||
return old | ||
|
||
def insert(self, event: Event) -> None: | ||
""" | ||
Inserts an event into the queue. | ||
""" | ||
trace('added event {event}', event=event) | ||
self.events.append(event) | ||
|
||
def push(self, char: int | bytes) -> None: | ||
""" | ||
Processes a character by updating the buffer and handling special key mappings. | ||
""" | ||
ord_char = char if isinstance(char, int) else ord(char) | ||
char = bytes(bytearray((ord_char,))) | ||
self.buf.append(ord_char) | ||
if char in self.keymap: | ||
if self.keymap is self.compiled_keymap: | ||
#sanity check, buffer is empty when a special key comes | ||
encukou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert len(self.buf) == 1 | ||
k = self.keymap[char] | ||
trace('found map {k!r}', k=k) | ||
if isinstance(k, dict): | ||
self.keymap = k | ||
else: | ||
self.insert(Event('key', k, self.flush_buf())) | ||
self.keymap = self.compiled_keymap | ||
|
||
elif self.buf and self.buf[0] == 27: # escape | ||
# escape sequence not recognized by our keymap: propagate it | ||
# outside so that i can be recognized as an M-... key (see also | ||
# the docstring in keymap.py | ||
trace('unrecognized escape sequence, propagating...') | ||
self.keymap = self.compiled_keymap | ||
self.insert(Event('key', '\033', bytearray(b'\033'))) | ||
for _c in self.flush_buf()[1:]: | ||
self.push(_c) | ||
|
||
else: | ||
try: | ||
decoded = bytes(self.buf).decode(self.encoding) | ||
except UnicodeError: | ||
return | ||
else: | ||
self.insert(Event('key', decoded, self.flush_buf())) | ||
self.keymap = self.compiled_keymap |
3 changes: 3 additions & 0 deletions
3
Misc/NEWS.d/next/Library/2024-09-16-17-03-52.gh-issue-124096.znin0O.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Turn on virtual terminal mode in REPL Windows console to emit sequences that | ||
bracketed-paste modes require. If the terminal may not support paste modes, | ||
provides a flag to help callers turn off related features. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi! Maintainer of Windows Terminal (and/or Windows Console) here 🙂
In the general case, this is a fragile way to detect support. When Windows Terminal is used as the "default console host", it doesn't have a chance to inject its environment variables; this means that any sessions spawned outside of Terminal but which get launched into it will not try to enable bracketed paste.
It looks like the only thing this PR does with
__vt_bracketed_paste
is check whether it should enable it, and that it doesn't gate any other behavior.Fortunately, bracketed paste is safe to enable on all console hosts on Windows that support
VIRTUAL_TERMINAL_INPUT
--even if they do not appear to support it. It'll just no-op when it's not available.If you really want to check (later!) whether it got enabled, you can request a private mode report with
DECRQM
(\e[?2004$p
); it will return\e[?2004;X$y
whereX
is1
if it is set and2
if it is cleared. If it doesn't reply, the console host is too old to support mode queries.You could also wait to see the first
\e[200~
to determine whether it had been turned on for a given paste.