git-bug

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