blob: 8ec8b373e8fd3d7e7d68eab748f78b4898f87ef5 [file] [log] [blame]
# Bash auto-completion for west subcommands and flags. To initialize, run
#
# source west-completion.bash
#
# To make it persistent, add it to e.g. your .bashrc.
__west_previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
# The following function is based on code from:
#
# bash_completion - programmable completion functions for bash 3.2+
#
# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
# © 2009-2010, Bash Completion Maintainers
# <bash-completion-devel@lists.alioth.debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# 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/>.
#
# The latest version of this software can be obtained here:
#
# http://bash-completion.alioth.debian.org/
#
# RELEASE: 2.x
# This function can be used to access a tokenized list of words
# on the command line:
#
# __git_reassemble_comp_words_by_ref '=:'
# if test "${words_[cword_-1]}" = -w
# then
# ...
# fi
#
# The argument should be a collection of characters from the list of
# word completion separators (COMP_WORDBREAKS) to treat as ordinary
# characters.
#
# This is roughly equivalent to going back in time and setting
# COMP_WORDBREAKS to exclude those characters. The intent is to
# make option types like --date=<type> and <rev>:<path> easy to
# recognize by treating each shell word as a single token.
#
# It is best not to set COMP_WORDBREAKS directly because the value is
# shared with other completion scripts. By the time the completion
# function gets called, COMP_WORDS has already been populated so local
# changes to COMP_WORDBREAKS have no effect.
#
# Output: words_, cword_, cur_.
__west_reassemble_comp_words_by_ref()
{
local exclude i j first
# Which word separators to exclude?
exclude="${1//[^$COMP_WORDBREAKS]}"
cword_=$COMP_CWORD
if [ -z "$exclude" ]; then
words_=("${COMP_WORDS[@]}")
return
fi
# List of word completion separators has shrunk;
# re-assemble words to complete.
for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
# Append each nonempty word consisting of just
# word separator characters to the current word.
first=t
while
[ $i -gt 0 ] &&
[ -n "${COMP_WORDS[$i]}" ] &&
# word consists of excluded word separators
[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
do
# Attach to the previous token,
# unless the previous token is the command name.
if [ $j -ge 2 ] && [ -n "$first" ]; then
((j--))
fi
first=
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
if (($i < ${#COMP_WORDS[@]} - 1)); then
((i++))
else
# Done.
return
fi
done
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
done
}
if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
_get_comp_words_by_ref ()
{
local exclude cur_ words_ cword_
if [ "$1" = "-n" ]; then
exclude=$2
shift 2
fi
__west_reassemble_comp_words_by_ref "$exclude"
cur_=${words_[cword_]}
while [ $# -gt 0 ]; do
case "$1" in
cur)
cur=$cur_
;;
prev)
prev=${words_[$cword_-1]}
;;
words)
words=("${words_[@]}")
;;
cword)
cword=$cword_
;;
esac
shift
done
}
fi
if ! type _tilde >/dev/null 2>&1; then
# Perform tilde (~) completion
# @return True (0) if completion needs further processing,
# False (> 0) if tilde is followed by a valid username, completions
# are put in COMPREPLY and no further processing is necessary.
_tilde()
{
local result=0
if [[ $1 == \~* && $1 != */* ]]; then
# Try generate ~username completions
COMPREPLY=( $( compgen -P '~' -u -- "${1#\~}" ) )
result=${#COMPREPLY[@]}
# 2>/dev/null for direct invocation, e.g. in the _tilde unit test
[[ $result -gt 0 ]] && compopt -o filenames 2>/dev/null
fi
return $result
}
fi
if ! type _quote_readline_by_ref >/dev/null 2>&1; then
# This function quotes the argument in a way so that readline dequoting
# results in the original argument. This is necessary for at least
# `compgen' which requires its arguments quoted/escaped:
#
# $ ls "a'b/"
# c
# $ compgen -f "a'b/" # Wrong, doesn't return output
# $ compgen -f "a\'b/" # Good
# a\'b/c
#
# See also:
# - http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html
# - http://www.mail-archive.com/bash-completion-devel@lists.alioth.\
# debian.org/msg01944.html
# @param $1 Argument to quote
# @param $2 Name of variable to return result to
_quote_readline_by_ref()
{
if [ -z "$1" ]; then
# avoid quoting if empty
printf -v $2 %s "$1"
elif [[ $1 == \'* ]]; then
# Leave out first character
printf -v $2 %s "${1:1}"
elif [[ $1 == \~* ]]; then
# avoid escaping first ~
printf -v $2 \~%q "${1:1}"
else
printf -v $2 %q "$1"
fi
# Replace double escaping ( \\ ) by single ( \ )
# This happens always when argument is already escaped at cmdline,
# and passed to this function as e.g.: file\ with\ spaces
[[ ${!2} == *\\* ]] && printf -v $2 %s "${1//\\\\/\\}"
# If result becomes quoted like this: $'string', re-evaluate in order to
# drop the additional quoting. See also: http://www.mail-archive.com/
# bash-completion-devel@lists.alioth.debian.org/msg01942.html
[[ ${!2} == \$* ]] && eval $2=${!2}
} # _quote_readline_by_ref()
fi
# This function turns on "-o filenames" behavior dynamically. It is present
# for bash < 4 reasons. See http://bugs.debian.org/272660#64 for info about
# the bash < 4 compgen hack.
_compopt_o_filenames()
{
# We test for compopt availability first because directly invoking it on
# bash < 4 at this point may cause terminal echo to be turned off for some
# reason, see https://bugzilla.redhat.com/653669 for more info.
type compopt &>/dev/null && compopt -o filenames 2>/dev/null || \
compgen -f /non-existing-dir/ >/dev/null
}
if ! type _filedir >/dev/null 2>&1; then
# This function performs file and directory completion. It's better than
# simply using 'compgen -f', because it honours spaces in filenames.
# @param $1 If `-d', complete only on directories. Otherwise filter/pick only
# completions with `.$1' and the uppercase version of it as file
# extension.
#
_filedir()
{
local IFS=$'\n'
_tilde "$cur" || return
local -a toks
local x tmp
x=$( compgen -d -- "$cur" ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
if [[ "$1" != -d ]]; then
local quoted
_quote_readline_by_ref "$cur" quoted
# Munge xspec to contain uppercase version too
# http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306
local xspec=${1:+"!*.@($1|${1^^})"}
x=$( compgen -f -X "$xspec" -- $quoted ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
# Try without filter if it failed to produce anything and configured to
[[ -n ${COMP_FILEDIR_FALLBACK:-} && -n "$1" && ${#toks[@]} -lt 1 ]] && \
x=$( compgen -f -- $quoted ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
fi
if [[ ${#toks[@]} -ne 0 ]]; then
# 2>/dev/null for direct invocation, e.g. in the _filedir unit test
_compopt_o_filenames
COMPREPLY+=( "${toks[@]}" )
fi
} # _filedir()
fi
# Misc helpers taken from Docker:
# https://github.com/docker/docker-ce/blob/master/components/cli/contrib/completion/bash/docker
# __west_pos_first_nonflag finds the position of the first word that is neither
# option nor an option's argument. If there are options that require arguments,
# you should pass a glob describing those options, e.g. "--option1|-o|--option2"
# Use this function to restrict completions to exact positions after the argument list.
__west_pos_first_nonflag()
{
local argument_flags=$1
local counter=$((${subcommand_pos:-${command_pos}} + 1))
while [ "$counter" -le "$cword" ]; do
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
(( counter++ ))
# eat "=" in case of --option=arg syntax
[ "${words[$counter]}" = "=" ] && (( counter++ ))
else
case "${words[$counter]}" in
-*)
;;
*)
break
;;
esac
fi
# Bash splits words at "=", retaining "=" as a word, examples:
# "--debug=false" => 3 words, "--log-opt syslog-facility=daemon" => 4 words
while [ "${words[$counter + 1]}" = "=" ] ; do
counter=$(( counter + 2))
done
(( counter++ ))
done
echo $counter
}
# __west_map_key_of_current_option returns `key` if we are currently completing the
# value of a map option (`key=value`) which matches the extglob given as an argument.
# This function is needed for key-specific completions.
__west_map_key_of_current_option()
{
local glob="$1"
local key glob_pos
if [ "$cur" = "=" ] ; then # key= case
key="$prev"
glob_pos=$((cword - 2))
elif [[ $cur == *=* ]] ; then # key=value case (OSX)
key=${cur%=*}
glob_pos=$((cword - 1))
elif [ "$prev" = "=" ] ; then
key=${words[$cword - 2]} # key=value case
glob_pos=$((cword - 3))
else
return
fi
[ "${words[$glob_pos]}" = "=" ] && ((glob_pos--)) # --option=key=value syntax
[[ ${words[$glob_pos]} == @($glob) ]] && echo "$key"
}
# __west_value_of_option returns the value of the first option matching `option_glob`.
# Valid values for `option_glob` are option names like `--log-level` and globs like
# `--log-level|-l`
# Only positions between the command and the current word are considered.
__west_value_of_option()
{
local option_extglob=$(__west_to_extglob "$1")
local counter=$((command_pos + 1))
while [ "$counter" -lt "$cword" ]; do
case ${words[$counter]} in
$option_extglob )
echo "${words[$counter + 1]}"
break
;;
esac
(( counter++ ))
done
}
# __west_to_alternatives transforms a multiline list of strings into a single line
# string with the words separated by `|`.
# This is used to prepare arguments to __west_pos_first_nonflag().
__west_to_alternatives()
{
local parts=( $1 )
local IFS='|'
echo "${parts[*]}"
}
# __west_to_extglob transforms a multiline list of options into an extglob pattern
# suitable for use in case statements.
__west_to_extglob()
{
local extglob=$( __west_to_alternatives "$1" )
echo "@($extglob)"
}
__set_comp_dirs()
{
_filedir -d
}
__set_comp_files()
{
_filedir
}
# Sets completions for $cur, from the possibilities in $1..n
__set_comp()
{
# "${*:1}" gives a single argument with arguments $1..n
COMPREPLY=($(compgen -W "${*:1}" -- "$cur"))
}
__west_x()
{
west 2>/dev/null "$@"
}
__set_comp_west_projs()
{
__set_comp "$(__west_x list --format={name} "$@")"
}
__set_comp_west_boards()
{
__set_comp "$(__west_x boards --format={name} "$@")"
}
__comp_west_west()
{
case "$prev" in
--zephyr-base|-z)
__set_comp_dirs
return
;;
# We don't know how to autocomplete any others
$(__west_to_extglob "$global_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $global_bool_opts $global_args_opts
;;
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp ${cmds[*]}
fi
;;
esac
}
__comp_west_init()
{
local init_args_opts="
--manifest -m
--manifest-rev --mr
--local -l
"
case "$prev" in
--local|-l)
__set_comp_dirs
return
;;
esac
case "$cur" in
-*)
__set_comp $init_args_opts
;;
esac
}
__comp_west_update()
{
local update_bool_opts="
--keep-descendants -k
--rebase -r
"
case "$cur" in
-*)
__set_comp $update_bool_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_list()
{
local list_args_opts="
--format -f
"
case "$prev" in
# We don't know how to autocomplete those
$(__west_to_extglob "$list_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $list_args_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_manifest()
{
local manifest_bool_opts="
--freeze
"
local manifest_args_opts="
--out -o
"
case "$prev" in
--out|-o)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $manifest_bool_opts $manifest_args_opts
;;
esac
}
__comp_west_diff()
{
case "$cur" in
*)
__set_comp_west_projs
;;
esac
}
__comp_west_status()
{
case "$cur" in
*)
__set_comp_west_projs
;;
esac
}
__comp_west_forall()
{
local forall_args_opts="
-c
"
case "$prev" in
# We don't know how to autocomplete those
$(__west_to_extglob "$forall_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $forall_args_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_config()
{
local config_bool_opts="
--global
--local
--system
"
case "$cur" in
-*)
__set_comp $config_bool_opts
;;
esac
}
__comp_west_help()
{
case "$cur" in
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp ${cmds[*]}
fi
;;
esac
}
# Zephyr extension commands
__comp_west_completion()
{
case "$cur" in
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp "bash"
fi
;;
esac
}
__comp_west_boards()
{
local boards_args_opts="
--format -f --name -n
--arch-root --board-root
"
case "$prev" in
--format|-f|--name|-n)
# We don't know how to autocomplete these.
return
;;
--arch-root)
__set_comp_dirs
return
;;
--board-root)
__set_comp_dirs
return
;;
esac
case "$cur" in
-*)
__set_comp $boards_args_opts
;;
esac
}
__comp_west_build()
{
local build_bool_opts="
--cmake -c
--cmake-only
-n --just-print --dry-run --recon
--force -f
"
local build_args_opts="
--board -b
--build-dir -d
--target -t
--pristine -p
--build-opt -o
"
case "$prev" in
--board|-b)
__set_comp_west_boards
return
;;
--build-dir|-d)
__set_comp_dirs
return
;;
--pristine|-p)
__set_comp "auto always never"
return
;;
# We don't know how to autocomplete those
$(__west_to_extglob "$build_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $build_bool_opts $build_args_opts
;;
*)
__set_comp_dirs
;;
esac
}
__comp_west_sign()
{
local sign_bool_opts="
--force -f
--bin --no-bin
--hex --no-hex
"
local sign_args_opts="
--build-dir -d
--tool -t
--tool-path -p
-B --sbin
-H --shex
"
case "$prev" in
--build-dir|-d|--tool-path|-p)
__set_comp_dirs
return
;;
--tool|-t)
__set_comp "imgtool"
return
;;
-B|--sbin|-H|--shex)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $sign_bool_opts $sign_args_opts
;;
esac
}
__comp_west_runner_cmd()
{
# Common arguments for runners
local runner_bool_opts="
--context -H
--skip-rebuild
"
local runner_args_opts="
--build-dir -d
--cmake-cache -c
--runner -r
--board-dir
--elf-file
--hex-file
--bin-file
--gdb
--openocd
--openocd-search
"
case "$prev" in
--build-dir|-d|--cmake-cache|-c|--board-dir|--gdb|--openocd|--openocd-search)
__set_comp_dirs
return
;;
--elf-file|--hex-file|--bin-file)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $runner_bool_opts $runner_args_opts
;;
esac
}
__comp_west_flash()
{
__comp_west_runner_cmd
}
__comp_west_debug()
{
__comp_west_runner_cmd
}
__comp_west_debugserver()
{
__comp_west_runner_cmd
}
__comp_west_attach()
{
__comp_west_runner_cmd
}
__comp_west()
{
local previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
# Reset to default, to make sure compgen works properly
local IFS=$' \t\n'
local builtin_cmds=(
init
update
list
manifest
diff
status
forall
config
help
)
local zephyr_ext_cmds=(
completion
boards
build
sign
flash
debug
debugserver
attach
zephyr-export
)
local cmds=(${builtin_cmds[*]} ${zephyr_ext_cmds[*]})
# Global options for all commands
local global_bool_opts="
--help -h
--verbose -v
--version -V
"
local global_args_opts="
--zephyr-base -z
"
COMPREPLY=()
local cur words cword prev
_get_comp_words_by_ref -n : cur words cword prev
local command='west' command_pos=0
local counter=1
while [ "$counter" -lt "$cword" ]; do
case "${words[$counter]}" in
west)
return 0
;;
$(__west_to_extglob "$global_args_opts") )
(( counter++ ))
;;
-*)
;;
=)
(( counter++ ))
;;
*)
command="${words[$counter]}"
command_pos=$counter
break
;;
esac
(( counter++ ))
done
# Construct the function name to be called
local completions_func=__comp_west_${command//-/_}
#echo "comp_func: ${completions_func}"
declare -F $completions_func >/dev/null && $completions_func
# Restore the user's extglob setting
eval "$previous_extglob_setting"
return 0
}
eval "$__west_previous_extglob_setting"
unset __west_previous_extglob_setting
complete -F __comp_west west