]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-128388: pyrepl on Windows: add meta and ctrl+arrow keybindings (GH-128389...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 3 Mar 2025 15:57:53 +0000 (16:57 +0100)
committerGitHub <noreply@github.com>
Mon, 3 Mar 2025 15:57:53 +0000 (16:57 +0100)
gh-128388: pyrepl on Windows: add meta and ctrl+arrow keybindings (GH-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.
(cherry picked from commit 688f3a0d4b94874ff6d72af3baafd8bbf911153e)

Co-authored-by: Paulie Peña <203125+paulie4@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
Lib/_pyrepl/windows_console.py
Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst [new file with mode: 0644]

index fffadd5e2ec28e34bb22b055f0d48ddab4cba209..48f328e4de3ce35cfa5e8b82d7a0e308f649390e 100644 (file)
@@ -102,6 +102,10 @@ MOVE_UP = "\x1b[{}A"
 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
@@ -407,31 +411,37 @@ class WindowsConsole(Console):
                     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:
         """
diff --git a/Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst b/Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst
new file mode 100644 (file)
index 0000000..5bef0fd
--- /dev/null
@@ -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``.