Repository: spf13/cobra
Pull request #1048 (2020-02-29 00:52:03)

Fish completion (including support for Go custom completion)
This is an implementation of Fish completion based on the Custom Go Completions of PR https://github.com/spf13/cobra/pull/1035


The PR takes a different approach to completion.  Instead of coding the entire completion in shell script, it delegates to the Go program the responsibility of determining the completion choices.  This makes it simpler to implement completion support, as Cobra provides all the necessary info to easily determine completions.

It automatically supports custom completions once they have been migrated to the Custom Go Completion method of PR #1035.

And it supports automatically adding the "usage" string for command and flag completions to describe these completions.  This is something bash completion doesn't have.

I haven't yet tried to support the other annotations `BashCompFilenameExt`, `BashCompOneRequiredFlag` or `BashCompSubdirsInDir`.

To easily try this out I have adapted `kubectl` in my repo to use this PR. Bear in mind that I haven't migrated the custom completions for `kubectl` (e.g., `kubectl get <TAB>`) to use the new Custom Go Completions of #1035, so custom completions will not yet work with this PR.  The `kubectl` that works with basic completions is here: https://github.com/VilledeMontreal/kubernetes/tree/feat/fishCompletion

My experience with Fish is limited, so it would be good for others to try this out and provide feedback.


marckhouzam: I will get this one ready for review ASAP.
marckhouzam: Ok, I just pushed the rebased feature.  

However, I'd like to take the time to make a small improvement I recently thought of.   Both Fish shell and Zsh allow for completions to be associated with a description shown to the user.  This PR currently provides the description for sub-commands (the `Command.Short` string) and flags (the `Flag.Usage` string) , just like Cobra's current zsh completion does.  With a couple of minor changes I believe we can also allow `ValidArgs` and `ValidArgsFunction` to support providing descriptions for their completions.

It won't change the feature much, so it is fine to start playing with it already.
marckhouzam: Here is how to test this feature.

Slightly modified test program that can generate the Fish completion script:
```
package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	ValidArgs: []string{ /*"pod",*/ "rs", "ds"},
	Args:      cobra.ExactArgs(1),
	Use:       "testprog1",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("rootCmd called")
	},
}

var childCmd = &cobra.Command{
	Use:   "pod",
	Short: "Start the pod",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
		return []string{"these", "are", "custom", "completions"}, cobra.BashCompDirectiveDefault
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("child called")
	},
}

var childCmd2 = &cobra.Command{
	Use:   "svc",
	Short: "Launch the service",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
		return []string{"second", "level"}, cobra.BashCompDirectiveDefault
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("child called")
	},
}

var completionCmd = &cobra.Command{
	Use:                   "completion [bash|fish] [--no-descriptions]",
	Short:                 "Generate completion script",
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "fish"},
	Args:                  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			cmd.Root().GenBashCompletion(os.Stdout)
			break
		case "fish":
			cmd.Root().GenFishCompletion(os.Stdout, !completionNoDesc)
			break
		}
	},
}

var (
	completionNoDesc bool
	testFlag         string
)

func main() {
	completionCmd.PersistentFlags().BoolVar(
		&completionNoDesc,
		"no-descriptions", false,
		"disable completion description for shells that support it")
	completionCmd.PersistentFlags().StringVar(
		&testFlag,
		"test-flag", "",
		"just to test")
	rootCmd.AddCommand(childCmd)
	rootCmd.AddCommand(completionCmd)
	childCmd.AddCommand(childCmd2)
	rootCmd.Execute()
}
```

Some tests step:
```
GO111MODULE=on go build -o testprog1 main.go
fish --version   # fish, version 3.1.0
fish
./testprog1 completion fish | source

./testprog1 <TAB>
# Should show: completion  (Generate completion script)  ds  pod  (Start the pod)  rs
# The descriptions are shown in parentheses.  Notice that the lack of description for ValidArgs
# is kind of annoying in the output.  That is what I want to improve.
# Notice also that both sub-commands and ValidArgs are being shown (based on fixing #1076)

./testprog1 pod <TAB>
# Should show: are  completions  custom  svc  (Launch the service)  these

./testprog1 completion --<TAB>
# Should show: --no-descriptions  (disable completion description for shells that support it)  --test-flag  (just to test)  --test-flag=  (just to test)

# To disable descriptions (program can choose to make this an option for users)
./testprog1 completion fish --no-descriptions | source

./testprog1 <TAB>
# Should now show: completion ds  pod rs

./testprog1 pod <TAB>
# Should now show: are  completions  custom  svc  these

./testprog1 completion --<TAB>
# Should now show: --no-descriptions  --test-flag --test-flag=  

# To debug the ValidArgsFunction code
./testprog1 __complete com
# After pressing <enter> should show:
completion
:4
Completion ended with directive: BashCompDirectiveNoFileComp
```
marckhouzam: Alright, this is ready for review. 

I've made it possible to add a description text in `ValidArgs` like so:
```
&cobra.Command{
        ...
	ValidArgs: []string{"bash\tCompletion for bash", "fish\tCompletion for fish"},
        ...
}
``` 
and in a similar fashion in the output of `ValidArgsFunction`.  It will work for Fish completion and will be silently ignored for Bash completion (bash does not support descriptions).

However, I expect that Cobra's Zsh completion requires some adjustments for this to be handled properly.  But I don't yet understand the details of the Zsh completion done by Cobra. Therefore, I simply haven't documented the concept of adding descriptions to  `ValidArgs` or `ValidArgsFunction` just yet.  But for programs like `helm` and `kubectl` that don't use Cobra's Zsh completion, it will be possible to use right away.

marckhouzam: > Wow, great work on this! Went through your testing script and everything works.
> I also tested that the source file could be loaded during fish initialization

Thanks @jpmcb!  I actually had not tested for fish initialization, so it is great that you did and confirm it works.

> Do we want to recommend in `fish_completions.md` users place the custom completion script in their `~./config/fish/conf.d` folder or simply source it as you had?

We could mention both in the readme, but in the end it will be up to the program using Cobra to make the final recommendation.  Is one better than the other in the context of the Fish shell?

Also, let me ask about a point I haven't quite figured out for Fish.  Notice in the script the lines:
```
# Remove any pre-existing completions for the program since we will be handling all of them
# TODO this cleanup is not sufficient.  Fish completions are only loaded once the user triggers
# them, so the below deletion will not work as it is run too early.  What else can we do?
complete -c testprog1 -e
```
The `TODO` is about completions already provided by Fish.  For example, Fish provides a completion script for `helm` (see https://github.com/fish-shell/fish-shell/blob/master/share/completions/helm.fish).  The problem is that this script is out of date and provides invalid completions for the latest version of Helm.  I'm looking for a way to force the loading of the original Fish completions for `helm` so that once we load the new script of this PR, it will delete the old ones.  I haven't found the solution yet...

marckhouzam: Oh, I forgot to mention that I updated the test program in the comment above to include extra descriptions.
marckhouzam: I rebased this PR to the master branch after the `BashCompDirective` to `ShellCompDirective` change.
marckhouzam: Great investigation @jpmcb on the whole loading completions.  I have also test following your recommendation and you are correct that adding the completion script to `~/.config/fish/completions/helm.fish` does take precedence over the helm completion script shipped with the Fish shell.

> Maybe even on install, the application could automatically place it's completion script into the vendor directory at /usr/share/fish/vendor_completions.d/some-program.fish.

This is a good idea.  For example, the Homebrew recipe install bash and zsh completion script for Helm; it would eventually also install the Fish completion as well.

But for the people that prefer to source the completion script, there would not be a good way to erase the completions shipped with Fish itself.  I think the thing to do would be to contribute to the Fish project and use the Helm-generated completion as the default fish completion.  Something to think about once all this is in.
marckhouzam: @jpmcb I notice you always use the `--no-descriptions` flag.  Is there a reason for that?  I'm looking for confirmation that there is value in providing this option to users.
jpmcb: No reason beyond it being the most recent command in my history and creating a little less noise. I do think it has value and that it's useful for people who don't want the long descriptions. I say leave it in!

@jharshman I think this is ready to go!

Patch:
diff --git a/args.go b/args.go
index c4d820b..70e9b26 100644
--- a/args.go
+++ b/args.go
@@ -2,6 +2,7 @@ package cobra
 
 import (
 	"fmt"
+	"strings"
 )
 
 type PositionalArgs func(cmd *Command, args []string) error
@@ -34,8 +35,15 @@ func NoArgs(cmd *Command, args []string) error {
 // OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
 func OnlyValidArgs(cmd *Command, args []string) error {
 	if len(cmd.ValidArgs) > 0 {
+		// Remove any description that may be included in ValidArgs.
+		// A description is following a tab character.
+		var validArgs []string
+		for _, v := range cmd.ValidArgs {
+			validArgs = append(validArgs, strings.Split(v, "\t")[0])
+		}
+
 		for _, v := range args {
-			if !stringInSlice(v, cmd.ValidArgs) {
+			if !stringInSlice(v, validArgs) {
 				return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
 			}
 		}
diff --git a/bash_completions.go b/bash_completions.go
index b4098f4..1e27188 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -344,7 +344,7 @@ __%[1]s_handle_word()
     __%[1]s_handle_word
 }
 
-`, name, ShellCompRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
+`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
 }
 
 func writePostscript(buf *bytes.Buffer, name string) {
@@ -548,6 +548,9 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
 	buf.WriteString("    must_have_one_noun=()\n")
 	sort.Sort(sort.StringSlice(cmd.ValidArgs))
 	for _, value := range cmd.ValidArgs {
+		// Remove any description that may be included following a tab character.
+		// Descriptions are not supported by bash completion.
+		value = strings.Split(value, "\t")[0]
 		buf.WriteString(fmt.Sprintf("    must_have_one_noun+=(%q)\n", value))
 	}
 	if cmd.ValidArgsFunction != nil {
diff --git a/bash_completions.md b/bash_completions.md
index e1590e1..e61a3a6 100644
--- a/bash_completions.md
+++ b/bash_completions.md
@@ -115,6 +115,8 @@ in this example again instead of the replication controllers.
 
 In some cases it is not possible to provide a list of possible completions in advance.  Instead, the list of completions must be determined at execution-time.  Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command.
 
+**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion.  It is therefore recommended to use *Custom Completions written in Go*.
+
 #### 1. Custom completions of nouns written in Go
 
 In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command.  Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both.
@@ -301,6 +303,8 @@ So while there are many other files in the CWD it only shows me subdirs and thos
 
 As for nouns, Cobra provides two ways of defining dynamic completion of flags.  Note that both these methods can be used along-side each other as long as they are not both used for the same flag.
 
+**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion.  It is therefore recommended to use *Custom Completions written in Go*.
+
 ## 1. Custom completions of flags written in Go
 
 To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner:
diff --git a/custom_completions.go b/custom_completions.go
index 81f1bc3..ba57327 100644
--- a/custom_completions.go
+++ b/custom_completions.go
@@ -9,9 +9,14 @@ import (
 	"github.com/spf13/pflag"
 )
 
-// ShellCompRequestCmd is the name of the hidden command that is used to request
-// completion results from the program.  It is used by the shell completion script.
-const ShellCompRequestCmd = "__complete"
+const (
+	// ShellCompRequestCmd is the name of the hidden command that is used to request
+	// completion results from the program.  It is used by the shell completion scripts.
+	ShellCompRequestCmd = "__complete"
+	// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
+	// completion results without their description.  It is used by the shell completion scripts.
+	ShellCompNoDescRequestCmd = "__completeNoDesc"
+)
 
 // Global map of flag completion functions.
 var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
@@ -77,6 +82,7 @@ func (d ShellCompDirective) string() string {
 func (c *Command) initCompleteCmd(args []string) {
 	completeCmd := &Command{
 		Use:                   fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
+		Aliases:               []string{ShellCompNoDescRequestCmd},
 		DisableFlagsInUseLine: true,
 		Hidden:                true,
 		DisableFlagParsing:    true,
@@ -93,7 +99,12 @@ func (c *Command) initCompleteCmd(args []string) {
 				// 2- Even without completions, we need to print the directive
 			}
 
+			noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
 			for _, comp := range completions {
+				if noDescriptions {
+					// Remove any description that may be included following a tab character.
+					comp = strings.Split(comp, "\t")[0]
+				}
 				// Print each possible completion to stdout for the completion script to consume.
 				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
 			}
@@ -139,6 +150,27 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 		return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
 	}
 
+	// When doing completion of a flag name, as soon as an argument starts with
+	// a '-' we know it is a flag.  We cannot use isFlagArg() here as it requires
+	// the flag to be complete
+	if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
+		// We are completing a flag name
+		finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+			completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+		})
+		finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+			completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+		})
+
+		directive := ShellCompDirectiveDefault
+		if len(completions) > 0 {
+			if strings.HasSuffix(completions[0], "=") {
+				directive = ShellCompDirectiveNoSpace
+			}
+		}
+		return finalCmd, completions, directive, nil
+	}
+
 	var flag *pflag.Flag
 	if !finalCmd.DisableFlagParsing {
 		// We only do flag completion if we are allowed to parse flags
@@ -150,6 +182,33 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 		}
 	}
 
+	if flag == nil {
+		// Complete subcommand names
+		for _, subCmd := range finalCmd.Commands() {
+			if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) {
+				completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
+			}
+		}
+
+		if len(finalCmd.ValidArgs) > 0 {
+			// Always complete ValidArgs, even if we are completing a subcommand name.
+			// This is for commands that have both subcommands and ValidArgs.
+			for _, validArg := range finalCmd.ValidArgs {
+				if strings.HasPrefix(validArg, toComplete) {
+					completions = append(completions, validArg)
+				}
+			}
+
+			// If there are ValidArgs specified (even if they don't match), we stop completion.
+			// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
+			return finalCmd, completions, ShellCompDirectiveNoFileComp, nil
+		}
+
+		// Always let the logic continue so as to add any ValidArgsFunction completions,
+		// even if we already found sub-commands.
+		// This is for commands that have subcommands but also specify a ValidArgsFunction.
+	}
+
 	// Parse the flags and extract the arguments to prepare for calling the completion function
 	if err = finalCmd.ParseFlags(finalArgs); err != nil {
 		return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
@@ -179,6 +238,32 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 	return finalCmd, completions, directive, nil
 }
 
+func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
+	if nonCompletableFlag(flag) {
+		return []string{}
+	}
+
+	var completions []string
+	flagName := "--" + flag.Name
+	if strings.HasPrefix(flagName, toComplete) {
+		// Flag without the =
+		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
+
+		if len(flag.NoOptDefVal) == 0 {
+			// Flag requires a value, so it can be suffixed with =
+			flagName += "="
+			completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
+		}
+	}
+
+	flagName = "-" + flag.Shorthand
+	if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
+		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
+	}
+
+	return completions
+}
+
 func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
 	var flagName string
 	trimmedArgs := args
diff --git a/fish_completions.go b/fish_completions.go
new file mode 100644
index 0000000..c83609c
--- /dev/null
+++ b/fish_completions.go
@@ -0,0 +1,172 @@
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+)
+
+func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) {
+	compCmd := ShellCompRequestCmd
+	if !includeDesc {
+		compCmd = ShellCompNoDescRequestCmd
+	}
+	buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name))
+	buf.WriteString(fmt.Sprintf(`
+function __%[1]s_debug
+    set file "$BASH_COMP_DEBUG_FILE"
+    if test -n "$file"
+        echo "$argv" >> $file
+    end
+end
+
+function __%[1]s_perform_completion
+    __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv"
+
+    set args (string split -- " " "$argv")
+    set lastArg "$args[-1]"
+
+    __%[1]s_debug "args: $args"
+    __%[1]s_debug "last arg: $lastArg"
+
+    set emptyArg ""
+    if test -z "$lastArg"
+        __%[1]s_debug "Setting emptyArg"
+        set emptyArg \"\"
+    end
+    __%[1]s_debug "emptyArg: $emptyArg"
+
+    set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg"
+    __%[1]s_debug "Calling $requestComp"
+
+    set results (eval $requestComp 2> /dev/null)
+    set comps $results[1..-2]
+    set directiveLine $results[-1]
+
+    # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
+    # completions must be prefixed with the flag
+    set flagPrefix (string match -r -- '-.*=' "$lastArg")
+
+    __%[1]s_debug "Comps: $comps"
+    __%[1]s_debug "DirectiveLine: $directiveLine"
+    __%[1]s_debug "flagPrefix: $flagPrefix"
+
+    for comp in $comps
+        printf "%%s%%s\n" "$flagPrefix" "$comp"
+    end
+
+    printf "%%s\n" "$directiveLine"
+end
+
+# This function does three things:
+# 1- Obtain the completions and store them in the global __%[1]s_comp_results
+# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed
+#    and unset it otherwise
+# 3- Return true if the completion results are not empty
+function __%[1]s_prepare_completions
+    # Start fresh
+    set --erase __%[1]s_comp_do_file_comp
+    set --erase __%[1]s_comp_results
+
+    # Check if the command-line is already provided.  This is useful for testing.
+    if not set --query __%[1]s_comp_commandLine
+        set __%[1]s_comp_commandLine (commandline)
+    end
+    __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
+
+    set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine")
+    set --erase __%[1]s_comp_commandLine
+    __%[1]s_debug "Completion results: $results"
+
+    if test -z "$results"
+        __%[1]s_debug "No completion, probably due to a failure"
+        # Might as well do file completion, in case it helps
+        set --global __%[1]s_comp_do_file_comp 1
+        return 0
+    end
+
+    set directive (string sub --start 2 $results[-1])
+    set --global __%[1]s_comp_results $results[1..-2]
+
+    __%[1]s_debug "Completions are: $__%[1]s_comp_results"
+    __%[1]s_debug "Directive is: $directive"
+
+    if test -z "$directive"
+        set directive 0
+    end
+
+    set compErr (math (math --scale 0 $directive / %[3]d) %% 2)
+    if test $compErr -eq 1
+        __%[1]s_debug "Received error directive: aborting."
+        # Might as well do file completion, in case it helps
+        set --global __%[1]s_comp_do_file_comp 1
+        return 0
+    end
+
+    set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
+    set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
+
+    __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
+
+    # Important not to quote the variable for count to work
+    set numComps (count $__%[1]s_comp_results)
+    __%[1]s_debug "numComps: $numComps"
+
+    if test $numComps -eq 1; and test $nospace -ne 0
+        # To support the "nospace" directive we trick the shell
+        # by outputting an extra, longer completion.
+        __%[1]s_debug "Adding second completion to perform nospace directive"
+        set --append __%[1]s_comp_results $__%[1]s_comp_results[1].
+    end
+
+    if test $numComps -eq 0; and test $nofiles -eq 0
+        __%[1]s_debug "Requesting file completion"
+        set --global __%[1]s_comp_do_file_comp 1
+    end
+
+    # If we don't want file completion, we must return true even if there
+    # are no completions found.  This is because fish will perform the last
+    # completion command, even if its condition is false, if no other
+    # completion command was triggered
+    return (not set --query __%[1]s_comp_do_file_comp)
+end
+
+# Remove any pre-existing completions for the program since we will be handling all of them
+# TODO this cleanup is not sufficient.  Fish completions are only loaded once the user triggers
+# them, so the below deletion will not work as it is run too early.  What else can we do?
+complete -c %[1]s -e
+
+# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
+# is called first.  It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
+#
+# This completion will be run second as complete commands are added FILO.
+# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
+complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp'
+
+# This completion will be run first as complete commands are added FILO.
+# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp.
+# It provides the program's completion choices.
+complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
+
+`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
+}
+
+// GenFishCompletion generates fish completion file and writes to the passed writer.
+func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error {
+	buf := new(bytes.Buffer)
+	genFishComp(buf, c.Name(), includeDesc)
+	_, err := buf.WriteTo(w)
+	return err
+}
+
+// GenFishCompletionFile generates fish completion file.
+func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error {
+	outFile, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer outFile.Close()
+
+	return c.GenFishCompletion(outFile, includeDesc)
+}
diff --git a/fish_completions.md b/fish_completions.md
new file mode 100644
index 0000000..6bfe5f8
--- /dev/null
+++ b/fish_completions.md
@@ -0,0 +1,7 @@
+## Generating Fish Completions for your own cobra.Command
+
+Cobra supports native Fish completions generated from the root `cobra.Command`.  You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information.  You can choose to make this option configurable by your users.
+
+### Limitations
+
+* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not.


Test patch:
diff --git a/custom_completions_test.go b/custom_completions_test.go
index ea6a6d9..8615483 100644
--- a/custom_completions_test.go
+++ b/custom_completions_test.go
@@ -12,7 +12,7 @@ func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, Sh
 	}
 
 	var completions []string
-	for _, comp := range []string{"one", "two"} {
+	for _, comp := range []string{"one\tThe first", "two\tThe second"} {
 		if strings.HasPrefix(comp, toComplete) {
 			completions = append(completions, comp)
 		}
@@ -26,7 +26,7 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S
 	}
 
 	var completions []string
-	for _, comp := range []string{"three", "four"} {
+	for _, comp := range []string{"three\tThe third", "four\tThe fourth"} {
 		if strings.HasPrefix(comp, toComplete) {
 			completions = append(completions, comp)
 		}
@@ -42,7 +42,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) {
 	}
 
 	// Test completing an empty string
-	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "")
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -58,7 +58,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) {
 	}
 
 	// Check completing with a prefix
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -86,7 +86,7 @@ func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) {
 	}
 
 	// Check completing with wrong number of args
-	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "unexpectedArg", "t")
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -115,7 +115,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 	rootCmd.AddCommand(child1Cmd, child2Cmd)
 
 	// Test completion of first sub-command with empty argument
-	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -131,7 +131,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 	}
 
 	// Test completion of first sub-command with a prefix to complete
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -146,7 +146,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 	}
 
 	// Check completing with wrong number of args
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -160,7 +160,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 	}
 
 	// Test completion of second sub-command with empty argument
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -175,7 +175,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 		t.Errorf("expected: %q, got: %q", expected, output)
 	}
 
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -190,7 +190,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) {
 	}
 
 	// Check completing with wrong number of args
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -215,7 +215,7 @@ func TestValidArgsFuncAliases(t *testing.T) {
 	rootCmd.AddCommand(child)
 
 	// Test completion of first sub-command with empty argument
-	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "son", "")
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -231,7 +231,7 @@ func TestValidArgsFuncAliases(t *testing.T) {
 	}
 
 	// Test completion of first sub-command with a prefix to complete
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "daughter", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -246,7 +246,7 @@ func TestValidArgsFuncAliases(t *testing.T) {
 	}
 
 	// Check completing with wrong number of args
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "son", "unexpectedArg", "t")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -260,7 +260,7 @@ func TestValidArgsFuncAliases(t *testing.T) {
 	}
 }
 
-func TestValidArgsFuncInScript(t *testing.T) {
+func TestValidArgsFuncInBashScript(t *testing.T) {
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
 	child := &Command{
 		Use:               "child",
@@ -276,7 +276,7 @@ func TestValidArgsFuncInScript(t *testing.T) {
 	check(t, output, "has_completion_function=1")
 }
 
-func TestNoValidArgsFuncInScript(t *testing.T) {
+func TestNoValidArgsFuncInBashScript(t *testing.T) {
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
 	child := &Command{
 		Use: "child",
@@ -291,6 +291,55 @@ func TestNoValidArgsFuncInScript(t *testing.T) {
 	checkOmit(t, output, "has_completion_function=1")
 }
 
+func TestCompleteCmdInBashScript(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use:               "child",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	buf := new(bytes.Buffer)
+	rootCmd.GenBashCompletion(buf)
+	output := buf.String()
+
+	check(t, output, ShellCompNoDescRequestCmd)
+}
+
+func TestCompleteNoDesCmdInFishScript(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use:               "child",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	buf := new(bytes.Buffer)
+	rootCmd.GenFishCompletion(buf, false)
+	output := buf.String()
+
+	check(t, output, ShellCompNoDescRequestCmd)
+}
+
+func TestCompleteCmdInFishScript(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use:               "child",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	buf := new(bytes.Buffer)
+	rootCmd.GenFishCompletion(buf, true)
+	output := buf.String()
+
+	check(t, output, ShellCompRequestCmd)
+	checkOmit(t, output, ShellCompNoDescRequestCmd)
+}
+
 func TestFlagCompletionInGo(t *testing.T) {
 	rootCmd := &Command{
 		Use: "root",
@@ -299,7 +348,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
 	rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
 		completions := []string{}
-		for _, comp := range []string{"1", "2", "10"} {
+		for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
 			if strings.HasPrefix(comp, toComplete) {
 				completions = append(completions, comp)
 			}
@@ -309,7 +358,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	rootCmd.Flags().String("filename", "", "Enter a filename")
 	rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
 		completions := []string{}
-		for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} {
+		for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
 			if strings.HasPrefix(comp, toComplete) {
 				completions = append(completions, comp)
 			}
@@ -318,7 +367,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	})
 
 	// Test completing an empty string
-	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "")
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -335,7 +384,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	}
 
 	// Check completing with a prefix
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -351,7 +400,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	}
 
 	// Test completing an empty string
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -368,7 +417,7 @@ func TestFlagCompletionInGo(t *testing.T) {
 	}
 
 	// Check completing with a prefix
-	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f")
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f")
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -383,3 +432,200 @@ func TestFlagCompletionInGo(t *testing.T) {
 		t.Errorf("expected: %q, got: %q", expected, output)
 	}
 }
+
+func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child1Cmd := &Command{
+		Use:               "child1",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	child2Cmd := &Command{
+		Use:               "child2",
+		ValidArgsFunction: validArgsFunc2,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child1Cmd, child2Cmd)
+
+	// Test completion of first sub-command with empty argument
+	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one\tThe first",
+		"two\tThe second",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completion of first sub-command with a prefix to complete
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two\tThe second",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with wrong number of args
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completion of second sub-command with empty argument
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"three\tThe third",
+		"four\tThe fourth",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"three\tThe third",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with wrong number of args
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagCompletionInGoWithDesc(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
+	rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		completions := []string{}
+		for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
+			if strings.HasPrefix(comp, toComplete) {
+				completions = append(completions, comp)
+			}
+		}
+		return completions, ShellCompDirectiveDefault
+	})
+	rootCmd.Flags().String("filename", "", "Enter a filename")
+	rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		completions := []string{}
+		for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
+			if strings.HasPrefix(comp, toComplete) {
+				completions = append(completions, comp)
+			}
+		}
+		return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
+	})
+
+	// Test completing an empty string
+	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"1\tThe first",
+		"2\tThe second",
+		"10\tThe tenth",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"1\tThe first",
+		"10\tThe tenth",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completing an empty string
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"file.yaml\tYAML format",
+		"myfile.json\tJSON format",
+		"file.xml\tXML format",
+		":6",
+		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"file.yaml\tYAML format",
+		"file.xml\tXML format",
+		":6",
+		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}