I'm trying to figure out how to get file completion to work at any word
position on the command line after a set of characters. As listed in a shell these characters would be [ =+-\'\"()]
(the whitespace is tab and space). Zsh will do this, but only after the backtick character, '`', or $(
. mksh does this except not after the characters [+-]
.
By word position on the command line, I'm talking about each set of characters you type out which are delimited by space and a few other characters. For example,
print Hello World
,
has three words
at positions 1-3. At position 1, when you're first typing stuff in, completion is pretty much perfect. File completion works after all of the characters I mentioned. After the first word
, the completion system gets more limited since it's smart. This is useful for commands, but limiting where you can do file completion isn't particularly helpful.
Here are some examples of where file completion doesn't work for me but should in my opinion:
: ${a:=/...}
echo "${a:-/...}"
make LDFLAGS+='-nostdlib /.../crt1.o /.../crti.o ...'
env a=/... b=/... ...
I've looked at rebinding '^I'
(tab) with the handful of different completion widgets Zsh comes with and changing my zstyle ':completion:*'
lines. Nothing has worked so far to change this default Zsh behaviour. I'm thinking I need to create a completion function that I can add to the end of my zstyle ':completion:*' completer ...
line as a last resort completion.
In the completion function, one route would be to cut out the current word
I want to complete, complete it, and then re-insert the completion back into the line if that's possible. It could also be more like _precommand
which shifts the second word
to the first word
so that normal command completion works.
I was able to modify _precommand
so that you can complete commands at any word
position. This is the new file, I named it _commando
and added its directory to my fpath:
#compdef -
# precommands is made local in _main_complete
precommands+=($words[1,$(( CURRENT -1 ))])
shift words
CURRENT=1
_normal
To use it I added it to the end of my ':completion:*' completer ...
line in my zshrc so it works with every program in $path
. Basically whatever word
you're typing in is considered the first word
, so command completion works at every word
position on the command line.
I'm trying to figure out a way to do the same thing for file completion, but it looks a little more complicated at first glace. I'm not really sure where to go with this, so I'm looking to get some help on this.
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
...
These few lines allow typeset completion after each slash in a command like this:
typeset file1=/... file2=~/... file3=/...
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
}
You can use this by adding it to a zstyle
line like this:
zstyle ':completion:*' completer _complete _reallyforcefilecompletion
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
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
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
EDIT: There were a couple lines I forgot to copy into the function. Probably should have checked before posting. I think it's good now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With