Path: blob/main/vendor/github.com/spf13/cobra/bash_completionsV2.go
2875 views
// Copyright 2013-2023 The Cobra Authors1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314package cobra1516import (17"bytes"18"fmt"19"io"20"os"21)2223func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error {24buf := new(bytes.Buffer)25genBashComp(buf, c.Name(), includeDesc)26_, err := buf.WriteTo(w)27return err28}2930func genBashComp(buf io.StringWriter, name string, includeDesc bool) {31compCmd := ShellCompRequestCmd32if !includeDesc {33compCmd = ShellCompNoDescRequestCmd34}3536WriteStringAndCheck(buf, fmt.Sprintf(`# bash completion V2 for %-36[1]s -*- shell-script -*-3738__%[1]s_debug()39{40if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then41echo "$*" >> "${BASH_COMP_DEBUG_FILE}"42fi43}4445# Macs have bash3 for which the bash-completion package doesn't include46# _init_completion. This is a minimal version of that function.47__%[1]s_init_completion()48{49COMPREPLY=()50_get_comp_words_by_ref "$@" cur prev words cword51}5253# This function calls the %[1]s program to obtain the completion54# results and the directive. It fills the 'out' and 'directive' vars.55__%[1]s_get_completion_results() {56local requestComp lastParam lastChar args5758# Prepare the command to request completions for the program.59# Calling ${words[0]} instead of directly %[1]s allows handling aliases60args=("${words[@]:1}")61requestComp="${words[0]} %[2]s ${args[*]}"6263lastParam=${words[$((${#words[@]}-1))]}64lastChar=${lastParam:$((${#lastParam}-1)):1}65__%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"6667if [[ -z ${cur} && ${lastChar} != = ]]; then68# If the last parameter is complete (there is a space following it)69# We add an extra empty parameter so we can indicate this to the go method.70__%[1]s_debug "Adding extra empty parameter"71requestComp="${requestComp} ''"72fi7374# When completing a flag with an = (e.g., %[1]s -n=<TAB>)75# bash focuses on the part after the =, so we need to remove76# the flag part from $cur77if [[ ${cur} == -*=* ]]; then78cur="${cur#*=}"79fi8081__%[1]s_debug "Calling ${requestComp}"82# Use eval to handle any environment variables and such83out=$(eval "${requestComp}" 2>/dev/null)8485# Extract the directive integer at the very end of the output following a colon (:)86directive=${out##*:}87# Remove the directive88out=${out%%:*}89if [[ ${directive} == "${out}" ]]; then90# There is not directive specified91directive=092fi93__%[1]s_debug "The completion directive is: ${directive}"94__%[1]s_debug "The completions are: ${out}"95}9697__%[1]s_process_completion_results() {98local shellCompDirectiveError=%[3]d99local shellCompDirectiveNoSpace=%[4]d100local shellCompDirectiveNoFileComp=%[5]d101local shellCompDirectiveFilterFileExt=%[6]d102local shellCompDirectiveFilterDirs=%[7]d103local shellCompDirectiveKeepOrder=%[8]d104105if (((directive & shellCompDirectiveError) != 0)); then106# Error code. No completion.107__%[1]s_debug "Received error from custom completion go code"108return109else110if (((directive & shellCompDirectiveNoSpace) != 0)); then111if [[ $(type -t compopt) == builtin ]]; then112__%[1]s_debug "Activating no space"113compopt -o nospace114else115__%[1]s_debug "No space directive not supported in this version of bash"116fi117fi118if (((directive & shellCompDirectiveKeepOrder) != 0)); then119if [[ $(type -t compopt) == builtin ]]; then120# no sort isn't supported for bash less than < 4.4121if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then122__%[1]s_debug "No sort directive not supported in this version of bash"123else124__%[1]s_debug "Activating keep order"125compopt -o nosort126fi127else128__%[1]s_debug "No sort directive not supported in this version of bash"129fi130fi131if (((directive & shellCompDirectiveNoFileComp) != 0)); then132if [[ $(type -t compopt) == builtin ]]; then133__%[1]s_debug "Activating no file completion"134compopt +o default135else136__%[1]s_debug "No file completion directive not supported in this version of bash"137fi138fi139fi140141# Separate activeHelp from normal completions142local completions=()143local activeHelp=()144__%[1]s_extract_activeHelp145146if (((directive & shellCompDirectiveFilterFileExt) != 0)); then147# File extension filtering148local fullFilter="" filter filteringCmd149150# Do not use quotes around the $completions variable or else newline151# characters will be kept.152for filter in ${completions[*]}; do153fullFilter+="$filter|"154done155156filteringCmd="_filedir $fullFilter"157__%[1]s_debug "File filtering command: $filteringCmd"158$filteringCmd159elif (((directive & shellCompDirectiveFilterDirs) != 0)); then160# File completion for directories only161162local subdir163subdir=${completions[0]}164if [[ -n $subdir ]]; then165__%[1]s_debug "Listing directories in $subdir"166pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return167else168__%[1]s_debug "Listing directories in ."169_filedir -d170fi171else172__%[1]s_handle_completion_types173fi174175__%[1]s_handle_special_char "$cur" :176__%[1]s_handle_special_char "$cur" =177178# Print the activeHelp statements before we finish179__%[1]s_handle_activeHelp180}181182__%[1]s_handle_activeHelp() {183# Print the activeHelp statements184if ((${#activeHelp[*]} != 0)); then185if [ -z $COMP_TYPE ]; then186# Bash v3 does not set the COMP_TYPE variable.187printf "\n";188printf "%%s\n" "${activeHelp[@]}"189printf "\n"190__%[1]s_reprint_commandLine191return192fi193194# Only print ActiveHelp on the second TAB press195if [ $COMP_TYPE -eq 63 ]; then196printf "\n"197printf "%%s\n" "${activeHelp[@]}"198199if ((${#COMPREPLY[*]} == 0)); then200# When there are no completion choices from the program, file completion201# may kick in if the program has not disabled it; in such a case, we want202# to know if any files will match what the user typed, so that we know if203# there will be completions presented, so that we know how to handle ActiveHelp.204# To find out, we actually trigger the file completion ourselves;205# the call to _filedir will fill COMPREPLY if files match.206if (((directive & shellCompDirectiveNoFileComp) == 0)); then207__%[1]s_debug "Listing files"208_filedir209fi210fi211212if ((${#COMPREPLY[*]} != 0)); then213# If there are completion choices to be shown, print a delimiter.214# Re-printing the command-line will automatically be done215# by the shell when it prints the completion choices.216printf -- "--"217else218# When there are no completion choices at all, we need219# to re-print the command-line since the shell will220# not be doing it itself.221__%[1]s_reprint_commandLine222fi223elif [ $COMP_TYPE -eq 37 ] || [ $COMP_TYPE -eq 42 ]; then224# For completion type: menu-complete/menu-complete-backward and insert-completions225# the completions are immediately inserted into the command-line, so we first226# print the activeHelp message and reprint the command-line since the shell won't.227printf "\n"228printf "%%s\n" "${activeHelp[@]}"229230__%[1]s_reprint_commandLine231fi232fi233}234235__%[1]s_reprint_commandLine() {236# The prompt format is only available from bash 4.4.237# We test if it is available before using it.238if (x=${PS1@P}) 2> /dev/null; then239printf "%%s" "${PS1@P}${COMP_LINE[@]}"240else241# Can't print the prompt. Just print the242# text the user had typed, it is workable enough.243printf "%%s" "${COMP_LINE[@]}"244fi245}246247# Separate activeHelp lines from real completions.248# Fills the $activeHelp and $completions arrays.249__%[1]s_extract_activeHelp() {250local activeHelpMarker="%[9]s"251local endIndex=${#activeHelpMarker}252253while IFS='' read -r comp; do254[[ -z $comp ]] && continue255256if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then257comp=${comp:endIndex}258__%[1]s_debug "ActiveHelp found: $comp"259if [[ -n $comp ]]; then260activeHelp+=("$comp")261fi262else263# Not an activeHelp line but a normal completion264completions+=("$comp")265fi266done <<<"${out}"267}268269__%[1]s_handle_completion_types() {270__%[1]s_debug "__%[1]s_handle_completion_types: COMP_TYPE is $COMP_TYPE"271272case $COMP_TYPE in27337|42)274# Type: menu-complete/menu-complete-backward and insert-completions275# If the user requested inserting one completion at a time, or all276# completions at once on the command-line we must remove the descriptions.277# https://github.com/spf13/cobra/issues/1508278279# If there are no completions, we don't need to do anything280(( ${#completions[@]} == 0 )) && return 0281282local tab=$'\t'283284# Strip any description and escape the completion to handled special characters285IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]%%%%$tab*}")286287# Only consider the completions that match288IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")289290# compgen looses the escaping so we need to escape all completions again since they will291# all be inserted on the command-line.292IFS=$'\n' read -ra COMPREPLY -d '' < <(printf "%%q\n" "${COMPREPLY[@]}")293;;294295*)296# Type: complete (normal completion)297__%[1]s_handle_standard_completion_case298;;299esac300}301302__%[1]s_handle_standard_completion_case() {303local tab=$'\t'304305# If there are no completions, we don't need to do anything306(( ${#completions[@]} == 0 )) && return 0307308# Short circuit to optimize if we don't have descriptions309if [[ "${completions[*]}" != *$tab* ]]; then310# First, escape the completions to handle special characters311IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]}")312# Only consider the completions that match what the user typed313IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")314315# compgen looses the escaping so, if there is only a single completion, we need to316# escape it again because it will be inserted on the command-line. If there are multiple317# completions, we don't want to escape them because they will be printed in a list318# and we don't want to show escape characters in that list.319if (( ${#COMPREPLY[@]} == 1 )); then320COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]}")321fi322return 0323fi324325local longest=0326local compline327# Look for the longest completion so that we can format things nicely328while IFS='' read -r compline; do329[[ -z $compline ]] && continue330331# Before checking if the completion matches what the user typed,332# we need to strip any description and escape the completion to handle special333# characters because those escape characters are part of what the user typed.334# Don't call "printf" in a sub-shell because it will be much slower335# since we are in a loop.336printf -v comp "%%q" "${compline%%%%$tab*}" &>/dev/null || comp=$(printf "%%q" "${compline%%%%$tab*}")337338# Only consider the completions that match339[[ $comp == "$cur"* ]] || continue340341# The completions matches. Add it to the list of full completions including342# its description. We don't escape the completion because it may get printed343# in a list if there are more than one and we don't want show escape characters344# in that list.345COMPREPLY+=("$compline")346347# Strip any description before checking the length, and again, don't escape348# the completion because this length is only used when printing the completions349# in a list and we don't want show escape characters in that list.350comp=${compline%%%%$tab*}351if ((${#comp}>longest)); then352longest=${#comp}353fi354done < <(printf "%%s\n" "${completions[@]}")355356# If there is a single completion left, remove the description text and escape any special characters357if ((${#COMPREPLY[*]} == 1)); then358__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"359COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]%%%%$tab*}")360__%[1]s_debug "Removed description from single completion, which is now: ${COMPREPLY[0]}"361else362# Format the descriptions363__%[1]s_format_comp_descriptions $longest364fi365}366367__%[1]s_handle_special_char()368{369local comp="$1"370local char=$2371if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then372local word=${comp%%"${comp##*${char}}"}373local idx=${#COMPREPLY[*]}374while ((--idx >= 0)); do375COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}376done377fi378}379380__%[1]s_format_comp_descriptions()381{382local tab=$'\t'383local comp desc maxdesclength384local longest=$1385386local i ci387for ci in ${!COMPREPLY[*]}; do388comp=${COMPREPLY[ci]}389# Properly format the description string which follows a tab character if there is one390if [[ "$comp" == *$tab* ]]; then391__%[1]s_debug "Original comp: $comp"392desc=${comp#*$tab}393comp=${comp%%%%$tab*}394395# $COLUMNS stores the current shell width.396# Remove an extra 4 because we add 2 spaces and 2 parentheses.397maxdesclength=$(( COLUMNS - longest - 4 ))398399# Make sure we can fit a description of at least 8 characters400# if we are to align the descriptions.401if ((maxdesclength > 8)); then402# Add the proper number of spaces to align the descriptions403for ((i = ${#comp} ; i < longest ; i++)); do404comp+=" "405done406else407# Don't pad the descriptions so we can fit more text after the completion408maxdesclength=$(( COLUMNS - ${#comp} - 4 ))409fi410411# If there is enough space for any description text,412# truncate the descriptions that are too long for the shell width413if ((maxdesclength > 0)); then414if ((${#desc} > maxdesclength)); then415desc=${desc:0:$(( maxdesclength - 1 ))}416desc+="…"417fi418comp+=" ($desc)"419fi420COMPREPLY[ci]=$comp421__%[1]s_debug "Final comp: $comp"422fi423done424}425426__start_%[1]s()427{428local cur prev words cword split429430COMPREPLY=()431432# Call _init_completion from the bash-completion package433# to prepare the arguments properly434if declare -F _init_completion >/dev/null 2>&1; then435_init_completion -n =: || return436else437__%[1]s_init_completion -n =: || return438fi439440__%[1]s_debug441__%[1]s_debug "========= starting completion logic =========="442__%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"443444# The user could have moved the cursor backwards on the command-line.445# We need to trigger completion from the $cword location, so we need446# to truncate the command-line ($words) up to the $cword location.447words=("${words[@]:0:$cword+1}")448__%[1]s_debug "Truncated words[*]: ${words[*]},"449450local out directive451__%[1]s_get_completion_results452__%[1]s_process_completion_results453}454455if [[ $(type -t compopt) = "builtin" ]]; then456complete -o default -F __start_%[1]s %[1]s457else458complete -o default -o nospace -F __start_%[1]s %[1]s459fi460461# ex: ts=4 sw=4 et filetype=sh462`, name, compCmd,463ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,464ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,465activeHelpMarker))466}467468// GenBashCompletionFileV2 generates Bash completion version 2.469func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error {470outFile, err := os.Create(filename)471if err != nil {472return err473}474defer outFile.Close()475476return c.GenBashCompletionV2(outFile, includeDesc)477}478479// GenBashCompletionV2 generates Bash completion file version 2480// and writes it to the passed writer.481func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error {482return c.genBashCompletion(w, includeDesc)483}484485486