git-bug

  1# powershell completion for git-bug                              -*- shell-script -*-
  2
  3function __git-bug_debug {
  4    if ($env:BASH_COMP_DEBUG_FILE) {
  5        "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
  6    }
  7}
  8
  9filter __git-bug_escapeStringWithSpecialChars {
 10    $_ -replace '\s|#|@|\$|;|,|''|\{|\}|\(|\)|"|`|\||<|>|&','`$&'
 11}
 12
 13[scriptblock]${__git_bugCompleterBlock} = {
 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    __git-bug_debug ""
 25    __git-bug_debug "========= starting completion logic =========="
 26    __git-bug_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    __git-bug_debug "Truncated command: $Command"
 37
 38    $ShellCompDirectiveError=1
 39    $ShellCompDirectiveNoSpace=2
 40    $ShellCompDirectiveNoFileComp=4
 41    $ShellCompDirectiveFilterFileExt=8
 42    $ShellCompDirectiveFilterDirs=16
 43    $ShellCompDirectiveKeepOrder=32
 44
 45    # Prepare the command to request completions for the program.
 46    # Split the command at the first space to separate the program and arguments.
 47    $Program,$Arguments = $Command.Split(" ",2)
 48
 49    $RequestComp="$Program __completeNoDesc $Arguments"
 50    __git-bug_debug "RequestComp: $RequestComp"
 51
 52    # we cannot use $WordToComplete because it
 53    # has the wrong values if the cursor was moved
 54    # so use the last argument
 55    if ($WordToComplete -ne "" ) {
 56        $WordToComplete = $Arguments.Split(" ")[-1]
 57    }
 58    __git-bug_debug "New WordToComplete: $WordToComplete"
 59
 60
 61    # Check for flag with equal sign
 62    $IsEqualFlag = ($WordToComplete -Like "--*=*" )
 63    if ( $IsEqualFlag ) {
 64        __git-bug_debug "Completing equal sign flag"
 65        # Remove the flag part
 66        $Flag,$WordToComplete = $WordToComplete.Split("=",2)
 67    }
 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        __git-bug_debug "Adding extra empty parameter"
 73        # PowerShell 7.2+ changed the way how the arguments are passed to executables,
 74        # so for pre-7.2 or when Legacy argument passing is enabled we need to use
 75        # `"`" to pass an empty argument, a "" or '' does not work!!!
 76        if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or
 77            ($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or
 78            (($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and
 79              $PSNativeCommandArgumentPassing -eq 'Legacy')) {
 80             $RequestComp="$RequestComp" + ' `"`"'
 81        } else {
 82             $RequestComp="$RequestComp" + ' ""'
 83        }
 84    }
 85
 86    __git-bug_debug "Calling $RequestComp"
 87    # First disable ActiveHelp which is not supported for Powershell
 88    ${env:GIT_BUG_ACTIVE_HELP}=0
 89
 90    #call the command store the output in $out and redirect stderr and stdout to null
 91    # $Out is an array contains each line per element
 92    Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
 93
 94    # get directive from last line
 95    [int]$Directive = $Out[-1].TrimStart(':')
 96    if ($Directive -eq "") {
 97        # There is no directive specified
 98        $Directive = 0
 99    }
100    __git-bug_debug "The completion directive is: $Directive"
101
102    # remove directive (last element) from out
103    $Out = $Out | Where-Object { $_ -ne $Out[-1] }
104    __git-bug_debug "The completions are: $Out"
105
106    if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
107        # Error code.  No completion.
108        __git-bug_debug "Received error from custom completion go code"
109        return
110    }
111
112    $Longest = 0
113    [Array]$Values = $Out | ForEach-Object {
114        #Split the output in name and description
115        $Name, $Description = $_.Split("`t",2)
116        __git-bug_debug "Name: $Name Description: $Description"
117
118        # Look for the longest completion so that we can format things nicely
119        if ($Longest -lt $Name.Length) {
120            $Longest = $Name.Length
121        }
122
123        # Set the description to a one space string if there is none set.
124        # This is needed because the CompletionResult does not accept an empty string as argument
125        if (-Not $Description) {
126            $Description = " "
127        }
128        @{Name="$Name";Description="$Description"}
129    }
130
131
132    $Space = " "
133    if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) {
134        # remove the space here
135        __git-bug_debug "ShellCompDirectiveNoSpace is called"
136        $Space = ""
137    }
138
139    if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
140       (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 ))  {
141        __git-bug_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 an equal sign flag
152        if ( $IsEqualFlag ) {
153            __git-bug_debug "Join the equal sign flag back to the completion value"
154            $_.Name = $Flag + "=" + $_.Name
155        }
156    }
157
158    # we sort the values in ascending order by name if keep order isn't passed
159    if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) {
160        $Values = $Values | Sort-Object -Property Name
161    }
162
163    if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
164        __git-bug_debug "ShellCompDirectiveNoFileComp is called"
165
166        if ($Values.Length -eq 0) {
167            # Just print an empty string here so the
168            # shell does not start to complete paths.
169            # We cannot use CompletionResult here because
170            # it does not accept an empty string as argument.
171            ""
172            return
173        }
174    }
175
176    # Get the current mode
177    $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
178    __git-bug_debug "Mode: $Mode"
179
180    $Values | ForEach-Object {
181
182        # store temporary because switch will overwrite $_
183        $comp = $_
184
185        # PowerShell supports three different completion modes
186        # - TabCompleteNext (default windows style - on each key press the next option is displayed)
187        # - Complete (works like bash)
188        # - MenuComplete (works like zsh)
189        # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>
190
191        # CompletionResult Arguments:
192        # 1) CompletionText text to be used as the auto completion result
193        # 2) ListItemText   text to be displayed in the suggestion list
194        # 3) ResultType     type of completion result
195        # 4) ToolTip        text for the tooltip with details about the object
196
197        switch ($Mode) {
198
199            # bash like
200            "Complete" {
201
202                if ($Values.Length -eq 1) {
203                    __git-bug_debug "Only one completion left"
204
205                    # insert space after value
206                    [System.Management.Automation.CompletionResult]::new($($comp.Name | __git-bug_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
207
208                } else {
209                    # Add the proper number of spaces to align the descriptions
210                    while($comp.Name.Length -lt $Longest) {
211                        $comp.Name = $comp.Name + " "
212                    }
213
214                    # Check for empty description and only add parentheses if needed
215                    if ($($comp.Description) -eq " " ) {
216                        $Description = ""
217                    } else {
218                        $Description = "  ($($comp.Description))"
219                    }
220
221                    [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)")
222                }
223             }
224
225            # zsh like
226            "MenuComplete" {
227                # insert space after value
228                # MenuComplete will automatically show the ToolTip of
229                # the highlighted value at the bottom of the suggestions.
230                [System.Management.Automation.CompletionResult]::new($($comp.Name | __git-bug_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
231            }
232
233            # TabCompleteNext and in case we get something unknown
234            Default {
235                # Like MenuComplete but we don't want to add a space here because
236                # the user need to press space anyway to get the completion.
237                # Description will not be shown because that's not possible with TabCompleteNext
238                [System.Management.Automation.CompletionResult]::new($($comp.Name | __git-bug_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
239            }
240        }
241
242    }
243}
244
245Register-ArgumentCompleter -CommandName 'git-bug' -ScriptBlock ${__git_bugCompleterBlock}