From 4c7c3c58be26e60dfe826da63bd4cac513cfdf83 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 10 Mar 2012 22:36:48 +0100 Subject: Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases --- Doc/library/pdb.rst | 5 +++ Doc/whatsnew/3.3.rst | 8 +++++ Lib/pdb.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 3 ++ 4 files changed, 111 insertions(+) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 1e9de63..f4e37ac 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -38,6 +38,11 @@ of the debugger is:: > (1)?() (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 example:: diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index bccb40d..26c42b5 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -777,6 +777,14 @@ name :mod:`distutils2`. .. 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 ----- 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'] diff --git a/Misc/NEWS b/Misc/NEWS index 91acc45..29e8899 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -37,6 +37,9 @@ Library data or close method) for the Python implementation as well. 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 ----------------- -- cgit v0.12