Skip to content

Commit

Permalink
pythongh-128388: pyrepl on Windows: add meta and ctrl+arrow keybindin…
Browse files Browse the repository at this point in the history
…gs (pythonGH-128389)

Fix `Lib/_pyrepl/windows_console.py` to support more keybindings, like the
`Ctrl`+`←` and `Ctrl`+`→` word-skipping keybindings and those with meta (i.e. Alt),
e.g. to `kill-word` or `backward-kill-word`.

Specifics: if Ctrl is pressed, emit "ctrl left" and "ctrl right" instead of just "left" or
"right," and if Meta/Alt is pressed, emit the special key code for meta before
emitting the other key that was pressed.

Co-authored-by: Hugo van Kemenade <[email protected]>
Co-authored-by: Pieter Eendebak <[email protected]>
  • Loading branch information
3 people authored Jan 10, 2025
1 parent baf6571 commit 688f3a0
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 16 deletions.
42 changes: 26 additions & 16 deletions Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
MOVE_DOWN = "\x1b[{}B"
CLEAR = "\x1b[H\x1b[J"

# State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str
ALT_ACTIVE = 0x01 | 0x02
CTRL_ACTIVE = 0x04 | 0x08


class _error(Exception):
pass
Expand Down Expand Up @@ -407,31 +411,37 @@ def get_event(self, block: bool = True) -> Event | None:
continue
return None

key = rec.Event.KeyEvent.uChar.UnicodeChar
key_event = rec.Event.KeyEvent
raw_key = key = key_event.uChar.UnicodeChar

if rec.Event.KeyEvent.uChar.UnicodeChar == "\r":
# Make enter make unix-like
if key == "\r":
# Make enter unix-like
return Event(evt="key", data="\n", raw=b"\n")
elif rec.Event.KeyEvent.wVirtualKeyCode == 8:
elif key_event.wVirtualKeyCode == 8:
# Turn backspace directly into the command
return Event(
evt="key",
data="backspace",
raw=rec.Event.KeyEvent.uChar.UnicodeChar,
)
elif rec.Event.KeyEvent.uChar.UnicodeChar == "\x00":
key = "backspace"
elif key == "\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
)
key = VK_MAP.get(key_event.wVirtualKeyCode)
if key:
if key_event.dwControlKeyState & CTRL_ACTIVE:
key = f"ctrl {key}"
elif key_event.dwControlKeyState & ALT_ACTIVE:
# queue the key, return the meta command
self.event_queue.insert(0, Event(evt="key", data=key, raw=key))
return Event(evt="key", data="\033") # keymap.py uses this for meta
return Event(evt="key", data=key, raw=key)
if block:
continue

return None

return Event(evt="key", data=key, raw=rec.Event.KeyEvent.uChar.UnicodeChar)
if key_event.dwControlKeyState & ALT_ACTIVE:
# queue the key, return the meta command
self.event_queue.insert(0, Event(evt="key", data=key, raw=raw_key))
return Event(evt="key", data="\033") # keymap.py uses this for meta

return Event(evt="key", data=key, raw=raw_key)

def push_char(self, char: int | bytes) -> None:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``PyREPL`` on Windows to support more keybindings, like the :kbd:`Control-` and :kbd:`Control-` word-skipping keybindings and those with meta (i.e. :kbd:`Alt`), e.g. :kbd:`Alt-d` to ``kill-word`` or :kbd:`Alt-Backspace` ``backward-kill-word``.

0 comments on commit 688f3a0

Please sign in to comment.