Skip to content
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-127947: Repeat PyREPL key events on Windows when wRepeatCount > 1 #127948

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def __init__(
self.height = 25
self.__offset = 0
self.event_queue: deque[Event] = deque()
self.key_repeat_queue: deque[Event] = deque()
try:
self.out = io._WindowsConsoleIO(self.output_fd, "w") # type: ignore[attr-defined]
except ValueError:
Expand Down Expand Up @@ -390,6 +391,9 @@ def get_event(self, block: bool = True) -> Event | None:
"""Return an Event instance. Returns None if |block| is false
and there is no event pending, otherwise waits for the
completion of an event."""
if self.key_repeat_queue:
return self.key_repeat_queue.pop()

if self.event_queue:
return self.event_queue.pop()

Expand All @@ -407,31 +411,41 @@ def get_event(self, block: bool = True) -> Event | None:
continue
return None

key = rec.Event.KeyEvent.uChar.UnicodeChar

if rec.Event.KeyEvent.uChar.UnicodeChar == "\r":
# Make enter make unix-like
return Event(evt="key", data="\n", raw=b"\n")
elif rec.Event.KeyEvent.wVirtualKeyCode == 8:
# Turn backspace directly into the command
event = self._event_from_keyevent(rec.Event.KeyEvent)
if event is None and block:
continue

if event is not None:
# Queue this key event to be repeated if wRepeatCount > 1, such as when a 'dead key' is pressed twice
for _ in range(rec.Event.KeyEvent.wRepeatCount - 1):
self.key_repeat_queue.appendleft(event)

return event

def _event_from_keyevent(self, keyevent: KeyEvent) -> Event | None:
key = keyevent.uChar.UnicodeChar

if keyevent.uChar.UnicodeChar == "\r":
# Make enter make unix-like
return Event(evt="key", data="\n", raw=b"\n")
elif keyevent.wVirtualKeyCode == 8:
# Turn backspace directly into the command
return Event(
evt="key",
data="backspace",
raw=keyevent.uChar.UnicodeChar,
)
elif keyevent.uChar.UnicodeChar == "\x00":
# Handle special keys like arrow keys and translate them into the appropriate command
code = VK_MAP.get(keyevent.wVirtualKeyCode)
if code:
return Event(
evt="key",
data="backspace",
raw=rec.Event.KeyEvent.uChar.UnicodeChar,
evt="key", data=code, raw=keyevent.uChar.UnicodeChar
)
elif rec.Event.KeyEvent.uChar.UnicodeChar == "\x00":
# Handle special keys like arrow keys and translate them into the appropriate command
code = VK_MAP.get(rec.Event.KeyEvent.wVirtualKeyCode)
if code:
return Event(
evt="key", data=code, raw=rec.Event.KeyEvent.uChar.UnicodeChar
)
if block:
continue

return None
return None

return Event(evt="key", data=key, raw=rec.Event.KeyEvent.uChar.UnicodeChar)
return Event(evt="key", data=key, raw=keyevent.uChar.UnicodeChar)

def push_char(self, char: int | bytes) -> None:
"""
Expand Down Expand Up @@ -479,7 +493,7 @@ def wait(self, timeout: float | None) -> bool:
# Poor man's Windows select loop
start_time = time.time()
while True:
if msvcrt.kbhit(): # type: ignore[attr-defined]
if msvcrt.kbhit() or self.key_repeat_queue: # type: ignore[attr-defined]
return True
if timeout and time.time() - start_time > timeout / 1000:
return False
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Repeat PyREPL key events on Windows when wRepeatCount > 1. This fixes double dead key presses from only being typed once in the Windows Terminal.
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
Loading