Skip to content

Commit

Permalink
WIP debugger: add 'show_output' option for single key press on output…
Browse files Browse the repository at this point in the history
… screen

Introduce the 'show_output' config option, pass its value from the
debugger's .show_output() method to the StoppedScreen class. Default
to the 'python_input' selection for backwards compatibility. Support
'single_key' as an alternative, which accepts additional optional
keywords: 'silent' to not prompt for key presses, and several sets
of terminating key presses like 'o_space_enter', 'enter_only', or
'any_key' to reduce behavioural changes from to earlier versions.

  show_output = python_input
  show_output = single_key
  show_output = single_key silent o_space_enter

In the absence of terminating key press specs, any key press leaves
the output screen. The implementation uses Python select(3) which may
not work with stdin on Windows. The StoppedScreen implementation lends
itself to more keywords and other ways of getting user input if desired.

TODO
- Config file handling is incomplete. Implements the keyword and its
  default value, but lacks documentation and UI controls.
- Move NonBlockingConsole to a better location, common support or
  platform dependent?
- Drop diagnostics.
- Is it confusing to have too many configuration options? Just have
  "single key, any key" as one boolean choice while the default is
  the input(3) invocation that is portable to all platforms?
- Maybe adjust Python style where required.
  • Loading branch information
gsigh committed Jan 6, 2025
1 parent f0b2aa5 commit df36b29
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 3 deletions.
70 changes: 67 additions & 3 deletions pudb/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
import bdb
import gc
import os
import select
import sys
import termios
import tty
from collections import deque
from functools import partial
from itertools import count
Expand Down Expand Up @@ -762,17 +765,78 @@ def get_lines(self, debugger_ui):

# }}}

class NonBlockingConsole(object):

def __init__(self, timeout = 0):
self.timeout = timeout

def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self

def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)

def get_data(self):
rset, wset, eset = select.select([ sys.stdin, ], [], [], self.timeout)
if sys.stdin in rset:
return sys.stdin.read(1)
return None

class StoppedScreen:
def __init__(self, screen):
def __init__(self, screen, show_output = None):
# print("config option: {}".format(show_output), flush = True, file = sys.stderr)
self.screen = screen
self.show_output = show_output
if "single_key" in show_output:
show_output = self.show_output.replace("single_key", "")
self.with_prompt = "silent" not in show_output
show_output = show_output.replace("silent", "")
show_output = show_output.strip()
# TODO Alternatively pick key names from remaining bare words?
# Could be perceived as more intuitive by users.
self.term_keys = {
"o_space_enter": "o \n",
"any_key": None,
"enter_only": "\n",
}.get(show_output, None)
else:
self.show_output = "python_input"

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):
if "single_key" in self.show_output:
# Locally modified behaviour.
# BEWARE! The urwid screen is stopped when this method
# executes. Curses calls are not available, neither are
# urwid widgets nor key event handlers. This is why the
# default activity is to invoke Python's input(3).
# That's why something non-portable is done, fiddling
# with terminal settings. Which get restored afterwards.
if self.with_prompt:
print("Hit a key to return ", end = "", flush = True)
with NonBlockingConsole(timeout = None) as nbc:
while True:
key = nbc.get_data()
if not self.term_keys:
break
if key in self.term_keys:
break
if self.with_prompt:
print("")
return
# The default behaviour, requires pressing ENTER.
# Also the fall through for unmigrated configurations.
if "python_input" in self.show_output or not self.show_output:
input("Hit Enter to return:")
return

class DebuggerUI(FrameVarInfoKeeper):
# {{{ constructor
Expand Down Expand Up @@ -2082,8 +2146,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, CONFIG["show_output"]) as s:
s.press_key_to_return()

def reload_breakpoints_and_redisplay():
reload_breakpoints()
Expand Down
5 changes: 5 additions & 0 deletions pudb/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def load_config():
conf_dict.setdefault("display", "auto")

conf_dict.setdefault("prompt_on_quit", "True")
conf_dict.setdefault("show_output", "python_input")

conf_dict.setdefault("hide_cmdline_win", "False")

Expand Down Expand Up @@ -220,6 +221,8 @@ def _update_config(check_box, new_state, option_newvalue):
conf_dict.update(new_conf_dict)
_update_prompt_on_quit()

# XXX need to handle "show_output" here?

elif option == "hide_cmdline_win":
new_conf_dict["hide_cmdline_win"] = not check_box.get_state()
conf_dict.update(new_conf_dict)
Expand Down Expand Up @@ -267,6 +270,8 @@ def _update_config(check_box, new_state, option_newvalue):
bool(conf_dict["prompt_on_quit"]), on_state_change=_update_config,
user_data=("prompt_on_quit", None))

# XXX need to handle "show_output" here?

hide_cmdline_win = urwid.CheckBox("Hide command line"
f"({conf_dict['hotkeys_toggle_cmdline_focus']}) window "
"when not in use",
Expand Down

0 comments on commit df36b29

Please sign in to comment.