From 283e7c345d3b878b418a13c937d4a15f639e1f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:56:51 -0500 Subject: [PATCH 1/9] pyrepl on Windows: add meta and ctrl+arrow keybindings --- Lib/_pyrepl/windows_console.py | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index d457d2b5a338eb..42931778f0a79d 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -102,6 +102,11 @@ 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 +CTRL_OR_ALT_ACTIVE = ALT_ACTIVE | CTRL_ACTIVE + class _error(Exception): pass @@ -407,31 +412,33 @@ 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) + code = VK_MAP.get(key_event.wVirtualKeyCode) if code: - return Event( - evt="key", data=code, raw=rec.Event.KeyEvent.uChar.UnicodeChar - ) + if code in ("left", "right") and (ctrlstate := key_event.dwControlKeyState) and ctrlstate & CTRL_OR_ALT_ACTIVE: + code = f"ctrl {code}" + return Event(evt="key", data=code, raw=key) if block: continue return None - return Event(evt="key", data=key, raw=rec.Event.KeyEvent.uChar.UnicodeChar) + if (ctrlstate := key_event.dwControlKeyState) and ctrlstate & ALT_ACTIVE: + # first send meta, then send the key + 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: """ From d3df8a2624956853d0ad0ba8317226403fddc032 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:24:45 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst 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 index 00000000000000..4cfd0530434cf1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst @@ -0,0 +1 @@ +Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to :data:`kill-word` or data:`backward-kill-word`. From 459b379c17889e82d99f5782d60d0069e483b396 Mon Sep 17 00:00:00 2001 From: paulie4 Date: Wed, 1 Jan 2025 14:27:19 -0500 Subject: [PATCH 3/9] `2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst`: fix typo --- .../next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4cfd0530434cf1..cbfc952f37a7d1 100644 --- 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 @@ -1 +1 @@ -Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to :data:`kill-word` or data:`backward-kill-word`. +Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to :data:`kill-word` or :data:`backward-kill-word`. From abe1f627182e55f33c19751d0bb4dd392c68ffcd Mon Sep 17 00:00:00 2001 From: paulie4 <203125+paulie4@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:04:57 -0500 Subject: [PATCH 4/9] lint fix `Misc/NEWS.d/next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst` Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .../next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index cbfc952f37a7d1..25667253fe392f 100644 --- 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 @@ -1 +1 @@ -Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to :data:`kill-word` or :data:`backward-kill-word`. +Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to ``kill-word`` or ``backward-kill-word``. From 3d07c340979c8712756769c2bc7aeac325e2e2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:39:19 -0500 Subject: [PATCH 5/9] `Lib/_pyrepl/windows_console.py`: temp var `ctrlstate` is not needed Co-authored-by: Pieter Eendebak --- Lib/_pyrepl/windows_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 42931778f0a79d..417c959f2499f0 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -433,7 +433,7 @@ def get_event(self, block: bool = True) -> Event | None: return None - if (ctrlstate := key_event.dwControlKeyState) and ctrlstate & ALT_ACTIVE: + if key_event.dwControlKeyState & ALT_ACTIVE: # first send meta, then send the key 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 From 16f2a13bf238fc4dfcd4c59d24a0b91ed78ff0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:40:19 -0500 Subject: [PATCH 6/9] `Lib/_pyrepl/windows_console.py`: clearer comment Co-authored-by: Pieter Eendebak --- Lib/_pyrepl/windows_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 417c959f2499f0..928c2ff1cf4576 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -434,7 +434,7 @@ def get_event(self, block: bool = True) -> Event | None: return None if key_event.dwControlKeyState & ALT_ACTIVE: - # first send meta, then send the key + # 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 From e3f643874ef045d92d734e19bfd2e7bf83ac81d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:59:18 -0500 Subject: [PATCH 7/9] PR suggestions --- Lib/_pyrepl/windows_console.py | 2 +- .../next/Library/2025-01-01-19-24-43.gh-issue-128388.8UdMz_.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 928c2ff1cf4576..ca716a746f1228 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -425,7 +425,7 @@ def get_event(self, block: bool = True) -> Event | None: # Handle special keys like arrow keys and translate them into the appropriate command code = VK_MAP.get(key_event.wVirtualKeyCode) if code: - if code in ("left", "right") and (ctrlstate := key_event.dwControlKeyState) and ctrlstate & CTRL_OR_ALT_ACTIVE: + if code in ("left", "right") and key_event.dwControlKeyState & CTRL_OR_ALT_ACTIVE: code = f"ctrl {code}" return Event(evt="key", data=code, raw=key) if block: 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 index 25667253fe392f..5bef0fd6bcac17 100644 --- 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 @@ -1 +1 @@ -Fix :source:`Lib/_pyrepl/windows_console.py` to support more keybindings, like the :kbd:`Control-←` and :kbd:`Control-→` word-skipping keybindings and those with meta (i.e. Alt), e.g. to ``kill-word`` or ``backward-kill-word``. +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``. From 46b22d1ec64171f924145afbd368a9ee60f74c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:40:07 -0500 Subject: [PATCH 8/9] =?UTF-8?q?`Lib\=5Fpyrepl\windows=5Fconsole.py`:=20fix?= =?UTF-8?q?=20`Alt`+`=E2=86=90`=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/_pyrepl/windows_console.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index ca716a746f1228..bd1decf9d86c72 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -105,7 +105,6 @@ def __init__(self, err: int | None, descr: str | None = None) -> None: # State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str ALT_ACTIVE = 0x01 | 0x02 CTRL_ACTIVE = 0x04 | 0x08 -CTRL_OR_ALT_ACTIVE = ALT_ACTIVE | CTRL_ACTIVE class _error(Exception): @@ -423,11 +422,15 @@ def get_event(self, block: bool = True) -> Event | None: key = "backspace" elif key == "\x00": # Handle special keys like arrow keys and translate them into the appropriate command - code = VK_MAP.get(key_event.wVirtualKeyCode) - if code: - if code in ("left", "right") and key_event.dwControlKeyState & CTRL_OR_ALT_ACTIVE: - code = f"ctrl {code}" - return Event(evt="key", data=code, raw=key) + key = VK_MAP.get(key_event.wVirtualKeyCode) + if key: + if key in ("left", "right") and 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 From 55e4fbff85403812b886d02206a5176e43d0a7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulie=20Pe=C3=B1a?= <203125+paulie4@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:28:46 -0500 Subject: [PATCH 9/9] `Lib/_pyrepl/windows_console.py`: standardize `Ctrl`+special key Co-authored-by: Pieter Eendebak --- Lib/_pyrepl/windows_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index bd1decf9d86c72..e738fd09c65758 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -424,7 +424,7 @@ def get_event(self, block: bool = True) -> Event | None: # Handle special keys like arrow keys and translate them into the appropriate command key = VK_MAP.get(key_event.wVirtualKeyCode) if key: - if key in ("left", "right") and key_event.dwControlKeyState & CTRL_ACTIVE: + if key_event.dwControlKeyState & CTRL_ACTIVE: key = f"ctrl {key}" elif key_event.dwControlKeyState & ALT_ACTIVE: # queue the key, return the meta command