1package cobra 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8) 9 10// GenZshCompletionFile generates zsh completion file including descriptions. 11func (c *Command) GenZshCompletionFile(filename string) error { 12 return c.genZshCompletionFile(filename, true) 13} 14 15// GenZshCompletion generates zsh completion file including descriptions 16// and writes it to the passed writer. 17func (c *Command) GenZshCompletion(w io.Writer) error { 18 return c.genZshCompletion(w, true) 19} 20 21// GenZshCompletionFileNoDesc generates zsh completion file without descriptions. 22func (c *Command) GenZshCompletionFileNoDesc(filename string) error { 23 return c.genZshCompletionFile(filename, false) 24} 25 26// GenZshCompletionNoDesc generates zsh completion file without descriptions 27// and writes it to the passed writer. 28func (c *Command) GenZshCompletionNoDesc(w io.Writer) error { 29 return c.genZshCompletion(w, false) 30} 31 32// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was 33// not consistent with Bash completion. It has therefore been disabled. 34// Instead, when no other completion is specified, file completion is done by 35// default for every argument. One can disable file completion on a per-argument 36// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp. 37// To achieve file extension filtering, one can use ValidArgsFunction and 38// ShellCompDirectiveFilterFileExt. 39// 40// Deprecated 41func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { 42 return nil 43} 44 45// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore 46// been disabled. 47// To achieve the same behavior across all shells, one can use 48// ValidArgs (for the first argument only) or ValidArgsFunction for 49// any argument (can include the first one also). 50// 51// Deprecated 52func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { 53 return nil 54} 55 56func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error { 57 outFile, err := os.Create(filename) 58 if err != nil { 59 return err 60 } 61 defer outFile.Close() 62 63 return c.genZshCompletion(outFile, includeDesc) 64} 65 66func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { 67 buf := new(bytes.Buffer) 68 genZshComp(buf, c.Name(), includeDesc) 69 _, err := buf.WriteTo(w) 70 return err 71} 72 73func genZshComp(buf io.StringWriter, name string, includeDesc bool) { 74 compCmd := ShellCompRequestCmd 75 if !includeDesc { 76 compCmd = ShellCompNoDescRequestCmd 77 } 78 WriteStringAndCheck(buf, fmt.Sprintf(`#compdef _%[1]s %[1]s 79 80# zsh completion for %-36[1]s -*- shell-script -*- 81 82__%[1]s_debug() 83{ 84 local file="$BASH_COMP_DEBUG_FILE" 85 if [[ -n ${file} ]]; then 86 echo "$*" >> "${file}" 87 fi 88} 89 90_%[1]s() 91{ 92 local shellCompDirectiveError=%[3]d 93 local shellCompDirectiveNoSpace=%[4]d 94 local shellCompDirectiveNoFileComp=%[5]d 95 local shellCompDirectiveFilterFileExt=%[6]d 96 local shellCompDirectiveFilterDirs=%[7]d 97 98 local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp 99 local -a completions 100 101 __%[1]s_debug "\n========= starting completion logic ==========" 102 __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" 103 104 # The user could have moved the cursor backwards on the command-line. 105 # We need to trigger completion from the $CURRENT location, so we need 106 # to truncate the command-line ($words) up to the $CURRENT location. 107 # (We cannot use $CURSOR as its value does not work when a command is an alias.) 108 words=("${=words[1,CURRENT]}") 109 __%[1]s_debug "Truncated words[*]: ${words[*]}," 110 111 lastParam=${words[-1]} 112 lastChar=${lastParam[-1]} 113 __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" 114 115 # For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>) 116 # completions must be prefixed with the flag 117 setopt local_options BASH_REMATCH 118 if [[ "${lastParam}" =~ '-.*=' ]]; then 119 # We are dealing with a flag with an = 120 flagPrefix="-P ${BASH_REMATCH}" 121 fi 122 123 # Prepare the command to obtain completions 124 requestComp="${words[1]} %[2]s ${words[2,-1]}" 125 if [ "${lastChar}" = "" ]; then 126 # If the last parameter is complete (there is a space following it) 127 # We add an extra empty parameter so we can indicate this to the go completion code. 128 __%[1]s_debug "Adding extra empty parameter" 129 requestComp="${requestComp} \"\"" 130 fi 131 132 __%[1]s_debug "About to call: eval ${requestComp}" 133 134 # Use eval to handle any environment variables and such 135 out=$(eval ${requestComp} 2>/dev/null) 136 __%[1]s_debug "completion output: ${out}" 137 138 # Extract the directive integer following a : from the last line 139 local lastLine 140 while IFS='\n' read -r line; do 141 lastLine=${line} 142 done < <(printf "%%s\n" "${out[@]}") 143 __%[1]s_debug "last line: ${lastLine}" 144 145 if [ "${lastLine[1]}" = : ]; then 146 directive=${lastLine[2,-1]} 147 # Remove the directive including the : and the newline 148 local suffix 149 (( suffix=${#lastLine}+2)) 150 out=${out[1,-$suffix]} 151 else 152 # There is no directive specified. Leave $out as is. 153 __%[1]s_debug "No directive found. Setting do default" 154 directive=0 155 fi 156 157 __%[1]s_debug "directive: ${directive}" 158 __%[1]s_debug "completions: ${out}" 159 __%[1]s_debug "flagPrefix: ${flagPrefix}" 160 161 if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then 162 __%[1]s_debug "Completion received error. Ignoring completions." 163 return 164 fi 165 166 compCount=0 167 while IFS='\n' read -r comp; do 168 if [ -n "$comp" ]; then 169 # If requested, completions are returned with a description. 170 # The description is preceded by a TAB character. 171 # For zsh's _describe, we need to use a : instead of a TAB. 172 # We first need to escape any : as part of the completion itself. 173 comp=${comp//:/\\:} 174 175 local tab=$(printf '\t') 176 comp=${comp//$tab/:} 177 178 ((compCount++)) 179 __%[1]s_debug "Adding completion: ${comp}" 180 completions+=${comp} 181 lastComp=$comp 182 fi 183 done < <(printf "%%s\n" "${out[@]}") 184 185 if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then 186 # File extension filtering 187 local filteringCmd 188 filteringCmd='_files' 189 for filter in ${completions[@]}; do 190 if [ ${filter[1]} != '*' ]; then 191 # zsh requires a glob pattern to do file filtering 192 filter="\*.$filter" 193 fi 194 filteringCmd+=" -g $filter" 195 done 196 filteringCmd+=" ${flagPrefix}" 197 198 __%[1]s_debug "File filtering command: $filteringCmd" 199 _arguments '*:filename:'"$filteringCmd" 200 elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then 201 # File completion for directories only 202 local subDir 203 subdir="${completions[1]}" 204 if [ -n "$subdir" ]; then 205 __%[1]s_debug "Listing directories in $subdir" 206 pushd "${subdir}" >/dev/null 2>&1 207 else 208 __%[1]s_debug "Listing directories in ." 209 fi 210 211 _arguments '*:dirname:_files -/'" ${flagPrefix}" 212 if [ -n "$subdir" ]; then 213 popd >/dev/null 2>&1 214 fi 215 elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then 216 __%[1]s_debug "Activating nospace." 217 # We can use compadd here as there is no description when 218 # there is only one completion. 219 compadd -S '' "${lastComp}" 220 elif [ ${compCount} -eq 0 ]; then 221 if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then 222 __%[1]s_debug "deactivating file completion" 223 else 224 # Perform file completion 225 __%[1]s_debug "activating file completion" 226 _arguments '*:filename:_files'" ${flagPrefix}" 227 fi 228 else 229 _describe "completions" completions $(echo $flagPrefix) 230 fi 231} 232 233# don't run the completion function when being source-ed or eval-ed 234if [ "$funcstack[1]" = "_%[1]s" ]; then 235 _%[1]s 236fi 237`, name, compCmd, 238 ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, 239 ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) 240} 241