From 8d46e6f478ff39b0fc31ae9e74dfce8a456ccb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 9 Apr 2022 10:14:00 +0300 Subject: [PATCH 1/6] feat(bash_completion): scratch `_comp_return_hook` --- bash_completion | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bash_completion b/bash_completion index 6b53346dcf6..daac932032d 100644 --- a/bash_completion +++ b/bash_completion @@ -1485,6 +1485,14 @@ _comp_variable_assignments() return 0 } +_comp_return_hook() +{ + ((${#FUNCNAME[*]} != 2)) && return # this _will_ need some refinement and thought + echo "Hello from return hook for ${FUNCNAME[1]}" + echo "words: ${words[@]}" + echo "COMPREPLY: ${COMPREPLY[@]}" +} + # 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 +1530,8 @@ _comp_initialize() { local exclude="" opt_split="" outx="" errx="" inx="" + trap _comp_return_hook RETURN + local flag OPTIND=1 OPTARG="" OPTERR=0 while getopts "n:e:o:i:s" flag "$@"; do case $flag in From 3c7a9cf5fb414ed36884f83e6ea53ff71444707e Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 12 Apr 2022 17:34:18 +0900 Subject: [PATCH 2/6] feat(bash_completion): support `BASH_COMPLETION_FINALIZE{,_CMD}_HOOKS` - deal with nested completion initializations - rename the function `_comp_{return_hook => finalize}` - add an associative array `BASH_COMPLETION_FINALIZE_CMD_HOOKS` originally suggested as `_comp_return_hooks` in Ref. [1] - add a new array `BASH_COMPLETION_FINALIZE_HOOKS` [1] https://github.com/scop/bash-completion/issues/720#issuecomment-1093764792 --- bash_completion | 73 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/bash_completion b/bash_completion index daac932032d..4f69bd23501 100644 --- a/bash_completion +++ b/bash_completion @@ -1485,13 +1485,51 @@ _comp_variable_assignments() return 0 } -_comp_return_hook() -{ - ((${#FUNCNAME[*]} != 2)) && return # this _will_ need some refinement and thought - echo "Hello from return hook for ${FUNCNAME[1]}" - echo "words: ${words[@]}" - echo "COMPREPLY: ${COMPREPLY[@]}" +_comp_finalize__depth=() +_comp_finalize__target=() +_comp_finalize__original_return_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 + +_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]-} _comp_local_hook + if [[ $cmd ]]; then + _comp_local_hook=${BASH_COMPLETION_FINALIZE_CMD_HOOKS[$cmd]-} + eval -- "$_comp_local_hook" + fi + + # Call general finalizers + if [[ ${BASH_COMPLETION_FINALIZE_HOOKS[*]+set} ]]; then + for _comp_local_hook in "${BASH_COMPLETION_FINALIZE_HOOKS[@]}"; do + eval -- "$_comp_local_hook" + done + fi + fi + + unset -v '_comp_finalize__depth[-1]' + unset -v '_comp_finalize__target[-1]' + if ((${#_comp_finalize__depth[@]} == 0)); then + eval -- "${_comp_finalize__original_return_trap:-trap - RETURN}" + _comp_finalize__original_return_trap= + break + fi + done } +# Note: We need to set "trace" function attribute of _comp_finalize to +# make the trap restoration by "trap - RETURN" take effect in the +# upper level. +declare -ft _comp_finalize # Initialize completion and deal with various general things: do file # and variable completion where appropriate, and adjust prev, words, @@ -1530,7 +1568,28 @@ _comp_initialize() { local exclude="" opt_split="" outx="" errx="" inx="" - trap _comp_return_hook RETURN + 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 + 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 + trap _comp_finalize RETURN + 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 From 6c9536bf30d2c2ace982d698a897a8bf4fe0837f Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 8 May 2022 20:06:15 +0900 Subject: [PATCH 3/6] refactor(_comp_finalize): use a simple prefix for local variables --- bash_completion | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bash_completion b/bash_completion index 4f69bd23501..1033a29c080 100644 --- a/bash_completion +++ b/bash_completion @@ -1503,16 +1503,16 @@ _comp_finalize() 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]-} _comp_local_hook + local cmd=${words[0]-} _hook if [[ $cmd ]]; then - _comp_local_hook=${BASH_COMPLETION_FINALIZE_CMD_HOOKS[$cmd]-} - eval -- "$_comp_local_hook" + _hook=${BASH_COMPLETION_FINALIZE_CMD_HOOKS[$cmd]-} + eval -- "$_hook" fi # Call general finalizers if [[ ${BASH_COMPLETION_FINALIZE_HOOKS[*]+set} ]]; then - for _comp_local_hook in "${BASH_COMPLETION_FINALIZE_HOOKS[@]}"; do - eval -- "$_comp_local_hook" + for _hook in "${BASH_COMPLETION_FINALIZE_HOOKS[@]}"; do + eval -- "$_hook" done fi fi From 6d0830f8d81ea97e4ccc1c545cca3c0547e4ea65 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Thu, 1 Sep 2022 13:23:30 +0900 Subject: [PATCH 4/6] fix(_comp_finalize): avoid negative indices in unset for bash 4.2 --- bash_completion | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bash_completion b/bash_completion index 1033a29c080..08988777fe5 100644 --- a/bash_completion +++ b/bash_completion @@ -1517,8 +1517,11 @@ _comp_finalize() fi fi - unset -v '_comp_finalize__depth[-1]' - unset -v '_comp_finalize__target[-1]' + # 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 eval -- "${_comp_finalize__original_return_trap:-trap - RETURN}" _comp_finalize__original_return_trap= From 7c04d0d2bdad1515647ccbc5104c0c5a75f164b4 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Thu, 1 Sep 2022 14:49:57 +0900 Subject: [PATCH 5/6] fix(_comp_finalize): work around INT --- bash_completion | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/bash_completion b/bash_completion index 08988777fe5..1a4b9c06b1f 100644 --- a/bash_completion +++ b/bash_completion @@ -1488,6 +1488,7 @@ _comp_variable_assignments() _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. @@ -1497,6 +1498,28 @@ declare -gA BASH_COMPLETION_FINALIZE_CMD_HOOKS # 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 @@ -1523,16 +1546,15 @@ _comp_finalize() unset -v '_comp_finalize__depth[${#_comp_finalize__depth[@]}-1]' unset -v '_comp_finalize__target[${#_comp_finalize__target[@]}-1]' if ((${#_comp_finalize__depth[@]} == 0)); then - eval -- "${_comp_finalize__original_return_trap:-trap - RETURN}" - _comp_finalize__original_return_trap= + _comp_finalize__clear break fi done } -# Note: We need to set "trace" function attribute of _comp_finalize to -# make the trap restoration by "trap - RETURN" take effect in the -# upper level. -declare -ft _comp_finalize +# 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, @@ -1576,6 +1598,7 @@ _comp_initialize() # 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 @@ -1588,7 +1611,15 @@ _comp_initialize() # 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]-}") From b7f6544dd526f2285305dea6459d9c9872d253fe Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 20 Mar 2023 08:05:37 +0900 Subject: [PATCH 6/6] refactor: fold comment lines on column 80 --- bash_completion | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bash_completion b/bash_completion index 1a4b9c06b1f..b273d7ea42a 100644 --- a/bash_completion +++ b/bash_completion @@ -1594,21 +1594,22 @@ _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. ] + # 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. + # 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. + # 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