]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112948: Make pdb completion similar to repl completion (#112950)
authorTian Gao <gaogaotiantian@hotmail.com>
Mon, 25 Mar 2024 15:18:09 +0000 (08:18 -0700)
committerGitHub <noreply@github.com>
Mon, 25 Mar 2024 15:18:09 +0000 (15:18 +0000)
Lib/pdb.py
Lib/test/test_pdb.py
Misc/NEWS.d/next/Library/2023-12-11-00-51-51.gh-issue-112948.k-OKp5.rst [new file with mode: 0644]

index 88ea900e63f42b6657088feeacb237f0b80f7db5..f8f42ddcdb2b203aae5d3834bf37b0207935d583 100755 (executable)
@@ -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
index 69691e930562bc74e1e61e11fc994d94e2508ef2..9ee994e3fe309d2b220752a8875b66fe9a55a9f1 100644 (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
diff --git a/Misc/NEWS.d/next/Library/2023-12-11-00-51-51.gh-issue-112948.k-OKp5.rst b/Misc/NEWS.d/next/Library/2023-12-11-00-51-51.gh-issue-112948.k-OKp5.rst
new file mode 100644 (file)
index 0000000..0925a7c
--- /dev/null
@@ -0,0 +1 @@
+Make completion of :mod:`pdb` similar to Python REPL