]> granicus.if.org Git - python/commitdiff
Close #14210: add command argument completion to pdb: complete file names, global...
authorGeorg Brandl <georg@python.org>
Sat, 10 Mar 2012 21:36:48 +0000 (22:36 +0100)
committerGeorg Brandl <georg@python.org>
Sat, 10 Mar 2012 21:36:48 +0000 (22:36 +0100)
Doc/library/pdb.rst
Doc/whatsnew/3.3.rst
Lib/pdb.py
Misc/NEWS

index 1e9de63b69ff4e9ac0be5e70a03fa1f557c66029..f4e37ac2a6b7800517fa7f718235a6e9c54f0081 100644 (file)
@@ -38,6 +38,11 @@ of the debugger is::
    > <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::
 
index bccb40dc57c20f53b1b1b739500817ff6c0c4f8d..26c42b541675417e4f671d8d02b4308488700e88 100644 (file)
@@ -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
 -----
 
index 6776a3f4cf65dd27e2f9793c218bbb680bebebc3..30433916535eb6a812660aad4b7b34f67a905a6d 100755 (executable)
@@ -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']
index 91acc455f18050f677f646edd0b7714c8d8a58d4..29e88994211efdec68cda337da80a94bd00c8d81 100644 (file)
--- 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
 -----------------