Tab completion in Python's raw_input() Tab completion in Python's raw_input() python python

Tab completion in Python's raw_input()


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 osimport reimport readlineCOMMANDS = ['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 delimitersreadline.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 extraEnter section name: extra foo<tab>foo.py foo.txt foo/Enter section name: extra foo/<tab>foo/bar.txt foo/baz.txtEnter section name: extra foo/bar.txt

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

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


This is enough to enable built in directory tab completion with raw_input():

import readlinereadline.parse_and_bind("tab: complete")


This version is for python3, uses pathlib, and a minimalistic version that tab completes files/dirs. It is based on some of the above answers, but only works for files/dirs.

#!/usr/bin/pythonimport pathlibimport readlinedef complete_path(text, state):    incomplete_path = pathlib.Path(text)    if incomplete_path.is_dir():        completions = [p.as_posix() for p in incomplete_path.iterdir()]    elif incomplete_path.exists():        completions = [incomplete_path]    else:        exists_parts = pathlib.Path('.')        for part in incomplete_path.parts:            test_next_part = exists_parts / part            if test_next_part.exists():                exists_parts = test_next_part        completions = []        for p in exists_parts.iterdir():            p_str = p.as_posix()            if p_str.startswith(text):                completions.append(p_str)    return completions[state]# we want to treat '/' as part of a word, so override the delimitersreadline.set_completer_delims(' \t\n;')readline.parse_and_bind("tab: complete")readline.set_completer(complete_path)print(input('tab complete a filename: '))