Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases
This commit is contained in:
parent
a08e7e1c5d
commit
4c7c3c58be
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
95
Lib/pdb.py
95
Lib/pdb.py
|
@ -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']
|
||||||
|
|
|
@ -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
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue