[Python-checkins] cpython: Close #14210: add command argument completion to pdb: complete file names,

georg.brandl python-checkins at python.org
Sat Mar 10 22:36:53 CET 2012


http://hg.python.org/cpython/rev/01a25f638c83
changeset:   75521:01a25f638c83
user:        Georg Brandl <georg at python.org>
date:        Sat Mar 10 22:36:48 2012 +0100
summary:
  Close #14210: add command argument completion to pdb: complete file names, global/local variables, aliases

files:
  Doc/library/pdb.rst  |   5 +
  Doc/whatsnew/3.3.rst |   8 ++
  Lib/pdb.py           |  95 ++++++++++++++++++++++++++++++++
  Misc/NEWS            |   3 +
  4 files changed, 111 insertions(+), 0 deletions(-)


diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst
--- a/Doc/library/pdb.rst
+++ b/Doc/library/pdb.rst
@@ -38,6 +38,11 @@
    > <string>(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
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -777,6 +777,14 @@
 .. 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
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -73,6 +73,7 @@
 import bdb
 import dis
 import code
+import glob
 import pprint
 import signal
 import inspect
@@ -155,6 +156,8 @@
         # 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 @@
     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 @@
             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 @@
 
     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 @@
         """
         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 @@
                 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 @@
                 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 @@
             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 @@
                 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 @@
                 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 @@
         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 @@
         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 @@
             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 @@
         # None of the above...
         self.message(type(value))
 
+    complete_whatis = _complete_expression
+
     def do_display(self, arg):
         """display [expression]
 
@@ -1244,6 +1330,8 @@
             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 @@
         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 @@
         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
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -37,6 +37,9 @@
   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
 -----------------
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list