I have a command ts
which takes as its arguments a command and that command's arguments. I want tab-completion to "do the right thing", namely:
When I type, for example ts foo<tab>
bash should try to auto-complete and find any command beginning with foo
. I've got this working thus:
complete -F __ts_bash_completions ts
__ts_bash_completions () {
COMPREPLY=();
# if there's only one word on the command line
if [ "$COMP_CWORD" -eq 1 ]; then
COMPREPLY=($(compgen -c -- "${COMP_WORDS[COMP_CWORD]}"));
Once it has a complete command name, I want tab-completions for subsequent arguments to use the tab-completion semantics of that first argument. If there is no completion function defined for that first argument I have that working just fine too. Carrying on from the above, this works:
# otherwise ...
else
local command_completion_function="$(complete -p ${COMP_WORDS[1]} 2>/dev/null|sed 's/.*-F \([^ ]*\) .*/\1/')"
if [ -z "$command_completion_function" ]; then
COMPREPLY=($(compgen -W "$(ls)" -- "${COMP_WORDS[COMP_CWORD]}"));
But where I'm struggling is with the last requirement, that if there is a completion function available it should be used, and I just can't get that damned thing to work. I've seen this question and its answer and tried to generalise it thus. Again, carrying on from the above:
else
echo using $command_completion_function
echo COMP_CWORD="$COMP_CWORD"
echo COMP_LINE="$COMP_LINE"
echo COMP_WORDS="${COMP_WORDS[*]}"
COMP_CWORD=$(( $COMP_CWORD - 1 ))
COMP_LINE=$(echo $COMP_LINE|sed "s/^${COMP_WORDS[0]} //")
unset COMP_WORDS[0]
echo COMP_CWORD="$COMP_CWORD"
echo COMP_LINE="$COMP_LINE"
echo COMP_WORDS="${COMP_WORDS[*]}"
$command_completion_function
fi
fi
}
Everything looks OK to me as far as I can tell from the echo
of the various COMP_*
variables, but can anyone spot the silly mistake?
I've figured it out. The error was that if there was a completion function already defined for a command I would call that, but I wasn't dealing with the case where that returns nothing. My test case was ls
, which on my machine has ...
$ complete -p ls
complete -o bashdefault -o default -F _fzf_path_completion ls
Note the -o bashdefault -o default
, which kick in if the function doesn't set a COMPREPLY
. -o bashdefault
says "if we've got nothing, apply the bash defaults". -o default
says "if we've still got nothing, apply the readline defaults".
So the fix is to say this at the top of the file:
complete -o bashdefault -o default -F __ts_bash_completions ts
There's probably a way of applying those only if the command passed as the first argument has them defined for its completions, but just applying them globally is good enough for my use cases.
The complete fixed script reads:
complete -o bashdefault -o default -F __ts_bash_completions ts
__ts_bash_completions () {
COMPREPLY=();
if [ "$COMP_CWORD" -eq 1 ]; then
COMPREPLY=($(compgen -c -- "${COMP_WORDS[COMP_CWORD]}"));
else
local command_completion_function="$(complete -p ${COMP_WORDS[1]} 2>/dev/null|sed 's/.*-F \([^ ]*\) .*/\1/')"
if [ ! -z "$command_completion_function" ]; then
COMP_CWORD=$(( COMP_CWORD - 1 ))
COMP_LINE=$(echo $COMP_LINE|sed "s/^${COMP_WORDS[0]} //")
COMP_WORDS=( "${COMP_WORDS[@]:1}" )
$command_completion_function "${COMP_WORDS[0]}" "$2" "$3"
fi
fi
}