Search code examples
pythonraw-input

Tab completion in Python's raw_input()


i know i can do this to get the effect of tab completion in python sure.

import readline
COMMANDS = ['extra', 'extension', 'stuff', 'errors',
            'email', 'foobar', 'foo']

def complete(text, state):
    for cmd in COMMANDS:
        if cmd.startswith(text):
            if not state:
                return cmd
            else:
                state -= 1

readline.parse_and_bind("tab: complete")
readline.set_completer(complete)
raw_input('Enter section name: ')

I am now interested in doing tab completion with directories. (/home/user/doc >tab)

How would i go about doing such a task?


Solution

  • Here is a quick example of how to perform incremental completion of file system paths. I've modified your example, organizing it into a class where methods named complete_[name] indicate top-level commands.

    I've switched the completion function to use the internal readline buffer to determine the state of the overall completion, which makes the state logic a bit simpler. The path completion is in the _complete_path(path) method, and I've hooked up the extra command to perform path completions on its arguments.

    I'm sure the code could be further simplified but it should provide you a decent starting point:

    import os
    import re
    import readline
    
    COMMANDS = ['extra', 'extension', 'stuff', 'errors',
                'email', 'foobar', 'foo']
    RE_SPACE = re.compile('.*\s+$', re.M)
    
    class Completer(object):
    
        def _listdir(self, root):
            "List directory 'root' appending the path separator to subdirs."
            res = []
            for name in os.listdir(root):
                path = os.path.join(root, name)
                if os.path.isdir(path):
                    name += os.sep
                res.append(name)
            return res
    
        def _complete_path(self, path=None):
            "Perform completion of filesystem path."
            if not path:
                return self._listdir('.')
            dirname, rest = os.path.split(path)
            tmp = dirname if dirname else '.'
            res = [os.path.join(dirname, p)
                    for p in self._listdir(tmp) if p.startswith(rest)]
            # more than one match, or single match which does not exist (typo)
            if len(res) > 1 or not os.path.exists(path):
                return res
            # resolved to a single directory, so return list of files below it
            if os.path.isdir(path):
                return [os.path.join(path, p) for p in self._listdir(path)]
            # exact file match terminates this completion
            return [path + ' ']
    
        def complete_extra(self, args):
            "Completions for the 'extra' command."
            if not args:
                return self._complete_path('.')
            # treat the last arg as a path and complete it
            return self._complete_path(args[-1])
    
        def complete(self, text, state):
            "Generic readline completion entry point."
            buffer = readline.get_line_buffer()
            line = readline.get_line_buffer().split()
            # show all commands
            if not line:
                return [c + ' ' for c in COMMANDS][state]
            # account for last argument ending in a space
            if RE_SPACE.match(buffer):
                line.append('')
            # resolve command to the implementation function
            cmd = line[0].strip()
            if cmd in COMMANDS:
                impl = getattr(self, 'complete_%s' % cmd)
                args = line[1:]
                if args:
                    return (impl(args) + [None])[state]
                return [cmd + ' '][state]
            results = [c + ' ' for c in COMMANDS if c.startswith(cmd)] + [None]
            return results[state]
    
    comp = Completer()
    # we want to treat '/' as part of a word, so override the delimiters
    readline.set_completer_delims(' \t\n;')
    readline.parse_and_bind("tab: complete")
    readline.set_completer(comp.complete)
    raw_input('Enter section name: ')
    

    Usage:

    % python complete.py 
    Enter section name: ext<tab>
    extension extra
    Enter section name: extra foo<tab>
    foo.py foo.txt foo/
    Enter section name: extra foo/<tab>
    foo/bar.txt foo/baz.txt
    Enter section name: extra foo/bar.txt
    

    Update It will complete paths from the root if the user types /:

    % python complete.py
    Enter section name: extra /Use<tab>
    /Users/.localized  /Users/Shared/  /Users/user1 /Users/user2
    Enter section name: extra /Users/use<tab>
    /Users/user1  /Users/user2