226 lines
6.9 KiB
Bash
226 lines
6.9 KiB
Bash
# shellcheck shell=bash
|
|
#
|
|
# Copyright (C) 2017 Canonical Ltd
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 3 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# etelpmoc is the reverse of complete: it de-serialises the tab completion
|
|
# request into the appropriate environment variables expected by the tab
|
|
# completion tools, performs whatever action is wanted, and serialises the
|
|
# result. It accomplishes this by having functions override the builtin
|
|
# completion commands.
|
|
#
|
|
# this always runs "inside", in the same environment you get when doing "snap
|
|
# run --shell", and snap-exec is the one setting the first argument to the
|
|
# completion script set in the snap. The rest of the arguments come through
|
|
# from snap-run --command=complete <snap> <args...>
|
|
|
|
_die() {
|
|
echo "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
|
|
_die "ERROR: this is meant to be run, not sourced."
|
|
fi
|
|
|
|
if [[ "${#@}" -lt 8 ]]; then
|
|
_die "USAGE: $0 <script> <COMP_TYPE> <COMP_KEY> <COMP_POINT> <COMP_CWORD> <COMP_WORDBREAKS> <COMP_LINE> cmd [args...]"
|
|
fi
|
|
|
|
# De-serialize the command line arguments and populate tab completion environment
|
|
_compscript="$1"
|
|
shift
|
|
COMP_TYPE="$1"
|
|
shift
|
|
COMP_KEY="$1"
|
|
shift
|
|
COMP_POINT="$1"
|
|
shift
|
|
COMP_CWORD="$1"
|
|
shift
|
|
COMP_WORDBREAKS="$1"
|
|
shift
|
|
# duplication, but whitespace is eaten and that throws off COMP_POINT
|
|
COMP_LINE="$1"
|
|
shift
|
|
# rest of the args is the command itself
|
|
COMP_WORDS=("$@")
|
|
|
|
COMPREPLY=()
|
|
|
|
if [[ ! "$_compscript" ]]; then
|
|
_die "ERROR: completion script filename can't be empty"
|
|
fi
|
|
if [[ ! -f "$_compscript" ]]; then
|
|
_die "ERROR: completion script does not exist"
|
|
fi
|
|
|
|
# Source the bash-completion library functions and common completion setup
|
|
# shellcheck disable=SC1091
|
|
. /usr/share/bash-completion/bash_completion
|
|
# Now source the snap's 'completer' script itself
|
|
# shellcheck disable=SC1090
|
|
. "$_compscript"
|
|
|
|
# _compopts is an associative array, which keys are options. The options are
|
|
# described in bash(1)'s description of the -o option to the "complete"
|
|
# builtin, and they affect how the completion options are presented to the user
|
|
# (e.g. adding a slash for directories, whether to add a space after the
|
|
# completion, etc). These need setting in the user's environment so need
|
|
# serializing separately from the completions themselves.
|
|
declare -A _compopts
|
|
|
|
# wrap compgen, setting _compopts for any options given.
|
|
# (as these options need handling separately from the completions)
|
|
compgen() {
|
|
local opt
|
|
|
|
while getopts :o: opt; do
|
|
case "$opt" in
|
|
o)
|
|
_compopts["$OPTARG"]=1
|
|
;;
|
|
*)
|
|
# Do nothing, explicitly. This silences shellcheck's detector
|
|
# of unhandled command line options.
|
|
;;
|
|
esac
|
|
done
|
|
builtin compgen "$@"
|
|
}
|
|
|
|
# compopt replaces the original compopt with one that just sets/unsets entries
|
|
# in _compopts
|
|
compopt() {
|
|
local i
|
|
|
|
for ((i=0; i<$#; i++)); do
|
|
# in bash, ${!x} does variable indirection. Thus if x=1, ${!x} becomes $1.
|
|
case "${!i}" in
|
|
-o)
|
|
((i++))
|
|
_compopts[${!i}]=1
|
|
;;
|
|
+o)
|
|
((i++))
|
|
unset _compopts[${!i}]
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
_compfunc="_minimal"
|
|
_compact=""
|
|
# this is a lot more complicated than it should be, but it's how you
|
|
# get the result of 'complete -p "$1"' into an array, splitting it as
|
|
# the shell would.
|
|
readarray -t _comp < <(xargs -n1 < <(complete -p "$1") )
|
|
# _comp is now an array of the appropriate 'complete' invocation, word-split as
|
|
# the shell would, so we can now inspect it with getopts to determine the
|
|
# appropriate completion action.
|
|
# Unfortunately shellcheck doesn't know about readarray:
|
|
# shellcheck disable=SC2154
|
|
if [[ "${_comp[*]}" ]]; then
|
|
while getopts :abcdefgjksuvA:C:W:o:F: opt "${_comp[@]:1}"; do
|
|
case "$opt" in
|
|
a)
|
|
_compact="alias"
|
|
;;
|
|
b)
|
|
_compact="builtin"
|
|
;;
|
|
c)
|
|
_compact="command"
|
|
;;
|
|
d)
|
|
_compact="directory"
|
|
;;
|
|
e)
|
|
_compact="export"
|
|
;;
|
|
f)
|
|
_compact="file"
|
|
;;
|
|
g)
|
|
_compact="group"
|
|
;;
|
|
j)
|
|
_compact="job"
|
|
;;
|
|
k)
|
|
_compact="keyword"
|
|
;;
|
|
s)
|
|
_compact="service"
|
|
;;
|
|
u)
|
|
_compact="user"
|
|
;;
|
|
v)
|
|
_compact="variable"
|
|
;;
|
|
A)
|
|
_compact="$OPTARG"
|
|
;;
|
|
o)
|
|
_compopts["$OPTARG"]=1
|
|
;;
|
|
C|F)
|
|
_compfunc="$OPTARG"
|
|
;;
|
|
W)
|
|
readarray -t COMPREPLY < <( builtin compgen -W "$OPTARG" -- "${COMP_WORDS[COMP_CWORD]}" )
|
|
_compfunc=""
|
|
;;
|
|
*)
|
|
# P, G, S, and X are not supported yet
|
|
_die "ERROR: unknown option -$OPTARG"
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
_bounce=""
|
|
case "$_compact" in
|
|
# these are for completing things that'll be interpreted by the
|
|
# "outside" bash, so send them back to be completed there.
|
|
"alias"|"export"|"job"|"variable")
|
|
_bounce="$_compact"
|
|
;;
|
|
esac
|
|
|
|
if [ ! "$_bounce" ]; then
|
|
if [ "$_compact" ]; then
|
|
readarray -t COMPREPLY < <( builtin compgen -A "$_compact" -- "${COMP_WORDS[COMP_CWORD]}" )
|
|
elif [ "$_compfunc" ]; then
|
|
# execute completion function (or the command if -C)
|
|
|
|
# from https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html:
|
|
# When the function or command is invoked, the first argument ($1) is
|
|
# the name of the command whose arguments are being completed, the
|
|
# second argument ($2) is the word being completed, and the third
|
|
# argument ($3) is the word preceding the word being completed on the
|
|
# current command line.
|
|
# that's "$1" "${COMP_WORDS[COMP_CWORD]}" and "${COMP_WORDS[COMP_CWORD-1]}"
|
|
# (probably)
|
|
$_compfunc "$1" "${COMP_WORDS[COMP_CWORD]}" "${COMP_WORDS[COMP_CWORD-1]}"
|
|
fi
|
|
fi
|
|
|
|
# print completions to stdout
|
|
echo "${!_compopts[@]}"
|
|
echo "$_bounce"
|
|
echo ""
|
|
printf "%s\\n" "${COMPREPLY[@]}"
|