powershell_completions.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
  2. // can be downloaded separately for windows 7 or 8.1).
  3. package cobra
  4. import (
  5. "bytes"
  6. "fmt"
  7. "io"
  8. "os"
  9. )
  10. func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) {
  11. compCmd := ShellCompRequestCmd
  12. if !includeDesc {
  13. compCmd = ShellCompNoDescRequestCmd
  14. }
  15. WriteStringAndCheck(buf, fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*-
  16. function __%[1]s_debug {
  17. if ($env:BASH_COMP_DEBUG_FILE) {
  18. "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
  19. }
  20. }
  21. filter __%[1]s_escapeStringWithSpecialChars {
  22. `+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
  23. }
  24. Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
  25. param(
  26. $WordToComplete,
  27. $CommandAst,
  28. $CursorPosition
  29. )
  30. # Get the current command line and convert into a string
  31. $Command = $CommandAst.CommandElements
  32. $Command = "$Command"
  33. __%[1]s_debug ""
  34. __%[1]s_debug "========= starting completion logic =========="
  35. __%[1]s_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition"
  36. # The user could have moved the cursor backwards on the command-line.
  37. # We need to trigger completion from the $CursorPosition location, so we need
  38. # to truncate the command-line ($Command) up to the $CursorPosition location.
  39. # Make sure the $Command is longer then the $CursorPosition before we truncate.
  40. # This happens because the $Command does not include the last space.
  41. if ($Command.Length -gt $CursorPosition) {
  42. $Command=$Command.Substring(0,$CursorPosition)
  43. }
  44. __%[1]s_debug "Truncated command: $Command"
  45. $ShellCompDirectiveError=%[3]d
  46. $ShellCompDirectiveNoSpace=%[4]d
  47. $ShellCompDirectiveNoFileComp=%[5]d
  48. $ShellCompDirectiveFilterFileExt=%[6]d
  49. $ShellCompDirectiveFilterDirs=%[7]d
  50. # Prepare the command to request completions for the program.
  51. # Split the command at the first space to separate the program and arguments.
  52. $Program,$Arguments = $Command.Split(" ",2)
  53. $RequestComp="$Program %[2]s $Arguments"
  54. __%[1]s_debug "RequestComp: $RequestComp"
  55. # we cannot use $WordToComplete because it
  56. # has the wrong values if the cursor was moved
  57. # so use the last argument
  58. if ($WordToComplete -ne "" ) {
  59. $WordToComplete = $Arguments.Split(" ")[-1]
  60. }
  61. __%[1]s_debug "New WordToComplete: $WordToComplete"
  62. # Check for flag with equal sign
  63. $IsEqualFlag = ($WordToComplete -Like "--*=*" )
  64. if ( $IsEqualFlag ) {
  65. __%[1]s_debug "Completing equal sign flag"
  66. # Remove the flag part
  67. $Flag,$WordToComplete = $WordToComplete.Split("=",2)
  68. }
  69. if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) {
  70. # If the last parameter is complete (there is a space following it)
  71. # We add an extra empty parameter so we can indicate this to the go method.
  72. __%[1]s_debug "Adding extra empty parameter"
  73. `+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+`
  74. `+" $RequestComp=\"$RequestComp\" + ' `\"`\"' "+`
  75. }
  76. __%[1]s_debug "Calling $RequestComp"
  77. #call the command store the output in $out and redirect stderr and stdout to null
  78. # $Out is an array contains each line per element
  79. Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
  80. # get directive from last line
  81. [int]$Directive = $Out[-1].TrimStart(':')
  82. if ($Directive -eq "") {
  83. # There is no directive specified
  84. $Directive = 0
  85. }
  86. __%[1]s_debug "The completion directive is: $Directive"
  87. # remove directive (last element) from out
  88. $Out = $Out | Where-Object { $_ -ne $Out[-1] }
  89. __%[1]s_debug "The completions are: $Out"
  90. if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
  91. # Error code. No completion.
  92. __%[1]s_debug "Received error from custom completion go code"
  93. return
  94. }
  95. $Longest = 0
  96. $Values = $Out | ForEach-Object {
  97. #Split the output in name and description
  98. `+" $Name, $Description = $_.Split(\"`t\",2)"+`
  99. __%[1]s_debug "Name: $Name Description: $Description"
  100. # Look for the longest completion so that we can format things nicely
  101. if ($Longest -lt $Name.Length) {
  102. $Longest = $Name.Length
  103. }
  104. # Set the description to a one space string if there is none set.
  105. # This is needed because the CompletionResult does not accept an empty string as argument
  106. if (-Not $Description) {
  107. $Description = " "
  108. }
  109. @{Name="$Name";Description="$Description"}
  110. }
  111. $Space = " "
  112. if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) {
  113. # remove the space here
  114. __%[1]s_debug "ShellCompDirectiveNoSpace is called"
  115. $Space = ""
  116. }
  117. if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
  118. __%[1]s_debug "ShellCompDirectiveNoFileComp is called"
  119. if ($Values.Length -eq 0) {
  120. # Just print an empty string here so the
  121. # shell does not start to complete paths.
  122. # We cannot use CompletionResult here because
  123. # it does not accept an empty string as argument.
  124. ""
  125. return
  126. }
  127. }
  128. if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
  129. (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) {
  130. __%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"
  131. # return here to prevent the completion of the extensions
  132. return
  133. }
  134. $Values = $Values | Where-Object {
  135. # filter the result
  136. $_.Name -like "$WordToComplete*"
  137. # Join the flag back if we have a equal sign flag
  138. if ( $IsEqualFlag ) {
  139. __%[1]s_debug "Join the equal sign flag back to the completion value"
  140. $_.Name = $Flag + "=" + $_.Name
  141. }
  142. }
  143. # Get the current mode
  144. $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
  145. __%[1]s_debug "Mode: $Mode"
  146. $Values | ForEach-Object {
  147. # store temporay because switch will overwrite $_
  148. $comp = $_
  149. # PowerShell supports three different completion modes
  150. # - TabCompleteNext (default windows style - on each key press the next option is displayed)
  151. # - Complete (works like bash)
  152. # - MenuComplete (works like zsh)
  153. # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>
  154. # CompletionResult Arguments:
  155. # 1) CompletionText text to be used as the auto completion result
  156. # 2) ListItemText text to be displayed in the suggestion list
  157. # 3) ResultType type of completion result
  158. # 4) ToolTip text for the tooltip with details about the object
  159. switch ($Mode) {
  160. # bash like
  161. "Complete" {
  162. if ($Values.Length -eq 1) {
  163. __%[1]s_debug "Only one completion left"
  164. # insert space after value
  165. [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
  166. } else {
  167. # Add the proper number of spaces to align the descriptions
  168. while($comp.Name.Length -lt $Longest) {
  169. $comp.Name = $comp.Name + " "
  170. }
  171. # Check for empty description and only add parentheses if needed
  172. if ($($comp.Description) -eq " " ) {
  173. $Description = ""
  174. } else {
  175. $Description = " ($($comp.Description))"
  176. }
  177. [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
  178. }
  179. }
  180. # zsh like
  181. "MenuComplete" {
  182. # insert space after value
  183. # MenuComplete will automatically show the ToolTip of
  184. # the highlighted value at the bottom of the suggestions.
  185. [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
  186. }
  187. # TabCompleteNext and in case we get something unknown
  188. Default {
  189. # Like MenuComplete but we don't want to add a space here because
  190. # the user need to press space anyway to get the completion.
  191. # Description will not be shown because thats not possible with TabCompleteNext
  192. [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
  193. }
  194. }
  195. }
  196. }
  197. `, name, compCmd,
  198. ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
  199. ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
  200. }
  201. func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
  202. buf := new(bytes.Buffer)
  203. genPowerShellComp(buf, c.Name(), includeDesc)
  204. _, err := buf.WriteTo(w)
  205. return err
  206. }
  207. func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error {
  208. outFile, err := os.Create(filename)
  209. if err != nil {
  210. return err
  211. }
  212. defer outFile.Close()
  213. return c.genPowerShellCompletion(outFile, includeDesc)
  214. }
  215. // GenPowerShellCompletionFile generates powershell completion file without descriptions.
  216. func (c *Command) GenPowerShellCompletionFile(filename string) error {
  217. return c.genPowerShellCompletionFile(filename, false)
  218. }
  219. // GenPowerShellCompletion generates powershell completion file without descriptions
  220. // and writes it to the passed writer.
  221. func (c *Command) GenPowerShellCompletion(w io.Writer) error {
  222. return c.genPowerShellCompletion(w, false)
  223. }
  224. // GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions.
  225. func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error {
  226. return c.genPowerShellCompletionFile(filename, true)
  227. }
  228. // GenPowerShellCompletionWithDesc generates powershell completion file with descriptions
  229. // and writes it to the passed writer.
  230. func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error {
  231. return c.genPowerShellCompletion(w, true)
  232. }