1# bash completion V2 for git-bug -*- shell-script -*-
2
3__git-bug_debug()
4{
5 if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
6 echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
7 fi
8}
9
10# Macs have bash3 for which the bash-completion package doesn't include
11# _init_completion. This is a minimal version of that function.
12__git-bug_init_completion()
13{
14 COMPREPLY=()
15 _get_comp_words_by_ref "$@" cur prev words cword
16}
17
18# This function calls the git-bug program to obtain the completion
19# results and the directive. It fills the 'out' and 'directive' vars.
20__git-bug_get_completion_results() {
21 local requestComp lastParam lastChar args
22
23 # Prepare the command to request completions for the program.
24 # Calling ${words[0]} instead of directly git-bug allows handling aliases
25 args=("${words[@]:1}")
26 requestComp="${words[0]} __complete ${args[*]}"
27
28 lastParam=${words[$((${#words[@]}-1))]}
29 lastChar=${lastParam:$((${#lastParam}-1)):1}
30 __git-bug_debug "lastParam ${lastParam}, lastChar ${lastChar}"
31
32 if [[ -z ${cur} && ${lastChar} != = ]]; then
33 # If the last parameter is complete (there is a space following it)
34 # We add an extra empty parameter so we can indicate this to the go method.
35 __git-bug_debug "Adding extra empty parameter"
36 requestComp="${requestComp} ''"
37 fi
38
39 # When completing a flag with an = (e.g., git-bug -n=<TAB>)
40 # bash focuses on the part after the =, so we need to remove
41 # the flag part from $cur
42 if [[ ${cur} == -*=* ]]; then
43 cur="${cur#*=}"
44 fi
45
46 __git-bug_debug "Calling ${requestComp}"
47 # Use eval to handle any environment variables and such
48 out=$(eval "${requestComp}" 2>/dev/null)
49
50 # Extract the directive integer at the very end of the output following a colon (:)
51 directive=${out##*:}
52 # Remove the directive
53 out=${out%:*}
54 if [[ ${directive} == "${out}" ]]; then
55 # There is not directive specified
56 directive=0
57 fi
58 __git-bug_debug "The completion directive is: ${directive}"
59 __git-bug_debug "The completions are: ${out}"
60}
61
62__git-bug_process_completion_results() {
63 local shellCompDirectiveError=1
64 local shellCompDirectiveNoSpace=2
65 local shellCompDirectiveNoFileComp=4
66 local shellCompDirectiveFilterFileExt=8
67 local shellCompDirectiveFilterDirs=16
68 local shellCompDirectiveKeepOrder=32
69
70 if (((directive & shellCompDirectiveError) != 0)); then
71 # Error code. No completion.
72 __git-bug_debug "Received error from custom completion go code"
73 return
74 else
75 if (((directive & shellCompDirectiveNoSpace) != 0)); then
76 if [[ $(type -t compopt) == builtin ]]; then
77 __git-bug_debug "Activating no space"
78 compopt -o nospace
79 else
80 __git-bug_debug "No space directive not supported in this version of bash"
81 fi
82 fi
83 if (((directive & shellCompDirectiveKeepOrder) != 0)); then
84 if [[ $(type -t compopt) == builtin ]]; then
85 # no sort isn't supported for bash less than < 4.4
86 if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
87 __git-bug_debug "No sort directive not supported in this version of bash"
88 else
89 __git-bug_debug "Activating keep order"
90 compopt -o nosort
91 fi
92 else
93 __git-bug_debug "No sort directive not supported in this version of bash"
94 fi
95 fi
96 if (((directive & shellCompDirectiveNoFileComp) != 0)); then
97 if [[ $(type -t compopt) == builtin ]]; then
98 __git-bug_debug "Activating no file completion"
99 compopt +o default
100 else
101 __git-bug_debug "No file completion directive not supported in this version of bash"
102 fi
103 fi
104 fi
105
106 # Separate activeHelp from normal completions
107 local completions=()
108 local activeHelp=()
109 __git-bug_extract_activeHelp
110
111 if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
112 # File extension filtering
113 local fullFilter filter filteringCmd
114
115 # Do not use quotes around the $completions variable or else newline
116 # characters will be kept.
117 for filter in ${completions[*]}; do
118 fullFilter+="$filter|"
119 done
120
121 filteringCmd="_filedir $fullFilter"
122 __git-bug_debug "File filtering command: $filteringCmd"
123 $filteringCmd
124 elif (((directive & shellCompDirectiveFilterDirs) != 0)); then
125 # File completion for directories only
126
127 local subdir
128 subdir=${completions[0]}
129 if [[ -n $subdir ]]; then
130 __git-bug_debug "Listing directories in $subdir"
131 pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
132 else
133 __git-bug_debug "Listing directories in ."
134 _filedir -d
135 fi
136 else
137 __git-bug_handle_completion_types
138 fi
139
140 __git-bug_handle_special_char "$cur" :
141 __git-bug_handle_special_char "$cur" =
142
143 # Print the activeHelp statements before we finish
144 if ((${#activeHelp[*]} != 0)); then
145 printf "\n";
146 printf "%s\n" "${activeHelp[@]}"
147 printf "\n"
148
149 # The prompt format is only available from bash 4.4.
150 # We test if it is available before using it.
151 if (x=${PS1@P}) 2> /dev/null; then
152 printf "%s" "${PS1@P}${COMP_LINE[@]}"
153 else
154 # Can't print the prompt. Just print the
155 # text the user had typed, it is workable enough.
156 printf "%s" "${COMP_LINE[@]}"
157 fi
158 fi
159}
160
161# Separate activeHelp lines from real completions.
162# Fills the $activeHelp and $completions arrays.
163__git-bug_extract_activeHelp() {
164 local activeHelpMarker="_activeHelp_ "
165 local endIndex=${#activeHelpMarker}
166
167 while IFS='' read -r comp; do
168 if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
169 comp=${comp:endIndex}
170 __git-bug_debug "ActiveHelp found: $comp"
171 if [[ -n $comp ]]; then
172 activeHelp+=("$comp")
173 fi
174 else
175 # Not an activeHelp line but a normal completion
176 completions+=("$comp")
177 fi
178 done <<<"${out}"
179}
180
181__git-bug_handle_completion_types() {
182 __git-bug_debug "__git-bug_handle_completion_types: COMP_TYPE is $COMP_TYPE"
183
184 case $COMP_TYPE in
185 37|42)
186 # Type: menu-complete/menu-complete-backward and insert-completions
187 # If the user requested inserting one completion at a time, or all
188 # completions at once on the command-line we must remove the descriptions.
189 # https://github.com/spf13/cobra/issues/1508
190 local tab=$'\t' comp
191 while IFS='' read -r comp; do
192 [[ -z $comp ]] && continue
193 # Strip any description
194 comp=${comp%%$tab*}
195 # Only consider the completions that match
196 if [[ $comp == "$cur"* ]]; then
197 COMPREPLY+=("$comp")
198 fi
199 done < <(printf "%s\n" "${completions[@]}")
200 ;;
201
202 *)
203 # Type: complete (normal completion)
204 __git-bug_handle_standard_completion_case
205 ;;
206 esac
207}
208
209__git-bug_handle_standard_completion_case() {
210 local tab=$'\t' comp
211
212 # Short circuit to optimize if we don't have descriptions
213 if [[ "${completions[*]}" != *$tab* ]]; then
214 IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
215 return 0
216 fi
217
218 local longest=0
219 local compline
220 # Look for the longest completion so that we can format things nicely
221 while IFS='' read -r compline; do
222 [[ -z $compline ]] && continue
223 # Strip any description before checking the length
224 comp=${compline%%$tab*}
225 # Only consider the completions that match
226 [[ $comp == "$cur"* ]] || continue
227 COMPREPLY+=("$compline")
228 if ((${#comp}>longest)); then
229 longest=${#comp}
230 fi
231 done < <(printf "%s\n" "${completions[@]}")
232
233 # If there is a single completion left, remove the description text
234 if ((${#COMPREPLY[*]} == 1)); then
235 __git-bug_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
236 comp="${COMPREPLY[0]%%$tab*}"
237 __git-bug_debug "Removed description from single completion, which is now: ${comp}"
238 COMPREPLY[0]=$comp
239 else # Format the descriptions
240 __git-bug_format_comp_descriptions $longest
241 fi
242}
243
244__git-bug_handle_special_char()
245{
246 local comp="$1"
247 local char=$2
248 if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
249 local word=${comp%"${comp##*${char}}"}
250 local idx=${#COMPREPLY[*]}
251 while ((--idx >= 0)); do
252 COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
253 done
254 fi
255}
256
257__git-bug_format_comp_descriptions()
258{
259 local tab=$'\t'
260 local comp desc maxdesclength
261 local longest=$1
262
263 local i ci
264 for ci in ${!COMPREPLY[*]}; do
265 comp=${COMPREPLY[ci]}
266 # Properly format the description string which follows a tab character if there is one
267 if [[ "$comp" == *$tab* ]]; then
268 __git-bug_debug "Original comp: $comp"
269 desc=${comp#*$tab}
270 comp=${comp%%$tab*}
271
272 # $COLUMNS stores the current shell width.
273 # Remove an extra 4 because we add 2 spaces and 2 parentheses.
274 maxdesclength=$(( COLUMNS - longest - 4 ))
275
276 # Make sure we can fit a description of at least 8 characters
277 # if we are to align the descriptions.
278 if ((maxdesclength > 8)); then
279 # Add the proper number of spaces to align the descriptions
280 for ((i = ${#comp} ; i < longest ; i++)); do
281 comp+=" "
282 done
283 else
284 # Don't pad the descriptions so we can fit more text after the completion
285 maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
286 fi
287
288 # If there is enough space for any description text,
289 # truncate the descriptions that are too long for the shell width
290 if ((maxdesclength > 0)); then
291 if ((${#desc} > maxdesclength)); then
292 desc=${desc:0:$(( maxdesclength - 1 ))}
293 desc+="…"
294 fi
295 comp+=" ($desc)"
296 fi
297 COMPREPLY[ci]=$comp
298 __git-bug_debug "Final comp: $comp"
299 fi
300 done
301}
302
303__start_git-bug()
304{
305 local cur prev words cword split
306
307 COMPREPLY=()
308
309 # Call _init_completion from the bash-completion package
310 # to prepare the arguments properly
311 if declare -F _init_completion >/dev/null 2>&1; then
312 _init_completion -n =: || return
313 else
314 __git-bug_init_completion -n =: || return
315 fi
316
317 __git-bug_debug
318 __git-bug_debug "========= starting completion logic =========="
319 __git-bug_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
320
321 # The user could have moved the cursor backwards on the command-line.
322 # We need to trigger completion from the $cword location, so we need
323 # to truncate the command-line ($words) up to the $cword location.
324 words=("${words[@]:0:$cword+1}")
325 __git-bug_debug "Truncated words[*]: ${words[*]},"
326
327 local out directive
328 __git-bug_get_completion_results
329 __git-bug_process_completion_results
330}
331
332if [[ $(type -t compopt) = "builtin" ]]; then
333 complete -o default -F __start_git-bug git-bug
334else
335 complete -o default -o nospace -F __start_git-bug git-bug
336fi
337
338# ex: ts=4 sw=4 et filetype=sh
339
340# Custom bash code to connect the git completion for "git bug" to the
341# git-bug completion for "git-bug"
342_git_bug() {
343 local cur prev words cword split
344
345 COMPREPLY=()
346
347 # Call _init_completion from the bash-completion package
348 # to prepare the arguments properly
349 if declare -F _init_completion >/dev/null 2>&1; then
350 _init_completion -n "=:" || return
351 else
352 __git-bug_init_completion -n "=:" || return
353 fi
354
355 # START PATCH
356 # replace in the array ("git","bug", ...) to ("git-bug", ...) and adjust the index in cword
357 words=("git-bug" "${words[@]:2}")
358 cword=$(($cword-1))
359 # END PATCH
360
361 __git-bug_debug
362 __git-bug_debug "========= starting completion logic =========="
363 __git-bug_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
364
365 # The user could have moved the cursor backwards on the command-line.
366 # We need to trigger completion from the $cword location, so we need
367 # to truncate the command-line ($words) up to the $cword location.
368 words=("${words[@]:0:$cword+1}")
369 __git-bug_debug "Truncated words[*]: ${words[*]},"
370
371 local out directive
372 __git-bug_get_completion_results
373 __git-bug_process_completion_results
374}