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