powershell completion with custom comp (#1208)
The current powershell completion is not very capable. Let's port it to the go custom completion logic to have a unified experience accross all shells. 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>` To keep it backwards compatible `GenPowerShellCompletion` will not display descriptions. Use `GenPowerShellCompletionWithDesc` instead. Descriptions will only be displayed with `MenuComplete` or `Complete`. Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
parent
471c9ac367
commit
a4ab3fa09e
@ -1,6 +1,3 @@
|
|||||||
// PowerShell completions are based on the amazing work from clap:
|
|
||||||
// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs
|
|
||||||
//
|
|
||||||
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
|
||||||
// can be downloaded separately for windows 7 or 8.1).
|
// can be downloaded separately for windows 7 or 8.1).
|
||||||
|
|
||||||
@ -11,90 +8,278 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var powerShellCompletionTemplate = `using namespace System.Management.Automation
|
func genPowerShellComp(buf *bytes.Buffer, name string, includeDesc bool) {
|
||||||
using namespace System.Management.Automation.Language
|
compCmd := ShellCompRequestCmd
|
||||||
Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
|
if !includeDesc {
|
||||||
param($wordToComplete, $commandAst, $cursorPosition)
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
$commandElements = $commandAst.CommandElements
|
|
||||||
$command = @(
|
|
||||||
'%s'
|
|
||||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
|
||||||
$element = $commandElements[$i]
|
|
||||||
if ($element -isnot [StringConstantExpressionAst] -or
|
|
||||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
|
||||||
$element.Value.StartsWith('-')) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
$element.Value
|
buf.WriteString(fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*-
|
||||||
}
|
|
||||||
) -join ';'
|
|
||||||
$completions = @(switch ($command) {%s
|
|
||||||
})
|
|
||||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
|
||||||
Sort-Object -Property ListItemText
|
|
||||||
}`
|
|
||||||
|
|
||||||
func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) {
|
function __%[1]s_debug {
|
||||||
var cmdName string
|
if ($env:BASH_COMP_DEBUG_FILE) {
|
||||||
if previousCommandName == "" {
|
"$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE"
|
||||||
cmdName = cmd.Name()
|
}
|
||||||
} else {
|
}
|
||||||
cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name())
|
|
||||||
|
filter __%[1]s_escapeStringWithSpecialChars {
|
||||||
|
`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
|
||||||
|
}
|
||||||
|
|
||||||
|
Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
|
||||||
|
param(
|
||||||
|
$WordToComplete,
|
||||||
|
$CommandAst,
|
||||||
|
$CursorPosition
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the current command line and convert into a string
|
||||||
|
$Command = $CommandAst.CommandElements
|
||||||
|
$Command = "$Command"
|
||||||
|
|
||||||
|
__%[1]s_debug ""
|
||||||
|
__%[1]s_debug "========= starting completion logic =========="
|
||||||
|
__%[1]s_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)
|
||||||
|
}
|
||||||
|
__%[1]s_debug "Truncated command: $Command"
|
||||||
|
|
||||||
|
$ShellCompDirectiveError=%[3]d
|
||||||
|
$ShellCompDirectiveNoSpace=%[4]d
|
||||||
|
$ShellCompDirectiveNoFileComp=%[5]d
|
||||||
|
$ShellCompDirectiveFilterFileExt=%[6]d
|
||||||
|
$ShellCompDirectiveFilterDirs=%[7]d
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
$RequestComp="$Program %[2]s $Arguments"
|
||||||
|
__%[1]s_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]
|
||||||
|
}
|
||||||
|
__%[1]s_debug "New WordToComplete: $WordToComplete"
|
||||||
|
|
||||||
|
|
||||||
|
# Check for flag with equal sign
|
||||||
|
$IsEqualFlag = ($WordToComplete -Like "--*=*" )
|
||||||
|
if ( $IsEqualFlag ) {
|
||||||
|
__%[1]s_debug "Completing equal sign flag"
|
||||||
|
# Remove the flag part
|
||||||
|
$Flag,$WordToComplete = $WordToComplete.Split("=",2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, "\n '%s' {", cmdName)
|
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.
|
||||||
|
__%[1]s_debug "Adding extra empty parameter"
|
||||||
|
`+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+`
|
||||||
|
`+" $RequestComp=\"$RequestComp\" + ' `\"`\"' "+`
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
__%[1]s_debug "Calling $RequestComp"
|
||||||
if nonCompletableFlag(flag) {
|
#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
|
||||||
|
}
|
||||||
|
__%[1]s_debug "The completion directive is: $Directive"
|
||||||
|
|
||||||
|
# remove directive (last element) from out
|
||||||
|
$Out = $Out | Where-Object { $_ -ne $Out[-1] }
|
||||||
|
__%[1]s_debug "The completions are: $Out"
|
||||||
|
|
||||||
|
if (($Directive -band $ShellCompDirectiveError) -ne 0 ) {
|
||||||
|
# Error code. No completion.
|
||||||
|
__%[1]s_debug "Received error from custom completion go code"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
usage := escapeStringForPowerShell(flag.Usage)
|
|
||||||
if len(flag.Shorthand) > 0 {
|
|
||||||
fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, subCmd := range cmd.Commands() {
|
$Longest = 0
|
||||||
usage := escapeStringForPowerShell(subCmd.Short)
|
$Values = $Out | ForEach-Object {
|
||||||
fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage)
|
#Split the output in name and description
|
||||||
|
`+" $Name, $Description = $_.Split(\"`t\",2)"+`
|
||||||
|
__%[1]s_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
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprint(out, "\n break\n }")
|
# 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
|
||||||
|
__%[1]s_debug "ShellCompDirectiveNoSpace is called"
|
||||||
|
$Space = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
|
||||||
|
__%[1]s_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
|
||||||
|
(($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) {
|
||||||
|
__%[1]s_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 a equal sign flag
|
||||||
|
if ( $IsEqualFlag ) {
|
||||||
|
__%[1]s_debug "Join the equal sign flag back to the completion value"
|
||||||
|
$_.Name = $Flag + "=" + $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the current mode
|
||||||
|
$Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
|
||||||
|
__%[1]s_debug "Mode: $Mode"
|
||||||
|
|
||||||
|
$Values | ForEach-Object {
|
||||||
|
|
||||||
|
# store temporay 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) {
|
||||||
|
__%[1]s_debug "Only one completion left"
|
||||||
|
|
||||||
|
# insert space after value
|
||||||
|
[System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_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 | __%[1]s_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 thats not possible with TabCompleteNext
|
||||||
|
[System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, subCmd := range cmd.Commands() {
|
|
||||||
generatePowerShellSubcommandCases(out, subCmd, cmdName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
`, name, compCmd,
|
||||||
func escapeStringForPowerShell(s string) string {
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
return strings.Replace(s, "'", "''", -1)
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer.
|
func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
|
||||||
func (c *Command) GenPowerShellCompletion(w io.Writer) error {
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
genPowerShellComp(buf, c.Name(), includeDesc)
|
||||||
var subCommandCases bytes.Buffer
|
|
||||||
generatePowerShellSubcommandCases(&subCommandCases, c, "")
|
|
||||||
fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String())
|
|
||||||
|
|
||||||
_, err := buf.WriteTo(w)
|
_, err := buf.WriteTo(w)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenPowerShellCompletionFile generates PowerShell completion file.
|
func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error {
|
||||||
func (c *Command) GenPowerShellCompletionFile(filename string) error {
|
|
||||||
outFile, err := os.Create(filename)
|
outFile, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
return c.GenPowerShellCompletion(outFile)
|
return c.genPowerShellCompletion(outFile, includeDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionFile generates powershell completion file without descriptions.
|
||||||
|
func (c *Command) GenPowerShellCompletionFile(filename string) error {
|
||||||
|
return c.genPowerShellCompletionFile(filename, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletion generates powershell completion file without descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenPowerShellCompletion(w io.Writer) error {
|
||||||
|
return c.genPowerShellCompletion(w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions.
|
||||||
|
func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error {
|
||||||
|
return c.genPowerShellCompletionFile(filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenPowerShellCompletionWithDesc generates powershell completion file with descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error {
|
||||||
|
return c.genPowerShellCompletion(w, true)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,3 @@
|
|||||||
# Generating PowerShell Completions For Your Own cobra.Command
|
# Generating PowerShell Completions For Your Own cobra.Command
|
||||||
|
|
||||||
Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles.
|
Please refer to [Shell Completions](shell_completions.md#powershell-completions) for details.
|
||||||
|
|
||||||
*Note*: PowerShell completions have not (yet?) been aligned to Cobra's generic shell completion support. This implies the PowerShell completions are not as rich as for other shells (see [What's not yet supported](#whats-not-yet-supported)), and may behave slightly differently. They are still very useful for PowerShell users.
|
|
||||||
|
|
||||||
# What's supported
|
|
||||||
|
|
||||||
- Completion for subcommands using their `.Short` description
|
|
||||||
- Completion for non-hidden flags using their `.Name` and `.Shorthand`
|
|
||||||
|
|
||||||
# What's not yet supported
|
|
||||||
|
|
||||||
- Command aliases
|
|
||||||
- Required, filename or custom flags (they will work like normal flags)
|
|
||||||
- Custom completion scripts
|
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPowerShellCompletion(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
name string
|
|
||||||
root *Command
|
|
||||||
expectedExpressions []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "trivial",
|
|
||||||
root: &Command{Use: "trivialapp"},
|
|
||||||
expectedExpressions: []string{
|
|
||||||
"Register-ArgumentCompleter -Native -CommandName 'trivialapp' -ScriptBlock",
|
|
||||||
"$command = @(\n 'trivialapp'\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tree",
|
|
||||||
root: func() *Command {
|
|
||||||
r := &Command{Use: "tree"}
|
|
||||||
|
|
||||||
sub1 := &Command{Use: "sub1"}
|
|
||||||
r.AddCommand(sub1)
|
|
||||||
|
|
||||||
sub11 := &Command{Use: "sub11"}
|
|
||||||
sub12 := &Command{Use: "sub12"}
|
|
||||||
|
|
||||||
sub1.AddCommand(sub11)
|
|
||||||
sub1.AddCommand(sub12)
|
|
||||||
|
|
||||||
sub2 := &Command{Use: "sub2"}
|
|
||||||
r.AddCommand(sub2)
|
|
||||||
|
|
||||||
sub21 := &Command{Use: "sub21"}
|
|
||||||
sub22 := &Command{Use: "sub22"}
|
|
||||||
|
|
||||||
sub2.AddCommand(sub21)
|
|
||||||
sub2.AddCommand(sub22)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}(),
|
|
||||||
expectedExpressions: []string{
|
|
||||||
"'tree'",
|
|
||||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"[CompletionResult]::new('sub2', 'sub2', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"'tree;sub1'",
|
|
||||||
"[CompletionResult]::new('sub11', 'sub11', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"[CompletionResult]::new('sub12', 'sub12', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"'tree;sub1;sub11'",
|
|
||||||
"'tree;sub1;sub12'",
|
|
||||||
"'tree;sub2'",
|
|
||||||
"[CompletionResult]::new('sub21', 'sub21', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"[CompletionResult]::new('sub22', 'sub22', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"'tree;sub2;sub21'",
|
|
||||||
"'tree;sub2;sub22'",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flags",
|
|
||||||
root: func() *Command {
|
|
||||||
r := &Command{Use: "flags"}
|
|
||||||
r.Flags().StringP("flag1", "a", "", "")
|
|
||||||
r.Flags().String("flag2", "", "")
|
|
||||||
|
|
||||||
sub1 := &Command{Use: "sub1"}
|
|
||||||
sub1.Flags().StringP("flag3", "c", "", "")
|
|
||||||
r.AddCommand(sub1)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}(),
|
|
||||||
expectedExpressions: []string{
|
|
||||||
"'flags'",
|
|
||||||
"[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, '')",
|
|
||||||
"[CompletionResult]::new('--flag1', 'flag1', [CompletionResultType]::ParameterName, '')",
|
|
||||||
"[CompletionResult]::new('--flag2', 'flag2', [CompletionResultType]::ParameterName, '')",
|
|
||||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')",
|
|
||||||
"'flags;sub1'",
|
|
||||||
"[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, '')",
|
|
||||||
"[CompletionResult]::new('--flag3', 'flag3', [CompletionResultType]::ParameterName, '')",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "usage",
|
|
||||||
root: func() *Command {
|
|
||||||
r := &Command{Use: "usage"}
|
|
||||||
r.Flags().String("flag", "", "this describes the usage of the 'flag' flag")
|
|
||||||
|
|
||||||
sub1 := &Command{
|
|
||||||
Use: "sub1",
|
|
||||||
Short: "short describes 'sub1'",
|
|
||||||
}
|
|
||||||
r.AddCommand(sub1)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}(),
|
|
||||||
expectedExpressions: []string{
|
|
||||||
"[CompletionResult]::new('--flag', 'flag', [CompletionResultType]::ParameterName, 'this describes the usage of the ''flag'' flag')",
|
|
||||||
"[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, 'short describes ''sub1''')",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tcs {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
tc.root.GenPowerShellCompletion(buf)
|
|
||||||
output := buf.String()
|
|
||||||
|
|
||||||
for _, expectedExpression := range tc.expectedExpressions {
|
|
||||||
if !strings.Contains(output, expectedExpression) {
|
|
||||||
t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,6 +49,14 @@ $ yourprogram completion fish | source
|
|||||||
|
|
||||||
# To load completions for each session, execute once:
|
# To load completions for each session, execute once:
|
||||||
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
|
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
|
||||||
|
|
||||||
|
Powershell:
|
||||||
|
|
||||||
|
PS> yourprogram completion powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
|
# To load completions for every new session, run:
|
||||||
|
PS> yourprogram completion powershell > yourprogram.ps1
|
||||||
|
# and source this file from your powershell profile.
|
||||||
`,
|
`,
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
@ -316,7 +324,7 @@ cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string,
|
|||||||
```
|
```
|
||||||
### Descriptions for completions
|
### Descriptions for completions
|
||||||
|
|
||||||
Both `zsh` and `fish` allow for descriptions to annotate completion choices. For commands and flags, Cobra will provide the descriptions automatically, based on usage information. For example, using zsh:
|
`zsh`, `fish` and `powershell` allow for descriptions to annotate completion choices. For commands and flags, Cobra will provide the descriptions automatically, based on usage information. For example, using zsh:
|
||||||
```
|
```
|
||||||
$ helm s[tab]
|
$ helm s[tab]
|
||||||
search -- search for a keyword in charts
|
search -- search for a keyword in charts
|
||||||
@ -390,7 +398,7 @@ search show status
|
|||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `zsh` (including the use of the `BashCompCustom` flag annotation).
|
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `zsh` (including the use of the `BashCompCustom` flag annotation).
|
||||||
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`).
|
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`, `powershell`).
|
||||||
* The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`.
|
* The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`.
|
||||||
* You should instead use `RegisterFlagCompletionFunc()`.
|
* You should instead use `RegisterFlagCompletionFunc()`.
|
||||||
|
|
||||||
@ -416,7 +424,7 @@ search show status
|
|||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `fish` (including the use of the `BashCompCustom` flag annotation).
|
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `fish` (including the use of the `BashCompCustom` flag annotation).
|
||||||
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`).
|
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`, `powershell`).
|
||||||
* The function `MarkFlagCustom()` is not supported and will be ignored for `fish`.
|
* The function `MarkFlagCustom()` is not supported and will be ignored for `fish`.
|
||||||
* You should instead use `RegisterFlagCompletionFunc()`.
|
* You should instead use `RegisterFlagCompletionFunc()`.
|
||||||
* The following flag completion annotations are not supported and will be ignored for `fish`:
|
* The following flag completion annotations are not supported and will be ignored for `fish`:
|
||||||
@ -431,4 +439,46 @@ search show status
|
|||||||
|
|
||||||
## PowerShell completions
|
## PowerShell completions
|
||||||
|
|
||||||
Please refer to [PowerShell Completions](powershell_completions.md) for details.
|
Cobra supports native PowerShell completions generated from the root `cobra.Command`. You can use the `command.GenPowerShellCompletion()` or `command.GenPowerShellCompletionFile()` functions. To include descriptions use `command.GenPowerShellCompletionWithDesc()` and `command.GenPowerShellCompletionFileWithDesc()`. Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users.
|
||||||
|
|
||||||
|
The script is designed to support all three Powershell 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>`. Descriptions are only displayed when using the `Complete` or `MenuComplete` mode.
|
||||||
|
|
||||||
|
Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles.
|
||||||
|
|
||||||
|
```
|
||||||
|
# With descriptions and Mode 'Complete'
|
||||||
|
$ helm s[tab]
|
||||||
|
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
|
||||||
|
|
||||||
|
# With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions.
|
||||||
|
$ helm s[tab]
|
||||||
|
search show status
|
||||||
|
|
||||||
|
search for a keyword in charts
|
||||||
|
|
||||||
|
# Without descriptions
|
||||||
|
$ helm s[tab]
|
||||||
|
search show status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `powershell` (including the use of the `BashCompCustom` flag annotation).
|
||||||
|
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`, `powershell`).
|
||||||
|
* The function `MarkFlagCustom()` is not supported and will be ignored for `powershell`.
|
||||||
|
* You should instead use `RegisterFlagCompletionFunc()`.
|
||||||
|
* The following flag completion annotations are not supported and will be ignored for `powershell`:
|
||||||
|
* `BashCompFilenameExt` (filtering by file extension)
|
||||||
|
* `BashCompSubdirsInDir` (filtering by directory)
|
||||||
|
* The functions corresponding to the above annotations are consequently not supported and will be ignored for `powershell`:
|
||||||
|
* `MarkFlagFilename()` and `MarkPersistentFlagFilename()` (filtering by file extension)
|
||||||
|
* `MarkFlagDirname()` and `MarkPersistentFlagDirname()` (filtering by directory)
|
||||||
|
* Similarly, the following completion directives are not supported and will be ignored for `powershell`:
|
||||||
|
* `ShellCompDirectiveFilterFileExt` (filtering by file extension)
|
||||||
|
* `ShellCompDirectiveFilterDirs` (filtering by directory)
|
Loading…
Reference in New Issue
Block a user