diff --git a/active_help.md b/active_help.md index dafbd1790..86d23f675 100644 --- a/active_help.md +++ b/active_help.md @@ -124,26 +124,31 @@ ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([ return comps, cobra.ShellCompDirectiveNoFileComp }, ``` -**Note**: If the string "0" is used for `cmd.Root().ActiveHelpConfig`, it will automatically be handled by Cobra and will completely disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help. +**Note 1**: If the string "0" is used for `cmd.Root().ActiveHelpConfig`, it will automatically be handled by Cobra and will completely disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help. -**Note**: Cobra transparently passes the `cmd.ActiveHelpConfig` string you specified back to your program when completion is invoked. You can therefore define any scheme you choose for your program; you are not limited to using integer levels for the configuration of Active Help. +**Note 2**: Cobra transparently passes the `cmd.ActiveHelpConfig` string you specified back to your program when completion is invoked. You can therefore define any scheme you choose for your program; you are not limited to using integer levels for the configuration of Active Help. However, the reserved "0" value can also be sent to you program and you should be prepared for it. ## Debugging Active Help Debugging your Active Help code is done in the same way as debugging the dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. -When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the `--__activeHelpCfg` flag (as is done by the generated completion scripts). For example, we can test deactivating some Active Help as shown below: +When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable (as you can find in the generated completion scripts). That variable is named `_ACTIVE_HELP` where any `-` is replaced by an `_`. For example, we can test deactivating some Active Help as shown below: ``` -$ bin/helm __complete --__activeHelpCfg=1 install wordpress bitnami/h +$ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h bitnami/haproxy bitnami/harbor _activeHelp_ WARNING: cannot re-use a name that is still in use :0 Completion ended with directive: ShellCompDirectiveDefault -$ bin/helm __complete --__activeHelpCfg=0 install wordpress bitnami/h +$ HELM_ACTIVE_HELP=0 bin/helm __complete install wordpress bitnami/h bitnami/haproxy bitnami/harbor :0 Completion ended with directive: ShellCompDirectiveDefault ``` + +If a user wants to disable Active Help for every single program based on Cobra, the global environment variable `COBRA_ACTIVE_HELP` can be used as follows: +``` +export COBRA_ACTIVE_HELP=0 +``` \ No newline at end of file diff --git a/bash_completions.go b/bash_completions.go index ee81217b7..742dc60ae 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -74,7 +74,7 @@ __%[1]s_handle_go_custom_completion() # Calling ${words[0]} instead of directly %[1]s allows to handle aliases args=("${words[@]:1}") # Disable ActiveHelp which is not supported for bash completion v1 - requestComp="${words[0]} %[2]s --%[8]s=0 ${args[*]}" + requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -378,7 +378,7 @@ __%[1]s_handle_word() `, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } func writePostscript(buf io.StringWriter, name string) { diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 6b5c05b44..871728f65 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -45,7 +45,7 @@ __%[1]s_get_completion_results() { # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases args=("${words[@]:1}") - requestComp="${words[0]} %[2]s --%[9]s=%[10]s ${args[*]}" + requestComp="%[9]s=${%[9]s-%[10]s} ${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -316,7 +316,7 @@ fi `, cmd.Name(), compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - activeHelpMarker, activeHelpCfgFlagName, cmd.Root().ActiveHelpConfig)) + activeHelpMarker, activeHelpEnvVar(cmd.Name()), cmd.Root().ActiveHelpConfig)) } // GenBashCompletionFileV2 generates Bash completion version 2. diff --git a/completions.go b/completions.go index 9a9c61090..ddc049d4b 100644 --- a/completions.go +++ b/completions.go @@ -24,11 +24,6 @@ var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, // lock for reading and writing from flagCompletionFunctions var flagCompletionMutex = &sync.RWMutex{} -var ( - activeHelpCfgFlagName = "__activeHelpCfg" - activeHelpCfgFlagValue string -) - // ShellCompDirective is a bit map representing the different behaviors the shell // can be instructed to have once completions have been provided. type ShellCompDirective int @@ -87,7 +82,10 @@ const ( compCmdNoDescFlagDesc = "disable completion descriptions" compCmdNoDescFlagDefault = false - activeHelpMarker = "_activeHelp_ " + activeHelpMarker = "_activeHelp_ " + activeHelpEnvVarSuffix = "_ACTIVE_HELP" + activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP" + activeHelpGlobalDisable = "0" ) // CompletionOptions are the options to control shell completion @@ -102,6 +100,11 @@ type CompletionOptions struct { DisableDescriptions bool } +func activeHelpEnvVar(name string) string { + activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) + return strings.ReplaceAll(activeHelpEnvVar, "-", "_") +} + // 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) { @@ -166,21 +169,16 @@ func (d ShellCompDirective) string() string { // Adds a special hidden command that can be used to request custom completions. func (c *Command) initCompleteCmd(args []string) { completeCmd := &Command{ - Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), - Aliases: []string{ShellCompNoDescRequestCmd}, - Hidden: true, - DisableFlagParsing: true, - Args: MinimumNArgs(1), - Short: "Request shell completion choices for the specified command-line", + Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), + Aliases: []string{ShellCompNoDescRequestCmd}, + 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", "to request completion choices for the specified command-line.", ShellCompRequestCmd), Run: func(cmd *Command, args []string) { - args, _ = cmd.extractCompleteCmdFlags(args) - if err := cmd.ValidateArgs(args); err != nil { - CompErrorln(err.Error()) - return - } - finalCmd, completions, directive, err := cmd.getCompletions(args) if err != nil { CompErrorln(err.Error()) @@ -191,7 +189,7 @@ func (c *Command) initCompleteCmd(args []string) { noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) for _, comp := range completions { - if finalCmd.ActiveHelpConfig == "0" { + if finalCmd.ActiveHelpConfig == activeHelpGlobalDisable { // Remove all activeHelp entries in this case if strings.HasPrefix(comp, activeHelpMarker) { continue @@ -241,30 +239,6 @@ func (c *Command) initCompleteCmd(args []string) { } } -func (c *Command) extractCompleteCmdFlags(args []string) ([]string, error) { - var remainingArgs []string - validFlags := []string{activeHelpCfgFlagName} - for _, validFlag := range validFlags { - for i := 0; i < len(args); i++ { - arg := args[i] - if strings.HasPrefix(arg, "--"+validFlag) { - if pos := strings.Index(arg, "="); pos > -1 { - activeHelpCfgFlagValue = arg[pos+1:] - } else { - // Value is in the next argument - i++ - if i < len(args) { - activeHelpCfgFlagValue = args[i] - } - } - } else { - remainingArgs = append(remainingArgs, arg) - } - } - } - return remainingArgs, nil -} - func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) { // The last argument, which is not completely typed by the user, // should not be part of the list of arguments @@ -474,7 +448,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // First set the ActiveHelpConfig value to make it available when // calling the completion function. We also set it on the root, // just in case users try to access it from there. - finalCmd.ActiveHelpConfig = activeHelpCfgFlagValue + // First check the global environment variable to see if it is + // disabling active help, and if it is not, use the program-specific var. + activeHelpVar := os.Getenv(activeHelpGlobalEnvVar) + if activeHelpVar != activeHelpGlobalDisable { + activeHelpVar = os.Getenv(activeHelpEnvVar(c.Root().Name())) + } + + finalCmd.ActiveHelpConfig = activeHelpVar finalCmd.Root().ActiveHelpConfig = finalCmd.ActiveHelpConfig comps, directive = completionFn(finalCmd, finalArgs, toComplete) diff --git a/fish_completions.go b/fish_completions.go index ecad508bd..f27a01cea 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -39,7 +39,7 @@ function __%[1]s_perform_completion __%[1]s_debug "last arg: $lastArg" # Disable ActiveHelp which is not supported for fish shell - set -l requestComp "$args[1] %[3]s --%[9]s=0 $args[2..-1] $lastArg" + set -l requestComp "%[9]s=0 $args[1] %[3]s $args[2..-1] $lastArg" __%[1]s_debug "Calling $requestComp" set -l results (eval $requestComp 2> /dev/null) @@ -197,7 +197,7 @@ complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/powershell_completions.go b/powershell_completions.go index 2bdd0ae8c..e28920097 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -61,9 +61,8 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # 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) - - # Also disable ActiveHelp which is not supported for Powershell - $RequestComp="$Program %[2]s --%[8]s=0 $Arguments" + + $RequestComp="$Program %[2]s $Arguments" __%[1]s_debug "RequestComp: $RequestComp" # we cannot use $WordToComplete because it @@ -92,6 +91,9 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } __%[1]s_debug "Calling $RequestComp" + # First disable ActiveHelp which is not supported for Powershell + $env:%[8]s=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 @@ -243,7 +245,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { diff --git a/zsh_completions.go b/zsh_completions.go index 35914d8c3..babadaaaa 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -121,7 +121,7 @@ _%[1]s() fi # Prepare the command to obtain completions - requestComp="${words[1]} %[2]s --%[9]s=%[10]s ${words[2,-1]}" + requestComp="%[9]s=${%[9]s-%[10]s} ${words[1]} %[2]s ${words[2,-1]}" if [ "${lastChar}" = "" ]; then # 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 completion code. @@ -283,5 +283,5 @@ fi `, cmd.Name(), compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - activeHelpMarker, activeHelpCfgFlagName, cmd.Root().ActiveHelpConfig)) + activeHelpMarker, activeHelpEnvVar(cmd.Name()), cmd.ActiveHelpConfig)) }