From e352f79dc65727c37d25018020ea6783c77c8e73 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Fri, 9 May 2025 08:34:38 +0200 Subject: [PATCH 1/4] initial minimal needed changes --- Lib/_pyrepl/commands.py | 7 +++++++ Lib/_pyrepl/windows_console.py | 8 ++++++++ Lib/test/test_pyrepl/test_unix_console.py | 1 + Lib/test/test_pyrepl/test_windows_console.py | 1 + 4 files changed, 17 insertions(+) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 2354fbb2ec2c1e..0d644a0e269e18 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -370,6 +370,13 @@ def do(self) -> None: r = self.reader text = self.event * r.get_arg() r.insert(text) + if r.paste_mode: + data = "" + ev = r.console.getpending() + data += ev.data + if data: + r.insert(data) + r.last_refresh_cache.invalidated = True class insert_nl(EditCommand): diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 95749198b3b2f9..15d05deaf82cef 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -554,9 +554,17 @@ def getpending(self) -> Event: recs, rec_count = self._read_input_bulk(False, 1024) for i in range(rec_count): rec = recs[i] + # In case of a legacy console, we do not only receive a keydown + # event, but also a keyup event - and for uppercase letters + # an additional SHIFT_PRESSED event. if rec and rec.EventType == KEY_EVENT: key_event = rec.Event.KeyEvent + if not key_event.bKeyDown: + continue ch = key_event.uChar.UnicodeChar + if ch == "\x00": + # ignore SHIFT_PRESSED and special keys + continue if ch == "\r": ch += "\n" e.data += ch diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index c447b310c49a06..b3f7dc028fe210 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -20,6 +20,7 @@ def unix_console(events, **kwargs): console = UnixConsole() console.get_event = MagicMock(side_effect=events) + console.getpending = MagicMock(return_value=Event("key", "")) height = kwargs.get("height", 25) width = kwargs.get("width", 80) diff --git a/Lib/test/test_pyrepl/test_windows_console.py b/Lib/test/test_pyrepl/test_windows_console.py index e7bab226b31ddf..a52ae96a83ddde 100644 --- a/Lib/test/test_pyrepl/test_windows_console.py +++ b/Lib/test/test_pyrepl/test_windows_console.py @@ -35,6 +35,7 @@ class WindowsConsoleTests(TestCase): def console(self, events, **kwargs) -> Console: console = WindowsConsole() console.get_event = MagicMock(side_effect=events) + console.getpending = MagicMock(return_value=Event("key", "")) console.wait = MagicMock() console._scroll = MagicMock() console._hide_cursor = MagicMock() From 4dc423885460d61fe0a8f0bbb56ea5714ce3ce5a Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Fri, 9 May 2025 08:50:17 +0200 Subject: [PATCH 2/4] move blocking / waiting to get_event like in unix_console.py --- Lib/_pyrepl/windows_console.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 15d05deaf82cef..c56dcd6d7dd434 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -419,10 +419,7 @@ def _getscrollbacksize(self) -> int: return info.srWindow.Bottom # type: ignore[no-any-return] - def _read_input(self, block: bool = True) -> INPUT_RECORD | None: - if not block and not self.wait(timeout=0): - return None - + def _read_input(self) -> INPUT_RECORD | None: rec = INPUT_RECORD() read = DWORD() if not ReadConsoleInput(InHandle, rec, 1, read): @@ -431,14 +428,10 @@ def _read_input(self, block: bool = True) -> INPUT_RECORD | None: return rec def _read_input_bulk( - self, block: bool, n: int + self, n: int ) -> tuple[ctypes.Array[INPUT_RECORD], int]: rec = (n * INPUT_RECORD)() read = DWORD() - - if not block and not self.wait(timeout=0): - return rec, 0 - if not ReadConsoleInput(InHandle, rec, n, read): raise WinError(GetLastError()) @@ -449,8 +442,11 @@ def get_event(self, block: bool = True) -> Event | None: and there is no event pending, otherwise waits for the completion of an event.""" + if not block and not self.wait(timeout=0): + return None + while self.event_queue.empty(): - rec = self._read_input(block) + rec = self._read_input() if rec is None: return None @@ -551,7 +547,7 @@ def getpending(self) -> Event: if e2: e.data += e2.data - recs, rec_count = self._read_input_bulk(False, 1024) + recs, rec_count = self._read_input_bulk(1024) for i in range(rec_count): rec = recs[i] # In case of a legacy console, we do not only receive a keydown From 7f02127dd45ca416be18a3e2f1e7f519a3c76334 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Fri, 9 May 2025 08:55:07 +0200 Subject: [PATCH 3/4] remove console.wait(100) in perform_bracketed_paste --- Lib/_pyrepl/commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 0d644a0e269e18..50c824995d85b8 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -491,7 +491,6 @@ def do(self) -> None: data = "" start = time.time() while done not in data: - self.reader.console.wait(100) ev = self.reader.console.getpending() data += ev.data trace( From 7c06902fbd99350906be6bda7729d36723580da0 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Fri, 9 May 2025 09:10:55 +0200 Subject: [PATCH 4/4] blurb it --- .../next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst b/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst new file mode 100644 index 00000000000000..00b556c6a336ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst @@ -0,0 +1,2 @@ +Speedup pasting in ``PyREPL`` on Windows in a legacy console. Patch by Chris +Eibl.