2023-03-06 05:28:31 +03:00
// Copyright 2013-2023 The Cobra Authors
2022-09-16 14:55:56 +03:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2020-04-03 22:43:43 +03:00
package cobra
import (
"fmt"
"os"
2023-12-17 21:50:59 +03:00
"regexp"
"strconv"
2020-04-03 22:43:43 +03:00
"strings"
2021-07-02 18:25:47 +03:00
"sync"
2020-04-03 22:43:43 +03:00
"github.com/spf13/pflag"
)
2020-04-10 22:56:28 +03:00
const (
// ShellCompRequestCmd is the name of the hidden command that is used to request
// completion results from the program. It is used by the shell completion scripts.
ShellCompRequestCmd = "__complete"
// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
// completion results without their description. It is used by the shell completion scripts.
ShellCompNoDescRequestCmd = "__completeNoDesc"
)
2020-04-03 22:43:43 +03:00
2021-07-02 18:25:47 +03:00
// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
var flagCompletionFunctions = map [ * pflag . Flag ] func ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective ) { }
// lock for reading and writing from flagCompletionFunctions
var flagCompletionMutex = & sync . RWMutex { }
2020-04-06 20:28:44 +03:00
// ShellCompDirective is a bit map representing the different behaviors the shell
2020-04-03 22:43:43 +03:00
// can be instructed to have once completions have been provided.
2020-04-06 20:28:44 +03:00
type ShellCompDirective int
2020-04-03 22:43:43 +03:00
2021-07-01 18:47:10 +03:00
type flagCompError struct {
subCommand string
flagName string
}
func ( e * flagCompError ) Error ( ) string {
return "Subcommand '" + e . subCommand + "' does not support flag '" + e . flagName + "'"
}
2020-04-03 22:43:43 +03:00
const (
2020-04-06 20:28:44 +03:00
// ShellCompDirectiveError indicates an error occurred and completions should be ignored.
ShellCompDirectiveError ShellCompDirective = 1 << iota
2020-04-03 22:43:43 +03:00
2020-04-06 20:28:44 +03:00
// ShellCompDirectiveNoSpace indicates that the shell should not add a space
2020-04-03 22:43:43 +03:00
// after the completion even if there is a single completion provided.
2020-04-06 20:28:44 +03:00
ShellCompDirectiveNoSpace
2020-04-03 22:43:43 +03:00
2020-04-06 20:28:44 +03:00
// ShellCompDirectiveNoFileComp indicates that the shell should not provide
2020-04-03 22:43:43 +03:00
// file completion even when no completion is provided.
2020-04-06 20:28:44 +03:00
ShellCompDirectiveNoFileComp
2020-04-03 22:43:43 +03:00
2020-06-29 22:52:14 +03:00
// ShellCompDirectiveFilterFileExt indicates that the provided completions
// should be used as file extension filters.
// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
// is a shortcut to using this directive explicitly. The BashCompFilenameExt
// annotation can also be used to obtain the same behavior for flags.
ShellCompDirectiveFilterFileExt
// ShellCompDirectiveFilterDirs indicates that only directory names should
// be provided in file completion. To request directory names within another
// directory, the returned completions should specify the directory within
// which to search. The BashCompSubdirsInDir annotation can be used to
// obtain the same behavior but only for flags.
ShellCompDirectiveFilterDirs
2023-02-25 23:57:12 +03:00
// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
// in which the completions are provided
ShellCompDirectiveKeepOrder
2020-06-29 22:52:14 +03:00
// ===========================================================================
// All directives using iota should be above this one.
// For internal use.
shellCompDirectiveMaxValue
2020-04-06 20:28:44 +03:00
// ShellCompDirectiveDefault indicates to let the shell perform its default
2020-04-03 22:43:43 +03:00
// behavior after completions have been provided.
2020-06-29 22:52:14 +03:00
// This one must be last to avoid messing up the iota count.
2020-04-06 20:28:44 +03:00
ShellCompDirectiveDefault ShellCompDirective = 0
2020-04-03 22:43:43 +03:00
)
2021-02-15 20:47:01 +03:00
const (
// Constants for the completion command
compCmdName = "completion"
compCmdNoDescFlagName = "no-descriptions"
compCmdNoDescFlagDesc = "disable completion descriptions"
compCmdNoDescFlagDefault = false
)
// CompletionOptions are the options to control shell completion
type CompletionOptions struct {
// DisableDefaultCmd prevents Cobra from creating a default 'completion' command
DisableDefaultCmd bool
// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
// for shells that support completion descriptions
DisableNoDescFlag bool
// DisableDescriptions turns off all completion descriptions for shells
// that support them
DisableDescriptions bool
2021-12-08 02:02:02 +03:00
// HiddenDefaultCmd makes the default 'completion' command hidden
HiddenDefaultCmd bool
2021-02-15 20:47:01 +03:00
}
// NoFileCompletions can be used to disable file completion for commands that should
// not trigger file completions.
func NoFileCompletions ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective ) {
return nil , ShellCompDirectiveNoFileComp
}
2022-03-18 04:37:54 +03:00
// FixedCompletions can be used to create a completion function which always
// returns the same results.
func FixedCompletions ( choices [ ] string , directive ShellCompDirective ) func ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective ) {
return func ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective ) {
return choices , directive
}
}
2020-04-03 22:43:43 +03:00
// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
2020-04-06 20:28:44 +03:00
func ( c * Command ) RegisterFlagCompletionFunc ( flagName string , f func ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective ) ) error {
2020-04-03 22:43:43 +03:00
flag := c . Flag ( flagName )
if flag == nil {
return fmt . Errorf ( "RegisterFlagCompletionFunc: flag '%s' does not exist" , flagName )
}
2021-07-02 18:25:47 +03:00
flagCompletionMutex . Lock ( )
defer flagCompletionMutex . Unlock ( )
2021-07-01 00:49:30 +03:00
2021-07-02 18:25:47 +03:00
if _ , exists := flagCompletionFunctions [ flag ] ; exists {
2020-04-03 22:43:43 +03:00
return fmt . Errorf ( "RegisterFlagCompletionFunc: flag '%s' already registered" , flagName )
}
2021-07-02 18:25:47 +03:00
flagCompletionFunctions [ flag ] = f
2020-04-03 22:43:43 +03:00
return nil
}
2023-11-02 18:23:08 +03:00
// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.
func ( c * Command ) GetFlagCompletionFunc ( flagName string ) ( func ( * Command , [ ] string , string ) ( [ ] string , ShellCompDirective ) , bool ) {
flag := c . Flag ( flagName )
if flag == nil {
return nil , false
}
2023-09-26 03:04:25 +03:00
flagCompletionMutex . RLock ( )
defer flagCompletionMutex . RUnlock ( )
completionFunc , exists := flagCompletionFunctions [ flag ]
return completionFunc , exists
}
2020-04-03 22:43:43 +03:00
// Returns a string listing the different directive enabled in the specified parameter
2020-04-06 20:28:44 +03:00
func ( d ShellCompDirective ) string ( ) string {
2020-04-03 22:43:43 +03:00
var directives [ ] string
2020-04-06 20:28:44 +03:00
if d & ShellCompDirectiveError != 0 {
directives = append ( directives , "ShellCompDirectiveError" )
2020-04-03 22:43:43 +03:00
}
2020-04-06 20:28:44 +03:00
if d & ShellCompDirectiveNoSpace != 0 {
directives = append ( directives , "ShellCompDirectiveNoSpace" )
2020-04-03 22:43:43 +03:00
}
2020-04-06 20:28:44 +03:00
if d & ShellCompDirectiveNoFileComp != 0 {
directives = append ( directives , "ShellCompDirectiveNoFileComp" )
2020-04-03 22:43:43 +03:00
}
2020-06-29 22:52:14 +03:00
if d & ShellCompDirectiveFilterFileExt != 0 {
directives = append ( directives , "ShellCompDirectiveFilterFileExt" )
}
if d & ShellCompDirectiveFilterDirs != 0 {
directives = append ( directives , "ShellCompDirectiveFilterDirs" )
}
2023-02-25 23:57:12 +03:00
if d & ShellCompDirectiveKeepOrder != 0 {
directives = append ( directives , "ShellCompDirectiveKeepOrder" )
}
2020-04-03 22:43:43 +03:00
if len ( directives ) == 0 {
2020-04-06 20:28:44 +03:00
directives = append ( directives , "ShellCompDirectiveDefault" )
2020-04-03 22:43:43 +03:00
}
2020-06-29 22:52:14 +03:00
if d >= shellCompDirectiveMaxValue {
2020-04-06 20:28:44 +03:00
return fmt . Sprintf ( "ERROR: unexpected ShellCompDirective value: %d" , d )
2020-04-03 22:43:43 +03:00
}
return strings . Join ( directives , ", " )
}
2022-12-25 23:08:39 +03:00
// initCompleteCmd adds a special hidden command that can be used to request custom completions.
2020-04-03 22:43:43 +03:00
func ( c * Command ) initCompleteCmd ( args [ ] string ) {
completeCmd := & Command {
2020-04-06 20:28:44 +03:00
Use : fmt . Sprintf ( "%s [command-line]" , ShellCompRequestCmd ) ,
2020-04-10 22:56:28 +03:00
Aliases : [ ] string { ShellCompNoDescRequestCmd } ,
2020-04-03 22:43:43 +03:00
DisableFlagsInUseLine : true ,
Hidden : true ,
DisableFlagParsing : true ,
Args : MinimumNArgs ( 1 ) ,
Short : "Request shell completion choices for the specified command-line" ,
Long : fmt . Sprintf ( "%[2]s is a special command that is used by the shell completion logic\n%[1]s" ,
2020-04-06 20:28:44 +03:00
"to request completion choices for the specified command-line." , ShellCompRequestCmd ) ,
2020-04-03 22:43:43 +03:00
Run : func ( cmd * Command , args [ ] string ) {
finalCmd , completions , directive , err := cmd . getCompletions ( args )
if err != nil {
CompErrorln ( err . Error ( ) )
// Keep going for multiple reasons:
// 1- There could be some valid completions even though there was an error
// 2- Even without completions, we need to print the directive
}
2023-12-17 21:50:59 +03:00
noDescriptions := cmd . CalledAs ( ) == ShellCompNoDescRequestCmd
if ! noDescriptions {
if doDescriptions , err := strconv . ParseBool ( getEnvConfig ( cmd , configEnvVarSuffixDescriptions ) ) ; err == nil {
noDescriptions = ! doDescriptions
}
}
2023-11-23 20:24:33 +03:00
noActiveHelp := GetActiveHelpConfig ( finalCmd ) == activeHelpGlobalDisable
out := finalCmd . OutOrStdout ( )
2020-04-03 22:43:43 +03:00
for _ , comp := range completions {
2023-11-23 20:24:33 +03:00
if noActiveHelp && strings . HasPrefix ( comp , activeHelpMarker ) {
// Remove all activeHelp entries if it's disabled.
continue
2022-06-16 03:08:16 +03:00
}
2020-04-10 22:56:28 +03:00
if noDescriptions {
// Remove any description that may be included following a tab character.
2023-11-23 20:24:33 +03:00
comp = strings . SplitN ( comp , "\t" , 2 ) [ 0 ]
2020-04-10 22:56:28 +03:00
}
2020-06-29 22:52:14 +03:00
2020-09-09 20:27:42 +03:00
// Make sure we only write the first line to the output.
// This is needed if a description contains a linebreak.
// Otherwise the shell scripts will interpret the other lines as new flags
// and could therefore provide a wrong completion.
2023-11-23 20:24:33 +03:00
comp = strings . SplitN ( comp , "\n" , 2 ) [ 0 ]
2020-09-09 20:27:42 +03:00
2020-06-29 22:52:14 +03:00
// Finally trim the completion. This is especially important to get rid
// of a trailing tab when there are no description following it.
// For example, a sub-command without a description should not be completed
// with a tab at the end (or else zsh will show a -- following it
// although there is no description).
comp = strings . TrimSpace ( comp )
2023-11-23 20:24:33 +03:00
// Print each possible completion to the output for the completion script to consume.
fmt . Fprintln ( out , comp )
2020-04-03 22:43:43 +03:00
}
// As the last printout, print the completion directive for the completion script to parse.
// The directive integer must be that last character following a single colon (:).
// The completion script expects :<directive>
2023-11-23 20:24:33 +03:00
fmt . Fprintf ( out , ":%d\n" , directive )
2020-04-03 22:43:43 +03:00
// Print some helpful info to stderr for the user to understand.
// Output from stderr must be ignored by the completion script.
fmt . Fprintf ( finalCmd . ErrOrStderr ( ) , "Completion ended with directive: %s\n" , directive . string ( ) )
} ,
}
c . AddCommand ( completeCmd )
subCmd , _ , err := c . Find ( args )
2020-04-06 20:28:44 +03:00
if err != nil || subCmd . Name ( ) != ShellCompRequestCmd {
2020-04-03 22:43:43 +03:00
// Only create this special command if it is actually being called.
// This reduces possible side-effects of creating such a command;
// for example, having this command would cause problems to a
// cobra program that only consists of the root command, since this
// command would cause the root command to suddenly have a subcommand.
c . RemoveCommand ( completeCmd )
}
}
2020-04-06 20:28:44 +03:00
func ( c * Command ) getCompletions ( args [ ] string ) ( * Command , [ ] string , ShellCompDirective , error ) {
2020-04-03 22:43:43 +03:00
// The last argument, which is not completely typed by the user,
// should not be part of the list of arguments
toComplete := args [ len ( args ) - 1 ]
trimmedArgs := args [ : len ( args ) - 1 ]
2020-09-09 18:34:51 +03:00
var finalCmd * Command
var finalArgs [ ] string
var err error
2020-04-03 22:43:43 +03:00
// Find the real command for which completion must be performed
2020-09-09 18:34:51 +03:00
// check if we need to traverse here to parse local flags on parent commands
if c . Root ( ) . TraverseChildren {
finalCmd , finalArgs , err = c . Root ( ) . Traverse ( trimmedArgs )
} else {
2021-12-14 21:22:22 +03:00
// For Root commands that don't specify any value for their Args fields, when we call
// Find(), if those Root commands don't have any sub-commands, they will accept arguments.
// However, because we have added the __complete sub-command in the current code path, the
// call to Find() -> legacyArgs() will return an error if there are any arguments.
// To avoid this, we first remove the __complete command to get back to having no sub-commands.
rootCmd := c . Root ( )
if len ( rootCmd . Commands ( ) ) == 1 {
rootCmd . RemoveCommand ( c )
}
finalCmd , finalArgs , err = rootCmd . Find ( trimmedArgs )
2020-09-09 18:34:51 +03:00
}
2020-04-03 22:43:43 +03:00
if err != nil {
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
2024-05-19 05:12:02 +03:00
return c , [ ] string { } , ShellCompDirectiveDefault , fmt . Errorf ( "unable to find a command for arguments: %v" , trimmedArgs )
2020-06-29 22:52:14 +03:00
}
2021-05-03 19:33:57 +03:00
finalCmd . ctx = c . ctx
2020-06-29 22:52:14 +03:00
2022-10-05 01:41:30 +03:00
// These flags are normally added when `execute()` is called on `finalCmd`,
// however, when doing completion, we don't call `finalCmd.execute()`.
2023-10-28 23:10:06 +03:00
// Let's add the --help and --version flag ourselves but only if the finalCmd
// has not disabled flag parsing; if flag parsing is disabled, it is up to the
// finalCmd itself to handle the completion of *all* flags.
if ! finalCmd . DisableFlagParsing {
finalCmd . InitDefaultHelpFlag ( )
finalCmd . InitDefaultVersionFlag ( )
}
2022-10-05 01:41:30 +03:00
2020-06-29 22:52:14 +03:00
// Check if we are doing flag value completion before parsing the flags.
// This is important because if we are completing a flag value, we need to also
// remove the flag name argument from the list of finalArgs or else the parsing
// could fail due to an invalid value (incomplete) for the flag.
2021-07-01 18:47:10 +03:00
flag , finalArgs , toComplete , flagErr := checkIfFlagCompletion ( finalCmd , finalArgs , toComplete )
// Check if interspersed is false or -- was set on a previous arg.
// This works by counting the arguments. Normally -- is not counted as arg but
// if -- was already set or interspersed is false and there is already one arg then
// the extra added -- is counted as arg.
flagCompletion := true
_ = finalCmd . ParseFlags ( append ( finalArgs , "--" ) )
newArgCount := finalCmd . Flags ( ) . NArg ( )
2020-06-29 22:52:14 +03:00
// Parse the flags early so we can check if required flags are set
if err = finalCmd . ParseFlags ( finalArgs ) ; err != nil {
return finalCmd , [ ] string { } , ShellCompDirectiveDefault , fmt . Errorf ( "Error while parsing flags from args %v: %s" , finalArgs , err . Error ( ) )
}
2021-07-01 18:47:10 +03:00
realArgCount := finalCmd . Flags ( ) . NArg ( )
if newArgCount > realArgCount {
// don't do flag completion (see above)
flagCompletion = false
}
// Error while attempting to parse flags
if flagErr != nil {
// If error type is flagCompError and we don't want flagCompletion we should ignore the error
if _ , ok := flagErr . ( * flagCompError ) ; ! ( ok && ! flagCompletion ) {
return finalCmd , [ ] string { } , ShellCompDirectiveDefault , flagErr
}
}
2022-10-05 01:41:30 +03:00
// Look for the --help or --version flags. If they are present,
// there should be no further completions.
if helpOrVersionFlagPresent ( finalCmd ) {
return finalCmd , [ ] string { } , ShellCompDirectiveNoFileComp , nil
}
2021-11-01 22:01:33 +03:00
// We only remove the flags from the arguments if DisableFlagParsing is not set.
// This is important for commands which have requested to do their own flag completion.
if ! finalCmd . DisableFlagParsing {
finalArgs = finalCmd . Flags ( ) . Args ( )
}
2021-07-01 18:47:10 +03:00
if flag != nil && flagCompletion {
2020-06-29 22:52:14 +03:00
// Check if we are completing a flag value subject to annotations
if validExts , present := flag . Annotations [ BashCompFilenameExt ] ; present {
if len ( validExts ) != 0 {
// File completion filtered by extensions
return finalCmd , validExts , ShellCompDirectiveFilterFileExt , nil
}
// The annotation requests simple file completion. There is no reason to do
// that since it is the default behavior anyway. Let's ignore this annotation
// in case the program also registered a completion function for this flag.
// Even though it is a mistake on the program's side, let's be nice when we can.
}
if subDir , present := flag . Annotations [ BashCompSubdirsInDir ] ; present {
if len ( subDir ) == 1 {
// Directory completion from within a directory
return finalCmd , subDir , ShellCompDirectiveFilterDirs , nil
}
// Directory completion
return finalCmd , [ ] string { } , ShellCompDirectiveFilterDirs , nil
}
2020-04-03 22:43:43 +03:00
}
2021-11-01 22:01:33 +03:00
var completions [ ] string
var directive ShellCompDirective
2022-06-21 05:04:28 +03:00
// Enforce flag groups before doing flag completions
finalCmd . enforceFlagGroupsForCompletion ( )
2021-11-01 22:01:33 +03:00
// Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
2022-03-14 14:05:53 +03:00
// doing this allows for completion of persistent flag names even for commands that disable flag parsing.
2021-11-01 22:01:33 +03:00
//
2020-04-10 22:56:28 +03:00
// When doing completion of a flag name, as soon as an argument starts with
// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
2020-06-29 22:52:14 +03:00
// the flag name to be complete
2021-07-01 18:47:10 +03:00
if flag == nil && len ( toComplete ) > 0 && toComplete [ 0 ] == '-' && ! strings . Contains ( toComplete , "=" ) && flagCompletion {
2020-06-29 22:52:14 +03:00
// First check for required flags
completions = completeRequireFlags ( finalCmd , toComplete )
// If we have not found any required flags, only then can we show regular flags
if len ( completions ) == 0 {
doCompleteFlags := func ( flag * pflag . Flag ) {
if ! flag . Changed ||
strings . Contains ( flag . Value . Type ( ) , "Slice" ) ||
2024-07-28 19:18:07 +03:00
strings . Contains ( flag . Value . Type ( ) , "Array" ) ||
strings . HasPrefix ( flag . Value . Type ( ) , "stringTo" ) {
// If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo)
2020-06-29 22:52:14 +03:00
// we suggest it as a completion
completions = append ( completions , getFlagNameCompletions ( flag , toComplete ) ... )
}
2020-04-10 22:56:28 +03:00
}
2020-06-29 22:52:14 +03:00
// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
// non-inherited flags.
finalCmd . InheritedFlags ( ) . VisitAll ( func ( flag * pflag . Flag ) {
doCompleteFlags ( flag )
} )
2023-10-28 23:10:06 +03:00
// Try to complete non-inherited flags even if DisableFlagParsing==true.
// This allows programs to tell Cobra about flags for completion even
// if the actual parsing of flags is not done by Cobra.
// For instance, Helm uses this to provide flag name completion for
// some of its plugins.
2020-06-29 22:52:14 +03:00
finalCmd . NonInheritedFlags ( ) . VisitAll ( func ( flag * pflag . Flag ) {
doCompleteFlags ( flag )
} )
}
2021-11-01 22:01:33 +03:00
directive = ShellCompDirectiveNoFileComp
2020-06-29 22:52:14 +03:00
if len ( completions ) == 1 && strings . HasSuffix ( completions [ 0 ] , "=" ) {
// If there is a single completion, the shell usually adds a space
// after the completion. We don't want that if the flag ends with an =
directive = ShellCompDirectiveNoSpace
2020-04-10 22:56:28 +03:00
}
2021-11-01 22:01:33 +03:00
if ! finalCmd . DisableFlagParsing {
// If DisableFlagParsing==false, we have completed the flags as known by Cobra;
// we can return what we found.
// If DisableFlagParsing==true, Cobra may not be aware of all flags, so we
// let the logic continue to see if ValidArgsFunction needs to be called.
return finalCmd , completions , directive , nil
2020-09-09 18:34:51 +03:00
}
2021-11-01 22:01:33 +03:00
} else {
directive = ShellCompDirectiveDefault
if flag == nil {
foundLocalNonPersistentFlag := false
// If TraverseChildren is true on the root command we don't check for
// local flags because we can use a local flag on a parent command
if ! finalCmd . Root ( ) . TraverseChildren {
// Check if there are any local, non-persistent flags on the command-line
localNonPersistentFlags := finalCmd . LocalNonPersistentFlags ( )
finalCmd . NonInheritedFlags ( ) . VisitAll ( func ( flag * pflag . Flag ) {
if localNonPersistentFlags . Lookup ( flag . Name ) != nil && flag . Changed {
foundLocalNonPersistentFlag = true
}
} )
}
2020-06-29 22:52:14 +03:00
2021-11-01 22:01:33 +03:00
// Complete subcommand names, including the help command
if len ( finalArgs ) == 0 && ! foundLocalNonPersistentFlag {
// We only complete sub-commands if:
// - there are no arguments on the command-line and
// - there are no local, non-persistent flags on the command-line or TraverseChildren is true
for _ , subCmd := range finalCmd . Commands ( ) {
if subCmd . IsAvailableCommand ( ) || subCmd == finalCmd . helpCommand {
if strings . HasPrefix ( subCmd . Name ( ) , toComplete ) {
completions = append ( completions , fmt . Sprintf ( "%s\t%s" , subCmd . Name ( ) , subCmd . Short ) )
}
directive = ShellCompDirectiveNoFileComp
2020-06-29 22:52:14 +03:00
}
2020-06-16 23:49:26 +03:00
}
2020-04-10 22:56:28 +03:00
}
2021-11-01 22:01:33 +03:00
// Complete required flags even without the '-' prefix
completions = append ( completions , completeRequireFlags ( finalCmd , toComplete ) ... )
// Always complete ValidArgs, even if we are completing a subcommand name.
// This is for commands that have both subcommands and ValidArgs.
if len ( finalCmd . ValidArgs ) > 0 {
if len ( finalArgs ) == 0 {
// ValidArgs are only for the first argument
for _ , validArg := range finalCmd . ValidArgs {
if strings . HasPrefix ( validArg , toComplete ) {
completions = append ( completions , validArg )
}
2020-06-29 22:52:14 +03:00
}
2021-11-01 22:01:33 +03:00
directive = ShellCompDirectiveNoFileComp
// If no completions were found within commands or ValidArgs,
// see if there are any ArgAliases that should be completed.
if len ( completions ) == 0 {
for _ , argAlias := range finalCmd . ArgAliases {
if strings . HasPrefix ( argAlias , toComplete ) {
completions = append ( completions , argAlias )
}
2020-06-29 22:52:14 +03:00
}
}
2020-04-10 22:56:28 +03:00
}
2021-11-01 22:01:33 +03:00
// If there are ValidArgs specified (even if they don't match), we stop completion.
// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
return finalCmd , completions , directive , nil
2020-04-10 22:56:28 +03:00
}
2021-11-01 22:01:33 +03:00
// Let the logic continue so as to add any ValidArgsFunction completions,
// even if we already found sub-commands.
// This is for commands that have subcommands but also specify a ValidArgsFunction.
2020-04-10 22:56:28 +03:00
}
}
2020-04-03 22:43:43 +03:00
// Find the completion function for the flag or command
2020-04-06 20:28:44 +03:00
var completionFn func ( cmd * Command , args [ ] string , toComplete string ) ( [ ] string , ShellCompDirective )
2021-07-01 18:47:10 +03:00
if flag != nil && flagCompletion {
2021-07-02 18:25:47 +03:00
flagCompletionMutex . RLock ( )
completionFn = flagCompletionFunctions [ flag ]
flagCompletionMutex . RUnlock ( )
2020-04-03 22:43:43 +03:00
} else {
completionFn = finalCmd . ValidArgsFunction
}
2020-06-29 22:52:14 +03:00
if completionFn != nil {
// Go custom completion defined for this flag or command.
// Call the registered completion function to get the completions.
var comps [ ] string
comps , directive = completionFn ( finalCmd , finalArgs , toComplete )
completions = append ( completions , comps ... )
2020-04-03 22:43:43 +03:00
}
return finalCmd , completions , directive , nil
}
2022-10-05 01:41:30 +03:00
func helpOrVersionFlagPresent ( cmd * Command ) bool {
if versionFlag := cmd . Flags ( ) . Lookup ( "version" ) ; versionFlag != nil &&
len ( versionFlag . Annotations [ FlagSetByCobraAnnotation ] ) > 0 && versionFlag . Changed {
return true
}
if helpFlag := cmd . Flags ( ) . Lookup ( "help" ) ; helpFlag != nil &&
len ( helpFlag . Annotations [ FlagSetByCobraAnnotation ] ) > 0 && helpFlag . Changed {
return true
}
return false
}
2020-04-10 22:56:28 +03:00
func getFlagNameCompletions ( flag * pflag . Flag , toComplete string ) [ ] string {
if nonCompletableFlag ( flag ) {
return [ ] string { }
}
var completions [ ] string
flagName := "--" + flag . Name
if strings . HasPrefix ( flagName , toComplete ) {
// Flag without the =
completions = append ( completions , fmt . Sprintf ( "%s\t%s" , flagName , flag . Usage ) )
2020-06-29 22:52:14 +03:00
// Why suggest both long forms: --flag and --flag= ?
// This forces the user to *always* have to type either an = or a space after the flag name.
// Let's be nice and avoid making users have to do that.
// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
// The = form will still work, we just won't suggest it.
// This also makes the list of suggested flags shorter as we avoid all the = forms.
//
// if len(flag.NoOptDefVal) == 0 {
// // Flag requires a value, so it can be suffixed with =
// flagName += "="
// completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
// }
2020-04-10 22:56:28 +03:00
}
flagName = "-" + flag . Shorthand
if len ( flag . Shorthand ) > 0 && strings . HasPrefix ( flagName , toComplete ) {
completions = append ( completions , fmt . Sprintf ( "%s\t%s" , flagName , flag . Usage ) )
}
return completions
}
2020-06-29 22:52:14 +03:00
func completeRequireFlags ( finalCmd * Command , toComplete string ) [ ] string {
var completions [ ] string
doCompleteRequiredFlags := func ( flag * pflag . Flag ) {
if _ , present := flag . Annotations [ BashCompOneRequiredFlag ] ; present {
if ! flag . Changed {
// If the flag is not already present, we suggest it as a completion
completions = append ( completions , getFlagNameCompletions ( flag , toComplete ) ... )
}
}
}
// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
// non-inherited flags.
finalCmd . InheritedFlags ( ) . VisitAll ( func ( flag * pflag . Flag ) {
doCompleteRequiredFlags ( flag )
} )
finalCmd . NonInheritedFlags ( ) . VisitAll ( func ( flag * pflag . Flag ) {
doCompleteRequiredFlags ( flag )
} )
return completions
}
2020-04-03 22:43:43 +03:00
func checkIfFlagCompletion ( finalCmd * Command , args [ ] string , lastArg string ) ( * pflag . Flag , [ ] string , string , error ) {
2020-06-29 22:52:14 +03:00
if finalCmd . DisableFlagParsing {
// We only do flag completion if we are allowed to parse flags
// This is important for commands which have requested to do their own flag completion.
return nil , args , lastArg , nil
}
2020-04-03 22:43:43 +03:00
var flagName string
trimmedArgs := args
flagWithEqual := false
2021-07-01 18:47:10 +03:00
orgLastArg := lastArg
2020-06-29 22:52:14 +03:00
// When doing completion of a flag name, as soon as an argument starts with
// a '-' we know it is a flag. We cannot use isFlagArg() here as that function
// requires the flag name to be complete
if len ( lastArg ) > 0 && lastArg [ 0 ] == '-' {
2020-04-03 22:43:43 +03:00
if index := strings . Index ( lastArg , "=" ) ; index >= 0 {
2020-06-29 22:52:14 +03:00
// Flag with an =
2021-05-03 19:42:00 +03:00
if strings . HasPrefix ( lastArg [ : index ] , "--" ) {
// Flag has full name
flagName = lastArg [ 2 : index ]
} else {
// Flag is shorthand
// We have to get the last shorthand flag name
// e.g. `-asd` => d to provide the correct completion
// https://github.com/spf13/cobra/issues/1257
flagName = lastArg [ index - 1 : index ]
}
2020-04-03 22:43:43 +03:00
lastArg = lastArg [ index + 1 : ]
flagWithEqual = true
} else {
2020-06-29 22:52:14 +03:00
// Normal flag completion
return nil , args , lastArg , nil
2020-04-03 22:43:43 +03:00
}
}
if len ( flagName ) == 0 {
if len ( args ) > 0 {
prevArg := args [ len ( args ) - 1 ]
if isFlagArg ( prevArg ) {
// Only consider the case where the flag does not contain an =.
// If the flag contains an = it means it has already been fully processed,
// so we don't need to deal with it here.
if index := strings . Index ( prevArg , "=" ) ; index < 0 {
2021-05-03 19:42:00 +03:00
if strings . HasPrefix ( prevArg , "--" ) {
// Flag has full name
flagName = prevArg [ 2 : ]
} else {
// Flag is shorthand
// We have to get the last shorthand flag name
// e.g. `-asd` => d to provide the correct completion
// https://github.com/spf13/cobra/issues/1257
flagName = prevArg [ len ( prevArg ) - 1 : ]
}
2020-04-03 22:43:43 +03:00
// Remove the uncompleted flag or else there could be an error created
// for an invalid value for that flag
trimmedArgs = args [ : len ( args ) - 1 ]
}
}
}
}
if len ( flagName ) == 0 {
// Not doing flag completion
return nil , trimmedArgs , lastArg , nil
}
flag := findFlag ( finalCmd , flagName )
if flag == nil {
2021-07-01 18:47:10 +03:00
// Flag not supported by this command, the interspersed option might be set so return the original args
return nil , args , orgLastArg , & flagCompError { subCommand : finalCmd . Name ( ) , flagName : flagName }
2020-04-03 22:43:43 +03:00
}
if ! flagWithEqual {
if len ( flag . NoOptDefVal ) != 0 {
// We had assumed dealing with a two-word flag but the flag is a boolean flag.
// In that case, there is no value following it, so we are not really doing flag completion.
// Reset everything to do noun completion.
trimmedArgs = args
flag = nil
}
}
return flag , trimmedArgs , lastArg , nil
}
2022-10-11 02:06:30 +03:00
// InitDefaultCompletionCmd adds a default 'completion' command to c.
2021-02-15 20:47:01 +03:00
// This function will do nothing if any of the following is true:
// 1- the feature has been explicitly disabled by the program,
// 2- c has no subcommands (to avoid creating one),
// 3- c already has a 'completion' command provided by the program.
2022-10-11 02:06:30 +03:00
func ( c * Command ) InitDefaultCompletionCmd ( ) {
2021-02-15 20:47:01 +03:00
if c . CompletionOptions . DisableDefaultCmd || ! c . HasSubCommands ( ) {
return
}
for _ , cmd := range c . commands {
if cmd . Name ( ) == compCmdName || cmd . HasAlias ( compCmdName ) {
// A completion command is already available
return
}
}
haveNoDescFlag := ! c . CompletionOptions . DisableNoDescFlag && ! c . CompletionOptions . DisableDescriptions
completionCmd := & Command {
Use : compCmdName ,
2021-12-08 01:52:50 +03:00
Short : "Generate the autocompletion script for the specified shell" ,
Long : fmt . Sprintf ( ` Generate the autocompletion script for % [ 1 ] s for the specified shell .
2021-02-15 20:47:01 +03:00
See each sub - command ' s help for details on how to use the generated script .
` , c . Root ( ) . Name ( ) ) ,
Args : NoArgs ,
ValidArgsFunction : NoFileCompletions ,
2021-12-08 02:02:02 +03:00
Hidden : c . CompletionOptions . HiddenDefaultCmd ,
2022-10-10 23:59:11 +03:00
GroupID : c . completionCommandGroupID ,
2021-02-15 20:47:01 +03:00
}
c . AddCommand ( completionCmd )
out := c . OutOrStdout ( )
noDesc := c . CompletionOptions . DisableDescriptions
2021-12-08 01:52:50 +03:00
shortDesc := "Generate the autocompletion script for %s"
2021-02-15 20:47:01 +03:00
bash := & Command {
Use : "bash" ,
Short : fmt . Sprintf ( shortDesc , "bash" ) ,
2021-12-08 01:52:50 +03:00
Long : fmt . Sprintf ( ` Generate the autocompletion script for the bash shell .
2021-02-15 20:47:01 +03:00
This script depends on the ' bash - completion ' package .
If it is not installed already , you can install it via your OS ' s package manager .
To load completions in your current shell session :
2021-12-08 01:51:48 +03:00
source < ( % [ 1 ] s completion bash )
2021-02-15 20:47:01 +03:00
To load completions for every new session , execute once :
2021-12-08 01:51:48 +03:00
# # # # Linux :
% [ 1 ] s completion bash > / etc / bash_completion . d / % [ 1 ] s
# # # # macOS :
2022-06-13 05:22:49 +03:00
% [ 1 ] s completion bash > $ ( brew -- prefix ) / etc / bash_completion . d / % [ 1 ] s
2021-02-15 20:47:01 +03:00
You will need to start a new shell for this setup to take effect .
2021-12-08 02:01:22 +03:00
` , c . Root ( ) . Name ( ) ) ,
2021-02-15 20:47:01 +03:00
Args : NoArgs ,
DisableFlagsInUseLine : true ,
ValidArgsFunction : NoFileCompletions ,
RunE : func ( cmd * Command , args [ ] string ) error {
2021-07-01 00:24:58 +03:00
return cmd . Root ( ) . GenBashCompletionV2 ( out , ! noDesc )
2021-02-15 20:47:01 +03:00
} ,
}
2021-07-01 00:24:58 +03:00
if haveNoDescFlag {
bash . Flags ( ) . BoolVar ( & noDesc , compCmdNoDescFlagName , compCmdNoDescFlagDefault , compCmdNoDescFlagDesc )
}
2021-02-15 20:47:01 +03:00
zsh := & Command {
Use : "zsh" ,
Short : fmt . Sprintf ( shortDesc , "zsh" ) ,
2021-12-08 01:52:50 +03:00
Long : fmt . Sprintf ( ` Generate the autocompletion script for the zsh shell .
2021-02-15 20:47:01 +03:00
If shell completion is not already enabled in your environment you will need
to enable it . You can execute the following once :
2021-12-08 01:51:48 +03:00
echo "autoload -U compinit; compinit" >> ~ / . zshrc
2021-02-15 20:47:01 +03:00
2022-03-18 00:55:16 +03:00
To load completions in your current shell session :
2023-03-23 01:41:05 +03:00
source < ( % [ 1 ] s completion zsh )
2022-03-18 00:55:16 +03:00
2021-02-15 20:47:01 +03:00
To load completions for every new session , execute once :
2021-12-08 01:51:48 +03:00
# # # # Linux :
% [ 1 ] s completion zsh > "${fpath[1]}/_%[1]s"
# # # # macOS :
2022-06-13 05:22:49 +03:00
% [ 1 ] s completion zsh > $ ( brew -- prefix ) / share / zsh / site - functions / _ % [ 1 ] s
2021-02-15 20:47:01 +03:00
You will need to start a new shell for this setup to take effect .
` , c . Root ( ) . Name ( ) ) ,
Args : NoArgs ,
ValidArgsFunction : NoFileCompletions ,
RunE : func ( cmd * Command , args [ ] string ) error {
if noDesc {
return cmd . Root ( ) . GenZshCompletionNoDesc ( out )
}
return cmd . Root ( ) . GenZshCompletion ( out )
} ,
}
if haveNoDescFlag {
zsh . Flags ( ) . BoolVar ( & noDesc , compCmdNoDescFlagName , compCmdNoDescFlagDefault , compCmdNoDescFlagDesc )
}
fish := & Command {
Use : "fish" ,
Short : fmt . Sprintf ( shortDesc , "fish" ) ,
2021-12-08 01:52:50 +03:00
Long : fmt . Sprintf ( ` Generate the autocompletion script for the fish shell .
2021-02-15 20:47:01 +03:00
To load completions in your current shell session :
2021-12-08 01:51:48 +03:00
% [ 1 ] s completion fish | source
2021-02-15 20:47:01 +03:00
To load completions for every new session , execute once :
2021-12-08 01:51:48 +03:00
% [ 1 ] s completion fish > ~ / . config / fish / completions / % [ 1 ] s . fish
2021-02-15 20:47:01 +03:00
You will need to start a new shell for this setup to take effect .
` , c . Root ( ) . Name ( ) ) ,
Args : NoArgs ,
ValidArgsFunction : NoFileCompletions ,
RunE : func ( cmd * Command , args [ ] string ) error {
return cmd . Root ( ) . GenFishCompletion ( out , ! noDesc )
} ,
}
if haveNoDescFlag {
fish . Flags ( ) . BoolVar ( & noDesc , compCmdNoDescFlagName , compCmdNoDescFlagDefault , compCmdNoDescFlagDesc )
}
powershell := & Command {
Use : "powershell" ,
Short : fmt . Sprintf ( shortDesc , "powershell" ) ,
2021-12-08 01:52:50 +03:00
Long : fmt . Sprintf ( ` Generate the autocompletion script for powershell .
2021-02-15 20:47:01 +03:00
To load completions in your current shell session :
2021-12-08 01:51:48 +03:00
% [ 1 ] s completion powershell | Out - String | Invoke - Expression
2021-02-15 20:47:01 +03:00
To load completions for every new session , add the output of the above command
to your powershell profile .
` , c . Root ( ) . Name ( ) ) ,
Args : NoArgs ,
ValidArgsFunction : NoFileCompletions ,
RunE : func ( cmd * Command , args [ ] string ) error {
if noDesc {
return cmd . Root ( ) . GenPowerShellCompletion ( out )
}
return cmd . Root ( ) . GenPowerShellCompletionWithDesc ( out )
} ,
}
if haveNoDescFlag {
powershell . Flags ( ) . BoolVar ( & noDesc , compCmdNoDescFlagName , compCmdNoDescFlagDefault , compCmdNoDescFlagDesc )
}
2022-11-28 00:46:13 +03:00
nushell := & Command {
Use : "nushell" ,
Short : fmt . Sprintf ( shortDesc , "nushell" ) ,
Long : fmt . Sprintf ( ` Generate the autocompletion script for nushell .
2022-11-29 00:39:12 +03:00
To configure the Nushell cobra external completer for the first time :
2022-11-28 02:06:59 +03:00
# 1. Edit the nushell config file :
> config nu
2024-12-02 05:44:56 +03:00
# 2. Copy the completer to at the end of the file .
# 3. Add a section like the following below at the end of the file :
$ env . config . completions . external = {
enable : true
max_results : 100
completer : $ cobra_completer
}
NOTE : This completer will work for all cobra based commands .
More information can be found in the External Completions ( https : //www.nushell.sh/book/custom_completions.html#custom-descriptions) section of the Nushell book.
Information on setting up more than one external completer can be found in the Multiple completer ( https : //www.nushell.sh/cookbook/external_completers.html#multiple-completer) section of the Nushell cookbook.
` ) ,
2022-11-28 00:46:13 +03:00
Args : NoArgs ,
ValidArgsFunction : NoFileCompletions ,
RunE : func ( cmd * Command , args [ ] string ) error {
2022-11-29 00:39:12 +03:00
return cmd . Root ( ) . GenNushellCompletion ( out , ! noDesc )
2022-11-28 00:46:13 +03:00
} ,
}
if haveNoDescFlag {
nushell . Flags ( ) . BoolVar ( & noDesc , compCmdNoDescFlagName , compCmdNoDescFlagDefault , compCmdNoDescFlagDesc )
}
completionCmd . AddCommand ( bash , zsh , fish , powershell , nushell )
2021-02-15 20:47:01 +03:00
}
2020-04-03 22:43:43 +03:00
func findFlag ( cmd * Command , name string ) * pflag . Flag {
flagSet := cmd . Flags ( )
if len ( name ) == 1 {
// First convert the short flag into a long flag
// as the cmd.Flag() search only accepts long flags
if short := flagSet . ShorthandLookup ( name ) ; short != nil {
name = short . Name
} else {
set := cmd . InheritedFlags ( )
if short = set . ShorthandLookup ( name ) ; short != nil {
name = short . Name
} else {
return nil
}
}
}
return cmd . Flag ( name )
}
// CompDebug prints the specified string to the same file as where the
// completion script prints its logs.
// Note that completion printouts should never be on stdout as they would
// be wrongly interpreted as actual completion choices by the completion script.
func CompDebug ( msg string , printToStdErr bool ) {
msg = fmt . Sprintf ( "[Debug] %s" , msg )
// Such logs are only printed when the user has set the environment
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
if path := os . Getenv ( "BASH_COMP_DEBUG_FILE" ) ; path != "" {
f , err := os . OpenFile ( path ,
2022-11-28 00:46:13 +03:00
os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o644 )
2020-04-03 22:43:43 +03:00
if err == nil {
defer f . Close ( )
2021-02-08 03:08:50 +03:00
WriteStringAndCheck ( f , msg )
2020-04-03 22:43:43 +03:00
}
}
if printToStdErr {
// Must print to stderr for this not to be read by the completion script.
2021-02-08 03:08:50 +03:00
fmt . Fprint ( os . Stderr , msg )
2020-04-03 22:43:43 +03:00
}
}
// CompDebugln prints the specified string with a newline at the end
// to the same file as where the completion script prints its logs.
// Such logs are only printed when the user has set the environment
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
func CompDebugln ( msg string , printToStdErr bool ) {
CompDebug ( fmt . Sprintf ( "%s\n" , msg ) , printToStdErr )
}
// CompError prints the specified completion message to stderr.
func CompError ( msg string ) {
msg = fmt . Sprintf ( "[Error] %s" , msg )
CompDebug ( msg , true )
}
// CompErrorln prints the specified completion message to stderr with a newline at the end.
func CompErrorln ( msg string ) {
CompError ( fmt . Sprintf ( "%s\n" , msg ) )
}
2023-12-17 21:50:59 +03:00
// These values should not be changed: users will be using them explicitly.
const (
configEnvVarGlobalPrefix = "COBRA"
configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
)
var configEnvVarPrefixSubstRegexp = regexp . MustCompile ( ` [^A-Z0-9_] ` )
// configEnvVar returns the name of the program-specific configuration environment
// variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
func configEnvVar ( name , suffix string ) string {
// This format should not be changed: users will be using it explicitly.
v := strings . ToUpper ( fmt . Sprintf ( "%s_%s" , name , suffix ) )
v = configEnvVarPrefixSubstRegexp . ReplaceAllString ( v , "_" )
return v
}
// getEnvConfig returns the value of the configuration environment variable
// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
// case, with all non-ASCII-alphanumeric characters replaced by `_`.
// If the value is empty or not set, the value of the environment variable
// COBRA_<SUFFIX> is returned instead.
func getEnvConfig ( cmd * Command , suffix string ) string {
v := os . Getenv ( configEnvVar ( cmd . Root ( ) . Name ( ) , suffix ) )
if v == "" {
v = os . Getenv ( configEnvVar ( configEnvVarGlobalPrefix , suffix ) )
}
return v
}