Skip to content

Commit

Permalink
Merge pull request #12296 from keymanapp/feat/builder-autocomplete
Browse files Browse the repository at this point in the history
feat: add builder tab-completion script
  • Loading branch information
jahorton authored Jan 15, 2025
2 parents e1ae48e + 91289b6 commit da5152f
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 0 deletions.
26 changes: 26 additions & 0 deletions resources/builder.inc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,10 @@ _builder_parse_expanded_parameters() {
# internal reporting function, ignores all other parameters
_builder_report_dependencies
;;
--builder-completion-describe)
_builder_completion_describe
exit 0
;;
--offline)
_builder_offline=--offline
;;
Expand Down Expand Up @@ -1502,6 +1506,28 @@ _builder_pad() {
printf $fmt "$text1" "$text2"
}

_builder_completion_describe() {
printf '%s ' "${_builder_actions[@]}"
echo -n "; "
printf '%s ' "${_builder_targets[@]}"
echo -n "; "
# Remove all '+' suffixes from options; they're a config on the option, not part
# of the actual option text itself.
local _builder_opts=()
for e in "${!_builder_params[@]}"; do
if [[ $e =~ ^-- ]]; then
_builder_opts+=(${e%+*})
fi
done

# Add default options
_builder_opts+=( --verbose --debug --color --no-color --offline --help )
if builder_has_dependencies; then
_builder_opts+=( --deps --no-deps --force-deps )
fi
printf '%s ' "${_builder_opts[@]}"
}

builder_display_usage() {
local e program description

Expand Down
129 changes: 129 additions & 0 deletions resources/builder_completion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# As this script must be sourced by shells to be useful, no practical #shebang is possible.

# Note: must 'source' this file again in the terminal after any edits!
_comp_builder() {
# Does the file actually exist? If not, abort.
local CMD_PATH=`readlink -f "$1"`
if [ -z "${CMD_PATH}" ]; then
exit 0
fi

# Detect - is this a keyman repo? If not, exit early; we don't expect repo-external compatibility.
local CMD_DIR="$(dirname "${CMD_PATH}")"
cd "${CMD_DIR}"
# Works correctly even if the directory has no backing repository.
git remote get-url origin | grep -q 'github.com/keymanapp' || exit 0

# Some of the configuration differs when ZSH is the OS's primary terminal; this flag
# may be used to tailor this function accordingly.
local IS_ZSH=false
if [ "${SHELL##*/}" = "zsh" ]; then
IS_ZSH=true
fi

# Warning - zsh uses -A, not -a, for array-read operations!
# This is 'sourced' by the shell profile script, so macOS zsh terminal
# usage must be aware of this and adapt.
local READ_ARRAY="-a"
if [ $IS_ZSH = true ]; then
READ_ARRAY="-A"
fi

# Is it actually a builder-script? If not, abort. If so, it defines
# a special option used to provide us with completion-target data.
local builder_params=`${CMD_PATH} --builder-completion-describe` || exit 0

local BASE_NAME=$(basename "$CMD_PATH")
local BUILDER_ARG_STR="${COMP_LINE##*${BASE_NAME} }"

local builder_args
# Does not actually preserve an empty token at the end.
read -r $READ_ARRAY builder_args <<< "${BUILDER_ARG_STR}"

# Determine the current token (given the caret position) and
# all existing, already-completed actions, targets, and options
# for the script before the current token.
local current_token=
# If the caret is adjacent to non-whitespace - thus is editing
# a builder argument...
if [ ! -z "${COMP_WORDS[COMP_CWORD]}" ]; then
# Then note the last token as the token being edited...
current_token="${builder_args[-1]}"
# ... and thus not pre-completed.
unset builder_args[-1]
fi

# Parse the builder-description for completion target data.
local action_str target_str option_str
IFS=";" read -r action_str target_str option_str <<< "$builder_params"
local actions targets options
IFS=" " read -r $READ_ARRAY actions <<< "$action_str"
IFS=" " read -r $READ_ARRAY targets <<< "$target_str"
IFS=" " read -r $READ_ARRAY options <<< "$option_str"

local action target
if [[ $current_token =~ : ]]; then
IFS=: read -r action target <<< "$current_token"
target=:$target
else
action="$current_token"
target=
fi

local all=()
# If there is no $action component, it's a standalone target.

if [[ -z "$action" ]] && [[ -z "$target" ]]; then
all+="${actions[@]}"
all+=" ${targets[@]}"
all+=" ${options[@]}"

COMPREPLY=( $(compgen -W "${all[@]}" -- "${current_token}") )
elif [[ -z "$action" ]]; then
# It's an unpaired target.
all="${targets[@]}"
COMPREPLY=( $(compgen -W "${all[@]}" -- "${current_token}") )

if [ $IS_ZSH == false ]; then
# bash doesn't handle completion with colons well; we should remove the
# colon prefix from each entry. ZSH actually doesn't have this issue.
local i
for (( i=0; i<${#COMPREPLY[@]}; i++ )); do
COMPREPLY[$i]="${COMPREPLY[$i]##*:}"
done
fi
elif [[ -z "$target" ]]; then
# It's an untargeted action or an option.
all+="${actions[@]}"
all+=" ${options[@]}"

COMPREPLY=( $(compgen -W "${all[@]}" -- "${current_token}") )
else
# Ah, an action-target pair. We have an existing action, so we only need to
# complete the target part.

# First, build up the list of legal paired tokens.
local actiontargets=()
for e in "${targets[@]}"; do
actiontargets+=("${action}${e}")
done

# Now, complete from that.
all+="${actiontargets[@]}"
COMPREPLY=( $(compgen -W "${all[@]}" -- "${current_token}") )

if [ $IS_ZSH == false ]; then
# bash doesn't handle completion with colons well; we should remove the
# colon prefix from each entry. ZSH actually doesn't have this issue.
local i
for (( i=0; i<${#COMPREPLY[@]}; i++ )); do
COMPREPLY[$i]="${COMPREPLY[$i]##*:}"
done
fi
fi
}

# When this script is sourced by .bashrc or similar, this provides autocompletion for
# builder scripts named build.sh and test.sh.
complete -o bashdefault -F _comp_builder build.sh
complete -o bashdefault -F _comp_builder test.sh

0 comments on commit da5152f

Please sign in to comment.