Commits

Martin von Löwis committed 6f301b3

Patch #416224: add readline completion to cmd.Cmd.

Comments (0)

Files changed (4)

Doc/lib/libcmd.tex

 test harnesses, administrative tools, and prototypes that will
 later be wrapped in a more sophisticated interface.
 
-\begin{classdesc}{Cmd}{}
+\begin{classdesc}{Cmd}{\optional{completekey}}
 A \class{Cmd} instance or subclass instance is a line-oriented
 interpreter framework.  There is no good reason to instantiate
 \class{Cmd} itself; rather, it's useful as a superclass of an
 interpreter class you define yourself in order to inherit
 \class{Cmd}'s methods and encapsulate action methods.
+
+The optional argument is the \refmodule{readline} name of a completion
+key; it defaults to \code{``tab''}. If \var{completekey} is not
+\code{None} and \module{readline} is available, command completion is
+done automatically.
+
 \end{classdesc}
 
 \subsection{Cmd Objects}
 beginning with the character \character{!} is dispatched to the
 method \method{do_shell} (if such a method is defined).
 
+If completion is enabled, completing commands will be done
+automatically, and completing of commands args is done by calling
+\method{complete_foo()} with arguments \samp{text}, \samp{line},
+\samp{begidx}, \samp{endidx}.  \samp{text} is string we are matching
+against, all returned matches must begin with it.  \samp{line} is the
+current input line (lstripped), \samp{begidx} and \samp{endidx} are
+the beginning and end indexes of the text being matched, which could
+be used to provide different completion depending upon which position
+the argument is in.
+
 All subclasses of \class{Cmd} inherit a predefined \method{do_help}.
 This method, called with an argument \code{bar}, invokes the
 corresponding method \method{help_bar()}.  With no argument,
 error message and returns.
 \end{methoddesc}
 
+\begin{methoddesc}{completedefault}{text, line, begidx, endidx}
+Method called to complete an input line when no command-specific
+\code{complete_} method is available. By default, it returns an
+empty list.
+\end{methoddesc}
+
 \begin{methoddesc}{precmd}{}
 Hook method executed just before the command line is interpreted, but
 after the input prompt is generated and issued.  This
    commands, miscellaneous help topics, and undocumented commands.
 6. The command '?' is a synonym for `help'.  The command '!' is a synonym
    for `shell', if a do_shell method exists.
+7. If completion is enabled, completing commands will be done automatically,
+   and completing of commands args is done by calling complete_foo() with
+   arguments text, line, begidx, endidx.  text is string we are matching
+   against, all returned matches must begin with it.  line is the current
+   input line (lstripped), begidx and endidx are the beginning and end
+   indexes of the text being matched, which could be used to provide 
+   different completion depending upon which position the argument is in.
 
 The `default' method may be overridden to intercept commands for which there
 is no do_ method.
 
+The `completedefault' method may be overridden to intercept completions for
+commands that have no complete_ method. 
+
 The data member `self.ruler' sets the character used to draw separator lines
 in the help messages.  If empty, no ruler line is drawn.  It defaults to "=".
 
     nohelp = "*** No help on %s"
     use_rawinput = 1
 
-    def __init__(self): pass
+    def __init__(self, completekey='tab'): 
+        if completekey:
+            try:
+                import readline
+                readline.set_completer(self.complete)
+                readline.parse_and_bind(completekey+": complete")
+            except ImportError:
+                pass
 
     def cmdloop(self, intro=None):
         self.preloop()
     def postloop(self):
         pass
 
-    def onecmd(self, line):
+    def parseline(self, line):
         line = line.strip()
         if not line:
-            return self.emptyline()
+            return None, None, line
         elif line[0] == '?':
             line = 'help ' + line[1:]
         elif line[0] == '!':
             if hasattr(self, 'do_shell'):
                 line = 'shell ' + line[1:]
             else:
-                return self.default(line)
-        self.lastcmd = line
+                return None, None, line
         i, n = 0, len(line)
         while i < n and line[i] in self.identchars: i = i+1
         cmd, arg = line[:i], line[i:].strip()
+        return cmd, arg, line
+    
+    def onecmd(self, line):
+        cmd, arg, line = self.parseline(line)
+        if not line:
+            return self.emptyline()
+        if cmd is None:
+            return self.default(line)
+        self.lastcmd = line
         if cmd == '':
             return self.default(line)
         else:
     def default(self, line):
         print '*** Unknown syntax:', line
 
+    def completedefault(self, *ignored):
+        return []
+
+    def completenames(self, text, *ignored):
+        dotext = 'do_'+text
+        return [a[3:] for a in self.get_names() if a.startswith(dotext)]
+
+    def complete(self, text, state):
+        """Return the next possible completion for 'text'.
+
+        If a command has not been entered, then complete against command list.
+        Otherwise try to call complete_<command> to get list of completions.
+        """
+        if state == 0:
+            import readline
+            origline = readline.get_line_buffer()
+            line = origline.lstrip()
+            stripped = len(origline) - len(line)
+            begidx = readline.get_begidx() - stripped
+            endidx = readline.get_endidx() - stripped
+            if begidx>0:
+                cmd, args, foo = self.parseline(line)
+                if cmd == '':
+                    compfunc = self.completedefault
+                else:
+                    try:
+                        compfunc = getattr(self, 'complete_' + cmd)
+                    except AttributeError:
+                        compfunc = self.completedefault
+            else:
+                compfunc = self.completenames
+            self.completion_matches = compfunc(text, line, begidx, endidx)
+        try:
+            return self.completion_matches[state]
+        except IndexError:
+            return None
+    
+    def get_names(self):
+        # Inheritance says we have to look in class and
+        # base classes; order is not important.
+        names = []
+        classes = [self.__class__]
+        while classes:
+            aclass = classes[0]
+            if aclass.__bases__:
+                classes = classes + list(aclass.__bases__)
+            names = names + dir(aclass)
+            del classes[0]
+        return names
+
+    def complete_help(self, *args):
+        return self.completenames(*args)
+
     def do_help(self, arg):
         if arg:
             # XXX check arg syntax
                 return
             func()
         else:
-            # Inheritance says we have to look in class and
-            # base classes; order is not important.
-            names = []
-            classes = [self.__class__]
-            while classes:
-                aclass = classes[0]
-                if aclass.__bases__:
-                    classes = classes + list(aclass.__bases__)
-                names = names + dir(aclass)
-                del classes[0]
+            names = self.get_names()
             cmds_doc = []
             cmds_undoc = []
             help = {}
 
     class ProfileBrowser(cmd.Cmd):
         def __init__(self, profile=None):
+            cmd.Cmd.__init__(self)
             self.prompt = "% "
             if profile:
                 self.stats = Stats(profile)
   value using the minimal quoting required for the value; more
   reliable than using xml.sax.saxutils.escape() for attribute values.
 
+- Readline completion support for cmd.Cmd was added.
+
 New platforms
 
 C API