[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275import { ShellCompDirective } from './t';
// TODO: issue with -- -- completions
export function generate(name: string, exec: string): string { // Replace '-' and ':' with '_' for variable names const nameForVar = name.replace(/[-:]/g, '_');
// Determine the completion command // const compCmd = includeDesc ? "complete" : "complete";
// Shell completion directives const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError; const ShellCompDirectiveNoSpace = ShellCompDirective.ShellCompDirectiveNoSpace; const ShellCompDirectiveNoFileComp = ShellCompDirective.ShellCompDirectiveNoFileComp; const ShellCompDirectiveFilterFileExt = ShellCompDirective.ShellCompDirectiveFilterFileExt; const ShellCompDirectiveFilterDirs = ShellCompDirective.ShellCompDirectiveFilterDirs; const ShellCompDirectiveKeepOrder = ShellCompDirective.ShellCompDirectiveKeepOrder;
return `# powershell completion for ${name} -*- shell-script -*-
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 function __${name}_debug { if ($env:BASH_COMP_DEBUG_FILE) { "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE" } }
filter __${name}_escapeStringWithSpecialChars { $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|"|\\||<|>|&','\`$&' }
[scriptblock]$__${nameForVar}CompleterBlock = { param( $WordToComplete, $CommandAst, $CursorPosition )
# Get the current command line and convert into a string $Command = $CommandAst.CommandElements $Command = "$Command"
__${name}_debug "" __${name}_debug "========= starting completion logic ==========" __${name}_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition"
# The user could have moved the cursor backwards on the command-line. # We need to trigger completion from the $CursorPosition location, so we need # to truncate the command-line ($Command) up to the $CursorPosition location. # Make sure the $Command is longer then the $CursorPosition before we truncate. # This happens because the $Command does not include the last space. if ($Command.Length -gt $CursorPosition) { $Command = $Command.Substring(0, $CursorPosition) } __${name}_debug "Truncated command: $Command"
$ShellCompDirectiveError=${ShellCompDirectiveError} $ShellCompDirectiveNoSpace=${ShellCompDirectiveNoSpace} $ShellCompDirectiveNoFileComp=${ShellCompDirectiveNoFileComp} $ShellCompDirectiveFilterFileExt=${ShellCompDirectiveFilterFileExt} $ShellCompDirectiveFilterDirs=${ShellCompDirectiveFilterDirs} $ShellCompDirectiveKeepOrder=${ShellCompDirectiveKeepOrder}
# Prepare the command to request completions for the program. # Split the command at the first space to separate the program and arguments. $Program, $Arguments = $Command.Split(" ", 2)
$QuotedArgs = ($Arguments -split ' ' | ForEach-Object { "'" + ($_ -replace "'", "''") + "'" }) -join ' ' __${name}_debug "QuotedArgs: $QuotedArgs"
$RequestComp = "& ${exec} complete '--' $QuotedArgs" __${name}_debug "RequestComp: $RequestComp"
# we cannot use $WordToComplete because it # has the wrong values if the cursor was moved # so use the last argument if ($WordToComplete -ne "" ) { $WordToComplete = $Arguments.Split(" ")[-1] } __${name}_debug "New WordToComplete: $WordToComplete"
# Check for flag with equal sign $IsEqualFlag = ($WordToComplete -Like "--*=*" ) if ( $IsEqualFlag ) { __${name}_debug "Completing equal sign flag" # Remove the flag part $Flag, $WordToComplete = $WordToComplete.Split("=", 2) }
if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) { # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go method. __${name}_debug "Adding extra empty parameter" # PowerShell 7.2+ changed the way how the arguments are passed to executables, # so for pre-7.2 or when Legacy argument passing is enabled we need to use if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or ($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or (($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and $PSNativeCommandArgumentPassing -eq 'Legacy')) { $RequestComp="$RequestComp" + ' \`"\`"' } else { $RequestComp = "$RequestComp" + ' ""' } }
__${name}_debug "Calling $RequestComp" # First disable ActiveHelp which is not supported for Powershell $env:ActiveHelp = 0
# call the command store the output in $out and redirect stderr and stdout to null # $Out is an array contains each line per element Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null
# get directive from last line [int]$Directive = $Out[-1].TrimStart(':') if ($Directive -eq "") { # There is no directive specified $Directive = 0 } __${name}_debug "The completion directive is: $Directive"
# remove directive (last element) from out $Out = $Out | Where-Object { $_ -ne $Out[-1] } __${name}_debug "The completions are: $Out"
if (($Directive -band $ShellCompDirectiveError) -ne 0 ) { # Error code. No completion. __${name}_debug "Received error from custom completion go code" return }
$Longest = 0 [Array]$Values = $Out | ForEach-Object { # Split the output in name and description $Name, $Description = $_.Split("\`t", 2) __${name}_debug "Name: $Name Description: $Description"
# Look for the longest completion so that we can format things nicely if ($Longest -lt $Name.Length) { $Longest = $Name.Length }
# Set the description to a one space string if there is none set. # This is needed because the CompletionResult does not accept an empty string as argument if (-Not $Description) { $Description = " " } @{ Name = "$Name"; Description = "$Description" } }
$Space = " " if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) { # remove the space here __${name}_debug "ShellCompDirectiveNoSpace is called" $Space = "" }
if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { __${name}_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"
# return here to prevent the completion of the extensions return }
$Values = $Values | Where-Object { # filter the result $_.Name -like "$WordToComplete*"
# Join the flag back if we have an equal sign flag if ( $IsEqualFlag ) { __${name}_debug "Join the equal sign flag back to the completion value" $_.Name = $Flag + "=" + $_.Name } }
# we sort the values in ascending order by name if keep order isn't passed if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) { $Values = $Values | Sort-Object -Property Name }
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { __${name}_debug "ShellCompDirectiveNoFileComp is called"
if ($Values.Length -eq 0) { # Just print an empty string here so the # shell does not start to complete paths. # We cannot use CompletionResult here because # it does not accept an empty string as argument. "" return } }
# Get the current mode $Mode = (Get-PSReadLineKeyHandler | Where-Object { $_.Key -eq "Tab" }).Function __${name}_debug "Mode: $Mode"
$Values | ForEach-Object {
# store temporary because switch will overwrite $_ $comp = $_
# PowerShell supports three different completion modes # - TabCompleteNext (default windows style - on each key press the next option is displayed) # - Complete (works like bash) # - MenuComplete (works like zsh) # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function <mode>
# CompletionResult Arguments: # 1) CompletionText text to be used as the auto completion result # 2) ListItemText text to be displayed in the suggestion list # 3) ResultType type of completion result # 4) ToolTip text for the tooltip with details about the object
switch ($Mode) {
# bash like "Complete" {
if ($Values.Length -eq 1) { __${name}_debug "Only one completion left"
# insert space after value [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
} else { # Add the proper number of spaces to align the descriptions while($comp.Name.Length -lt $Longest) { $comp.Name = $comp.Name + " " }
# Check for empty description and only add parentheses if needed if ($($comp.Description) -eq " " ) { $Description = "" } else { $Description = " ($($comp.Description))" }
[System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") } }
# zsh like "MenuComplete" { # insert space after value # MenuComplete will automatically show the ToolTip of # the highlighted value at the bottom of the suggestions. [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") }
# TabCompleteNext and in case we get something unknown Default { # Like MenuComplete but we don't want to add a space here because # the user need to press space anyway to get the completion. # Description will not be shown because that's not possible with TabCompleteNext [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") } }
}}
Register-ArgumentCompleter -CommandName '${name}' -ScriptBlock $__${nameForVar}CompleterBlock`;}