From 0b9b626de46ae97127d078c48465db8e78a1c6cd Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Mon, 30 Dec 2024 18:34:51 +0100 Subject: [PATCH] debugger: leave output screen with single key press Introduce support code to get single key presses from a console. Support Windows (msvcrt) and POSIX supporting Unix platforms (select, termios). Fall back to Python's input() routine for Webassembly and Emscripten. Avoid the accumulation of repeated prompts on platforms that support single key presses. Only the backwards compatible fallback on minor platforms involves a prompt, and users can tell when they face that situation. This change increases usability, a single key press leaves the output screen after it was entered by pressing 'o'. Does not require a config item, platform detection is automatic. The approach uses Python core libraries, no external dependencies are involved. The implementation is considered maintainable, lends itself to future extension for more platforms as the need gets identified. --- .pylintrc-local.yml | 1 + pudb/debugger.py | 11 +++++-- pudb/lowlevel.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/.pylintrc-local.yml b/.pylintrc-local.yml index 2b1336aa..a8738862 100644 --- a/.pylintrc-local.yml +++ b/.pylintrc-local.yml @@ -4,3 +4,4 @@ - IPython - py.test - pytest + - msvcrt diff --git a/pudb/debugger.py b/pudb/debugger.py index 8a9c07ad..50e45884 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -35,7 +35,7 @@ import urwid -from pudb.lowlevel import decode_lines, ui_log +from pudb.lowlevel import ConsoleSingleKeyReader, decode_lines, ui_log from pudb.settings import get_save_config_path, load_config, save_config @@ -769,10 +769,15 @@ def __init__(self, screen): def __enter__(self): self.screen.stop() + return self def __exit__(self, exc_type, exc_value, exc_traceback): self.screen.start() + def press_key_to_return(self): + with ConsoleSingleKeyReader() as key_reader: + key_reader.get_single_key() + class DebuggerUI(FrameVarInfoKeeper): # {{{ constructor @@ -2082,8 +2087,8 @@ def shrink_sidebar(w, size, key): # {{{ top-level listeners def show_output(w, size, key): - with StoppedScreen(self.screen): - input("Hit Enter to return:") + with StoppedScreen(self.screen) as s: + s.press_key_to_return() def reload_breakpoints_and_redisplay(): reload_breakpoints() diff --git a/pudb/lowlevel.py b/pudb/lowlevel.py index bc37d111..2477b9c6 100644 --- a/pudb/lowlevel.py +++ b/pudb/lowlevel.py @@ -1,6 +1,7 @@ __copyright__ = """ Copyright (C) 2009-2017 Andreas Kloeckner Copyright (C) 2014-2017 Aaron Meurer +Copyright (C) 2024 Gerhard Sittig """ __license__ = """ @@ -27,6 +28,7 @@ import logging import sys from datetime import datetime +from enum import Enum, auto logfile = [None] @@ -283,4 +285,81 @@ def decode_lines(lines): # }}} + +# {{{ get single key press from console outside of curses + +class KeyReadImpl(Enum): + INPUT = auto() + GETCH = auto() + SELECT = auto() + + +_keyread_impl = KeyReadImpl.INPUT +if sys.platform in ("emscripten", "wasi"): + pass +elif sys.platform in ("win32",): + _keyread_impl = KeyReadImpl.GETCH +else: + _keyread_impl = KeyReadImpl.SELECT + + +class ConsoleSingleKeyReader: + """ + Get a single key press from a terminal without a prompt. + + Eliminates the necessity to press ENTER before other input also + becomes available. Avoids the accumulation of prompts on the screen + as was the case with Python's input() call. Is used in situations + where urwid is disabled and curses calls are not available. + + Supports major desktop platforms with special cases (msvcrt getch(), + termios and select). Transparently falls back to Python's input() + method. Call sites remain simple and straight forward. + """ + + def __enter__(self): + if _keyread_impl == KeyReadImpl.SELECT: + import termios + import tty + self.prev_settings = termios.tcgetattr(sys.stdin) + tty.setcbreak(sys.stdin.fileno()) + return self + + def __exit__(self, type, value, traceback): + if _keyread_impl == KeyReadImpl.SELECT: + import termios + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.prev_settings) + + def get_single_key(self): + if _keyread_impl == KeyReadImpl.GETCH: + import msvcrt + # https://docs.python.org/3/library/msvcrt.html#msvcrt.getch + # Most keys are returned in the first getch() call. Some + # special keys (function keys, cursor, keypad) require + # another call when the first returned '\0' or '\xe0'. + c = msvcrt.getch() + if c in ("\x00", "\xe0"): + c = msvcrt.getch() + return c + + elif _keyread_impl == KeyReadImpl.SELECT: + import select + rset, _, _ = select.select([sys.stdin], [], [], None) + assert sys.stdin in rset + return sys.stdin.read(1) + + # Strictly speaking putting the fallback here which requires + # pressing ENTER is not correct, this is the "non buffered" + # console support code. But it simplifies call sites. And is + # easy to tell by users because a prompt is provided. This is + # the most portable approach, and backwards compatible with + # earlier PuDB releases. It's a most appropriate default for + # otherwise unsupported platforms. Or when users choose to + # not accept single key presses, or keys other than ENTER. + else: + input("Hit Enter to return:") + return None + +# }}} + # vim: foldmethod=marker