Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases

This commit is contained in:
Georg Brandl 2012-03-10 22:36:48 +01:00
parent a08e7e1c5d
commit 4c7c3c58be
4 changed files with 111 additions and 0 deletions

View File

@ -38,6 +38,11 @@ of the debugger is::
> <string>(1)?() > <string>(1)?()
(Pdb) (Pdb)
.. versionchanged:: 3.3
Tab-completion via the :mod:`readline` module is available for commands and
command arguments, e.g. the current global and local names are offered as
arguments of the ``print`` command.
:file:`pdb.py` can also be invoked as a script to debug other scripts. For :file:`pdb.py` can also be invoked as a script to debug other scripts. For
example:: example::

View File

@ -777,6 +777,14 @@ name :mod:`distutils2`.
.. TODO add examples and howto to the packaging docs and link to them .. TODO add examples and howto to the packaging docs and link to them
pdb
---
* Tab-completion is now available not only for command names, but also their
arguments. For example, for the ``break`` command, function and file names
are completed. (Contributed by Georg Brandl in :issue:`14210`)
pydoc pydoc
----- -----

View File

@ -73,6 +73,7 @@ import cmd
import bdb import bdb
import dis import dis
import code import code
import glob
import pprint import pprint
import signal import signal
import inspect import inspect
@ -155,6 +156,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Try to load readline if it exists # Try to load readline if it exists
try: try:
import readline import readline
# remove some common file name delimiters
readline.set_completer_delims(' \t\n`@#$%^&*()=+[{]}\\|;:\'",<>?')
except ImportError: except ImportError:
pass pass
self.allow_kbdint = False self.allow_kbdint = False
@ -445,6 +448,61 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def error(self, msg): def error(self, msg):
print('***', msg, file=self.stdout) 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() # Command definitions, called by cmdloop()
# The argument is the remaining string on the command line # The argument is the remaining string on the command line
# Return true to exit from the command loop # Return true to exit from the command loop
@ -526,6 +584,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.commands_defining = False self.commands_defining = False
self.prompt = prompt_back self.prompt = prompt_back
complete_commands = _complete_bpnumber
def do_break(self, arg, temporary = 0): def do_break(self, arg, temporary = 0):
"""b(reak) [ ([filename:]lineno | function) [, condition] ] """b(reak) [ ([filename:]lineno | function) [, condition] ]
Without argument, list all breaks. Without argument, list all breaks.
@ -628,6 +688,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
do_b = do_break do_b = do_break
complete_break = _complete_location
complete_b = _complete_location
def do_tbreak(self, arg): def do_tbreak(self, arg):
"""tbreak [ ([filename:]lineno | function) [, condition] ] """tbreak [ ([filename:]lineno | function) [, condition] ]
Same arguments as break, but sets a temporary breakpoint: it 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) self.do_break(arg, 1)
complete_tbreak = _complete_location
def lineinfo(self, identifier): def lineinfo(self, identifier):
failed = (None, None, None) failed = (None, None, None)
# Input is identifier, may be in single quotes # Input is identifier, may be in single quotes
@ -704,6 +769,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
bp.enable() bp.enable()
self.message('Enabled %s' % bp) self.message('Enabled %s' % bp)
complete_enable = _complete_bpnumber
def do_disable(self, arg): def do_disable(self, arg):
"""disable bpnumber [bpnumber ...] """disable bpnumber [bpnumber ...]
Disables the breakpoints given as a space separated list of Disables the breakpoints given as a space separated list of
@ -722,6 +789,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
bp.disable() bp.disable()
self.message('Disabled %s' % bp) self.message('Disabled %s' % bp)
complete_disable = _complete_bpnumber
def do_condition(self, arg): def do_condition(self, arg):
"""condition bpnumber [condition] """condition bpnumber [condition]
Set a new condition for the breakpoint, an expression which Set a new condition for the breakpoint, an expression which
@ -745,6 +814,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
else: else:
self.message('New condition set for breakpoint %d.' % bp.number) self.message('New condition set for breakpoint %d.' % bp.number)
complete_condition = _complete_bpnumber
def do_ignore(self, arg): def do_ignore(self, arg):
"""ignore bpnumber [count] """ignore bpnumber [count]
Set the ignore count for the given breakpoint number. If 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.' self.message('Will stop next time breakpoint %d is reached.'
% bp.number) % bp.number)
complete_ignore = _complete_bpnumber
def do_clear(self, arg): def do_clear(self, arg):
"""cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]] """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
With a space separated list of breakpoint numbers, clear With a space separated list of breakpoint numbers, clear
@ -824,6 +897,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message('Deleted %s' % bp) self.message('Deleted %s' % bp)
do_cl = do_clear # 'c' is already an abbreviation for 'continue' do_cl = do_clear # 'c' is already an abbreviation for 'continue'
complete_clear = _complete_location
complete_cl = _complete_location
def do_where(self, arg): def do_where(self, arg):
"""w(here) """w(here)
Print a stack trace, with the most recent frame at the bottom. 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) sys.settrace(self.trace_dispatch)
self.lastcmd = p.lastcmd self.lastcmd = p.lastcmd
complete_debug = _complete_expression
def do_quit(self, arg): def do_quit(self, arg):
"""q(uit)\nexit """q(uit)\nexit
Quit from the debugger. The program being executed is aborted. Quit from the debugger. The program being executed is aborted.
@ -1093,6 +1171,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except: except:
pass pass
complete_print = _complete_expression
complete_p = _complete_expression
complete_pp = _complete_expression
def do_list(self, arg): def do_list(self, arg):
"""l(ist) [first [,last] | .] """l(ist) [first [,last] | .]
@ -1173,6 +1255,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return return
self._print_lines(lines, lineno) self._print_lines(lines, lineno)
complete_source = _complete_expression
def _print_lines(self, lines, start, breaks=(), frame=None): def _print_lines(self, lines, start, breaks=(), frame=None):
"""Print a range of lines.""" """Print a range of lines."""
if frame: if frame:
@ -1227,6 +1311,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# None of the above... # None of the above...
self.message(type(value)) self.message(type(value))
complete_whatis = _complete_expression
def do_display(self, arg): def do_display(self, arg):
"""display [expression] """display [expression]
@ -1244,6 +1330,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.displaying.setdefault(self.curframe, {})[arg] = val self.displaying.setdefault(self.curframe, {})[arg] = val
self.message('display %s: %r' % (arg, val)) self.message('display %s: %r' % (arg, val))
complete_display = _complete_expression
def do_undisplay(self, arg): def do_undisplay(self, arg):
"""undisplay [expression] """undisplay [expression]
@ -1259,6 +1347,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
else: else:
self.displaying.pop(self.curframe, None) 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): def do_interact(self, arg):
"""interact """interact
@ -1313,6 +1405,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if args[0] in self.aliases: if args[0] in self.aliases:
del self.aliases[args[0]] 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. # List of all the commands making the program resume execution.
commands_resuming = ['do_continue', 'do_step', 'do_next', 'do_return', commands_resuming = ['do_continue', 'do_step', 'do_next', 'do_return',
'do_quit', 'do_jump'] 'do_quit', 'do_jump']

View File

@ -37,6 +37,9 @@ Library
data or close method) for the Python implementation as well. data or close method) for the Python implementation as well.
Drop the no-op TreeBuilder().xml() method from the C implementation. Drop the no-op TreeBuilder().xml() method from the C implementation.
- Issue #14210: pdb now has tab-completion not only for command names, but
also for their arguments, wherever possible.
Extension Modules Extension Modules
----------------- -----------------