Skip to content

Commit 65406c0

Browse files
authored
Merge pull request #2308 from gabelluardo/refactor-bash
refactor: bash completion script
2 parents bc00bd4 + f07adff commit 65406c0

1 file changed

Lines changed: 88 additions & 46 deletions

File tree

autocomplete/bash_autocomplete

Lines changed: 88 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,105 @@
22

33
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.
44

5-
# Macs have bash3 for which the bash-completion package doesn't include
6-
# _init_completion. This is a minimal version of that function.
5+
# macOS still ships Bash 3, where bash-completion may not provide
6+
# _init_completion. This is a minimal compatible fallback.
77
__%[1]s_init_completion() {
88
COMPREPLY=()
9-
_get_comp_words_by_ref "$@" cur prev words cword
9+
if declare -F _comp_initialize >/dev/null 2>&1; then
10+
_comp_initialize "$@"
11+
else
12+
_get_comp_words_by_ref "$@" cur prev words cword
13+
fi
14+
}
15+
16+
__%[1]s_build_completion_request() {
17+
local -a words_before_cursor=("${COMP_WORDS[@]:0:${COMP_CWORD}}")
18+
local current_word="${COMP_WORDS[COMP_CWORD]}"
19+
20+
if [[ "${current_word}" == "-"* ]]; then
21+
printf '%%s %%s --generate-shell-completion' "${words_before_cursor[*]}" "${current_word}"
22+
else
23+
printf '%%s --generate-shell-completion' "${words_before_cursor[*]}"
24+
fi
25+
}
26+
27+
# Keep Bash 3 compatibility: associative arrays require Bash 4+, so
28+
# descriptions are looked up via parallel indexed arrays.
29+
__%[1]s_find_description_for() {
30+
local candidate="$1"
31+
local i
32+
33+
for i in "${!__cli_completion_tokens[@]}"; do
34+
if [[ "${__cli_completion_tokens[i]}" == "${candidate}" ]]; then
35+
printf '%%s' "${__cli_completion_descriptions[i]}"
36+
return 0
37+
fi
38+
done
39+
40+
return 1
1041
}
1142

1243
__%[1]s_bash_autocomplete() {
13-
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
14-
local cur opts base words
44+
local words=("${COMP_WORDS[@]}")
45+
46+
if [[ "${words[0]}" != "source" ]]; then
47+
local cur opts
48+
local cword="${COMP_CWORD}"
49+
local request_comp
50+
1551
COMPREPLY=()
16-
cur="${COMP_WORDS[COMP_CWORD]}"
17-
if declare -F _init_completion >/dev/null 2>&1; then
18-
_init_completion -n "=:" || return
19-
else
20-
__%[1]s_init_completion -n "=:" || return
21-
fi
22-
words=("${words[@]:0:$cword}")
23-
if [[ "$cur" == "-"* ]]; then
24-
requestComp="${words[*]} ${cur} --generate-shell-completion"
25-
else
26-
requestComp="${words[*]} --generate-shell-completion"
27-
fi
28-
opts=$(eval "${requestComp}" 2>/dev/null)
52+
cur="${words[$cword]}"
53+
54+
__%[1]s_init_completion -n "=:" || return
55+
56+
request_comp="$(__%[1]s_build_completion_request)"
57+
opts=$(eval "${request_comp}" 2>/dev/null)
58+
59+
# Completion output lines use "token:description" format.
60+
# Keep token/description in parallel arrays for Bash 3 compatibility.
61+
__cli_completion_tokens=()
62+
__cli_completion_descriptions=()
2963

30-
# Separate completions and descriptions
31-
local completions=()
32-
local descriptions=()
64+
local line
3365
local longest=0
3466
while IFS=$'\n' read -r line; do
35-
local comp_part desc_part
36-
if [[ "$line" == *:* ]]; then
37-
comp_part="${line%%:*}"
38-
desc_part="${line#*:}"
39-
else
40-
comp_part="$line"
41-
desc_part=""
42-
fi
43-
completions+=("$comp_part")
44-
descriptions+=("$desc_part")
45-
(( ${#comp_part} > longest )) && longest=${#comp_part}
46-
done <<< "$opts"
47-
48-
# Format completions with aligned descriptions
49-
for i in "${!completions[@]}"; do
50-
local padded_completion="${completions[i]}"
51-
local pad_len=$((longest - ${#padded_completion}))
52-
if (( pad_len > 0 )); then
53-
padded_completion="${padded_completion}$(head -c $pad_len < /dev/zero | tr '\0' ' ')"
54-
fi
67+
local token="${line}"
68+
local description=""
5569

56-
if [[ -n "${descriptions[i]}" ]]; then
57-
COMPREPLY+=("${padded_completion} -- ${descriptions[i]}")
58-
else
59-
COMPREPLY+=("${padded_completion}")
70+
if [[ "${line}" == *:* ]]; then
71+
token="${line%%:*}"
72+
description="${line#*:}"
73+
fi
74+
75+
if [[ -z "${token}" ]]; then
76+
continue
6077
fi
61-
done
78+
79+
__cli_completion_tokens+=("${token}")
80+
__cli_completion_descriptions+=("${description}")
81+
(( ${#token} > longest )) && longest=${#token}
82+
done <<< "${opts}"
83+
84+
local matches=( $(compgen -W "${__cli_completion_tokens[*]}" -- "${cur}") )
85+
86+
# COMP_TYPE=63 means Bash is listing matches (usually on second TAB).
87+
if [[ "${COMP_TYPE:-}" == "63" && ${#matches[@]} -gt 0 ]]; then
88+
local listed=()
89+
local candidate
90+
for candidate in "${matches[@]}"; do
91+
local desc="$(__%[1]s_find_description_for "${candidate}")"
92+
93+
if [[ -n "${desc}" ]]; then
94+
local padded="$(printf '%%-*s' "${longest}" "${candidate}")"
95+
listed+=("${padded} -- ${desc}")
96+
else
97+
listed+=("${candidate}")
98+
fi
99+
done
100+
COMPREPLY=("${listed[@]}")
101+
else
102+
COMPREPLY=("${matches[@]}")
103+
fi
62104

63105
return 0
64106
fi

0 commit comments

Comments
 (0)