-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
GH-145378: Use PyREPL as the default input console for pdb #145379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
c23190e
14ea66d
7b01466
ce2fc9a
4cc5801
f817371
5c3edfe
9a783dc
b04b192
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -352,6 +352,93 @@ def get_default_backend(): | |||||
| return _default_backend | ||||||
|
|
||||||
|
|
||||||
| def _pyrepl_available(): | ||||||
| """return whether pdb should use _pyrepl for input""" | ||||||
| if not os.getenv("PYTHON_BASIC_REPL"): | ||||||
| from _pyrepl.main import CAN_USE_PYREPL | ||||||
|
|
||||||
| return CAN_USE_PYREPL | ||||||
| return False | ||||||
|
|
||||||
|
|
||||||
| class PdbPyReplInput: | ||||||
| def __init__(self, pdb_instance, stdin, stdout, prompt): | ||||||
| import _pyrepl.readline | ||||||
|
|
||||||
| self.pdb_instance = pdb_instance | ||||||
| self.prompt = prompt | ||||||
| self.console = code.InteractiveConsole() | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this should be something like this: class _PdbInteractiveConsole(code.InteractiveConsole):
def __init__(self, ns=None, message=None):
self._message = message
super().__init__(locals=ns, local_exit=True)
def write(self, data):
if self._message is not None:
self._message(data, end='')
else:
super().write(data)
def _more_lines(self, text):
# Generic Python multi-line completeness heuristic.
# Strips pyrepl's trailing auto-indent before compiling.
src = text.rstrip(" \t")
n = len(src)
if n > 0 and text[n-1] == '\n':
text = src
try:
code_obj = self.compile(text, "<stdin>", "single")
except (OverflowError, SyntaxError, ValueError):
lines = text.splitlines(keepends=True)
if len(lines) == 1:
return False
last = lines[-1]
return ((last.startswith((" ", "\t")) or last.strip() != "")
and not last.endswith("\n"))
return code_obj is None
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if not (os.isatty(stdin.fileno())): | ||||||
| raise ValueError("stdin is not a TTY") | ||||||
| self.readline_wrapper = _pyrepl.readline._ReadlineWrapper( | ||||||
| f_in=stdin.fileno(), | ||||||
| f_out=stdout.fileno(), | ||||||
| config=_pyrepl.readline.ReadlineConfig( | ||||||
| completer_delims=frozenset(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?') | ||||||
| ) | ||||||
| ) | ||||||
|
|
||||||
| def readline(self): | ||||||
| from _pyrepl.simple_interact import _more_lines | ||||||
|
gaogaotiantian marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| def more_lines(text): | ||||||
|
gaogaotiantian marked this conversation as resolved.
|
||||||
| if text.strip() == "\x1a": | ||||||
| # Ctrl + Z raises EOFError to quit pdb | ||||||
| # This is similarly handled in simple_interact.py | ||||||
| raise EOFError | ||||||
| cmd, _, line = self.pdb_instance.parseline(text) | ||||||
| if not line or not cmd: | ||||||
| return False | ||||||
| func = getattr(self.pdb_instance, 'do_' + cmd, None) | ||||||
| if func is not None: | ||||||
| return False | ||||||
| return _more_lines(self.console, text) | ||||||
|
gaogaotiantian marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| try: | ||||||
| pyrepl_completer = self.readline_wrapper.get_completer() | ||||||
| self.readline_wrapper.set_completer(self.complete) | ||||||
| return ( | ||||||
| self.readline_wrapper.multiline_input( | ||||||
| more_lines, | ||||||
| self.prompt, | ||||||
| '... ' + ' ' * (len(self.prompt) - 4) | ||||||
| ) + '\n' | ||||||
| ) | ||||||
| except EOFError: | ||||||
| return 'EOF' | ||||||
| finally: | ||||||
| self.readline_wrapper.set_completer(pyrepl_completer) | ||||||
|
|
||||||
| def complete(self, text, state): | ||||||
| """ | ||||||
| This function is very similar to cmd.Cmd.complete. | ||||||
| However, cmd.Cmd.complete assumes that we use readline module, but | ||||||
| pyrepl does not use it. | ||||||
| """ | ||||||
| if state == 0: | ||||||
| origline = self.readline_wrapper.get_line_buffer() | ||||||
| line = origline.lstrip() | ||||||
| stripped = len(origline) - len(line) | ||||||
| begidx = self.readline_wrapper.get_begidx() - stripped | ||||||
| endidx = self.readline_wrapper.get_endidx() - stripped | ||||||
| if begidx>0: | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| cmd, args, foo = self.pdb_instance.parseline(line) | ||||||
| if not cmd: | ||||||
| compfunc = self.pdb_instance.completedefault | ||||||
| else: | ||||||
| try: | ||||||
| compfunc = getattr(self.pdb_instance, 'complete_' + cmd) | ||||||
| except AttributeError: | ||||||
| compfunc = self.pdb_instance.completedefault | ||||||
| else: | ||||||
| compfunc = self.pdb_instance.completenames | ||||||
| self.completion_matches = compfunc(text, line, begidx, endidx) | ||||||
| try: | ||||||
| return self.completion_matches[state] | ||||||
| except IndexError: | ||||||
| return None | ||||||
|
|
||||||
|
|
||||||
| class Pdb(bdb.Bdb, cmd.Cmd): | ||||||
| _previous_sigint_handler = None | ||||||
|
|
||||||
|
|
@@ -386,6 +473,12 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, | |||||
| except ImportError: | ||||||
| pass | ||||||
|
|
||||||
| self.pyrepl_input = None | ||||||
| if _pyrepl_available(): | ||||||
| try: | ||||||
| self.pyrepl_input = PdbPyReplInput(self, self.stdin, self.stdout, self.prompt) | ||||||
| except Exception: | ||||||
| pass | ||||||
| self.allow_kbdint = False | ||||||
| self.nosigint = nosigint | ||||||
| # Consider these characters as part of the command so when the users type | ||||||
|
|
@@ -624,14 +717,40 @@ def user_exception(self, frame, exc_info): | |||||
| self.message('%s%s' % (prefix, self._format_exc(exc_value))) | ||||||
| self.interaction(frame, exc_traceback) | ||||||
|
|
||||||
| @contextmanager | ||||||
| def _replace_attribute(self, attrs): | ||||||
| original_attrs = {} | ||||||
| for attr, value in attrs.items(): | ||||||
| original_attrs[attr] = getattr(self, attr) | ||||||
| setattr(self, attr, value) | ||||||
| try: | ||||||
| yield | ||||||
| finally: | ||||||
| for attr, value in original_attrs.items(): | ||||||
| setattr(self, attr, value) | ||||||
|
|
||||||
| @contextmanager | ||||||
| def _maybe_use_pyrepl_as_stdin(self): | ||||||
| if self.pyrepl_input is None: | ||||||
| yield | ||||||
| return | ||||||
|
|
||||||
| with self._replace_attribute({ | ||||||
| 'stdin': self.pyrepl_input, | ||||||
| 'use_rawinput': False, | ||||||
| 'prompt': '', | ||||||
| }): | ||||||
| yield | ||||||
|
|
||||||
| # General interaction function | ||||||
| def _cmdloop(self): | ||||||
| while True: | ||||||
| try: | ||||||
| # keyboard interrupts allow for an easy way to cancel | ||||||
| # the current command, so allow them during interactive input | ||||||
| self.allow_kbdint = True | ||||||
| self.cmdloop() | ||||||
| with self._maybe_use_pyrepl_as_stdin(): | ||||||
| self.cmdloop() | ||||||
| self.allow_kbdint = False | ||||||
| break | ||||||
| except KeyboardInterrupt: | ||||||
|
|
@@ -2364,10 +2483,20 @@ def do_interact(self, arg): | |||||
| contains all the (global and local) names found in the current scope. | ||||||
| """ | ||||||
| ns = {**self.curframe.f_globals, **self.curframe.f_locals} | ||||||
| with self._enable_rlcompleter(ns): | ||||||
| console = _PdbInteractiveConsole(ns, message=self.message) | ||||||
| console.interact(banner="*pdb interact start*", | ||||||
| exitmsg="*exit from pdb interact command*") | ||||||
| console = _PdbInteractiveConsole(ns, message=self.message) | ||||||
| if self.pyrepl_input is not None: | ||||||
| from _pyrepl.simple_interact import run_multiline_interactive_console | ||||||
| self.message("*pdb interact start*") | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please declare as common variables |
||||||
| try: | ||||||
| run_multiline_interactive_console(console) | ||||||
| except SystemExit: | ||||||
| pass | ||||||
| self.message("*exit from pdb interact command*") | ||||||
| else: | ||||||
| with self._enable_rlcompleter(ns): | ||||||
| console = _PdbInteractiveConsole(ns, message=self.message) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not redundant declaration from L2486? |
||||||
| console.interact(banner="*pdb interact start*", | ||||||
| exitmsg="*exit from pdb interact command*") | ||||||
|
|
||||||
| def do_alias(self, arg): | ||||||
| """alias [name [command]] | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Use ``PyREPL`` as the default input console for :mod:`pdb` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.