Tab completion in Python's raw_input()
Asked Answered
A

6

57

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?

Adjoint answered 12/4, 2011 at 14:44 Comment(0)
B
71

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
Byrnes answered 12/4, 2011 at 16:38 Comment(7)
If the user types /<tab> it will complete from the root, so /home/<tab> will work. I'll update the example. Are you looking for the file listing to show only the filenames?Byrnes
Which feature(s) are missing from the above example?Byrnes
@Byrnes how would you handle escaping spaces and specifying quotes ?Glassful
@Byrnes I fiddled with your code a bit to make it handle ".." and "~" and to not put a space after the file name. I'd like to put a GPL on some code I'm using it in. Is that ok? Do you have any requests about crediting you?Ciaracibber
@Byrnes do you know of a way to change the way autocomplete columnizes multiple results? There may be a difference between Unix/Linux and Windows on how this works but with a Windows dos shell with Python 2.7.3 i get really awkward columnized results.Lieutenancy
Is there any way to get it running on Windows as well? I tried it but when entering "c:/<tab>" it inserts a tab instead of showing the folders within c: 🤔Whereupon
@Byrnes This code works in python3, but only if you imported the file (complete.py) from a python console. If you run it directly using python complete.py the tab key is considered 4 spaces instead of tab completion key.Evangelize
P
9

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

import readline
readline.parse_and_bind("tab: complete")
Phantom answered 7/3, 2014 at 18:12 Comment(5)
It's probably worth stating that this doesn't work on Windows.Farrish
This solution does only work relative to the cwd. E.g. you cannot use auto completion for / if you are not currently in /Schaub
I cannot get this to work on windows 7 using python 2.7. i also tried pyreadline.Pustulate
For me on python 3.5.5 this only completes files/subdirectories in the current directory.Illustrate
This seems to complete relative to the current directory because / is in the list of default completion delimiters. See https://mcmap.net/q/911421/-how-to-make-a-python-script-quot-tab-complete-quot-directories-in-terminal on how to fix that and enable general tab completion.Assets
S
3

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/python

import pathlib
import readline


def 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 delimiters
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(complete_path)
print(input('tab complete a filename: '))
Scopoline answered 27/9, 2021 at 15:40 Comment(0)
M
1

For path completion

import os
import sys 
import readline
import glob

def path_completer(text, state):
    """ 
    This is the tab completer for systems paths.
    Only tested on *nix systems
    """
    line = readline.get_line_buffer().split()

    if '~' in text:
        text = os.path.expanduser('~')

    return [x for x in glob.glob(text+'*')][state]
    
if __name__=="__main__":

    readline.set_completer_delims('\t')
    readline.parse_and_bind("tab: complete")

    readline.set_completer(path_completer)
    ans = input("What file do you want? ")
    print(ans)

Note that I've refined the code found at https://gist.github.com/iamatypeofwalrus/5637895

Minesweeper answered 3/3, 2022 at 5:48 Comment(0)
C
1

On Some Systems™ you need to use different bindings. For example, I don't have GNU readline, so I need to use a different parse_and_bind text:

if 'libedit' in readline.__doc__:
    readline.parse_and_bind("bind ^I rl_complete")
else:
    readline.parse_and_bind("tab: complete")
Cherisecherish answered 10/3, 2023 at 13:16 Comment(3)
I don't have enough reputation to comment on the other answers, so this should really be a comment to the highest ranking answer.Cherisecherish
This if from https://mcmap.net/q/337423/-python-repl-tab-completion-on-macos btw.Cherisecherish
Thanks, this was needed to get it working on Mac! Also don't forget readline.parse_and_bind("bind -e") to get the default readline keybindings.Syndesmosis
D
0

In Python 3.11, the below code works for both directory and file selection.

import readline

readline.set_completer_delims(" \t\n;")
readline.parse_and_bind("tab: complete")
ip = input("Enter some text or hit tab for directory/filename: ")
print("You entered: ", ip)
Ducktail answered 8/5 at 3:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.