diff --git a/bash_completion b/bash_completion index 6b53346dcf6..b273d7ea42a 100644 --- a/bash_completion +++ b/bash_completion @@ -1485,6 +1485,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 @@ -1522,6 +1593,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