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