import linecache
from contextlib import contextmanager
+from rlcompleter import Completer
from typing import Union
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()
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:
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.
# 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
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