summaryrefslogtreecommitdiffstats
path: root/Lib/pdb.py
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2012-03-10 21:36:48 (GMT)
committerGeorg Brandl <georg@python.org>2012-03-10 21:36:48 (GMT)
commit4c7c3c58be26e60dfe826da63bd4cac513cfdf83 (patch)
tree72dd53ae277f4a8a64de23139e088b832111fdba /Lib/pdb.py
parenta08e7e1c5d280d87b77b2b49b785a5eadcca93c1 (diff)
downloadcpython-4c7c3c58be26e60dfe826da63bd4cac513cfdf83.zip
cpython-4c7c3c58be26e60dfe826da63bd4cac513cfdf83.tar.gz
cpython-4c7c3c58be26e60dfe826da63bd4cac513cfdf83.tar.bz2
Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases
Diffstat (limited to 'Lib/pdb.py')
-rwxr-xr-xLib/pdb.py95
1 files changed, 95 insertions, 0 deletions
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 6776a3f..3043391 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -73,6 +73,7 @@ import cmd
import bdb
import dis
import code
+import glob
import pprint
import signal
import inspect
@@ -155,6 +156,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Try to load readline if it exists
try:
import readline
+ # remove some common file name delimiters
+ readline.set_completer_delims(' \t\n`@#$%^&*()=+[{]}\\|;:\'",<>?')
except ImportError:
pass
self.allow_kbdint = False
@@ -445,6 +448,61 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def error(self, msg):
print('***', msg, file=self.stdout)
+ # Generic completion functions. Individual complete_foo methods can be
+ # assigned below to one of these functions.
+
+ def _complete_location(self, text, line, begidx, endidx):
+ # Complete a file/module/function location for break/tbreak/clear.
+ if line.strip().endswith((':', ',')):
+ # Here comes a line number or a condition which we can't complete.
+ return []
+ # First, try to find matching functions (i.e. expressions).
+ try:
+ ret = self._complete_expression(text, line, begidx, endidx)
+ except Exception:
+ ret = []
+ # Then, try to complete file names as well.
+ globs = glob.glob(text + '*')
+ for fn in globs:
+ if os.path.isdir(fn):
+ ret.append(fn + '/')
+ elif os.path.isfile(fn) and fn.lower().endswith(('.py', '.pyw')):
+ ret.append(fn + ':')
+ return ret
+
+ def _complete_bpnumber(self, text, line, begidx, endidx):
+ # Complete a breakpoint number. (This would be more helpful if we could
+ # display additional info along with the completions, such as file/line
+ # of the breakpoint.)
+ return [str(i) for i, bp in enumerate(bdb.Breakpoint.bpbynumber)
+ if bp is not None and str(i).startswith(text)]
+
+ def _complete_expression(self, text, line, begidx, endidx):
+ # Complete an arbitrary expression.
+ if not self.curframe:
+ return []
+ # Collect globals and locals. It is usually not really sensible to also
+ # complete builtins, and they clutter the namespace quite heavily, so we
+ # leave them out.
+ ns = self.curframe.f_globals.copy()
+ ns.update(self.curframe_locals)
+ if '.' in text:
+ # Walk an attribute chain up to the last part, similar to what
+ # rlcompleter does. This will bail if any of the parts are not
+ # simple attribute access, which is what we want.
+ dotted = text.split('.')
+ try:
+ obj = ns[dotted[0]]
+ for part in dotted[1:-1]:
+ obj = getattr(obj, part)
+ except (KeyError, AttributeError):
+ return []
+ prefix = '.'.join(dotted[:-1]) + '.'
+ return [prefix + n for n in dir(obj) if n.startswith(dotted[-1])]
+ else:
+ # Complete a simple name.
+ return [n for n in ns.keys() if n.startswith(text)]
+
# Command definitions, called by cmdloop()
# The argument is the remaining string on the command line
# Return true to exit from the command loop
@@ -526,6 +584,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.commands_defining = False
self.prompt = prompt_back
+ complete_commands = _complete_bpnumber
+
def do_break(self, arg, temporary = 0):
"""b(reak) [ ([filename:]lineno | function) [, condition] ]
Without argument, list all breaks.
@@ -628,6 +688,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
do_b = do_break
+ complete_break = _complete_location
+ complete_b = _complete_location
+
def do_tbreak(self, arg):
"""tbreak [ ([filename:]lineno | function) [, condition] ]
Same arguments as break, but sets a temporary breakpoint: it
@@ -635,6 +698,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
"""
self.do_break(arg, 1)
+ complete_tbreak = _complete_location
+
def lineinfo(self, identifier):
failed = (None, None, None)
# Input is identifier, may be in single quotes
@@ -704,6 +769,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
bp.enable()
self.message('Enabled %s' % bp)
+ complete_enable = _complete_bpnumber
+
def do_disable(self, arg):
"""disable bpnumber [bpnumber ...]
Disables the breakpoints given as a space separated list of
@@ -722,6 +789,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
bp.disable()
self.message('Disabled %s' % bp)
+ complete_disable = _complete_bpnumber
+
def do_condition(self, arg):
"""condition bpnumber [condition]
Set a new condition for the breakpoint, an expression which
@@ -745,6 +814,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
else:
self.message('New condition set for breakpoint %d.' % bp.number)
+ complete_condition = _complete_bpnumber
+
def do_ignore(self, arg):
"""ignore bpnumber [count]
Set the ignore count for the given breakpoint number. If
@@ -776,6 +847,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message('Will stop next time breakpoint %d is reached.'
% bp.number)
+ complete_ignore = _complete_bpnumber
+
def do_clear(self, arg):
"""cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
With a space separated list of breakpoint numbers, clear
@@ -824,6 +897,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message('Deleted %s' % bp)
do_cl = do_clear # 'c' is already an abbreviation for 'continue'
+ complete_clear = _complete_location
+ complete_cl = _complete_location
+
def do_where(self, arg):
"""w(here)
Print a stack trace, with the most recent frame at the bottom.
@@ -1007,6 +1083,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
sys.settrace(self.trace_dispatch)
self.lastcmd = p.lastcmd
+ complete_debug = _complete_expression
+
def do_quit(self, arg):
"""q(uit)\nexit
Quit from the debugger. The program being executed is aborted.
@@ -1093,6 +1171,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except:
pass
+ complete_print = _complete_expression
+ complete_p = _complete_expression
+ complete_pp = _complete_expression
+
def do_list(self, arg):
"""l(ist) [first [,last] | .]
@@ -1173,6 +1255,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return
self._print_lines(lines, lineno)
+ complete_source = _complete_expression
+
def _print_lines(self, lines, start, breaks=(), frame=None):
"""Print a range of lines."""
if frame:
@@ -1227,6 +1311,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# None of the above...
self.message(type(value))
+ complete_whatis = _complete_expression
+
def do_display(self, arg):
"""display [expression]
@@ -1244,6 +1330,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.displaying.setdefault(self.curframe, {})[arg] = val
self.message('display %s: %r' % (arg, val))
+ complete_display = _complete_expression
+
def do_undisplay(self, arg):
"""undisplay [expression]
@@ -1259,6 +1347,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
else:
self.displaying.pop(self.curframe, None)
+ def complete_undisplay(self, text, line, begidx, endidx):
+ return [e for e in self.displaying.get(self.curframe, {})
+ if e.startswith(text)]
+
def do_interact(self, arg):
"""interact
@@ -1313,6 +1405,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if args[0] in self.aliases:
del self.aliases[args[0]]
+ def complete_unalias(self, text, line, begidx, endidx):
+ return [a for a in self.aliases if a.startswith(text)]
+
# List of all the commands making the program resume execution.
commands_resuming = ['do_continue', 'do_step', 'do_next', 'do_return',
'do_quit', 'do_jump']