Skip to content

Commit 01e7405

Browse files
gh-112948: Make pdb completion similar to repl completion (#112950)
1 parent 9db2a8f commit 01e7405

File tree

3 files changed

+80
-15
lines changed

3 files changed

+80
-15
lines changed

Lib/pdb.py

+28-15
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import linecache
8888

8989
from contextlib import contextmanager
90+
from rlcompleter import Completer
9091
from typing import Union
9192

9293

@@ -573,20 +574,14 @@ def displayhook(self, obj):
573574
self.message(repr(obj))
574575

575576
@contextmanager
576-
def _disable_tab_completion(self):
577-
if self.use_rawinput and self.completekey == 'tab':
578-
try:
579-
import readline
580-
except ImportError:
581-
yield
582-
return
583-
try:
584-
readline.parse_and_bind('tab: self-insert')
585-
yield
586-
finally:
587-
readline.parse_and_bind('tab: complete')
588-
else:
577+
def _disable_command_completion(self):
578+
completenames = self.completenames
579+
try:
580+
self.completenames = self.completedefault
589581
yield
582+
finally:
583+
self.completenames = completenames
584+
return
590585

591586
def default(self, line):
592587
if line[:1] == '!': line = line[1:].strip()
@@ -595,7 +590,7 @@ def default(self, line):
595590
try:
596591
if (code := codeop.compile_command(line + '\n', '<stdin>', 'single')) is None:
597592
# Multi-line mode
598-
with self._disable_tab_completion():
593+
with self._disable_command_completion():
599594
buffer = line
600595
continue_prompt = "... "
601596
while (code := codeop.compile_command(buffer, '<stdin>', 'single')) is None:
@@ -771,7 +766,10 @@ def completenames(self, text, line, begidx, endidx):
771766
if commands:
772767
return commands
773768
else:
774-
return self._complete_expression(text, line, begidx, endidx)
769+
expressions = self._complete_expression(text, line, begidx, endidx)
770+
if expressions:
771+
return expressions
772+
return self.completedefault(text, line, begidx, endidx)
775773

776774
def _complete_location(self, text, line, begidx, endidx):
777775
# Complete a file/module/function location for break/tbreak/clear.
@@ -828,6 +826,21 @@ def _complete_expression(self, text, line, begidx, endidx):
828826
# Complete a simple name.
829827
return [n for n in ns.keys() if n.startswith(text)]
830828

829+
def completedefault(self, text, line, begidx, endidx):
830+
if text.startswith("$"):
831+
# Complete convenience variables
832+
conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {})
833+
return [f"${name}" for name in conv_vars if name.startswith(text[1:])]
834+
835+
# Use rlcompleter to do the completion
836+
state = 0
837+
matches = []
838+
completer = Completer(self.curframe.f_globals | self.curframe_locals)
839+
while (match := completer.complete(text, state)) is not None:
840+
matches.append(match)
841+
state += 1
842+
return matches
843+
831844
# Command definitions, called by cmdloop()
832845
# The argument is the remaining string on the command line
833846
# Return true to exit from the command loop

Lib/test/test_pdb.py

+51
Original file line numberDiff line numberDiff line change
@@ -3567,6 +3567,57 @@ def test_expression_completion(self):
35673567
self.assertIn(b'species', output)
35683568
self.assertIn(b'$_frame', output)
35693569

3570+
def test_builtin_completion(self):
3571+
script = textwrap.dedent("""
3572+
value = "speci"
3573+
import pdb; pdb.Pdb().set_trace()
3574+
""")
3575+
3576+
# Complete: print(value + 'al')
3577+
input = b"pri\tval\t + 'al')\n"
3578+
3579+
# Continue
3580+
input += b"c\n"
3581+
3582+
output = run_pty(script, input)
3583+
3584+
self.assertIn(b'special', output)
3585+
3586+
def test_local_namespace(self):
3587+
script = textwrap.dedent("""
3588+
def f():
3589+
original = "I live Pythin"
3590+
import pdb; pdb.Pdb().set_trace()
3591+
f()
3592+
""")
3593+
3594+
# Complete: original.replace('i', 'o')
3595+
input = b"orig\t.repl\t('i', 'o')\n"
3596+
3597+
# Continue
3598+
input += b"c\n"
3599+
3600+
output = run_pty(script, input)
3601+
3602+
self.assertIn(b'I love Python', output)
3603+
3604+
def test_multiline_completion(self):
3605+
script = textwrap.dedent("""
3606+
import pdb; pdb.Pdb().set_trace()
3607+
""")
3608+
3609+
input = b"def func():\n"
3610+
# Complete: \treturn 40 + 2
3611+
input += b"\tret\t 40 + 2\n"
3612+
input += b"\n"
3613+
# Complete: func()
3614+
input += b"fun\t()\n"
3615+
input += b"c\n"
3616+
3617+
output = run_pty(script, input)
3618+
3619+
self.assertIn(b'42', output)
3620+
35703621

35713622
def load_tests(loader, tests, pattern):
35723623
from test import test_pdb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make completion of :mod:`pdb` similar to Python REPL

0 commit comments

Comments
 (0)