diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 1cce5c3..c57c06b 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -59,19 +59,18 @@ __%[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 + # Previous args should already be escaped... + requestComp+=" ${args[*]::${#args[@]}-1}" + # ...but the current arg might not yet be escaped. + requestComp+=" $(printf "%%q" "${args[${#args[@]}-1]}")" + fi lastParam=${words[$((${#words[@]}-1))]} 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 @@ -173,8 +172,9 @@ __%[1]s_process_completion_results() { __%[1]s_handle_completion_types fi - __%[1]s_handle_special_char "$cur" : - __%[1]s_handle_special_char "$cur" = + __%[1]s_handle_wordbreaks "$cur" + + __%[1]s_debug "The final COMPREPLY: $(printf "%%s\n" "${COMPREPLY[@]}")" # Print the activeHelp statements before we finish if ((${#activeHelp[*]} != 0)); then @@ -224,20 +224,28 @@ __%[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 + 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[@]}") + fi + done + + IFS=$'\n' read -ra COMPREPLY -d '' < <(printf "%%q\n" "${COMPREPLY[@]}") ;; *) # Type: complete (normal completion) __%[1]s_handle_standard_completion_case + + # If there is a single completion left, escape the completion + if ((${#COMPREPLY[@]} == 1)); then + COMPREPLY[0]="$(printf "%%q" "${COMPREPLY[0]}")" + fi + ;; esac } @@ -247,7 +255,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,21 +284,30 @@ __%[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]="${comp}" else # Format the descriptions __%[1]s_format_comp_descriptions $longest fi } -__%[1]s_handle_special_char() +__%[1]s_handle_wordbreaks() { + if ((${#COMPREPLY[@]} == 0)); then + return; + fi + local comp="$1" - local char=$2 - if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then - local word=${comp%%"${comp##*${char}}"} - local idx=${#COMPREPLY[*]} - while ((--idx >= 0)); do - COMPREPLY[idx]=${COMPREPLY[idx]#"$word"} + local i prefix + for ((i=0; i < ${#comp}; i++)); do + local char="${comp:$i:1}" + if [[ "${COMP_WORDBREAKS}" == *"${char}"* ]]; then + prefix="${comp::$i+1}" + fi + done + + if [[ -n "${prefix}" ]]; then + for ((i=0; i < ${#COMPREPLY[@]}; i++)); do + COMPREPLY[i]=${COMPREPLY[i]#$prefix} done fi } @@ -342,6 +364,21 @@ __start_%[1]s() COMPREPLY=() + # Omit wordbreaks that would need to be escaped. + local wordbreaks i + for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do + local char="${COMP_WORDBREAKS:$i:1}" + if [[ $'\n\t ' == *"${char}"* ]]; then + wordbreaks+="${char}" + continue + fi + if [[ "${char}" == "$(printf "%%q" "${char}")" ]]; then + wordbreaks+="${char}" + continue + fi + done + COMP_WORDBREAKS="${wordbreaks}" + # Call _init_completion from the bash-completion package # to prepare the arguments properly if declare -F _init_completion >/dev/null 2>&1; then