Python的raw_input()中的标签补全

57 投票
5 回答
32664 浏览
提问于 2025-04-16 15:35

我知道我可以这样做来实现Python中的标签补全效果。

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: ')

现在我对如何在目录中实现标签补全很感兴趣。比如说在输入“/home/user/doc”后按下“tab”键。

我该怎么做才能实现这个功能呢?

5 个回答

3

这个版本是针对python3的,使用了pathlib库,并且是一个简化版,可以通过按Tab键来自动完成文件和目录的名称。它是基于上面一些回答的,但只适用于文件和目录。

#!/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: '))
8

这段代码足以让你在使用raw_input()的时候,能够自动补全目录和文件名。

import readline
readline.parse_and_bind("tab: complete")
71

这里有个简单的例子,教你怎么逐步完成文件系统的路径。我对你的例子做了一些修改,把它整理成一个类,里面的方法名是 complete_[name],表示顶层命令。

我把完成路径的功能改成使用内部的 readline 缓冲区,这样可以更简单地判断整体的完成状态。路径的完成逻辑放在 _complete_path(path) 方法里,我还把 extra 命令连接上,让它可以对参数进行路径补全。

我相信代码还可以进一步简化,但这应该能给你一个不错的起点:

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: ')

使用方法:

% 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

更新 如果用户输入 /,它会从根目录开始完成路径:

% 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

撰写回答