From 0a38c2ed2952c09cd7fae26eb922fb07f01fefa9 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:02:56 +0200 Subject: [PATCH 1/7] fix WindowsConsole.wait() --- Lib/_pyrepl/windows_console.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 47fd3fd8f8909b..630e8579f3ed8b 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -22,8 +22,6 @@ import io import os import sys -import time -import msvcrt import ctypes from ctypes.wintypes import ( @@ -108,6 +106,9 @@ def __init__(self, err: int | None, descr: str | None = None) -> None: ALT_ACTIVE = 0x01 | 0x02 CTRL_ACTIVE = 0x04 | 0x08 +WAIT_TIMEOUT = 0x102 +WAIT_FAILED = 0xFFFFFFFF + class _error(Exception): pass @@ -410,10 +411,10 @@ def _getscrollbacksize(self) -> int: def _read_input(self, block: bool = True) -> INPUT_RECORD | None: if not block: - events = DWORD() - if not GetNumberOfConsoleInputEvents(InHandle, events): - raise WinError(GetLastError()) - if not events.value: + ret = WaitForSingleObject(InHandle, 0) + if ret == WAIT_FAILED: + raise WinError(ctypes.get_last_error()) + elif ret == WAIT_TIMEOUT: return None rec = INPUT_RECORD() @@ -522,14 +523,9 @@ def getpending(self) -> Event: def wait(self, timeout: float | None) -> bool: """Wait for an event.""" - # Poor man's Windows select loop - start_time = time.time() - while True: - if msvcrt.kbhit(): # type: ignore[attr-defined] - return True - if timeout and time.time() - start_time > timeout / 1000: - return False - time.sleep(0.01) + ret = WaitForSingleObject(InHandle, int(timeout)) + if ret == WAIT_FAILED: + raise WinError(ctypes.get_last_error()) def repaint(self) -> None: raise NotImplementedError("No repaint support") @@ -649,14 +645,15 @@ class INPUT_RECORD(Structure): ReadConsoleInput.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, POINTER(DWORD)] ReadConsoleInput.restype = BOOL - GetNumberOfConsoleInputEvents = _KERNEL32.GetNumberOfConsoleInputEvents - GetNumberOfConsoleInputEvents.argtypes = [HANDLE, POINTER(DWORD)] - GetNumberOfConsoleInputEvents.restype = BOOL FlushConsoleInputBuffer = _KERNEL32.FlushConsoleInputBuffer FlushConsoleInputBuffer.argtypes = [HANDLE] FlushConsoleInputBuffer.restype = BOOL + WaitForSingleObject = _KERNEL32.WaitForSingleObject + WaitForSingleObject.argtypes = [HANDLE, DWORD] + WaitForSingleObject.restype = DWORD + OutHandle = GetStdHandle(STD_OUTPUT_HANDLE) InHandle = GetStdHandle(STD_INPUT_HANDLE) else: @@ -670,7 +667,7 @@ def _win_only(*args, **kwargs): GetConsoleMode = _win_only SetConsoleMode = _win_only ReadConsoleInput = _win_only - GetNumberOfConsoleInputEvents = _win_only FlushConsoleInputBuffer = _win_only + WaitForSingleObject = _win_only OutHandle = 0 InHandle = 0 From 05d26ff9c5159943897cf23beb134f8fd7555ee5 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:11:14 +0200 Subject: [PATCH 2/7] blurb it --- .../next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst b/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst new file mode 100644 index 00000000000000..f53b2bd3512139 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst @@ -0,0 +1 @@ +Speedup pasting in ``PyREPL`` on Windows. Fix by Chris Eibl. From cad6bfb50a84af0b617d99f9ed86cca5d10ea8fc Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:36:02 +0200 Subject: [PATCH 3/7] fix timeout=None --- Lib/_pyrepl/windows_console.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 630e8579f3ed8b..6bd7ae09d18c2e 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -109,6 +109,9 @@ def __init__(self, err: int | None, descr: str | None = None) -> None: WAIT_TIMEOUT = 0x102 WAIT_FAILED = 0xFFFFFFFF +# from winbase.h +INFINITE = 0xFFFFFFFF + class _error(Exception): pass @@ -523,7 +526,11 @@ def getpending(self) -> Event: def wait(self, timeout: float | None) -> bool: """Wait for an event.""" - ret = WaitForSingleObject(InHandle, int(timeout)) + if timeout is None: + timeout = INFINITE + else: + timeout = int(timeout) + ret = WaitForSingleObject(InHandle, timeout) if ret == WAIT_FAILED: raise WinError(ctypes.get_last_error()) From 3d1da666ba77d7de9761eb51fc79b0696ef98718 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:53:48 +0200 Subject: [PATCH 4/7] fix mypy for ctypes.get_last_error --- Lib/_pyrepl/windows_console.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 6bd7ae09d18c2e..284d772ab504d1 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -42,7 +42,7 @@ from .windows_eventqueue import EventQueue try: - from ctypes import GetLastError, WinDLL, windll, WinError # type: ignore[attr-defined] + from ctypes import get_last_error, GetLastError, WinDLL, windll, WinError # type: ignore[attr-defined] except: # Keep MyPy happy off Windows from ctypes import CDLL as WinDLL, cdll as windll @@ -50,6 +50,9 @@ def GetLastError() -> int: return 42 + def get_last_error() -> int: + return 42 + class WinError(OSError): # type: ignore[no-redef] def __init__(self, err: int | None, descr: str | None = None) -> None: self.err = err @@ -416,7 +419,7 @@ def _read_input(self, block: bool = True) -> INPUT_RECORD | None: if not block: ret = WaitForSingleObject(InHandle, 0) if ret == WAIT_FAILED: - raise WinError(ctypes.get_last_error()) + raise WinError(get_last_error()) elif ret == WAIT_TIMEOUT: return None @@ -532,7 +535,7 @@ def wait(self, timeout: float | None) -> bool: timeout = int(timeout) ret = WaitForSingleObject(InHandle, timeout) if ret == WAIT_FAILED: - raise WinError(ctypes.get_last_error()) + raise WinError(get_last_error()) def repaint(self) -> None: raise NotImplementedError("No repaint support") From 4098e3d5de951fbac4b7db95fbf2f333366d1d8c Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:07:20 +0200 Subject: [PATCH 5/7] fix return value of wait() and use it in _read_input() --- Lib/_pyrepl/windows_console.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 284d772ab504d1..17942c8df0731a 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -416,12 +416,8 @@ 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: - ret = WaitForSingleObject(InHandle, 0) - if ret == WAIT_FAILED: - raise WinError(get_last_error()) - elif ret == WAIT_TIMEOUT: - return None + if not block and not self.wait(timeout=0): + return None rec = INPUT_RECORD() read = DWORD() @@ -536,6 +532,9 @@ def wait(self, timeout: float | None) -> bool: ret = WaitForSingleObject(InHandle, timeout) if ret == WAIT_FAILED: raise WinError(get_last_error()) + elif ret == WAIT_TIMEOUT: + return False + return True def repaint(self) -> None: raise NotImplementedError("No repaint support") From bba26c92aaf4286fc5034df1895a390188436eb6 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:54:57 +0200 Subject: [PATCH 6/7] use self.paste_mode instead of self.in_bracketed_paste in class Reader --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 7fc2422dac9c3f..cbb3fb21b3aea3 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -633,7 +633,7 @@ def update_screen(self) -> None: def refresh(self) -> None: """Recalculate and refresh the screen.""" - if self.in_bracketed_paste and self.buffer and not self.buffer[-1] == "\n": + if self.paste_mode and self.buffer and not self.buffer[-1] == "\n": return # this call sets up self.cxy, so call it first. From 3c0cae902e6814ad1cdcd29aead0543442fc0cca Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:55:10 +0200 Subject: [PATCH 7/7] Revert "use self.paste_mode instead of self.in_bracketed_paste" This reverts commit bba26c92aaf4286fc5034df1895a390188436eb6. --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index cbb3fb21b3aea3..7fc2422dac9c3f 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -633,7 +633,7 @@ def update_screen(self) -> None: def refresh(self) -> None: """Recalculate and refresh the screen.""" - if self.paste_mode and self.buffer and not self.buffer[-1] == "\n": + if self.in_bracketed_paste and self.buffer and not self.buffer[-1] == "\n": return # this call sets up self.cxy, so call it first.