Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement BASH_COMPLETION_FINALIZE_HOOKS #739

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,77 @@ _comp_variable_assignments()
return 0
}

_comp_finalize__depth=()
_comp_finalize__target=()
_comp_finalize__original_return_trap=
_comp_finalize__original_int_trap=

# This associative array contains the finalizer commands with the key
# being the name of the completed command.
declare -gA BASH_COMPLETION_FINALIZE_CMD_HOOKS

# This array contains the general finalizer commands that will be
# executed for all the commands.
declare -ga BASH_COMPLETION_FINALIZE_HOOKS

# This array contains the finalizer commands that will be executed for the
# top-level bash-completion functions. Unlike BASH_COMPLETION_FINALIZE_HOOKS,
# these hooks are only called at the end of the top-level bash-completion.
# These hooks are ensured to be called even when the completion is canceled by
# SIGINT.
declare -ga BASH_COMPLETION_FINALIZE_TOPLEVEL_HOOKS

_comp_finalize__clear()
{
local _hook
if [[ ${BASH_COMPLETION_FINALIZE_TOPLEVEL_HOOKS[*]+set} ]]; then
for _hook in "${BASH_COMPLETION_FINALIZE_TOPLEVEL_HOOKS[@]}"; do
eval -- "$_hook"
done
fi
_comp_finalize__depth=()
_comp_finalize__target=()
eval -- "${_comp_finalize__original_int_trap:-trap - INT}"
eval -- "${_comp_finalize__original_return_trap:-trap - RETURN}"
_comp_finalize__original_int_trap=
_comp_finalize__original_return_trap=
}
_comp_finalize()
{
((${#_comp_finalize__depth[@]})) || return 0
while ((${#FUNCNAME[@]} <= ${_comp_finalize__depth[-1]})); do
if [[ ${#FUNCNAME[@]} -eq ${_comp_finalize__depth[-1]} && ${FUNCNAME[1]-} == "${_comp_finalize__target[-1]}" ]]; then
# Call finalizer for each command
local cmd=${words[0]-} _hook
if [[ $cmd ]]; then
_hook=${BASH_COMPLETION_FINALIZE_CMD_HOOKS[$cmd]-}
eval -- "$_hook"
fi

# Call general finalizers
if [[ ${BASH_COMPLETION_FINALIZE_HOOKS[*]+set} ]]; then
for _hook in "${BASH_COMPLETION_FINALIZE_HOOKS[@]}"; do
eval -- "$_hook"
done
fi
fi

# Note: bash 4.2 does not support negative array indices with `unset`
# like `unset -v 'arr[-1]'` even when the array has at least one
# element. It is supported in bash 4.3.
unset -v '_comp_finalize__depth[${#_comp_finalize__depth[@]}-1]'
unset -v '_comp_finalize__target[${#_comp_finalize__target[@]}-1]'
if ((${#_comp_finalize__depth[@]} == 0)); then
_comp_finalize__clear
break
fi
done
}
# Note: We need to set "trace" function attribute of _comp_finalize{,__clear}
# to make the trap restoration by "trap - RETURN" take effect in the upper
# level.
declare -ft _comp_finalize__clear _comp_finalize

# Initialize completion and deal with various general things: do file
# and variable completion where appropriate, and adjust prev, words,
# and cword as if no redirections exist so that completions do not
Expand Down Expand Up @@ -1372,6 +1443,39 @@ _comp_initialize()
{
local exclude="" opt_split="" outx="" errx="" inx=""

if ((${#FUNCNAME[@]} >= 2)); then
# Install "_comp_finalize" to the RETURN trap when "_init_completion"
# is called for the top-level completion. [ Note: the completion
# function may be called recursively using "_command_offset", etc. ]
if ((${#_comp_finalize__depth[@]} == 0)); then
_comp_finalize__original_int_trap=$(trap -p INT)
if shopt -q extdebug || shopt -qo functrace; then
# If extdebug / functrace is set, we need to explicitly save
# and restore the original trap handler because the outer trap
# handlers will be affected by "trap - RETURN" inside functions
# with these settings.
_comp_finalize__original_return_trap=$(trap -p RETURN)
else
# Otherwise, the outer RETURN trap will be restored when the
# RETURN trap is removed inside the functions using "trap -
# RETURN". So, we do not need to explicitly save the outer
# trap handler.
_comp_finalize__original_return_trap=
fi

# Note: Ignore the traps previously set by us to avoid infinite
# loop in case that the previously set traps remain by some
# accidents.
_comp_finalize__original_return_trap=${_comp_finalize__original_return_trap##"trap -- '_comp_finalize"*}
_comp_finalize__original_int_trap=${_comp_finalize__original_int_trap##"trap -- '_comp_finalize"*}

trap _comp_finalize RETURN
trap '_comp_finalize__clear; kill -INT "$BASHPID"' INT
fi
_comp_finalize__depth+=("${#FUNCNAME[@]}")
_comp_finalize__target+=("${FUNCNAME[1]-}")
fi

local flag OPTIND=1 OPTARG="" OPTERR=0
while getopts "n:e:o:i:s" flag "$@"; do
case $flag in
Expand Down
Loading