gh-112948: Make pdb completion similar to repl completion (#112950)

This commit is contained in:
Tian Gao 2024-03-25 08:18:09 -07:00 committed by GitHub
parent 9db2a8f914
commit 01e7405da4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 80 additions and 15 deletions

View File

@ -87,6 +87,7 @@ import traceback
import linecache
from contextlib import contextmanager
from rlcompleter import Completer
from typing import Union
@ -573,20 +574,14 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message(repr(obj))
@contextmanager
def _disable_tab_completion(self):
if self.use_rawinput and self.completekey == 'tab':
try:
import readline
except ImportError:
yield
return
try:
readline.parse_and_bind('tab: self-insert')
yield
finally:
readline.parse_and_bind('tab: complete')
else:
def _disable_command_completion(self):
completenames = self.completenames
try:
self.completenames = self.completedefault
yield
finally:
self.completenames = completenames
return
def default(self, line):
if line[:1] == '!': line = line[1:].strip()
@ -595,7 +590,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
try:
if (code := codeop.compile_command(line + '\n', '<stdin>', 'single')) is None:
# Multi-line mode
with self._disable_tab_completion():
with self._disable_command_completion():
buffer = line
continue_prompt = "... "
while (code := codeop.compile_command(buffer, '<stdin>', 'single')) is None:
@ -771,7 +766,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if commands:
return commands
else:
return self._complete_expression(text, line, begidx, endidx)
expressions = self._complete_expression(text, line, begidx, endidx)
if expressions:
return expressions
return self.completedefault(text, line, begidx, endidx)
def _complete_location(self, text, line, begidx, endidx):
# Complete a file/module/function location for break/tbreak/clear.
@ -828,6 +826,21 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Complete a simple name.
return [n for n in ns.keys() if n.startswith(text)]
def completedefault(self, text, line, begidx, endidx):
if text.startswith("$"):
# Complete convenience variables
conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {})
return [f"${name}" for name in conv_vars if name.startswith(text[1:])]
# Use rlcompleter to do the completion
state = 0
matches = []
completer = Completer(self.curframe.f_globals | self.curframe_locals)
while (match := completer.complete(text, state)) is not None:
matches.append(match)
state += 1
return matches
# Command definitions, called by cmdloop()
# The argument is the remaining string on the command line
# Return true to exit from the command loop

View File

@ -3567,6 +3567,57 @@ class PdbTestReadline(unittest.TestCase):
self.assertIn(b'species', output)
self.assertIn(b'$_frame', output)
def test_builtin_completion(self):
script = textwrap.dedent("""
value = "speci"
import pdb; pdb.Pdb().set_trace()
""")
# Complete: print(value + 'al')
input = b"pri\tval\t + 'al')\n"
# Continue
input += b"c\n"
output = run_pty(script, input)
self.assertIn(b'special', output)
def test_local_namespace(self):
script = textwrap.dedent("""
def f():
original = "I live Pythin"
import pdb; pdb.Pdb().set_trace()
f()
""")
# Complete: original.replace('i', 'o')
input = b"orig\t.repl\t('i', 'o')\n"
# Continue
input += b"c\n"
output = run_pty(script, input)
self.assertIn(b'I love Python', output)
def test_multiline_completion(self):
script = textwrap.dedent("""
import pdb; pdb.Pdb().set_trace()
""")
input = b"def func():\n"
# Complete: \treturn 40 + 2
input += b"\tret\t 40 + 2\n"
input += b"\n"
# Complete: func()
input += b"fun\t()\n"
input += b"c\n"
output = run_pty(script, input)
self.assertIn(b'42', output)
def load_tests(loader, tests, pattern):
from test import test_pdb

View File

@ -0,0 +1 @@
Make completion of :mod:`pdb` similar to Python REPL