Must do escaping earlier to match what the user typed

Also avoid using 'printf -v' by using "read" patterns, inspired by
PR #2126 from of "Jeffrey Faer".

Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
Co-authored-by: Jeffrey Faer <jeffrey.faer@gmail.com>
This commit is contained in:
Marc Khouzam 2025-01-17 21:45:47 -05:00
parent 74a2762d10
commit 42ce70a2a5

View file

@ -201,6 +201,8 @@ __%[1]s_extract_activeHelp() {
local endIndex=${#activeHelpMarker} local endIndex=${#activeHelpMarker}
while IFS='' read -r comp; do while IFS='' read -r comp; do
[[ -z $comp ]] && continue
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
comp=${comp:endIndex} comp=${comp:endIndex}
__%[1]s_debug "ActiveHelp found: $comp" __%[1]s_debug "ActiveHelp found: $comp"
@ -223,16 +225,21 @@ __%[1]s_handle_completion_types() {
# If the user requested inserting one completion at a time, or all # If the user requested inserting one completion at a time, or all
# completions at once on the command-line we must remove the descriptions. # completions at once on the command-line we must remove the descriptions.
# https://github.com/spf13/cobra/issues/1508 # https://github.com/spf13/cobra/issues/1508
local tab=$'\t' comp
while IFS='' read -r comp; do # If there are no completions, we don't need to do anything
[[ -z $comp ]] && continue (( ${#completions[@]} == 0 )) && return 0
# Strip any description
comp=${comp%%%%$tab*} local tab=$'\t'
# Only consider the completions that match
if [[ $comp == "$cur"* ]]; then # Strip any description and escape the completion to handled special characters
COMPREPLY+=( "$(printf %%q "$comp")" ) IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]%%%%$tab*}")
fi
done < <(printf "%%s\n" "${completions[@]}") # Only consider the completions that match
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
# compgen looses the escaping so we need to escape all completions again since they will
# all be inserted on the command-line.
IFS=$'\n' read -ra COMPREPLY -d '' < <(printf "%%q\n" "${COMPREPLY[@]}")
;; ;;
*) *)
@ -243,18 +250,25 @@ __%[1]s_handle_completion_types() {
} }
__%[1]s_handle_standard_completion_case() { __%[1]s_handle_standard_completion_case() {
local tab=$'\t' comp local tab=$'\t'
# If there are no completions, we don't need to do anything
(( ${#completions[@]} == 0 )) && return 0
# Short circuit to optimize if we don't have descriptions # Short circuit to optimize if we don't have descriptions
if [[ "${completions[*]}" != *$tab* ]]; then if [[ "${completions[*]}" != *$tab* ]]; then
local compgen_words=$(printf "%%s\n" "${completions[@]}") # First, escape the completions to handle special characters
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "$cur") IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]}")
# Only consider the completions that match what the user typed
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
# If there is a single completion left, escape the completion # compgen looses the escaping so, if there is only a single completion, we need to
if ((${#COMPREPLY[*]} == 1)); then # escape it again because it will be inserted on the command-line. If there are multiple
COMPREPLY[0]=$(printf %%q "${COMPREPLY[0]}") # completions, we don't want to escape them because they will be printed in a list
# and we don't want to show escape characters in that list.
if (( ${#COMPREPLY[@]} == 1 )); then
COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]}")
fi fi
return 0 return 0
fi fi
@ -263,23 +277,39 @@ __%[1]s_handle_standard_completion_case() {
# Look for the longest completion so that we can format things nicely # Look for the longest completion so that we can format things nicely
while IFS='' read -r compline; do while IFS='' read -r compline; do
[[ -z $compline ]] && continue [[ -z $compline ]] && continue
# Strip any description before checking the length
comp=${compline%%%%$tab*} # Before checking if the completion matches what the user typed,
# we need to strip any description and escape the completion to handle special
# characters because those escape characters are part of what the user typed.
# Don't call "printf" in a sub-shell because it will be much slower
# since we are in a loop.
printf -v comp "%%q" "${compline%%%%$tab*}" &>/dev/null || comp=$(printf "%%q" "${compline%%%%$tab*}")
# Only consider the completions that match # Only consider the completions that match
[[ $comp == "$cur"* ]] || continue [[ $comp == "$cur"* ]] || continue
# The completions matches. Add it to the list of full completions including
# its description. We don't escape the completion because it may get printed
# in a list if there are more than one and we don't want show escape characters
# in that list.
COMPREPLY+=("$compline") COMPREPLY+=("$compline")
# Strip any description before checking the length, and again, don't escape
# the completion because this length is only used when printing the completions
# in a list and we don't want show escape characters in that list.
comp=${compline%%%%$tab*}
if ((${#comp}>longest)); then if ((${#comp}>longest)); then
longest=${#comp} longest=${#comp}
fi fi
done < <(printf "%%s\n" "${completions[@]}") done < <(printf "%%s\n" "${completions[@]}")
# If there is a single completion left, remove the description text and escape the completion # If there is a single completion left, remove the description text and escape any special characters
if ((${#COMPREPLY[*]} == 1)); then if ((${#COMPREPLY[*]} == 1)); then
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
comp="${COMPREPLY[0]%%%%$tab*}" COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]%%%%$tab*}")
__%[1]s_debug "Removed description from single completion, which is now: ${comp}" __%[1]s_debug "Removed description from single completion, which is now: ${COMPREPLY[0]}"
COMPREPLY[0]=$(printf %%q "${comp}") else
else # Format the descriptions # Format the descriptions
__%[1]s_format_comp_descriptions $longest __%[1]s_format_comp_descriptions $longest
fi fi
} }