From c47be850d97572196c7369cbf00a01397148a3cf Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 6 Aug 2021 17:18:02 -0400 Subject: [PATCH] Add support for ActiveHelp Signed-off-by: Marc Khouzam --- README.md | 1 + active_help.md | 149 ++++++++++++++++++++++++++++++++++++++ bash_completions.go | 5 +- bash_completionsV2.go | 61 ++++++++++++---- command.go | 17 +++++ completions.go | 74 +++++++++++++++++-- fish_completions.go | 5 +- powershell_completions.go | 7 +- user_guide.md | 4 + zsh_completions.go | 39 ++++++++-- 10 files changed, 330 insertions(+), 32 deletions(-) create mode 100644 active_help.md diff --git a/README.md b/README.md index 074e3979f8..775e488e4e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis * [Suggestions when "unknown command" happens](user_guide.md#suggestions-when-unknown-command-happens) * [Generating documentation for your command](user_guide.md#generating-documentation-for-your-command) * [Generating shell completions](user_guide.md#generating-shell-completions) + * [Providing Active Help](user_guide.md#providing-active-help) - [Contributing](CONTRIBUTING.md) - [License](#license) diff --git a/active_help.md b/active_help.md new file mode 100644 index 0000000000..dafbd17901 --- /dev/null +++ b/active_help.md @@ -0,0 +1,149 @@ +# Active Help + +Active Help is a framework provided by Cobra which allows a program to define messages (hints, warnings, etc) that will be printed during program usage. It aims to make it easier for your users to learn how to use your program. If configured by the program, Active Help is printed when the user triggers shell completion. + +For example, +``` +bash-5.1$ helm repo add [tab] +You must choose a name for the repo you are adding. + +bash-5.1$ bin/helm package [tab] +Please specify the path to the chart to package + +bash-5.1$ bin/helm package [tab][tab] +bin/ internal/ scripts/ pkg/ testdata/ +``` +## Supported shells + +Active Help is currently only supported for the following shells: +- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only) +- Zsh + +## Adding Active Help messages + +As Active Help uses the shell completion system, the implementation of Active Help messages is done by enhancing custom dynamic completions. If you are not familiar with dynamic completions, please refer to [Shell Completions](shell_completions.md). + +Adding Active Help is done through the use of the `cobra.AppendActiveHelp(...)` function, where the program repeatedly adds Active Help messages to the list of completions. Keep reading for details. + +### Active Help for nouns + +Adding Active Help when completing a noun is done within the `ValidArgsFunction(...)` of a command. Please notice the use of `cobra.AppendActiveHelp(...)` in the following example: + +```go +cmd := &cobra.Command{ + Use: "add [NAME] [URL]", + Short: "add a chart repository", + Args: require.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + return addRepo(args) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding") + } else if len(args) == 1 { + comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding") + } else { + comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments") + } + return comps, cobra.ShellCompDirectiveNoFileComp + }, +} +``` +The example above defines the completions (none, in this specific example) as well as the Active Help messages for the `helm repo add` command. It yields the following behavior: +``` +bash-5.1$ helm repo add [tab] +You must choose a name for the repo you are adding + +bash-5.1$ helm repo add grafana [tab] +You must specify the URL for the repo you are adding + +bash-5.1$ helm repo add grafana https://grafana.github.io/helm-charts [tab] +This command does not take any more arguments +``` + +### Active Help for flags + +Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example: +```go +_ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 2 { + return cobra.AppendActiveHelp(nil, "You must first specify the chart to install before the --version flag can be completed"), cobra.ShellCompDirectiveNoFileComp + } + return compVersionFlag(args[1], toComplete) + }) +``` +The example above prints an Active Help message when not enough information was given by the user to complete the `--version` flag. +``` +bash-5.1$ bin/helm install myrelease --version 2.0.[tab] +You must first specify the chart to install before the --version flag can be completed + +bash-5.1$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab] +2.0.1 2.0.2 2.0.3 +``` + +## User control of Active Help + +You may want to allow your users to disable Active Help or choose between different levels of Active Help. It is entirely up to the program to define the level of configurability of Active Help that it wants to offer. Implementing the configuration of Active Help through Cobra is done as follows: + +1. Allow a user to specify the Active Help configuration she wants. This would normally be done when the user requests the generation of the shell completion script +1. Specify the Active Help configuration by setting the `rootCmd.ActiveHelpConfig` string before calling the Cobra API that generates the shell completion script +1. When in `cmd.ValidArgsFunction(...)` or a flag's completion function, read the configuration from the `cmd.ActiveHelpConfig` field and select what Active Help messages should or should not be added + +For example, a program that uses a `completion` command to generate the shell completion script can add a flag `--activehelp-level` to that command. The user would then use that flag to choose an Active Help level: +``` +bash-5.1$ source <(helm completion bash --activehelp-level 1) +``` +The code to pass that information to Cobra would look something like: +```go +cmd.Root().ActiveHelpConfig = strconv.Itoa(activeHelpLevel) +return cmd.Root().GenBashCompletionV2(out, true) +``` +This specified configuration will then be accessible whenever `cmd.ValidArgsFunction(...)` or a flag's completion function is called: +```go +ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + activeHelpLevel, err := strconv.Atoi(cmd.ActiveHelpConfig) + if err != nil { + activeHelpLevel = 2 // Highest level allowed by this program + } + + var comps []string + if len(args) == 0 { + if activeHelpLevel > 0 { + comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding") + } + } else if len(args) == 1 { + if activeHelpLevel > 0 { + comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding") + } + } else { + if activeHelpLevel > 1 { + comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments") + } + } + 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**: 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. + +## 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: +``` +$ bin/helm __complete --__activeHelpCfg=1 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 +bitnami/haproxy +bitnami/harbor +:0 +Completion ended with directive: ShellCompDirectiveDefault +``` diff --git a/bash_completions.go b/bash_completions.go index 733f4d1211..ee81217b7d 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -73,7 +73,8 @@ __%[1]s_handle_go_custom_completion() # 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 ${args[*]}" + # Disable ActiveHelp which is not supported for bash completion v1 + requestComp="${words[0]} %[2]s --%[8]s=0 ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -377,7 +378,7 @@ __%[1]s_handle_word() `, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) } func writePostscript(buf io.StringWriter, name string) { diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 8859b57c42..6b5c05b44f 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -9,12 +9,12 @@ import ( func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) - genBashComp(buf, c.Name(), includeDesc) + genBashComp(buf, c, includeDesc) _, err := buf.WriteTo(w) return err } -func genBashComp(buf io.StringWriter, name string, includeDesc bool) { +func genBashComp(buf io.StringWriter, cmd *Command, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd @@ -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 ${args[*]}" + requestComp="${words[0]} %[2]s --%[9]s=%[10]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -111,13 +111,18 @@ __%[1]s_process_completion_results() { fi fi + # Separate activeHelp from normal completions + local completions=() + local activeHelp=() + __%[1]s_extract_activeHelp + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local fullFilter filter filteringCmd - # Do not use quotes around the $out variable or else newline + # Do not use quotes around the $completions variable or else newline # characters will be kept. - for filter in ${out[*]}; do + for filter in ${completions[*]}; do fullFilter+="$filter|" done @@ -129,7 +134,7 @@ __%[1]s_process_completion_results() { # Use printf to strip any trailing newline local subdir - subdir=$(printf "%%s" "${out[0]}") + subdir=$(printf "%%s" "${completions[0]}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return @@ -143,6 +148,35 @@ __%[1]s_process_completion_results() { __%[1]s_handle_special_char "$cur" : __%[1]s_handle_special_char "$cur" = + + # Print the activeHelp statements before we finish + if [ ${#activeHelp} -ne 0 ]; then + printf "\n"; + printf "%%s\n" "${activeHelp[@]}" + printf "\n" + # This needs bash 4.4 + printf "%%s" "${PS1@P}${COMP_LINE[@]}" + fi +} + +# Separate activeHelp lines from real completions. +# Fills the $activeHelp and $completions arrays. +__%[1]s_extract_activeHelp() { + local activeHelpMarker="%[8]s" + local endIndex=${#activeHelpMarker} + + while IFS='' read -r comp; do + if [ "${comp:0:endIndex}" = "$activeHelpMarker" ]; then + comp=${comp:endIndex} + __%[1]s_debug "ActiveHelp found: $comp" + if [ -n "$comp" ]; then + activeHelp+=("$comp") + fi + else + # Not an activeHelp line but a normal completion + completions+=("$comp") + fi + done < <(printf "%%s\n" "${out[@]}") } __%[1]s_handle_standard_completion_case() { @@ -159,9 +193,9 @@ __%[1]s_handle_standard_completion_case() { if ((${#comp}>longest)); then longest=${#comp} fi - done < <(printf "%%s\n" "${out[@]}") + done < <(printf "%%s\n" "${completions[@]}") - local completions=() + local finalcomps=() while IFS='' read -r comp; do if [ -z "$comp" ]; then continue @@ -170,12 +204,12 @@ __%[1]s_handle_standard_completion_case() { __%[1]s_debug "Original comp: $comp" comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" __%[1]s_debug "Final comp: $comp" - completions+=("$comp") - done < <(printf "%%s\n" "${out[@]}") + finalcomps+=("$comp") + done < <(printf "%%s\n" "${completions[@]}") while IFS='' read -r comp; do COMPREPLY+=("$comp") - done < <(compgen -W "${completions[*]}" -- "$cur") + done < <(compgen -W "${finalcomps[*]}" -- "$cur") # If there is a single completion left, remove the description text if [ ${#COMPREPLY[*]} -eq 1 ]; then @@ -279,9 +313,10 @@ else fi # ex: ts=4 sw=4 et filetype=sh -`, name, compCmd, +`, cmd.Name(), compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + activeHelpMarker, activeHelpCfgFlagName, cmd.Root().ActiveHelpConfig)) } // GenBashCompletionFileV2 generates Bash completion version 2. diff --git a/command.go b/command.go index 2cc18891d7..e2c4a98b1e 100644 --- a/command.go +++ b/command.go @@ -222,6 +222,23 @@ type Command struct { // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. // Must be > 0. SuggestionsMinimumDistance int + + // ActiveHelpConfig is a string that can be used to communicate the level of activeHelp + // the user is interested in receiving. + // The program can set this string before generating the completion scripts. + // When setting this string, it MUST be set on the Root command. + // + // The program should read this string from within ValidArgsFunction or the flag value + // completion functions to make decisions on whether or not to append activeHelp messages. + // This string can be read directly from the command passed to the completion functions, + // or from the Root command. + // + // If the value 0 is used, it will automatically be handled by Cobra and + // will completely disable activeHelp output, even if some output was specified by + // the program. + // Any other value will not be interpreted by Cobra but only provided back + // to the program when ValidArgsFunction is called. + ActiveHelpConfig string } // Context returns underlying command context. If command wasn't diff --git a/completions.go b/completions.go index b849b9c844..9a9c610906 100644 --- a/completions.go +++ b/completions.go @@ -24,6 +24,11 @@ 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 @@ -81,6 +86,8 @@ const ( compCmdNoDescFlagName = "no-descriptions" compCmdNoDescFlagDesc = "disable completion descriptions" compCmdNoDescFlagDefault = false + + activeHelpMarker = "_activeHelp_ " ) // CompletionOptions are the options to control shell completion @@ -117,6 +124,17 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman return nil } +// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp. +// Such strings will be processed by the completion script and will be shown as ActiveHelp +// to the user. +// The array parameter should be the array that will contain the completions. +// This function can be called multiple times before and/or after completions are added to +// the array. Each time this function is called with the same array, the new +// ActiveHelp line will be shown below the previous ones when completion is triggered. +func AppendActiveHelp(compArray []string, activeHelpStr string) []string { + return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr)) +} + // Returns a string listing the different directive enabled in the specified parameter func (d ShellCompDirective) string() string { var directives []string @@ -148,16 +166,21 @@ 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}, - DisableFlagsInUseLine: true, - 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}, + 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()) @@ -168,6 +191,12 @@ func (c *Command) initCompleteCmd(args []string) { noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) for _, comp := range completions { + if finalCmd.ActiveHelpConfig == "0" { + // Remove all activeHelp entries in this case + if strings.HasPrefix(comp, activeHelpMarker) { + continue + } + } if noDescriptions { // Remove any description that may be included following a tab character. comp = strings.Split(comp, "\t")[0] @@ -212,6 +241,30 @@ 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 @@ -417,6 +470,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Go custom completion defined for this flag or command. // Call the registered completion function to get the completions. var comps []string + + // 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 + finalCmd.Root().ActiveHelpConfig = finalCmd.ActiveHelpConfig + comps, directive = completionFn(finalCmd, finalArgs, toComplete) completions = append(completions, comps...) } diff --git a/fish_completions.go b/fish_completions.go index bb57fd5685..ecad508bdb 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -38,7 +38,8 @@ function __%[1]s_perform_completion __%[1]s_debug "args: $args" __%[1]s_debug "last arg: $lastArg" - set -l requestComp "$args[1] %[3]s $args[2..-1] $lastArg" + # Disable ActiveHelp which is not supported for fish shell + set -l requestComp "$args[1] %[3]s --%[9]s=0 $args[2..-1] $lastArg" __%[1]s_debug "Calling $requestComp" set -l results (eval $requestComp 2> /dev/null) @@ -196,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)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/powershell_completions.go b/powershell_completions.go index 59234c09f1..2bdd0ae8cb 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -61,7 +61,9 @@ 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) - $RequestComp="$Program %[2]s $Arguments" + + # Also disable ActiveHelp which is not supported for Powershell + $RequestComp="$Program %[2]s --%[8]s=0 $Arguments" __%[1]s_debug "RequestComp: $RequestComp" # we cannot use $WordToComplete because it @@ -94,7 +96,6 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # $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 "") { @@ -242,7 +243,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpCfgFlagName)) } func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { diff --git a/user_guide.md b/user_guide.md index 311abce284..d52305cc4b 100644 --- a/user_guide.md +++ b/user_guide.md @@ -635,3 +635,7 @@ Cobra can generate documentation based on subcommands, flags, etc. Read more abo ## Generating shell completions Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md). + +## Providing Active Help + +Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users. Active Help are messages (hints, warnings, etc) printed as the program is being used. Read more about it in [Active Help](active_help.md). diff --git a/zsh_completions.go b/zsh_completions.go index 1afec30ea9..35914d8c3c 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -65,12 +65,12 @@ func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) - genZshComp(buf, c.Name(), includeDesc) + genZshComp(buf, c, includeDesc) _, err := buf.WriteTo(w) return err } -func genZshComp(buf io.StringWriter, name string, includeDesc bool) { +func genZshComp(buf io.StringWriter, cmd *Command, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd @@ -121,7 +121,7 @@ _%[1]s() fi # Prepare the command to obtain completions - requestComp="${words[1]} %[2]s ${words[2,-1]}" + requestComp="${words[1]} %[2]s --%[9]s=%[10]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. @@ -163,7 +163,24 @@ _%[1]s() return fi + local activeHelpMarker="%[8]s" + local endIndex=${#activeHelpMarker} + local startIndex=$((${#activeHelpMarker}+1)) + local hasActiveHelp=0 while IFS='\n' read -r comp; do + # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker) + if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then + __%[1]s_debug "ActiveHelp found: $comp" + comp="${comp[$startIndex,-1]}" + if [ -n "$comp" ]; then + compadd -x "${comp}" + __%[1]s_debug "ActiveHelp will need delimiter" + hasActiveHelp=1 + fi + + continue + fi + if [ -n "$comp" ]; then # If requested, completions are returned with a description. # The description is preceded by a TAB character. @@ -180,6 +197,17 @@ _%[1]s() fi done < <(printf "%%s\n" "${out[@]}") + # Add a delimiter after the activeHelp statements, but only if: + # - there are completions following the activeHelp statements, or + # - file completion will be performed (so there will be choices after the activeHelp) + if [ $hasActiveHelp -eq 1 ]; then + if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then + __%[1]s_debug "Adding activeHelp delimiter" + compadd -x "--" + hasActiveHelp=0 + fi + fi + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then __%[1]s_debug "Activating nospace." noSpace="-S ''" @@ -252,7 +280,8 @@ _%[1]s() if [ "$funcstack[1]" = "_%[1]s" ]; then _%[1]s fi -`, name, compCmd, +`, cmd.Name(), compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + activeHelpMarker, activeHelpCfgFlagName, cmd.Root().ActiveHelpConfig)) }