comp.bash 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. # Copyright 2013-2023 The Cobra Authors
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # bash completion V2 for %-36[1]s -*- shell-script -*-
  15. __%[1]s_debug()
  16. {
  17. if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
  18. echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
  19. fi
  20. }
  21. # Macs have bash3 for which the bash-completion package doesn't include
  22. # _init_completion. This is a minimal version of that function.
  23. __%[1]s_init_completion()
  24. {
  25. COMPREPLY=()
  26. _get_comp_words_by_ref "$@" cur prev words cword
  27. }
  28. # This function calls the %[1]s program to obtain the completion
  29. # results and the directive. It fills the 'out' and 'directive' vars.
  30. __%[1]s_get_completion_results() {
  31. local requestComp lastParam lastChar args
  32. # Prepare the command to request completions for the program.
  33. # Calling ${words[0]} instead of directly %[1]s allows handling aliases
  34. args=("${words[@]:1}")
  35. requestComp="${words[0]} %[2]s ${args[*]}"
  36. lastParam=${words[$((${#words[@]}-1))]}
  37. lastChar=${lastParam:$((${#lastParam}-1)):1}
  38. __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}"
  39. if [[ -z ${cur} && ${lastChar} != = ]]; then
  40. # If the last parameter is complete (there is a space following it)
  41. # We add an extra empty parameter so we can indicate this to the go method.
  42. __%[1]s_debug "Adding extra empty parameter"
  43. requestComp="${requestComp} ''"
  44. fi
  45. # When completing a flag with an = (e.g., %[1]s -n=<TAB>)
  46. # bash focuses on the part after the =, so we need to remove
  47. # the flag part from $cur
  48. if [[ ${cur} == -*=* ]]; then
  49. cur="${cur#*=}"
  50. fi
  51. __%[1]s_debug "Calling ${requestComp}"
  52. # Use eval to handle any environment variables and such
  53. out=$(eval "${requestComp}" 2>/dev/null)
  54. # Extract the directive integer at the very end of the output following a colon (:)
  55. directive=${out##*:}
  56. # Remove the directive
  57. out=${out%%:*}
  58. if [[ ${directive} == "${out}" ]]; then
  59. # There is not directive specified
  60. directive=0
  61. fi
  62. __%[1]s_debug "The completion directive is: ${directive}"
  63. __%[1]s_debug "The completions are: ${out}"
  64. }
  65. __%[1]s_process_completion_results() {
  66. local shellCompDirectiveError=%[3]d
  67. local shellCompDirectiveNoSpace=%[4]d
  68. local shellCompDirectiveNoFileComp=%[5]d
  69. local shellCompDirectiveFilterFileExt=%[6]d
  70. local shellCompDirectiveFilterDirs=%[7]d
  71. local shellCompDirectiveKeepOrder=%[8]d
  72. if (((directive & shellCompDirectiveError) != 0)); then
  73. # Error code. No completion.
  74. __%[1]s_debug "Received error from custom completion go code"
  75. return
  76. else
  77. if (((directive & shellCompDirectiveNoSpace) != 0)); then
  78. if [[ $(type -t compopt) == builtin ]]; then
  79. __%[1]s_debug "Activating no space"
  80. compopt -o nospace
  81. else
  82. __%[1]s_debug "No space directive not supported in this version of bash"
  83. fi
  84. fi
  85. if (((directive & shellCompDirectiveKeepOrder) != 0)); then
  86. if [[ $(type -t compopt) == builtin ]]; then
  87. # no sort isn't supported for bash less than < 4.4
  88. if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
  89. __%[1]s_debug "No sort directive not supported in this version of bash"
  90. else
  91. __%[1]s_debug "Activating keep order"
  92. compopt -o nosort
  93. fi
  94. else
  95. __%[1]s_debug "No sort directive not supported in this version of bash"
  96. fi
  97. fi
  98. if (((directive & shellCompDirectiveNoFileComp) != 0)); then
  99. if [[ $(type -t compopt) == builtin ]]; then
  100. __%[1]s_debug "Activating no file completion"
  101. compopt +o default
  102. else
  103. __%[1]s_debug "No file completion directive not supported in this version of bash"
  104. fi
  105. fi
  106. fi
  107. # Separate activeHelp from normal completions
  108. local completions=()
  109. while IFS='' read -r comp; do
  110. completions+=("$comp")
  111. done <<<"${out}"
  112. if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
  113. # File extension filtering
  114. local fullFilter filter filteringCmd
  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. filteringCmd="_filedir $fullFilter"
  121. __%[1]s_debug "File filtering command: $filteringCmd"
  122. $filteringCmd
  123. elif (((directive & shellCompDirectiveFilterDirs) != 0)); then
  124. # File completion for directories only
  125. local subdir
  126. subdir=${completions[0]}
  127. if [[ -n $subdir ]]; then
  128. __%[1]s_debug "Listing directories in $subdir"
  129. pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
  130. else
  131. __%[1]s_debug "Listing directories in ."
  132. _filedir -d
  133. fi
  134. else
  135. __%[1]s_handle_completion_types
  136. fi
  137. __%[1]s_handle_special_char "$cur" :
  138. __%[1]s_handle_special_char "$cur" =
  139. # Print the activeHelp statements before we finish
  140. if ((${#activeHelp[*]} != 0)); then
  141. printf "\n";
  142. printf "%%s\n" "${activeHelp[@]}"
  143. printf "\n"
  144. # The prompt format is only available from bash 4.4.
  145. # We test if it is available before using it.
  146. if (x=${PS1@P}) 2> /dev/null; then
  147. printf "%%s" "${PS1@P}${COMP_LINE[@]}"
  148. else
  149. # Can't print the prompt. Just print the
  150. # text the user had typed, it is workable enough.
  151. printf "%%s" "${COMP_LINE[@]}"
  152. fi
  153. fi
  154. }
  155. __%[1]s_handle_completion_types() {
  156. __%[1]s_debug "__%[1]s_handle_completion_types: COMP_TYPE is $COMP_TYPE"
  157. case $COMP_TYPE in
  158. 37|42)
  159. # Type: menu-complete/menu-complete-backward and insert-completions
  160. # If the user requested inserting one completion at a time, or all
  161. # completions at once on the command-line we must remove the descriptions.
  162. # https://github.com/spf13/cobra/issues/1508
  163. local tab=$'\t' comp
  164. while IFS='' read -r comp; do
  165. [[ -z $comp ]] && continue
  166. # Strip any description
  167. comp=${comp%%%%$tab*}
  168. # Only consider the completions that match
  169. if [[ $comp == "$cur"* ]]; then
  170. COMPREPLY+=("$comp")
  171. fi
  172. done < <(printf "%%s\n" "${completions[@]}")
  173. ;;
  174. *)
  175. # Type: complete (normal completion)
  176. __%[1]s_handle_standard_completion_case
  177. ;;
  178. esac
  179. }
  180. __%[1]s_handle_standard_completion_case() {
  181. local tab=$'\t' comp
  182. # Short circuit to optimize if we don't have descriptions
  183. if [[ "${completions[*]}" != *$tab* ]]; then
  184. IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
  185. return 0
  186. fi
  187. local longest=0
  188. local compline
  189. # Look for the longest completion so that we can format things nicely
  190. while IFS='' read -r compline; do
  191. [[ -z $compline ]] && continue
  192. # Strip any description before checking the length
  193. comp=${compline%%%%$tab*}
  194. # Only consider the completions that match
  195. [[ $comp == "$cur"* ]] || continue
  196. COMPREPLY+=("$compline")
  197. if ((${#comp}>longest)); then
  198. longest=${#comp}
  199. fi
  200. done < <(printf "%%s\n" "${completions[@]}")
  201. # If there is a single completion left, remove the description text
  202. if ((${#COMPREPLY[*]} == 1)); then
  203. __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
  204. comp="${COMPREPLY[0]%%%%$tab*}"
  205. __%[1]s_debug "Removed description from single completion, which is now: ${comp}"
  206. COMPREPLY[0]=$comp
  207. else # Format the descriptions
  208. __%[1]s_format_comp_descriptions $longest
  209. fi
  210. }
  211. __%[1]s_handle_special_char()
  212. {
  213. local comp="$1"
  214. local char=$2
  215. if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
  216. local word=${comp%%"${comp##*${char}}"}
  217. local idx=${#COMPREPLY[*]}
  218. while ((--idx >= 0)); do
  219. COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
  220. done
  221. fi
  222. }
  223. __%[1]s_format_comp_descriptions()
  224. {
  225. local tab=$'\t'
  226. local comp desc maxdesclength
  227. local longest=$1
  228. local i ci
  229. for ci in ${!COMPREPLY[*]}; do
  230. comp=${COMPREPLY[ci]}
  231. # Properly format the description string which follows a tab character if there is one
  232. if [[ "$comp" == *$tab* ]]; then
  233. __%[1]s_debug "Original comp: $comp"
  234. desc=${comp#*$tab}
  235. comp=${comp%%%%$tab*}
  236. # $COLUMNS stores the current shell width.
  237. # Remove an extra 4 because we add 2 spaces and 2 parentheses.
  238. maxdesclength=$(( COLUMNS - longest - 4 ))
  239. # Make sure we can fit a description of at least 8 characters
  240. # if we are to align the descriptions.
  241. if ((maxdesclength > 8)); then
  242. # Add the proper number of spaces to align the descriptions
  243. for ((i = ${#comp} ; i < longest ; i++)); do
  244. comp+=" "
  245. done
  246. else
  247. # Don't pad the descriptions so we can fit more text after the completion
  248. maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
  249. fi
  250. # If there is enough space for any description text,
  251. # truncate the descriptions that are too long for the shell width
  252. if ((maxdesclength > 0)); then
  253. if ((${#desc} > maxdesclength)); then
  254. desc=${desc:0:$(( maxdesclength - 1 ))}
  255. desc+="…"
  256. fi
  257. comp+=" ($desc)"
  258. fi
  259. COMPREPLY[ci]=$comp
  260. __%[1]s_debug "Final comp: $comp"
  261. fi
  262. done
  263. }
  264. __start_%[1]s()
  265. {
  266. local cur prev words cword split
  267. COMPREPLY=()
  268. # Call _init_completion from the bash-completion package
  269. # to prepare the arguments properly
  270. if declare -F _init_completion >/dev/null 2>&1; then
  271. _init_completion -n =: || return
  272. else
  273. __%[1]s_init_completion -n =: || return
  274. fi
  275. __%[1]s_debug
  276. __%[1]s_debug "========= starting completion logic =========="
  277. __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
  278. # The user could have moved the cursor backwards on the command-line.
  279. # We need to trigger completion from the $cword location, so we need
  280. # to truncate the command-line ($words) up to the $cword location.
  281. words=("${words[@]:0:$cword+1}")
  282. __%[1]s_debug "Truncated words[*]: ${words[*]},"
  283. local out directive
  284. __%[1]s_get_completion_results
  285. __%[1]s_process_completion_results
  286. }
  287. if [[ $(type -t compopt) = "builtin" ]]; then
  288. complete -o default -F __start_%[1]s %[1]s
  289. else
  290. complete -o default -o nospace -F __start_%[1]s %[1]s
  291. fi
  292. # ex: ts=4 sw=4 et filetype=sh