Python + readline + 自动补全(tab):为什么破折号和问号被视为单词分隔符?

7 投票
1 回答
3231 浏览
提问于 2025-04-17 02:51

大家好,程序员们,还有勇敢使用GNU-readline的朋友们,
几个月前,我开始使用Python的readline模块来开发一个类似命令行的应用程序。这个应用程序和文件或文件系统没有关系,它是为某个专有管理软件量身定制的解决方案。

昨天我发现某些特定的文本会导致意想不到的自动补全行为,但在文档中找不到解决办法。我非常需要你们的帮助。接下来我会给出一个例子,并附上一个代码片段,展示这个不想要的行为。

用于自动补全的值是:

aaa0   aaa1   aaa2   bbb_0  bbb_1  bbb_2
ccc-0  ccc-1  ccc-2  ddd?0  ddd?1  ddd?2

...然后不想要的行为如下(每个操作后面跟着结果输出,|符号表示光标位置):

  1. 输入'b'。
    输入> b|
  2. 按TAB键(在我的配置中,TAB键是用来自动补全的)。
    输入> bbb_|
  3. 再按一次TAB。你的文本保持不变,但会收到以下提示:
    bbb_0 bbb_1 bbb_2
    输入> bbb_|
  4. 输入'0'并按TAB。
    bbb_0 bbb_1 bbb_2
    输入> bbb_0 |

    注意'0'字符和光标之间的空格(下面的代码片段会解释这个)。
    到这里为止一切正常,尝试输入'a'也会得到类似的输出,只是没有下划线(aaa0, aaa1, aaa2)。
  5. 重新开始,输入'c'。
    输入> c
  6. 按TAB。
    输入> ccc-
  7. 再按一次TAB。
    aaa0 aaa1 aaa2 bbb_0 bbb_1 bbb_2 ccc-0 ccc-1 ccc-2 ddd?0 ddd?1 ddd?2
    输入> ccc-|

    这就是我问题的前半部分。所有值都被显示出来,而不是仅仅显示以'ccc-'开头的值。
  8. 输入'0'并按TAB。
    aaa0 aaa1 aaa2 bbb_0 bbb_1 bbb_2 ccc-0 ccc-1 ccc-2 ddd?0 ddd?1 ddd?2
    输入> ccc-0|

    这就是我问题的后半部分,你看,'0'字符和光标之间没有空格(同样,下面的代码片段会解释为什么应该有空格)。实际上,按TAB既没有改变文本,也没有显示提示,进一步按TAB的行为也是一样。

实际上,在第7步发生的事情是一个误解。Readline把'-'字符误认为是单词分隔符(如果你尝试自动补全'ddd?',问号'?'字符也是如此;其他常见的单词分隔符还有空格、制表符、'='等)。所以,由于当前行的缓冲区以单词分隔符结尾,那么就该开始一个新单词了,对吧?因此,在第7步(我们现在的位置),按TAB时会显示所有值。

在第8步,当行看起来像这样"输入> ccc-0|"时,按TAB没有效果,因为'-'作为单词分隔符,把这一行分成了两个单词:'ccc'和'0'。所以,要补全的单词是'0',但可惜的是,没有任何可能的值以'0'开头,因此没有效果。

现在,遗憾的是,这里没有对错之分。例如,在我的应用程序中,等号'='实际上一个单词分隔符,但'-'不是。我想这可能是配置的问题,但我还没找到配置哪些字符作为单词分隔符的方法。这就是我需要帮助的地方。

我说到做到,这里是我承诺的代码片段:

import readline
values = ['aaa0',  'aaa1',  'aaa2',  'bbb_0', 'bbb_1', 'bbb_2',
          'ccc-0', 'ccc-1', 'ccc-2', 'ddd?0', 'ddd?1', 'ddd?2']
def complete(text, state):
    matches = [v for v in values if v.startswith(text)]
    if len(matches) == 1 and matches[0] == text:
        # Add space if the current text is the same as the only match
        return "{} ".format(matches[0]) if state == 0 else None
    if state >= len(matches):
        return None
    return matches[state]
readline.set_completer(complete)
for line in ("tab: complete", "set show-all-if-unmodified on"):
    readline.parse_and_bind(line)
raw_input("Input> ")

各位朋友,请帮帮我!我保证会非常感激,甚至会回报你们的帮助。:)

非常感谢,
Amnon G

1 个回答

7

dir(readline) 的输出来看,函数 get_completer_delims()set_completer_delims() 看起来可能会很有用。实际上,readline 模块的文档中包含了:

set_completer_delims(...)
    set_completer_delims(string) -> None
    set the readline word delimiters for tab-completion

我觉得这正好描述了你想要的功能。这是在 Python 2.6.7 上的内容;如果你使用的是更早的版本,可能就没有这个功能了。

撰写回答