From 3c78872ad6ac054cb48004c67307f30a46023cb2 Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Tue, 27 Feb 2024 23:11:42 -0700 Subject: [PATCH 1/3] fix(bash): Fix bash completion for suggestions that contain special characters. Special characters include whitepace, so this is more general than #1743. This should also fix #1740. I added some test cases to cobra-completion-testing. This PR makes them pass. It also doesn't trip any of the performance regression tests. I'm happy to submit those tests as a PR as well. - https://github.com/JeffFaer/cobra-completion-testing/tree/special_characters - https://github.com/JeffFaer/cobra-completion-testing/commit/52254c1a25b9816cf31a617cf9667e61a1bd2259 --- bash_completionsV2.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 1cce5c32..cf3a361c 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -59,7 +59,10 @@ __%[1]s_get_completion_results() { # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows handling aliases args=("${words[@]:1}") - requestComp="${words[0]} %[2]s ${args[*]}" + requestComp="${words[0]} %[2]s" + if [[ "${#args[@]}" -gt 0 ]]; then + requestComp+="$(printf " %%q" "${args[@]}")" + fi lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -224,15 +227,24 @@ __%[1]s_handle_completion_types() { # completions at once on the command-line we must remove the descriptions. # https://github.com/spf13/cobra/issues/1508 local tab=$'\t' comp - while IFS='' read -r comp; do + local matches=() + for comp in "${completions[@]}"; do [[ -z $comp ]] && continue # Strip any description comp=${comp%%%%$tab*} # Only consider the completions that match if [[ $comp == "$cur"* ]]; then - COMPREPLY+=("$comp") - fi - done < <(printf "%%s\n" "${completions[@]}") + # Strictly speaking we could append directly to COMPREPLY here. + # But there's a pretty big performance hit involved with + # creating one subshell to printf %%q for each completion that + # matches. Instead, batch all the matches up so we can quote + # them all at once in a single printf call. + matches+=( "$comp" ) + fi + done + while IFS='' read -r comp; do + COMPREPLY+=( "$comp" ) + done < <(printf "%%q\n" "${matches[@]}") ;; *) @@ -247,7 +259,12 @@ __%[1]s_handle_standard_completion_case() { # Short circuit to optimize if we don't have descriptions if [[ "${completions[*]}" != *$tab* ]]; then - IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur") + # compgen's -W option respects shell quoting, so we need to escape. + local compgen_words="$(printf "%%q\n" "${completions[@]}")" + # compgen appears to respect shell quoting _after_ checking whether + # they have the right prefix, so we also need to quote cur. + local compgen_cur="$(printf "%%q" "${cur}")" + IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${compgen_words}" -- "${compgen_cur}") return 0 fi @@ -271,7 +288,7 @@ __%[1]s_handle_standard_completion_case() { __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" comp="${COMPREPLY[0]%%%%$tab*}" __%[1]s_debug "Removed description from single completion, which is now: ${comp}" - COMPREPLY[0]=$comp + COMPREPLY[0]="$(printf "%%q" "${comp}")" else # Format the descriptions __%[1]s_format_comp_descriptions $longest fi From fcc5b8c34c7e6185170da3a0acb5583333cc151e Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Fri, 19 Apr 2024 23:25:37 -0600 Subject: [PATCH 2/3] fix: We don't need an extra empty character when we're doing proper quoting. --- bash_completionsV2.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bash_completionsV2.go b/bash_completionsV2.go index cf3a361c..98c56968 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -68,13 +68,6 @@ __%[1]s_get_completion_results() { lastChar=${lastParam:$((${#lastParam}-1)):1} __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}" - if [[ -z ${cur} && ${lastChar} != = ]]; then - # If the last parameter is complete (there is a space following it) - # We add an extra empty parameter so we can indicate this to the go method. - __%[1]s_debug "Adding extra empty parameter" - requestComp="${requestComp} ''" - fi - # When completing a flag with an = (e.g., %[1]s -n=) # bash focuses on the part after the =, so we need to remove # the flag part from $cur From 9ac0f55513b5fc544a6dfd1e5cd1b206f452eb1b Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Wed, 1 May 2024 12:54:23 -0600 Subject: [PATCH 3/3] Don't quote different completion types. They're not going through compgen so they don't need it. --- bash_completionsV2.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 98c56968..79846bb4 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -220,24 +220,15 @@ __%[1]s_handle_completion_types() { # completions at once on the command-line we must remove the descriptions. # https://github.com/spf13/cobra/issues/1508 local tab=$'\t' comp - local matches=() for comp in "${completions[@]}"; do [[ -z $comp ]] && continue # Strip any description comp=${comp%%%%$tab*} # Only consider the completions that match if [[ $comp == "$cur"* ]]; then - # Strictly speaking we could append directly to COMPREPLY here. - # But there's a pretty big performance hit involved with - # creating one subshell to printf %%q for each completion that - # matches. Instead, batch all the matches up so we can quote - # them all at once in a single printf call. - matches+=( "$comp" ) + COMPREPLY+=( "$comp" ) fi done - while IFS='' read -r comp; do - COMPREPLY+=( "$comp" ) - done < <(printf "%%q\n" "${matches[@]}") ;; *)