Zsh:如何在一组字符后强制在所有位置完成文件?

jan*_*jan 5 zsh zshrc zsh-completion

我试图弄清楚如何word在一组字符之后使文件完成在命令行上的任何位置都能工作。如外壳中所列,这些字符将是[ =+-\'\"()](空格是制表符和空格)。Zsh会执行此操作,但仅在反引号'`'或之后$(。mksh会执行此操作,除非不在字符后面[+-]。

通过命令行中的单词位置,我说的是您键入的每组字符,这些字符由空格和其他几个字符分隔。例如,

print Hello World,

有三个words在1-3位。在位置1,当您第一次输入内容时,补全非常完美。我提到的所有字符都可以完成文件补全。在第一个之后word,完成系统由于具有智能性而受到更大的限制。这对命令很有用,但是限制可以完成文件的位置并不是特别有用。

以下是一些文件补全对我不起作用,但我认为应该这样做的示例:

: ${a:=/...}

echo "${a:-/...}"

make LDFLAGS+='-nostdlib /.../crt1.o /.../crti.o ...'

env a=/... b=/... ...

我已经看过'^I'Zsh随附的少数不同补全小部件和更改zstyle ':completion:*'行的重新绑定(选项卡)。到目前为止,没有任何事情可以更改此默认的Zsh行为。我在想我需要创建一个完成函数,可以将其添加到zstyle ':completion:*' completer ...行尾作为最后的完成。

在完成功能中,一种方法是切出word我要完成的电流,完成它,然后在可能的情况下将完成重新插入到行中。也可能更像_precommand是将第二个word转换为第一个,word以便正常的命令完成工作。

我能够进行修改,_precommand以便您可以在任何word位置完成命令。这是新文件,我_commando为其命名并将其目录添加到我的fpath中:

#compdef -                                                              

# precommands is made local in _main_complete
precommands+=($words[1,$(( CURRENT -1 ))])

shift words
CURRENT=1

_normal
Run Code Online (Sandbox Code Playgroud)

要使用它,我将其添加到':completion:*' completer ...zshrc的行末尾,以便它可与中的每个程序一起使用$path。基本上,word您键入的内容都被认为是第一个word,因此命令完成在word命令行上的每个位置都有效。

我正在尝试找出一种方法来完成文件完成操作,但是乍一看它看起来有些复杂。我不太确定该怎么做,因此希望在此方面寻求帮助。

jan*_*jan 3

I took a closer look at some of Zsh's builtin functions and noticed a few that have special completion behaviour. They belong to the typeset group, which has a function _typeset in the default fpath. I only needed to extract a few lines for what I wanted to do. These are the lines I extracted:

...
elif [[ "$PREFIX" = *\=* ]]; then
compstate[parameter]="${PREFIX%%\=*}"
compset -P 1 '*='
_value
...
Run Code Online (Sandbox Code Playgroud)

These few lines allow typeset completion after each slash in a command like this:

typeset file1=/... file2=~/... file3=/...
Run Code Online (Sandbox Code Playgroud)

I extrapolated from this to create the following function. You can modify it to put in your fpath. I just defined it in my zshrc like this:

_reallyforcefilecompletion() {
    local prefix_char

    for prefix_char in ' ' $'\t' '=' '+' '-' "'" '"' ')' ':'; do
        if [[ "$PREFIX" = *${prefix_char}* ]]; then
            if [[ "$PREFIX" = *[\'\"]* ]]; then
                compset -q -P "*${prefix_char}"
            else
                compset -P "*${prefix_char}"
            fi
            _value
            break
        fi
    done
}
Run Code Online (Sandbox Code Playgroud)

You can use this by adding it to a zstyle line like this:

zstyle ':completion:*' completer _complete _reallyforcefilecompletion
Run Code Online (Sandbox Code Playgroud)

This way, it's only used as a last resort so that smarter completions can try before it. Here's a little explanation of the function starting with the few variables and the command involved:

prefix_char: This gets set to each prefix character we want to complete after. For example, env a=123 has the prefix character =.

PREFIX: Initially this will be set to the part of the current word from the beginning of the word up to the position of the cursor; it may be altered to give a common prefix for all matches.

IPREFIX (not shown in code): compset moves string matches from PREFIX to IPREFIX so that the rest of PREFIX can be completed.

compset: This command simplifies modification of the special parameters, while its return status allows tests on them to be carried out.

_value: Not really sure about this one. The documentation states it plays some sort of role in completion.

Documentation for the completion system

The function: In the second line, we declare prefix_char local to avoid variable pollution. In line three, we start a for loop selecting each prefix_char we want to complete after. In the next if block, we check if the variable PREFIX ends with one of the prefix_chars we want to complete after and if PREFIX contains any quotes. Since PREFIX contains quotes, we use compset -q to basically allow quotes to be ignored so we can complete in them. compset -P strips PREFIX and moves it to IPREFIX, basically so it gets ignored and completion can work.

The next elif statement is for a PREFIX ending with prefix_char but not containing quotes, so we only use compset -P. I added the return 0 to break the loop. A more correct way to make this function would be in a case statement, but we're not using the compset return value, so this works. You don't see anything about file completion besides _value. For the most part we just told the system to ignore part of the word.


Basically this is what the function does. We have a line that looks like:

env TERM=linux PATH=/<---cursor here
Run Code Online (Sandbox Code Playgroud)

The cursor is at the end of that slash. This function allows PREFIX, which is PATH=, to be ignored, so we have:

env TERM=linux /<---cursor here
Run Code Online (Sandbox Code Playgroud)

You can complete a file there with PATH= removed. The function doesn't actually remove the PATH= though, it just recategorizes it as something to ignore.

With this function, you can now complete in all of the examples I listed in the question and a lot more.

One last thing to mention, adding this force-list line in your zshrc cripples this function somehow. It still works but seems to choke. This new force-list function is way better anyway.

zstyle ':completion:*' force-list always
Run Code Online (Sandbox Code Playgroud)

编辑:有几行我忘记复制到函数中。也许应该在发布之前检查一下。我觉得现在已经很好了。