Skip to content

Commit 4c7c3c5

Browse files
committed
Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases
1 parent a08e7e1 commit 4c7c3c5

4 files changed

Lines changed: 111 additions & 0 deletions

File tree

Doc/library/pdb.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ of the debugger is::
3838
> <string>(1)?()
3939
(Pdb)
4040

41+
.. versionchanged:: 3.3
42+
Tab-completion via the :mod:`readline` module is available for commands and
43+
command arguments, e.g. the current global and local names are offered as
44+
arguments of the ``print`` command.
45+
4146
:file:`pdb.py` can also be invoked as a script to debug other scripts. For
4247
example::
4348

Doc/whatsnew/3.3.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,14 @@ name :mod:`distutils2`.
777777
.. TODO add examples and howto to the packaging docs and link to them
778778
779779
780+
pdb
781+
---
782+
783+
* Tab-completion is now available not only for command names, but also their
784+
arguments. For example, for the ``break`` command, function and file names
785+
are completed. (Contributed by Georg Brandl in :issue:`14210`)
786+
787+
780788
pydoc
781789
-----
782790

Lib/pdb.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import bdb
7474
import dis
7575
import code
76+
import glob
7677
import pprint
7778
import signal
7879
import inspect
@@ -155,6 +156,8 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
155156
# Try to load readline if it exists
156157
try:
157158
import readline
159+
# remove some common file name delimiters
160+
readline.set_completer_delims(' \t\n`@#$%^&*()=+[{]}\\|;:\'",<>?')
158161
except ImportError:
159162
pass
160163
self.allow_kbdint = False
@@ -445,6 +448,61 @@ def message(self, msg):
445448
def error(self, msg):
446449
print('***', msg, file=self.stdout)
447450

451+
# Generic completion functions. Individual complete_foo methods can be
452+
# assigned below to one of these functions.
453+
454+
def _complete_location(self, text, line, begidx, endidx):
455+
# Complete a file/module/function location for break/tbreak/clear.
456+
if line.strip().endswith((':', ',')):
457+
# Here comes a line number or a condition which we can't complete.
458+
return []
459+
# First, try to find matching functions (i.e. expressions).
460+
try:
461+
ret = self._complete_expression(text, line, begidx, endidx)
462+
except Exception:
463+
ret = []
464+
# Then, try to complete file names as well.
465+
globs = glob.glob(text + '*')
466+
for fn in globs:
467+
if os.path.isdir(fn):
468+
ret.append(fn + '/')
469+
elif os.path.isfile(fn) and fn.lower().endswith(('.py', '.pyw')):
470+
ret.append(fn + ':')
471+
return ret
472+
473+
def _complete_bpnumber(self, text, line, begidx, endidx):
474+
# Complete a breakpoint number. (This would be more helpful if we could
475+
# display additional info along with the completions, such as file/line
476+
# of the breakpoint.)
477+
return [str(i) for i, bp in enumerate(bdb.Breakpoint.bpbynumber)
478+
if bp is not None and str(i).startswith(text)]
479+
480+
def _complete_expression(self, text, line, begidx, endidx):
481+
# Complete an arbitrary expression.
482+
if not self.curframe:
483+
return []
484+
# Collect globals and locals. It is usually not really sensible to also
485+
# complete builtins, and they clutter the namespace quite heavily, so we
486+
# leave them out.
487+
ns = self.curframe.f_globals.copy()
488+
ns.update(self.curframe_locals)
489+
if '.' in text:
490+
# Walk an attribute chain up to the last part, similar to what
491+
# rlcompleter does. This will bail if any of the parts are not
492+
# simple attribute access, which is what we want.
493+
dotted = text.split('.')
494+
try:
495+
obj = ns[dotted[0]]
496+
for part in dotted[1:-1]:
497+
obj = getattr(obj, part)
498+
except (KeyError, AttributeError):
499+
return []
500+
prefix = '.'.join(dotted[:-1]) + '.'
501+
return [prefix + n for n in dir(obj) if n.startswith(dotted[-1])]
502+
else:
503+
# Complete a simple name.
504+
return [n for n in ns.keys() if n.startswith(text)]
505+
448506
# Command definitions, called by cmdloop()
449507
# The argument is the remaining string on the command line
450508
# Return true to exit from the command loop
@@ -526,6 +584,8 @@ def do_commands(self, arg):
526584
self.commands_defining = False
527585
self.prompt = prompt_back
528586

587+
complete_commands = _complete_bpnumber
588+
529589
def do_break(self, arg, temporary = 0):
530590
"""b(reak) [ ([filename:]lineno | function) [, condition] ]
531591
Without argument, list all breaks.
@@ -628,13 +688,18 @@ def defaultFile(self):
628688

629689
do_b = do_break
630690

691+
complete_break = _complete_location
692+
complete_b = _complete_location
693+
631694
def do_tbreak(self, arg):
632695
"""tbreak [ ([filename:]lineno | function) [, condition] ]
633696
Same arguments as break, but sets a temporary breakpoint: it
634697
is automatically deleted when first hit.
635698
"""
636699
self.do_break(arg, 1)
637700

701+
complete_tbreak = _complete_location
702+
638703
def lineinfo(self, identifier):
639704
failed = (None, None, None)
640705
# Input is identifier, may be in single quotes
@@ -704,6 +769,8 @@ def do_enable(self, arg):
704769
bp.enable()
705770
self.message('Enabled %s' % bp)
706771

772+
complete_enable = _complete_bpnumber
773+
707774
def do_disable(self, arg):
708775
"""disable bpnumber [bpnumber ...]
709776
Disables the breakpoints given as a space separated list of
@@ -722,6 +789,8 @@ def do_disable(self, arg):
722789
bp.disable()
723790
self.message('Disabled %s' % bp)
724791

792+
complete_disable = _complete_bpnumber
793+
725794
def do_condition(self, arg):
726795
"""condition bpnumber [condition]
727796
Set a new condition for the breakpoint, an expression which
@@ -745,6 +814,8 @@ def do_condition(self, arg):
745814
else:
746815
self.message('New condition set for breakpoint %d.' % bp.number)
747816

817+
complete_condition = _complete_bpnumber
818+
748819
def do_ignore(self, arg):
749820
"""ignore bpnumber [count]
750821
Set the ignore count for the given breakpoint number. If
@@ -776,6 +847,8 @@ def do_ignore(self, arg):
776847
self.message('Will stop next time breakpoint %d is reached.'
777848
% bp.number)
778849

850+
complete_ignore = _complete_bpnumber
851+
779852
def do_clear(self, arg):
780853
"""cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
781854
With a space separated list of breakpoint numbers, clear
@@ -824,6 +897,9 @@ def do_clear(self, arg):
824897
self.message('Deleted %s' % bp)
825898
do_cl = do_clear # 'c' is already an abbreviation for 'continue'
826899

900+
complete_clear = _complete_location
901+
complete_cl = _complete_location
902+
827903
def do_where(self, arg):
828904
"""w(here)
829905
Print a stack trace, with the most recent frame at the bottom.
@@ -1007,6 +1083,8 @@ def do_debug(self, arg):
10071083
sys.settrace(self.trace_dispatch)
10081084
self.lastcmd = p.lastcmd
10091085

1086+
complete_debug = _complete_expression
1087+
10101088
def do_quit(self, arg):
10111089
"""q(uit)\nexit
10121090
Quit from the debugger. The program being executed is aborted.
@@ -1093,6 +1171,10 @@ def do_pp(self, arg):
10931171
except:
10941172
pass
10951173

1174+
complete_print = _complete_expression
1175+
complete_p = _complete_expression
1176+
complete_pp = _complete_expression
1177+
10961178
def do_list(self, arg):
10971179
"""l(ist) [first [,last] | .]
10981180
@@ -1173,6 +1255,8 @@ def do_source(self, arg):
11731255
return
11741256
self._print_lines(lines, lineno)
11751257

1258+
complete_source = _complete_expression
1259+
11761260
def _print_lines(self, lines, start, breaks=(), frame=None):
11771261
"""Print a range of lines."""
11781262
if frame:
@@ -1227,6 +1311,8 @@ def do_whatis(self, arg):
12271311
# None of the above...
12281312
self.message(type(value))
12291313

1314+
complete_whatis = _complete_expression
1315+
12301316
def do_display(self, arg):
12311317
"""display [expression]
12321318
@@ -1244,6 +1330,8 @@ def do_display(self, arg):
12441330
self.displaying.setdefault(self.curframe, {})[arg] = val
12451331
self.message('display %s: %r' % (arg, val))
12461332

1333+
complete_display = _complete_expression
1334+
12471335
def do_undisplay(self, arg):
12481336
"""undisplay [expression]
12491337
@@ -1259,6 +1347,10 @@ def do_undisplay(self, arg):
12591347
else:
12601348
self.displaying.pop(self.curframe, None)
12611349

1350+
def complete_undisplay(self, text, line, begidx, endidx):
1351+
return [e for e in self.displaying.get(self.curframe, {})
1352+
if e.startswith(text)]
1353+
12621354
def do_interact(self, arg):
12631355
"""interact
12641356
@@ -1313,6 +1405,9 @@ def do_unalias(self, arg):
13131405
if args[0] in self.aliases:
13141406
del self.aliases[args[0]]
13151407

1408+
def complete_unalias(self, text, line, begidx, endidx):
1409+
return [a for a in self.aliases if a.startswith(text)]
1410+
13161411
# List of all the commands making the program resume execution.
13171412
commands_resuming = ['do_continue', 'do_step', 'do_next', 'do_return',
13181413
'do_quit', 'do_jump']

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ Library
3737
data or close method) for the Python implementation as well.
3838
Drop the no-op TreeBuilder().xml() method from the C implementation.
3939

40+
- Issue #14210: pdb now has tab-completion not only for command names, but
41+
also for their arguments, wherever possible.
42+
4043
Extension Modules
4144
-----------------
4245

0 commit comments

Comments
 (0)