Repository: spf13/cobra
Pull request #1482 (2021-09-04 01:57:03)

Add Active Help support
This PR is a proposal to enhance the shell completion logic with a framework for what I call "Active Help".

Active Help are messages (hints, warnings, etc) that are printed during the use of the program using the shell completion system.  It aims to help teach how to use the program in an "active" fashion.

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

# This last one is normal completion but shows that it works with the previous active help trigger
bash-5.1$ bin/helm package [tab][tab]
bin/    internal/    scripts/    pkg/     testdata/
```
Active Help messages are defined by the program by adding them as custom completions using `ValidArgsFunction` and `RegisterFlagCompletionFunc` and a new function: `cobra.AppendActiveHelp(completionArray, activeHelpMessage)`.

Here is a 3 minute video illustrating how the feature can be used:

<a href="https://asciinema.org/a/421868" target="_blank"><img src="https://asciinema.org/a/421868.svg" height="200"/></a>

The PR contains [documentation](https://github.com/VilledeMontreal/cobra/blob/feat/ActiveHelp/active_help.md) describing how to use the feature.

The PR adds the feature to both `bash` and `zsh`.  It is probably do-able for `powershell` but I haven't tried.  It is not possible to implement for `fish` currently.

I'd be interested to know if this is something of interest to the community.

**Note**:
For bash, version >= 4.4 of bash provides a smoother experience.  In older versions of bash, when printing Active Help, the prompt will not be shown before what the user has typed, but will re-appear when normal completion is triggered.  For example:
```
bash-4.3# bin/helm c<TAB>
HINT: The most common actions are: search, pull, install, list

# Notice the lack of prompt in the next line.  This only happens when there is an Active Help message
bin/helm c<TAB>
HINT: The most common actions are: search, pull, install, list

bin/helm c<TAB>
completion    create
bash-4.3# bin/helm c
```

_Should Active Help be disabled completely for bash < 4.4, or is the lack of prompt acceptable?_

**Missing:**
- Updating the [cobra-completion-testing](https://github.com/marckhouzam/cobra-completion-testing) repo

**This can be seen in action using https://github.com/spf13/cobra-cli/pull/24**

**Here is a test program that adds some active help:**
<details>
<summary>Test program</summary>

```go
package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
)

var (
	completionNoDesc bool
	noMoreArgs       = "This command takes no more arguments"
)

var completionCmd = &cobra.Command{
	Use:   "completion [bash|bashv1|zsh|fish|powershell]",
	Short: "Generate completion script",
	Long: `Bash:
$ source <(testprog completion bash)

Zsh:
$ source <(testprog completion zsh)
$ compdef _testprog testprog

Fish:
$ testprog completion fish | source

PowerShell:
PS C:\> testprog completion powershell | Out-String | Invoke-Expression
`,
	DisableFlagsInUseLine: true,
	Args:                  cobra.ExactValidArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			cmd.Root().GenBashCompletionV2(os.Stdout, !completionNoDesc)
		case "bashv1":
			cmd.Root().GenBashCompletion(os.Stdout)
		case "zsh":
			if !completionNoDesc {
				cmd.Root().GenZshCompletion(os.Stdout)
			} else {
				cmd.Root().GenZshCompletionNoDesc(os.Stdout)
			}
		case "fish":
			cmd.Root().GenFishCompletion(os.Stdout, !completionNoDesc)
		case "powershell":
			if completionNoDesc {
				cmd.Root().GenPowerShellCompletion(os.Stdout)
			} else {
				cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
			}
		}
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return []string{
			"bashv1\tBash completion V1",
			"bash\tBash completion V2",
			"zsh\tZsh completion",
			"fish\tFish completion",
			"pwsh\tPowershell completions"}, cobra.ShellCompDirectiveNoFileComp
	},
}

var rootCmd = &cobra.Command{
	Use: "testprog",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("rootCmd called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return cobra.AppendActiveHelp(nil, "Choose a command to see different Active Help examples"),
			cobra.ShellCompDirectiveNoFileComp
	},
}

var onlyAH = &cobra.Command{
	Use:   "onlyAH",
	Short: "Command with Active Help but no args",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("onlyAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return cobra.AppendActiveHelp(nil, noMoreArgs), cobra.ShellCompDirectiveNoFileComp
	},
}

var argsAndAH = &cobra.Command{
	Use:   "argsAndAH",
	Short: "Command with Active Help and one arg",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("argsAndAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			comps := []string{"firstArg", "secondArg"}
			comps = cobra.AppendActiveHelp(comps, "Please specify a single argument")
			comps = cobra.AppendActiveHelp(comps, "The arguments should be shown below if they match what you typed")
			return comps, cobra.ShellCompDirectiveNoFileComp
		}
		return cobra.AppendActiveHelp(nil, noMoreArgs), cobra.ShellCompDirectiveNoFileComp
	},
}

var filesAndAH = &cobra.Command{
	Use:   "filesAndAH",
	Short: "Command with Active Help and a file path",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("filesAndAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return cobra.AppendActiveHelp(nil, "Please specify the path to a file"),
				cobra.ShellCompDirectiveDefault
		}
		return cobra.AppendActiveHelp(nil, noMoreArgs), cobra.ShellCompDirectiveNoFileComp
	},
}

var goFilesAndAH = &cobra.Command{
	Use:   "goFilesAndAH",
	Short: "Command with Active Help and a go file argument",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("goFilesAndAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			comps := cobra.AppendActiveHelp(nil, "Please specify the path to a file")
			comps = append(comps, "go")
			comps = cobra.AppendActiveHelp(comps, "On file ending with .go are allowed")
			return comps, cobra.ShellCompDirectiveFilterFileExt
		}
		return cobra.AppendActiveHelp(nil, noMoreArgs), cobra.ShellCompDirectiveNoFileComp

	},
}

var noSpaceAndAH = &cobra.Command{
	Use:   "noSpaceAndAH",
	Short: "Command with Active Help using the nospace directive",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("noSpaceAndAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		var comps []string
		if len(args) == 0 {
			if !strings.Contains(toComplete, "/") {
				comps = []string{"firstRepo/", "secondRepo/"}
				comps = cobra.AppendActiveHelp(comps, "Please choose a repository name")
				return comps, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
			}

			repo := toComplete[:strings.Index(toComplete, "/")+1]
			comps = []string{repo + "firstChart", repo + "secondChart"}
			comps = cobra.AppendActiveHelp(comps, "Please choose a chart name")
			return comps, cobra.ShellCompDirectiveNoFileComp
		}
		return cobra.AppendActiveHelp(comps, "This command takes no more arguments"), cobra.ShellCompDirectiveNoFileComp
	},
}

var levelsForAH = &cobra.Command{
	Use:   "levelsForAH",
	Short: "Command that uses different levels of active help",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("levelsForAH called")
	},
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		var comps []string
		if cobra.GetActiveHelpConfig(cmd) != "off" {
			comps = cobra.AppendActiveHelp(comps, "This msg displays as long as active help is not disabled")
			if cobra.GetActiveHelpConfig(cmd) == "on" {
				comps = cobra.AppendActiveHelp(comps, "This msg should not display if active help level is 'local'")
			}
		}

		comps = cobra.AppendActiveHelp(comps, noMoreArgs)
		return comps, cobra.ShellCompDirectiveNoFileComp

	},
}

func setFlags() {
	rootCmd.PersistentFlags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
	rootCmd.RegisterFlagCompletionFunc("theme", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		comps := []string{"themes"}
		comps = cobra.AppendActiveHelp(comps, "Please choose a theme from the list below")
		return comps, cobra.ShellCompDirectiveFilterDirs
	})

	rootCmd.PersistentFlags().Int("int", 0, "need an int as input")
	rootCmd.RegisterFlagCompletionFunc("int", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		comps := cobra.AppendActiveHelp(nil, "You must specify an integer")
		if len(toComplete) > 0 {
			if _, err := strconv.Atoi(toComplete); err != nil {
				comps = cobra.AppendActiveHelp(comps, "ERROR: What you have typed is not an integer")
			} else {
				comps = []string{toComplete}
			}
		}
		return comps, cobra.ShellCompDirectiveNoFileComp
	})

	rootCmd.PersistentFlags().Bool("bool", false, "boolean flag")
	rootCmd.RegisterFlagCompletionFunc("bool", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return cobra.AppendActiveHelp(nil, "This is a boolean flag.  To disable it, remove the flag, or use --bool=false (the = is mandatory)"),
			cobra.ShellCompDirectiveNoFileComp
	})

}

func main() {
	completionCmd.Flags().BoolVar(
		&completionNoDesc,
		"no-descriptions", false,
		"disable completion description for shells that support it")
	rootCmd.AddCommand(completionCmd)

	rootCmd.AddCommand(
		onlyAH,
		argsAndAH,
		filesAndAH,
		goFilesAndAH,
		noSpaceAndAH,
		levelsForAH,
	)

	setFlags()

	rootCmd.Execute()
}
```
</details>


/cc @jpmcb @jharshman @Luap99 

marckhouzam: Here is the helm branch that uses this feature: https://github.com/VilledeMontreal/helm/tree/feat/compInfo

Without needing a kubernetes cluster, one can try:
```
source <(bin/helm completion zsh) # or bash
bin/helm TAB
bin/helm repo add TAB
bin/helm repo add name TAB
bin/helm repo add name https TAB
bin/helm package TAB
bin/helm template TAB
```


I will make a smaller test program and post it in this PR.  I should have thought about doing that before.
marckhouzam: I've just added a test program in the description of the PR which shows different Active Help scenarios and how to use the active help configuration
jharshman: @marckhouzam This is a great IMO. I like the interactive nature of it and that it is opt-in. :rocket: 
marckhouzam: I've pushed an extra commit which uses the environment variable `COBRA_ACTIVEHELP` to configure Active Help instead of using a flag as mentioned by @Luap99.  Looking at that one commit (and possibly reverting it) allows to compare the two approaches.

A nice thing about using an env var is that a user could set a different active help setting for individual commands by wrapping them in an alias or script which sets the env var (although that would be a pretty advanced usage).

Using a name like `COBRA_ACTIVEHELP` allows to set an active help level for all Cobra programs.  However, I am afraid that contradicts the fact that different programs can choose whatever active help configuration they want to use.  For example, `helm` could use active help levels of `on/off/local` but `kubectl` could use `enabled/disabled/noremote`; in such a case, using a global name such as `COBRA_ACTIVEHELP` makes the configuration not work.

We could instead use the program name in the variable, something like `HELM_ACTIVEHELP` and `KUBECTL_ACTIVEHELP`, and maybe have the global `COBRA_ACTIVEHELP` to only allow to disable active help for every program.  I'm not sure if we need to go that far though.

Opinions on using a flag versus an env var?
github-actions[bot]: This PR is being marked as stale due to a long period of inactivity
marckhouzam: Still valid
marckhouzam: > Nice work, I really like the idea behind this. I think it is cool to display a message when your command does not take args for example.

I expect the most common use of ActiveHelp will/should be to display a message when there are no completions suggested.  When there are no completions, the user doesn't really know what to do, so an ActiveHelp message can explain (e.g., "This command does not take any more arguments", or "Please specify a name for the container that will be created").

> I tested this with bash and it works as I would expect.

Thanks for trying it out!


marckhouzam: I've created a draft PR for the cobra-cli that uses ActiveHelp.  I hope this show very easily how the feature can be used: https://github.com/spf13/cobra-cli/pull/24

@johnSchnake I've spoken to others about this but not to you.  I'd love your impressions on the feature.
github-actions[bot]: This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size.

Patch:
diff --git a/active_help.go b/active_help.go
new file mode 100644
index 0000000..0c63191
--- /dev/null
+++ b/active_help.go
@@ -0,0 +1,49 @@
+package cobra
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+const (
+	activeHelpMarker = "_activeHelp_ "
+	// The below values should not be changed: programs will be using them explicitly
+	// in their user documentation, and users will be using them explicitly.
+	activeHelpEnvVarSuffix  = "_ACTIVE_HELP"
+	activeHelpGlobalEnvVar  = "COBRA_ACTIVE_HELP"
+	activeHelpGlobalDisable = "0"
+)
+
+// 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))
+}
+
+// GetActiveHelpConfig returns the value of the ActiveHelp environment variable
+// <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the root command in upper
+// case, with all - replaced by _.
+// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP
+// is set to "0".
+func GetActiveHelpConfig(cmd *Command) string {
+	activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar)
+	if activeHelpCfg != activeHelpGlobalDisable {
+		activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name()))
+	}
+	return activeHelpCfg
+}
+
+// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment
+// variable.  It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
+// root command in upper case, with all - replaced by _.
+func activeHelpEnvVar(name string) string {
+	// This format should not be changed: users will be using it explicitly.
+	activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix))
+	return strings.ReplaceAll(activeHelpEnvVar, "-", "_")
+}
diff --git a/active_help.md b/active_help.md
new file mode 100644
index 0000000..5e7f59a
--- /dev/null
+++ b/active_help.md
@@ -0,0 +1,157 @@
+# 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/
+```
+
+**Hint**: A good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions to guide the user in knowing what is expected by the program.
+## Supported shells
+
+Active Help is currently only supported for the following shells:
+- Bash (using [bash completion V2](shell_completions.md#bash-completion-v2) only). Note that bash 4.4 or higher is required for the prompt to appear when an Active Help message is printed.
+- 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
+```
+**Hint**: As can be seen in the above example, a good place to use Active Help messages is when the normal completion system does not provide any suggestions. In such cases, Active Help nicely supplements the normal shell completions.
+
+### 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 type of configurability of Active Help that it wants to offer, if any.
+Allowing to configure Active Help is entirely optional; you can use Active Help in your program without doing anything about Active Help configuration.
+
+The way to configure Active Help is to use the program's Active Help environment
+variable.  That variable is named `<PROGRAM>_ACTIVE_HELP` where `<PROGRAM>` is the name of your 
+program in uppercase with any `-` replaced by an `_`.  The variable should be set by the user to whatever
+Active Help configuration values are supported by the program.
+
+For example, say `helm` has chosen to support three levels for Active Help: `on`, `off`, `local`.  Then a user
+would set the desired behavior to `local` by doing `export HELM_ACTIVE_HELP=local` in their shell.
+
+For simplicity, when in `cmd.ValidArgsFunction(...)` or a flag's completion function, the program should read the
+Active Help configuration using the `cobra.GetActiveHelpConfig(cmd)` function and select what Active Help messages
+should or should not be added (instead of reading the environment variable directly).
+
+For example:
+```go
+ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	activeHelpLevel := cobra.GetActiveHelpConfig(cmd)
+
+	var comps []string
+	if len(args) == 0 {
+		if activeHelpLevel != "off"  {
+			comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
+		}
+	} else if len(args) == 1 {
+		if activeHelpLevel != "off" {
+			comps = cobra.AppendActiveHelp(comps, "You must specify the URL for the repo you are adding")
+		}
+	} else {
+		if activeHelpLevel == "local" {
+			comps = cobra.AppendActiveHelp(comps, "This command does not take any more arguments")
+		}
+	}
+	return comps, cobra.ShellCompDirectiveNoFileComp
+},
+```
+**Note 1**: If the `<PROGRAM>_ACTIVE_HELP` environment variable is set to the string "0", Cobra will automatically 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 without having to call `cobra.GetActiveHelpConfig(cmd)` explicitly.
+
+**Note 2**: If a user wants to disable Active Help for every single program based on Cobra, she can set the environment variable `COBRA_ACTIVE_HELP` to "0".  In this case `cobra.GetActiveHelpConfig(cmd)` will return "0" no matter what the variable `<PROGRAM>_ACTIVE_HELP` is set to.
+
+**Note 3**: If the user does not set `<PROGRAM>_ACTIVE_HELP` or `COBRA_ACTIVE_HELP` (which will be a common case), the default value for the Active Help configuration returned by `cobra.GetActiveHelpConfig(cmd)` will be the empty string. 
+## Active Help with Cobra's default completion command
+
+Cobra provides a default `completion` command for programs that wish to use it.
+When using the default `completion` command, Active Help is configurable in the same
+fashion as described above using environment variables.  You may wish to document this in more
+details for your users.
+
+## Debugging Active Help
+
+Debugging your Active Help code is done in the same way as debugging your 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 active help environment variable.  That variable is named `<PROGRAM>_ACTIVE_HELP` where any `-` is replaced by an `_`.  For example, we can test deactivating some Active Help as shown below:
+```
+$ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h<ENTER>
+bitnami/haproxy
+bitnami/harbor
+_activeHelp_ WARNING: cannot re-use a name that is still in use
+:0
+Completion ended with directive: ShellCompDirectiveDefault
+
+$ HELM_ACTIVE_HELP=0 bin/helm __complete install wordpress bitnami/h<ENTER>
+bitnami/haproxy
+bitnami/harbor
+:0
+Completion ended with directive: ShellCompDirectiveDefault
+```
diff --git a/bash_completions.go b/bash_completions.go
index 3e1e528..cb7e195 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="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
 
     lastParam=${words[$((${#words[@]}-1))]}
     lastChar=${lastParam:$((${#lastParam}-1)):1}
@@ -383,7 +384,7 @@ __%[1]s_handle_word()
 
 `, name, ShellCompNoDescRequestCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
-		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
 }
 
 func writePostscript(buf io.StringWriter, name string) {
diff --git a/bash_completionsV2.go b/bash_completionsV2.go
index e61e27c..d295791 100644
--- a/bash_completionsV2.go
+++ b/bash_completionsV2.go
@@ -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}")
+        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,43 @@ __%[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"
+
+        # The prompt format is only available from bash 4.4.
+        # We test if it is available before using it.
+        if (x=${PS1@P}) 2> /dev/null; then
+            printf "%%s" "${PS1@P}${COMP_LINE[@]}"
+        else
+            # Can't print the prompt.  Just print the
+            # text the user had typed, it is workable enough.
+            printf "%%s" "${COMP_LINE[@]}"
+        fi
+    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_completion_types() {
@@ -163,7 +205,7 @@ __%[1]s_handle_completion_types() {
             if [[ $comp == "$cur"* ]]; then
                 COMPREPLY+=("$comp")
             fi
-        done < <(printf "%%s\n" "${out}")
+        done < <(printf "%%s\n" "${completions[@]}")
         ;;
 
     *)
@@ -177,8 +219,8 @@ __%[1]s_handle_standard_completion_case() {
     local tab=$'\t' comp
 
     # Short circuit to optimize if we don't have descriptions
-    if [[ $out != *$tab* ]]; then
-        IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "$out" -- "$cur")
+    if [[ ${completions[*]} != *$tab* ]]; then
+        IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
         return 0
     fi
 
@@ -195,7 +237,7 @@ __%[1]s_handle_standard_completion_case() {
         if ((${#comp}>longest)); then
             longest=${#comp}
         fi
-    done < <(printf "%%s\n" "${out}")
+    done < <(printf "%%s\n" "${completions[@]}")
 
     # If there is a single completion left, remove the description text
     if [ ${#COMPREPLY[*]} -eq 1 ]; then
@@ -305,7 +347,8 @@ fi
 # ex: ts=4 sw=4 et filetype=sh
 `, name, compCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
-		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
+		activeHelpMarker))
 }
 
 // GenBashCompletionFileV2 generates Bash completion version 2.
diff --git a/completions.go b/completions.go
index ce1caab..7a9a7fa 100644
--- a/completions.go
+++ b/completions.go
@@ -178,6 +178,12 @@ func (c *Command) initCompleteCmd(args []string) {
 
 			noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
 			for _, comp := range completions {
+				if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable {
+					// 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]
@@ -652,7 +658,7 @@ To load completions for every new session, execute once:
 
 #### macOS:
 
-	%[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
+	%[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
 
 You will need to start a new shell for this setup to take effect.
 `, c.Root().Name()),
@@ -689,7 +695,7 @@ To load completions for every new session, execute once:
 
 #### macOS:
 
-	%[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s
+	%[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s
 
 You will need to start a new shell for this setup to take effect.
 `, c.Root().Name()),
diff --git a/fish_completions.go b/fish_completions.go
index e14a06e..005ee6b 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 "%[9]s=0 $args[1] %[3]s $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, activeHelpEnvVar(name)))
 }
 
 // GenFishCompletion generates fish completion file and writes to the passed writer.
diff --git a/powershell_completions.go b/powershell_completions.go
index 62d719f..379e7c0 100644
--- a/powershell_completions.go
+++ b/powershell_completions.go
@@ -61,6 +61,7 @@ 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"
     __%[1]s_debug "RequestComp: $RequestComp"
 
@@ -90,11 +91,13 @@ 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
 
-
     # get directive from last line
     [int]$Directive = $Out[-1].TrimStart(':')
     if ($Directive -eq "") {
@@ -242,7 +245,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
 }
 `, name, compCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
-		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
 }
 
 func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error {
diff --git a/shell_completions.md b/shell_completions.md
index be17bf8..1e2058e 100644
--- a/shell_completions.md
+++ b/shell_completions.md
@@ -40,7 +40,7 @@ Bash:
   # Linux:
   $ %[1]s completion bash > /etc/bash_completion.d/%[1]s
   # macOS:
-  $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
+  $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
 
 Zsh:
 
diff --git a/user_guide.md b/user_guide.md
index cde4032..3a253de 100644
--- a/user_guide.md
+++ b/user_guide.md
@@ -660,3 +660,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 072921f..65cd94c 100644
--- a/zsh_completions.go
+++ b/zsh_completions.go
@@ -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 ''"
@@ -254,5 +282,6 @@ if [ "$funcstack[1]" = "_%[1]s" ]; then
 fi
 `, name, compCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
-		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs,
+		activeHelpMarker))
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0b3fb36..0c83f30 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
 
-      - uses: actions/setup-go@v2
+      - uses: actions/setup-go@v3
         with:
           go-version: '1.17'
 
@@ -41,7 +41,7 @@ jobs:
     runs-on: ${{ matrix.platform }}-latest
     steps:
 
-    - uses: actions/setup-go@v2
+    - uses: actions/setup-go@v3
       with:
         go-version: 1.${{ matrix.go }}.x
 
diff --git a/active_help_test.go b/active_help_test.go
new file mode 100644
index 0000000..524be88
--- /dev/null
+++ b/active_help_test.go
@@ -0,0 +1,386 @@
+package cobra
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+)
+
+const (
+	activeHelpMessage  = "This is an activeHelp message"
+	activeHelpMessage2 = "This is the rest of the activeHelp message"
+)
+
+func TestActiveHelpAlone(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	activeHelpFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := AppendActiveHelp(nil, activeHelpMessage)
+		return comps, ShellCompDirectiveDefault
+	}
+
+	// Test that activeHelp can be added to a root command
+	rootCmd.ValidArgsFunction = activeHelpFunc
+
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	rootCmd.ValidArgsFunction = nil
+
+	// Test that activeHelp can be added to a child command
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	childCmd.ValidArgsFunction = activeHelpFunc
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestActiveHelpWithComps(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	// Test that activeHelp can be added following other completions
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := []string{"first", "second"}
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		return comps, ShellCompDirectiveDefault
+	}
+
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"first",
+		"second",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that activeHelp can be added preceding other completions
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		var comps []string
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		comps = append(comps, []string{"first", "second"}...)
+		return comps, ShellCompDirectiveDefault
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		"first",
+		"second",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that activeHelp can be added interleaved with other completions
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := []string{"first"}
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		comps = append(comps, "second")
+		return comps, ShellCompDirectiveDefault
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"first",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		"second",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestMultiActiveHelp(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	// Test that multiple activeHelp message can be added
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := AppendActiveHelp(nil, activeHelpMessage)
+		comps = AppendActiveHelp(comps, activeHelpMessage2)
+		return comps, ShellCompDirectiveNoFileComp
+	}
+
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that multiple activeHelp messages can be used along with completions
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := []string{"first"}
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		comps = append(comps, "second")
+		comps = AppendActiveHelp(comps, activeHelpMessage2)
+		return comps, ShellCompDirectiveNoFileComp
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"first",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		"second",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestActiveHelpForFlag(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	flagname := "flag"
+	rootCmd.Flags().String(flagname, "", "A flag")
+
+	// Test that multiple activeHelp message can be added
+	_ = rootCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := []string{"first"}
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		comps = append(comps, "second")
+		comps = AppendActiveHelp(comps, activeHelpMessage2)
+		return comps, ShellCompDirectiveNoFileComp
+	})
+
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--flag", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"first",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage),
+		"second",
+		fmt.Sprintf("%s%s", activeHelpMarker, activeHelpMessage2),
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestConfigActiveHelp(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	activeHelpCfg := "someconfig,anotherconfig"
+	// Set the variable that the user would be setting
+	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
+
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
+		if receivedActiveHelpCfg != activeHelpCfg {
+			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
+		}
+		return nil, ShellCompDirectiveDefault
+	}
+
+	_, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	// Test active help config for a flag
+	activeHelpCfg = "a config for a flag"
+	// Set the variable that the completions scripts will be setting
+	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
+
+	flagname := "flag"
+	childCmd.Flags().String(flagname, "", "A flag")
+
+	// Test that multiple activeHelp message can be added
+	_ = childCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
+		if receivedActiveHelpCfg != activeHelpCfg {
+			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
+		}
+		return nil, ShellCompDirectiveDefault
+	})
+
+	_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "--flag", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
+
+func TestDisableActiveHelp(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	// Test the disabling of activeHelp using the specific program
+	// environment variable that the completions scripts will be setting.
+	// Make sure the disabling value is "0" by hard-coding it in the tests;
+	// this is for backwards-compatibility as programs will be using this value.
+	os.Setenv(activeHelpEnvVar(rootCmd.Name()), "0")
+
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		comps := []string{"first"}
+		comps = AppendActiveHelp(comps, activeHelpMessage)
+		return comps, ShellCompDirectiveDefault
+	}
+
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	os.Unsetenv(activeHelpEnvVar(rootCmd.Name()))
+
+	// Make sure there is no ActiveHelp in the output
+	expected := strings.Join([]string{
+		"first",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Now test the global disabling of ActiveHelp
+	os.Setenv(activeHelpGlobalEnvVar, "0")
+	// Set the specific variable, to make sure it is ignored when the global env
+	// var is set properly
+	os.Setenv(activeHelpEnvVar(rootCmd.Name()), "1")
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	// Make sure there is no ActiveHelp in the output
+	expected = strings.Join([]string{
+		"first",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Make sure that if the global env variable is set to anything else than
+	// the disable value it is ignored
+	os.Setenv(activeHelpGlobalEnvVar, "on")
+	// Set the specific variable, to make sure it is used (while ignoring the global env var)
+	activeHelpCfg := "1"
+	os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg)
+
+	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		receivedActiveHelpCfg := GetActiveHelpConfig(cmd)
+		if receivedActiveHelpCfg != activeHelpCfg {
+			t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg)
+		}
+		return nil, ShellCompDirectiveDefault
+	}
+
+	_, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "thechild", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
diff --git a/bash_completionsV2_test.go b/bash_completionsV2_test.go
new file mode 100644
index 0000000..a9a277e
--- /dev/null
+++ b/bash_completionsV2_test.go
@@ -0,0 +1,19 @@
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+)
+
+func TestBashCompletionV2WithActiveHelp(t *testing.T) {
+	c := &Command{Use: "c", Run: emptyRun}
+
+	buf := new(bytes.Buffer)
+	assertNoErr(t, c.GenBashCompletionV2(buf, true))
+	output := buf.String()
+
+	// check that active help is not being disabled
+	activeHelpVar := activeHelpEnvVar(c.Name())
+	checkOmit(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+}
diff --git a/bash_completions_test.go b/bash_completions_test.go
index 21d1fe0..6896e91 100644
--- a/bash_completions_test.go
+++ b/bash_completions_test.go
@@ -261,3 +261,15 @@ func TestBashCompletionTraverseChildren(t *testing.T) {
 	checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`)
 	checkOmit(t, output, `local_nonpersistent_flags+=("-b")`)
 }
+
+func TestBashCompletionNoActiveHelp(t *testing.T) {
+	c := &Command{Use: "c", Run: emptyRun}
+
+	buf := new(bytes.Buffer)
+	assertNoErr(t, c.GenBashCompletion(buf))
+	output := buf.String()
+
+	// check that active help is being disabled
+	activeHelpVar := activeHelpEnvVar(c.Name())
+	check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+}
diff --git a/fish_completions_test.go b/fish_completions_test.go
index a3171e4..37091dc 100644
--- a/fish_completions_test.go
+++ b/fish_completions_test.go
@@ -2,6 +2,7 @@ package cobra
 
 import (
 	"bytes"
+	"fmt"
 	"testing"
 )
 
@@ -67,3 +68,15 @@ func TestProgWithColon(t *testing.T) {
 	check(t, output, "-c root:colon")
 	checkOmit(t, output, "-c root_colon")
 }
+
+func TestFishCompletionNoActiveHelp(t *testing.T) {
+	c := &Command{Use: "c", Run: emptyRun}
+
+	buf := new(bytes.Buffer)
+	assertNoErr(t, c.GenFishCompletion(buf, true))
+	output := buf.String()
+
+	// check that active help is being disabled
+	activeHelpVar := activeHelpEnvVar(c.Name())
+	check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+}
diff --git a/power_completions_test.go b/power_completions_test.go
new file mode 100644
index 0000000..7713835
--- /dev/null
+++ b/power_completions_test.go
@@ -0,0 +1,19 @@
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+)
+
+func TestPwshCompletionNoActiveHelp(t *testing.T) {
+	c := &Command{Use: "c", Run: emptyRun}
+
+	buf := new(bytes.Buffer)
+	assertNoErr(t, c.GenPowerShellCompletion(buf))
+	output := buf.String()
+
+	// check that active help is being disabled
+	activeHelpVar := activeHelpEnvVar(c.Name())
+	check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+}
diff --git a/zsh_completions_test.go b/zsh_completions_test.go
new file mode 100644
index 0000000..b7addb4
--- /dev/null
+++ b/zsh_completions_test.go
@@ -0,0 +1,19 @@
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+)
+
+func TestZshCompletionWithActiveHelp(t *testing.T) {
+	c := &Command{Use: "c", Run: emptyRun}
+
+	buf := new(bytes.Buffer)
+	assertNoErr(t, c.GenZshCompletion(buf))
+	output := buf.String()
+
+	// check that active help is not being disabled
+	activeHelpVar := activeHelpEnvVar(c.Name())
+	checkOmit(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+}