Skip to content

Commit 1fc3323

Browse files
committed
feat(comp): Bring in Cobra's bash script with desc
This is the bash completion that has been proposed for Cobra in spf13/cobra#1146 Signed-off-by: Marc Khouzam <[email protected]>
1 parent 41c7fb6 commit 1fc3323

File tree

2 files changed

+287
-1
lines changed

2 files changed

+287
-1
lines changed

cmd/helm/completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
152152
}
153153

154154
func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
155-
err := cmd.Root().GenBashCompletionV2(out, !disableCompDescriptions)
155+
err := completion.GenBashCompletion(out, !disableCompDescriptions)
156156

157157
// In case the user renamed the helm binary (e.g., to be able to run
158158
// both helm2 and helm3), we hook the new binary name to the completion function
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
package completion
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// GenBashCompletion generates the bash completion script
12+
func GenBashCompletion(w io.Writer, includeDesc bool) error {
13+
compCmd := cobra.ShellCompRequestCmd
14+
if !includeDesc {
15+
compCmd = cobra.ShellCompNoDescRequestCmd
16+
}
17+
buf := new(bytes.Buffer)
18+
buf.WriteString(fmt.Sprintf(`# Helm's own bash completion
19+
20+
__helm_debug()
21+
{
22+
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
23+
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
24+
fi
25+
}
26+
27+
# Macs have bash3 for which the bash-completion package doesn't include
28+
# _init_completion. This is a minimal version of that function.
29+
__helm_init_completion()
30+
{
31+
COMPREPLY=()
32+
_get_comp_words_by_ref "$@" cur prev words cword
33+
}
34+
35+
# This function calls the helm program to obtain the completion
36+
# results and the directive. It fills the 'out' and 'directive' vars.
37+
__helm_get_completion_results() {
38+
local requestComp lastParam lastChar args
39+
40+
# Prepare the command to request completions for the program.
41+
# Calling ${words[0]} instead of directly helm allows to handle aliases
42+
args=("${words[@]:1}")
43+
requestComp="${words[0]} %[1]s ${args[*]}"
44+
45+
lastParam=${words[$((${#words[@]}-1))]}
46+
lastChar=${lastParam:$((${#lastParam}-1)):1}
47+
__helm_debug "lastParam ${lastParam}, lastChar ${lastChar}"
48+
49+
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
50+
# If the last parameter is complete (there is a space following it)
51+
# We add an extra empty parameter so we can indicate this to the go method.
52+
__helm_debug "Adding extra empty parameter"
53+
requestComp="${requestComp} ''"
54+
fi
55+
56+
# When completing a flag with an = (e.g., helm -n=<TAB>)
57+
# bash focuses on the part after the =, so we need to remove
58+
# the flag part from $cur
59+
if [[ "${cur}" == -*=* ]]; then
60+
cur="${cur#*=}"
61+
fi
62+
63+
__helm_debug "Calling ${requestComp}"
64+
# Use eval to handle any environment variables and such
65+
out=$(eval "${requestComp}" 2>/dev/null)
66+
67+
# Extract the directive integer at the very end of the output following a colon (:)
68+
directive=${out##*:}
69+
# Remove the directive
70+
out=${out%%:*}
71+
if [ "${directive}" = "${out}" ]; then
72+
# There is not directive specified
73+
directive=0
74+
fi
75+
__helm_debug "The completion directive is: ${directive}"
76+
__helm_debug "The completions are: ${out[*]}"
77+
}
78+
79+
__helm_process_completion_results() {
80+
local shellCompDirectiveError=%[2]d
81+
local shellCompDirectiveNoSpace=%[3]d
82+
local shellCompDirectiveNoFileComp=%[4]d
83+
local shellCompDirectiveFilterFileExt=%[5]d
84+
local shellCompDirectiveFilterDirs=%[6]d
85+
86+
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
87+
# Error code. No completion.
88+
__helm_debug "Received error from custom completion go code"
89+
return
90+
else
91+
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
92+
if [[ $(type -t compopt) = "builtin" ]]; then
93+
__helm_debug "Activating no space"
94+
compopt -o nospace
95+
else
96+
__helm_debug "No space directive not supported in this version of bash"
97+
fi
98+
fi
99+
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
100+
if [[ $(type -t compopt) = "builtin" ]]; then
101+
__helm_debug "Activating no file completion"
102+
compopt +o default
103+
else
104+
__helm_debug "No file completion directive not supported in this version of bash"
105+
fi
106+
fi
107+
fi
108+
109+
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
110+
# File extension filtering
111+
local fullFilter filter filteringCmd
112+
113+
# Do not use quotes around the $out variable or else newline
114+
# characters will be kept.
115+
for filter in ${out[*]}; do
116+
fullFilter+="$filter|"
117+
done
118+
119+
filteringCmd="_filedir $fullFilter"
120+
__helm_debug "File filtering command: $filteringCmd"
121+
$filteringCmd
122+
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
123+
# File completion for directories only
124+
125+
# Use printf to strip any trailing newline
126+
local subdir
127+
subdir=$(printf "%%s" "${out[0]}")
128+
if [ -n "$subdir" ]; then
129+
__helm_debug "Listing directories in $subdir"
130+
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
131+
else
132+
__helm_debug "Listing directories in ."
133+
_filedir -d
134+
fi
135+
else
136+
__helm_handle_standard_completion_case
137+
fi
138+
139+
__helm_handle_special_char "$cur" :
140+
__helm_handle_special_char "$cur" =
141+
}
142+
143+
__helm_handle_standard_completion_case() {
144+
local tab comp
145+
tab=$(printf '\t')
146+
147+
local longest=0
148+
# Look for the longest completion so that we can format things nicely
149+
while IFS='' read -r comp; do
150+
# Strip any description before checking the length
151+
comp=${comp%%%%$tab*}
152+
# Only consider the completions that match
153+
comp=$(compgen -W "$comp" -- "$cur")
154+
if ((${#comp}>longest)); then
155+
longest=${#comp}
156+
fi
157+
done < <(printf "%%s\n" "${out[@]}")
158+
159+
local completions=()
160+
while IFS='' read -r comp; do
161+
if [ -z "$comp" ]; then
162+
continue
163+
fi
164+
165+
__helm_debug "Original comp: $comp"
166+
comp="$(__helm_format_comp_descriptions "$comp" "$longest")"
167+
__helm_debug "Final comp: $comp"
168+
completions+=("$comp")
169+
done < <(printf "%%s\n" "${out[@]}")
170+
171+
# Filter completions on the prefix the user specified ($cur)
172+
# We do this here because we need to have already escaped any
173+
# special characters of descriptions with __helm_format_comp_descriptions
174+
while IFS='' read -r comp; do
175+
COMPREPLY+=("$comp")
176+
done < <(compgen -W "${completions[*]}" -- "$cur")
177+
178+
# If there is a single completion left, remove the description text
179+
if [ ${#COMPREPLY[*]} -eq 1 ]; then
180+
__helm_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
181+
comp="${COMPREPLY[0]%%%% *}"
182+
__helm_debug "Removed description from single completion, which is now: ${comp}"
183+
COMPREPLY=()
184+
COMPREPLY+=("$comp")
185+
fi
186+
}
187+
188+
__helm_handle_special_char()
189+
{
190+
local comp="$1"
191+
local char=$2
192+
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
193+
local word=${comp%%"${comp##*${char}}"}
194+
local idx=${#COMPREPLY[*]}
195+
while [[ $((--idx)) -ge 0 ]]; do
196+
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
197+
done
198+
fi
199+
}
200+
201+
__helm_format_comp_descriptions()
202+
{
203+
local tab
204+
tab=$(printf '\t')
205+
local comp="$1"
206+
local longest=$2
207+
208+
# Properly format the description string which follows a tab character if there is one
209+
if [[ "$comp" == *$tab* ]]; then
210+
desc=${comp#*$tab}
211+
comp=${comp%%%%$tab*}
212+
213+
# $COLUMNS stores the current shell width.
214+
# Remove an extra 4 because we add 2 spaces and 2 parentheses.
215+
maxdesclength=$(( COLUMNS - longest - 4 ))
216+
217+
# Make sure we can fit a description of at least 8 characters
218+
# if we are to align the descriptions.
219+
if [[ $maxdesclength -gt 8 ]]; then
220+
# Add the proper number of spaces to align the descriptions
221+
for ((i = ${#comp} ; i < longest ; i++)); do
222+
comp+=" "
223+
done
224+
else
225+
# Don't pad the descriptions so we can fit more text after the completion
226+
maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
227+
fi
228+
229+
# If there is enough space for any description text,
230+
# truncate the descriptions that are too long for the shell width
231+
if [ $maxdesclength -gt 0 ]; then
232+
if [ ${#desc} -gt $maxdesclength ]; then
233+
desc=${desc:0:$(( maxdesclength - 1 ))}
234+
desc+="…"
235+
fi
236+
comp+=" ($desc)"
237+
fi
238+
fi
239+
240+
# Must use printf to escape all special characters
241+
printf "%%q" "${comp}"
242+
}
243+
244+
__start_helm()
245+
{
246+
local cur prev words cword
247+
248+
COMPREPLY=()
249+
250+
# Call _init_completion from the bash-completion package
251+
# to prepare the arguments properly
252+
if declare -F _init_completion >/dev/null 2>&1; then
253+
_init_completion -n "=:" || return
254+
else
255+
__helm_init_completion -n "=:" || return
256+
fi
257+
258+
__helm_debug
259+
__helm_debug "========= starting completion logic =========="
260+
__helm_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
261+
262+
# The user could have moved the cursor backwards on the command-line.
263+
# We need to trigger completion from the $cword location, so we need
264+
# to truncate the command-line ($words) up to the $cword location.
265+
words=("${words[@]:0:$cword+1}")
266+
__helm_debug "Truncated words[*]: ${words[*]},"
267+
268+
local out directive
269+
__helm_get_completion_results
270+
__helm_process_completion_results
271+
}
272+
273+
if [[ $(type -t compopt) = "builtin" ]]; then
274+
complete -o default -F __start_helm helm
275+
else
276+
complete -o default -o nospace -F __start_helm helm
277+
fi
278+
279+
# ex: ts=4 sw=4 et filetype=sh
280+
`, compCmd,
281+
cobra.ShellCompDirectiveError, cobra.ShellCompDirectiveNoSpace, cobra.ShellCompDirectiveNoFileComp,
282+
cobra.ShellCompDirectiveFilterFileExt, cobra.ShellCompDirectiveFilterDirs))
283+
284+
_, err := buf.WriteTo(w)
285+
return err
286+
}

0 commit comments

Comments
 (0)