Skip to content

gh-133153: Use rlcompleter for pdb's interact command #133176

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

Merged
merged 4 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,22 @@ def completedefault(self, text, line, begidx, endidx):
state += 1
return matches

@contextmanager
def _enable_rlcompleter(self, ns):
try:
import readline
except ImportError:
yield
return

try:
old_completer = readline.get_completer()
completer = Completer(ns)
readline.set_completer(completer.complete)
yield
finally:
readline.set_completer(old_completer)

# Pdb meta commands, only intended to be used internally by pdb

def _pdbcmd_print_frame_status(self, arg):
Expand Down Expand Up @@ -2242,9 +2258,10 @@ 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}
console = _PdbInteractiveConsole(ns, message=self.message)
console.interact(banner="*pdb interact start*",
exitmsg="*exit from pdb interact command*")
with self._enable_rlcompleter(ns):
console = _PdbInteractiveConsole(ns, message=self.message)
console.interact(banner="*pdb interact start*",
exitmsg="*exit from pdb interact command*")

def do_alias(self, arg):
"""alias [name [command]]
Expand Down Expand Up @@ -2749,14 +2766,18 @@ def _read_reply(self):
self.error(f"Ignoring invalid message from client: {msg}")

def _complete_any(self, text, line, begidx, endidx):
if begidx == 0:
return self.completenames(text, line, begidx, endidx)

cmd = self.parseline(line)[0]
if cmd:
compfunc = getattr(self, "complete_" + cmd, self.completedefault)
else:
# If we're in 'interact' mode, we need to use the default completer
if self._interact_state:
compfunc = self.completedefault
else:
if begidx == 0:
return self.completenames(text, line, begidx, endidx)

cmd = self.parseline(line)[0]
if cmd:
compfunc = getattr(self, "complete_" + cmd, self.completedefault)
else:
compfunc = self.completedefault
return compfunc(text, line, begidx, endidx)

def cmdloop(self, intro=None):
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4855,6 +4855,34 @@ def func():
self.assertIn(b'4', output)
self.assertNotIn(b'Error', output)

def test_interact_completion(self):
script = textwrap.dedent("""
value = "speci"
import pdb; pdb.Pdb().set_trace()
""")

# Enter interact mode
input = b"interact\n"
# Should fail to complete 'display' because that's a pdb command
input += b"disp\t\n"
# 'value' should still work
input += b"val\t + 'al'\n"
# Let's define a function to test <tab>
input += b"def f():\n"
input += b"\treturn 42\n"
input += b"\n"
input += b"f() * 2\n"
# Exit interact mode
input += b"exit()\n"
# continue
input += b"c\n"

output = run_pty(script, input)

self.assertIn(b"'disp' is not defined", output)
self.assertIn(b'special', output)
self.assertIn(b'84', output)


def load_tests(loader, tests, pattern):
from test import test_pdb
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Do not complete :mod:`pdb` commands in ``interact`` mode of :mod:`pdb`.
Loading