Repository: spf13/cobra
Pull request #1070 (2020-03-30 02:35:23)

Zsh completion V2
This is a new implementation of Zsh completion but based on the Custom Go Completions of PRs #1035 and #1048.

The current Zsh completion has a major limitation in the fact that it does not support custom completions.  Furthermore, if it ever did, it would require the program using Cobra to implement a second, zsh-flavoured version of its bash completion code.

This v2 version allows to automatically re-use custom completions once they have been migrated to the Custom Go Completion solution of PR #1035.

*Advantages:*

* support custom completions (in Go only) (as mentioned above)
* proper support for  using `=` for flags
* fixed size completion script of around 100 lines, so very fast on shell startup
* allows disabling of descriptions (to feel like bash)
* when there are no other completions, provides file completion automatically. This can be turned off on a per command basis


mike-stewart: Is this going to be merged? I'm very interested in being able to apply the changes from PR 1035 for zsh completion.
mike-stewart: @marckhouzam I tried your last commit rebased onto master, with one modification to allow it to build (53b3354af71147874264fbd163c0fdc56f7c09a7). The custom completions work great, but all other completions are broken - not even completing the commands themselves. Is that expected or a bug?

Let me know if I can contribute in any way - I'm new to Go but could take a stab at it if you can point me in the right direction.
marckhouzam: Thanks @mike-stewart for trying it out.  
This PR is dependant on #1048. That's why completions don't work for you.

I'm about to post an updated version that will work.
marckhouzam: Ok, so this new version should work.
Here is a small test program I use:
```
package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	ValidArgs: []string{"replica\tanother", "rs\tdescription for 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\tverb", "custom\tadjective", "completions\tnoun, plural"}, 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|zsh1|zsh2|fish] [--no-descriptions]",
	Short:                 "Generate completion script",
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "zsh1\tOriginal zsh completion", "zsh2\tV2 zsh completion", "fish"},
	Args:                  cobra.ExactValidArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			cmd.Root().GenBashCompletion(os.Stdout)
			break
		case "zsh1":
			cmd.Root().GenZshCompletion(os.Stdout)
			break
		case "zsh2":
			cmd.Root().GenZshCompletionV2(os.Stdout, !completionNoDesc)
			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()
}
```
mike-stewart: @marckhouzam This works great on my (fairly simple) app w/ custom completions.

> when there are no other completions, provides file completion automatically. This can be turned off on a per command basis

What is the correct way to turn off this file completion?
marckhouzam: I'll direct you to the documentation so you can let us know if it's understandable or not.

Look for the description for `BashCompDirective` in https://github.com/spf13/cobra/blob/master/bash_completions.md#1-custom-completions-of-nouns-written-in-go
mike-stewart: @marckhouzam Yes, that works. I had implemented that already for some commands, but thought your comment suggested a way to disable file completion for commands where custom completion is *not* being used.

I worked around that by adding this to commands that I don't want any completion on:
```
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
	return nil, cobra.BashCompDirectiveNoFileComp
},
```

Thanks again for getting this branch working.
marckhouzam: > I worked around that by adding this to commands that I don't want any completion on:
> 
> ```
> ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
> 	return nil, cobra.BashCompDirectiveNoFileComp
> },
> ```

That's exactly how you should do it.  Should we add an example in the docs for this case, which I expect will be common or will users figure it out easily enough? As the first user, what do you think?
mike-stewart: @marckhouzam The workaround was obvious upon reading the code and documentation, but it right not have been immediately apparent to a casual reader unless they specifically set out to disable file completion. Adding it to the documentation would make it clearer that it's a "feature" that is available to users.
marckhouzam: I will rebase this later today to adapt to the `BashCompDirective` to `ShellCompDirective` change of #1082 .
marckhouzam: >  Adding it to the documentation would make it clearer that it's a "feature" that is available to users.

@mike-stewart would you like to make a small PR to add this explanation to the documentation?

marckhouzam: I rebased this PR after the `BashCompDirective` to `ShellCompDirective` change.
itskingori: > **This PR is not totally finished, but before spending more time on it, I wanted to know if there is some interest for such a change?**

@marckhouzam There's definitely interest in this change! 😅 I've been following your other PRs and was glad when they got merged and [v1.0.0 released](https://github.com/spf13/cobra/releases/tag/v1.0.0). The only thing missing is this PR.

For context I've used custom Go completions to implement completion of flag values using `RegisterFlagCompletionFunc` but if I understand correctly, that would work for Bash and Fish only at the moment. 
marckhouzam: I will soon get back to this PR. But first I want migrate the Helm project to using Cobra 1.0.0 🎉
marckhouzam: I looked a bit into the existing zsh completion support to see if we really need a V2 version or if we could completely fulfill the current zsh completion API with this new solution.  IIUC the current zsh solution has some APIs of its own that are not relevant to bash completion.  This seems confirmed by this comment from the original author of the current zsh completion solution:
https://github.com/spf13/cobra/pull/646#issuecomment-500731127

As that comment mentions, there really should not be different APIs for different shells but we should strive for a single API working for all shells.

I think we can get very close to that goal with the current PR.  But that confirms we do need to introduce a V2 version for zsh completion as this PR should not support the zsh-only API of the other zsh solution.

Now that I'm more comfortable about having to introduce a V2, I'll be able to get a polished version ready soon.

marckhouzam: I've rebased on master and updated the documentation. To my knowledge, all types of completions are supported except the ones enabled by `BashCompFilenameExt` (filtering by file extension), `BashCompOneRequiredFlag` (required flags) and `BashCompSubdirsInDir` (filtering by directory).

I'm not removing the WIP label because I'm still thinking about how to avoid having a V2 version.

The only two APIs that are zsh-specific are `MarkZshCompPositionalArgumentWords`
and `MarkZshCompPositionalArgumentFile` .  I believe the first one corresponds to `ValidArgs` so would be easy to handle.  IIUC, the second one does not have a corresponding one in Bash-completion, but maybe we should add one to standardize, and then be backwards-compatible for zsh.

I'll keep thinking about it.

marckhouzam: I've continued investigating the existing zsh-completion support.
As explained by the original author (https://github.com/spf13/cobra/pull/646#issuecomment-500821302), the current zsh support was designed independently of bash completion and is not well aligned to it.

There are multiple differences but one major difference is that the current zsh completion does not do file completion by default while bash does (see #886).  Say I have a program `testprog` that has no sub-commands and no `ValidArgs` then `./testprog [tab][tab]` will list all files for bash, but will not for zsh.  

To get file completion (first the first argument only) for zsh, the program developer would need to add the line
```
command.MarkZshCompPositionalArgumentFile(1)
```
On the other hand, to turn off file completion for bash, the developer would need to use the new `ShellCompDirectiveNoFileComp` with `ValidArgsFunction`.

This means that a developer that wants to support both bash and zsh must write code to handle zsh completion (current version) and different code for the bash completion.

I think we should strive to align completion behavior across all shells or else it becomes a nightmare for the program developers.

This leads me to backwards-compatibility.  I strongly feel that the new zsh completion of this PR should provide file completion by default like bash, as justified above.  This would technically be a breaking-change compared to the current zsh completion script.

So I think we are at the point of making a decision: do we do a V2 version for zsh like this PR is doing right now, or do we modify the current behavior of zsh completion to align with bash?

 @jharshman I'm looking for guidance from the maintainers, as this PR is close to finished and mostly need to know which direction to take with respect to V2 or not.  Thanks in advance.

jharshman: @marckhouzam Having all the shell completion work the same is definitely appealing.
@wfernandes @jpmcb what do you guys think?
jpmcb: > I think we should strive to align completion behavior across all shells or else it becomes a nightmare for the program developers.

I agree. Otherwise, I think we end up with "drift" between the different shells which creates pain for the cobra application developers. They end up having to maintain any number of different completions that all have different APIs for similar behavior. 

I'm in favor of introducing breaking changes for zsh completions that strives for closer parity among the various shell completions. And maybe we begin to deprecate the old version or announce that this is the new way to go about completions. But I'm not sure how this project in the past has approached deprecating features so I'll leave that up to @jharshman 
jharshman: @marckhouzam alright let's align the behavior even it it introduces some breaking changes.
We will handle the announcement and release.
marckhouzam: Alright, I like that too.  Thanks.
What I'll do is document whatever "breaking" change this PR introduces.
marckhouzam: I'm now working on supporting Required flags (the `BashCompOneRequiredFlag` annotation) in Go completions for this PR.  Whenever time allows, I need a review on the small fix of #1095 to get that working.  Thanks in advance. 
marckhouzam: Still a WIP but I've made good progress.  It is not fully tested yet.

This latest version **replaces** the current Zsh completion with a Zsh completion solution based on Go completions (without introducing a V2 API). 

Also, Go completions now support the bash completion annotations:
* BashCompFilenameExt (including Command.MarkFlagFilename() family)
    * this if for file filtering
    * supported by zsh as before
* BashCompSubdirsInDir
    * this is for filtering directories
    * newly supported by zsh
 * BashCompOneRequiredFlag (including Command.MarkFlagRequired() family)
    * this is for required flags
    * newly supported by zsh and fish

Still pending:
* update the documentation
* deal with the zsh-specific APIs in an sufficiently safe manner
marckhouzam: This is finally ready for review.

The changes to zsh completion behaviour were not as dramatic as I expected and mostly surround file completion being done by default now.

The API was kept backwards-compatible, with 2 functions being deprecated.

I've also standardized the documentation (in the last commit) to talk about "shell completion" instead of "bash completion".

Below is the description of the `zsh` changes, as taken from the proposed documentation:
https://github.com/VilledeMontreal/cobra/blob/feat/zshCompletionV2/zsh_completions.md#zsh-completions-standardization

### Deprecation summary

See further below for more details on these deprecations.

* `cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` is no longer needed.  It is therefore **deprecated** and silently ignored.
* `cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` is **deprecated** and silently ignored.
  * Instead use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt`.
* `cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored.
  * Instead use `ValidArgsFunction`.

### Behavioral changes

**Noun completion**
|Old behavior|New behavior|
|---|---|
|No file completion by default (opposite of bash)|File completion by default; use `ValidArgsFunction` with `ShellCompDirectiveNoFileComp` to turn off file completion on a per-argument basis|
`cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` used to turn on file completion on a per-argument position basis|File completion for all arguments by default; `cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored|
|`cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` used to turn on file completion **with glob filtering** on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored; use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt` for file **extension** filtering (not full glob filtering)|
|`cmd.MarkZshCompPositionalArgumentWords(pos, words[])` used to provide completion choices on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored; use `ValidArgsFunction` to achieve the same behavior|

**Flag-value completion**

|Old behavior|New behavior|
|---|---|
|No file completion by default (opposite of bash)|File completion by default; use `RegisterFlagCompletionFunc()` with `ShellCompDirectiveNoFileComp` to turn off file completion|
|`cmd.MarkFlagFilename(flag, []string{})` and similar used to turn on file completion|File completion by default; `cmd.MarkFlagFilename(flag, []string{})` no longer needed in this context and silently ignored|
|`cmd.MarkFlagFilename(flag, glob[])`  used to turn on file completion **with glob filtering** (syntax of `[]string{"*.yaml", "*.yml"}` incompatible with bash)|Will continue to work, however, support for bash syntax is added and should be used instead so as to work for all shells (`[]string{"yaml", "yml"}`)|
|`cmd.MarkFlagDirname(flag)` only completes directories (zsh-specific)|Has been added for all shells|
|Completion of a flag name does not repeat, unless flag is of type `*Array` or `*Slice` (not supported by bash)|Retained for `zsh` and added to `fish`|
|Completion of a flag name does not provide the `=` form (unlike bash)|Retained for `zsh` and added to `fish`|

**Improvements**

* Custom completion support (`ValidArgsFunction` and `RegisterFlagCompletionFunc()`)
* File completion by default if no other completions found
* Handling of required flags
* File extension filtering no longer mutually exclusive with bash usage
* Completion of directory names *within* another directory
* Support for `=` form of flags

marckhouzam: Here is a bit of a test program that I've been modifying back and forth during development.
```
package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/spf13/cobra"
)

var completionNoDesc bool

var completionCmd = &cobra.Command{
	Use:   "completion [bash|zsh|fish|powershell]",
	Short: "Generate completion script",
	Long: `To load completions:

Bash:

$ source <(yourprogram completion bash)

# To load completions for each session, execute once:
Linux:
  $ yourprogram completion bash > /etc/bash_completion.d/yourprogram
MacOS:
  $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram

Zsh:

$ source <(yourprogram completion zsh)

# To load completions for each session, execute once:
$ yourprogram completion zsh > "${fpath[1]}/_yourprogram"

Fish:

$ yourprogram completion fish | source

# To load completions for each session, execute once:
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
`,
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
	Args:                  cobra.ExactValidArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			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":
			cmd.Root().GenPowerShellCompletion(os.Stdout)
		}
	},
}

var rootCmd = &cobra.Command{
	Use: "testprog",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("rootCmd called")
	},
}

var vargsCmd = &cobra.Command{
	Use:       "vargs",
	Short:     "Cmd with vargs and subcmd",
	ValidArgs: []string{"one", "two", "three"},
	Args:      cobra.MinimumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("vargsCmd called")
	},
}

var childVargsCmd = &cobra.Command{
	Use:   "childVargs",
	Short: "Child command",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("childVargs called")
	},
}

// File filtering for the first argument
// Replaces MarkZshCompPositionalArgumentFile(1, string{"*.log", "*.txt"})
var filterFileCmd = &cobra.Command{
	Use:   "filterFile",
	Short: "filterFile [*.log,*.txt]",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return []string{"log", "txt"}, cobra.ShellCompDirectiveFilterFileExt
		}
		// No more expected arguments, so turn off file completion
		return nil, cobra.ShellCompDirectiveNoFileComp
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("filterFile called")
	},
}

// Turn off file completion for all arguments
var noFileCmd = &cobra.Command{
	Use:   "nofile",
	Short: "nofile",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return nil, cobra.ShellCompDirectiveNoFileComp
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("nofile called")
	},
}

// Full file completion for arguments
var fullFileCmd = &cobra.Command{
	Use:   "fullFile",
	Short: "fullFile [filename]",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("fullFile called")
	},
}

// Only allow directory name completion and only for the first argument
var dirOnlyCmd = &cobra.Command{
	Use:   "dirOnly",
	Short: "dirOnly [dirname]",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return nil, cobra.ShellCompDirectiveFilterDirs
		}
		// No more expected arguments, so turn off file completion
		return nil, cobra.ShellCompDirectiveNoFileComp
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("dirOnly called")
	},
}

// Only allow directory name from within a specified directory and only for the first argument
var subdirCmd = &cobra.Command{
	Use:   "subdir",
	Short: "subdir [sub-dirname]",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) == 0 {
			return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs | cobra.ShellCompDirectiveNoFileComp
		}
		// No more expected arguments, so turn off file completion
		return nil, cobra.ShellCompDirectiveNoFileComp
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("subdir called")
	},
}

// Test file completion when returning other completions
var compAndFileCmd = &cobra.Command{
	Use:   "compAndFile",
	Short: "compAndFile [sun|moon|<filename>]",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		var completions []string
		// If we don't filter on "toComplete", zsh and fish will not do file completion
		// even if the prefix typed by the user does not match the returned completions
		// e.g., ./testprog compAndFile f<TAB>
		// will only get file completion for bash, not zsh and fish, if we blindly return
		// the non-matching completions ("sun", "moon")
		for _, comp := range []string{"sun", "moon"} {
			if strings.HasPrefix(comp, toComplete) {
				completions = append(completions, comp)
			}
		}
		return completions, cobra.ShellCompDirectiveDefault
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("compAndFile called")
	},
}

func setFlags() {
	rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
	rootCmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})

	rootCmd.Flags().String("theme2", "", "theme to use (located in /themes/THEMENAME/)")
	rootCmd.RegisterFlagCompletionFunc("theme2", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs
	})

	rootCmd.Flags().String("customComp", "", "test custom comp for flags")
	rootCmd.RegisterFlagCompletionFunc("customComp", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		return []string{"firstComp\tthe first value", "secondComp\tthe second value", "forthComp\tthe forth value"}, cobra.ShellCompDirectiveNoFileComp
	})

	rootCmd.Flags().StringP("file", "f", "", "list files")

	rootCmd.Flags().BoolP("bool", "b", false, "bool flag")
	rootCmd.Flags().BoolSliceP("bslice", "B", nil, "bool slice flag")

	// rootCmd.Flags().String("required", "", "required flag")
	// rootCmd.MarkFlagRequired("required")
}

func main() {
	completionCmd.Flags().BoolVar(
		&completionNoDesc,
		"no-descriptions", false,
		"disable completion description for shells that support it")
	rootCmd.AddCommand(completionCmd)

	rootCmd.AddCommand(filterFileCmd)
	rootCmd.AddCommand(noFileCmd)
	rootCmd.AddCommand(fullFileCmd)
	rootCmd.AddCommand(dirOnlyCmd)
	rootCmd.AddCommand(subdirCmd)
	rootCmd.AddCommand(compAndFileCmd)

	rootCmd.AddCommand(vargsCmd)
	vargsCmd.AddCommand(childVargsCmd)

	setFlags()
	rootCmd.Execute()

	// childCmd1.AddCommand(childCmd2)

	// childCmd1.Flags().String(
	// 	"test-file2", "",
	// 	"just to test2")
	// childCmd1.RegisterFlagCompletionFunc("test-file2", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	// 	return []string{"test1", "test2"}, cobra.ShellCompDirectiveDefault
	// })
	// childCmd1.Flags().String(
	// 	"test-file", "",
	// 	"just to test")
	// childCmd1.RegisterFlagCompletionFunc("test-file", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	// 	return []string{"test1\tthis is test1", "test2\tthis is test2"}, cobra.ShellCompDirectiveDefault
	// })

	// rootCmd.Flags().String("filter", "", "only show log and txt files")
	// rootCmd.MarkFlagFilename("filter", "log", "txt")

	// rootCmd.Flags().StringArray("array", []string{"allo", "toi"}, "string array flag")

	// childCmd1.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent int flag")
	// childCmd1.MarkPersistentFlagRequired("requiredPersistent")
	// childCmd2.Flags().Bool("required", false, "required bool flag")
	// childCmd2.MarkFlagRequired("required")
	// childCmd2.Flags().String("notRequired", "", "not required string flag")
}
```
itskingori: @marckhouzam A couple of questions, based on this work:

1. Say I have a program `exampletool` that has a command `examplecommand` **with sub-commands** i.e. `command1` & `command2`. If you happen to define `ValidArgs` set to `command1` & `command2` (this isn't entirely necessary and probably isn't a sensible thing to do), you get a duplicate row at the bottom. Is that expected or should it de-duplicate? And should we have the mixed vertical and horizontal listing?
   ```console
   $ exampletool examplecommand
   command1      -- Example description something1
   command2      -- Example description something2
   command1  command2
   ```
2. Say I have a program `exampletool` that has a command `examplecommand` **with no sub-commands** but it accepts args i.e. `arg1` & `arg2`. If you happen to define `ValidArgs` set to `arg1` & `arg2`, you get the single row at the bottom listed horizontally. Is that expected behaviour or should all Zsh auto-completion output be listed vertically as above?
   ```console
   $ exampletool examplecommand
   arg1  arg2
   ```
marckhouzam: Thanks @itskingori for trying it out.

About #1, this is zsh's behavior when there are descriptions (vertical) and when there are none (horizontal). If you were to add descriptions to your ValidArgs, they would show vertically. E.g., `ValidArgs: []string{"command1\tdescription one", "command2\tanother description"}`.

It may seem surprising that `command1` and `command2` are duplicated, but since they have different descriptions, zsh shows both.
To confirm, my guess is that if you turn off descriptions by using `GenZshCompletionNoDesc()` there would be no duplicates anymore.

#2 is caused by the lack of descriptions in your ValidArgs.
itskingori: @marckhouzam ...

> Thanks @itskingori for trying it out.
> 
> About point 1, this is zsh's behavior when there are descriptions (vertical) and when there are none (horizontal). ...

Ah, noted. New to Zsh.

> ...If you were to add descriptions to your ValidArgs, they would show vertically. E.g., `ValidArgs: []string{"command1\tdescription one", "command2\tanother description"}`.

This is good to know. I didn't know how to add descriptions to `ValidArgs`.

> It may seem surprising that `command1` and `command2` are duplicated, but since they have different descriptions, zsh shows both.
> To confirm, my guess is that if you turn off descriptions by using `GenZshCompletionNoDesc()` there would be no duplicates anymore.
> 
> For point 2, similarly, it is caused by the lack of descriptions in your ValidArgs.

Understood. It's all good now ... gave this a spin late last night and it's working well. 👌 


marckhouzam: I found a problem with the current solution: it does not support completion from the middle of the command-line.  For example:
```
./testprog [tab][tab] zsh
```
will not show any completions, while the old zsh completion would show all possible sub-commands of `testprog`.

I'm hoping it is easy to fix.  Something along the lines of not passing the entire command-line to the `__complete` command, but only the part before the cursor.  I will work on it.
marckhouzam: > ```
> ./testprog [tab][tab] zsh
> ```

I've fixed this.
It was also not working in fish so I fixed it too.

I believe everything is ready.
marckhouzam: I just forced pushed a tiny change because while working on something else I noticed two small bugs for bash Go completion for the new file filtering and sub-directory filtering.

This PR is again ready for review @jharshman, @jpmcb.
The changes may seem pretty big, but almost 2/3 of the changes are tests and documentation.
I'm available to clarify any question you may have.
itskingori: @jharshman @marckhouzam Just wanted to mention that I've been using this branch for a while and it's been working quite well. And that was before the fix for the tiny issue mentioned in https://github.com/spf13/cobra/pull/1070#issuecomment-631848238.
marckhouzam: I did another forced push as I realized I had forgot to include the functionality of #288 in Go completions.

This particular change checks for the presence of a local, non-persistent flag on the command-line, and when present, no longer completes sub-command names, since a sub-command doesn't inherit such a flag and would fail.

I then went through the entire `bash-completions.go` file to make sure everything it supported was also supported in Go completions.  I found one more thing missing, which is the proper handling of `ArgAliases`.  I'll be fixing this soon.  After that this PR will be done done 😉 .


marckhouzam: Last forced-push added support for `ArgAliases` as done for bash completion.  This means that if there are no other valid completions, `ArgAliases` are completed.

@jharshman Sorry for the repeated fixed. This is done done.
marckhouzam: Murphy's law.  Just after I wrote I was done done, I noticed that a completion without an available description would be shown with a trailing `--` but no description.  I fixed it so that the `--` would not be shown.

That's it. I'm putting down my laptop.
marckhouzam: I was thinking about how to make this PR easier to review. It is pretty massive as it stands and I admit I wouldn't like to be on the receiving end.

Would splitting it into smaller increments, each in its own PR be something that would be desirable?

We could gradually build up the go completion logic with targeted PRs that are easier to test and review. They'd have to be tested with Fish completion though. Then at the very end, we would replace the current zsh logic with the new one once everything is in.
Would that make things easier @jharshman, @jpmcb?
jharshman: > Would splitting it into smaller increments, each in its own PR be something that would be desirable?

@marckhouzam Thank you for the consideration. This is a large but valuable PR but we will work through it. No need to split it up into smaller ones.
austintraver: This is a feature I passionately desire to have. @marckhouzam @jharshman is there any work I that still needs to be done before this P.R. is merged into master?
jharshman: @marckhouzam could you rebase against master to resolve the conflicts? 
marckhouzam: > @marckhouzam could you rebase against master to resolve the conflicts?

@jharshman Woops.  The conflict was caused by me with the completion of the help command PR (#1136).
I forgot I would need to rebase after it.

Done. 
jpmcb: I will take a look at this sometime this week - so sorry about falling off the face of the earth 🌎 

Review incoming soon!
jharshman: Code looks pretty good to me. Haven't tested it out yet but once I do and things look good this should be ready to merge.
itskingori: > Code looks pretty good to me. Haven't tested it out yet but once I do and things look good this should be ready to merge.

Not sure if this counts but we resorted to using this branch because we really wanted these changes and we haven't had any issues whatsoever.
jharshman: @marckhouzam can you squash your commits? 
marckhouzam: > can you squash your commits?

@jharshman I can if you prefer, but I actually worked at keeping each commit self contained and logical, with tests passing, with the goal of providing more information about these changes for future lookup.

But I leave it up to you. In light of this explanation, just let me know if you still prefer to have just one commit and I'll squash.
jharshman: @marckhouzam Keeping it trimmed down to one commit makes it easier to roll back changes if needed later on.
marckhouzam: @jharshman The squash is done.

While you guys are knee-deep into completion code, there is a small PR for Fish completion, #1122, that fixes a bug that also needs approval 😄 .  But I will surely need to rebase it if this PR goes in first, but I would do that ASAP.

Thanks for the continued reviews!

Patch:
diff --git a/README.md b/README.md
index a932dd7..1484240 100644
--- a/README.md
+++ b/README.md
@@ -28,8 +28,7 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis
   * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
   * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
   * [Generating documentation for your command](#generating-documentation-for-your-command)
-  * [Generating bash completions](#generating-bash-completions)
-  * [Generating zsh completions](#generating-zsh-completions)
+  * [Generating shell completions](#generating-shell-completions)
 - [Contributing](#contributing)
 - [License](#license)
 
@@ -50,7 +49,7 @@ Cobra provides:
 * Intelligent suggestions (`app srver`... did you mean `app server`?)
 * Automatic help generation for commands and flags
 * Automatic help flag recognition of `-h`, `--help`, etc.
-* Automatically generated bash autocomplete for your application
+* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
 * Automatically generated man pages for your application
 * Command aliases so you can change things without breaking them
 * The flexibility to define your own help, usage, etc.
@@ -720,14 +719,9 @@ Run 'kubectl help' for usage.
 
 Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
 
-## Generating bash completions
+## Generating shell completions
 
-Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible.  Read more about it in [Bash Completions](bash_completions.md).
-
-## Generating zsh completions
-
-Cobra can generate zsh-completion file. Read more about it in
-[Zsh Completions](zsh_completions.md).
+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).
 
 # Contributing
 
diff --git a/bash_completions.go b/bash_completions.go
index f562399..ab428cc 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -62,6 +62,12 @@ __%[1]s_handle_go_custom_completion()
 {
     __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
 
+    local shellCompDirectiveError=%[3]d
+    local shellCompDirectiveNoSpace=%[4]d
+    local shellCompDirectiveNoFileComp=%[5]d
+    local shellCompDirectiveFilterFileExt=%[6]d
+    local shellCompDirectiveFilterDirs=%[7]d
+
     local out requestComp lastParam lastChar comp directive args
 
     # Prepare the command to request completions for the program.
@@ -95,24 +101,50 @@ __%[1]s_handle_go_custom_completion()
     __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
     __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
 
-    if [ $((directive & %[3]d)) -ne 0 ]; then
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
         # Error code.  No completion.
         __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
         return
     else
-        if [ $((directive & %[4]d)) -ne 0 ]; then
+        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
             if [[ $(type -t compopt) = "builtin" ]]; then
                 __%[1]s_debug "${FUNCNAME[0]}: activating no space"
                 compopt -o nospace
             fi
         fi
-        if [ $((directive & %[5]d)) -ne 0 ]; then
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
             if [[ $(type -t compopt) = "builtin" ]]; then
                 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
                 compopt +o default
             fi
         fi
+    fi
 
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
+        # File extension filtering
+        local fullFilter filter filteringCmd
+        # Do not use quotes around the $out variable or else newline
+        # characters will be kept.
+        for filter in ${out[*]}; do
+            fullFilter+="$filter|"
+        done
+
+        filteringCmd="_filedir $fullFilter"
+        __%[1]s_debug "File filtering command: $filteringCmd"
+        $filteringCmd
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
+        # File completion for directories only
+        local subDir
+        # Use printf to strip any trailing newline
+        subdir=$(printf "%%s" "${out[0]}")
+        if [ -n "$subdir" ]; then
+            __%[1]s_debug "Listing directories in $subdir"
+            __%[1]s_handle_subdirs_in_dir_flag "$subdir"
+        else
+            __%[1]s_debug "Listing directories in ."
+            _filedir -d
+        fi
+    else
         while IFS='' read -r comp; do
             COMPREPLY+=("$comp")
         done < <(compgen -W "${out[*]}" -- "$cur")
@@ -343,7 +375,9 @@ __%[1]s_handle_word()
     __%[1]s_handle_word
 }
 
-`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
+`, name, ShellCompNoDescRequestCmd,
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
 }
 
 func writePostscript(buf *bytes.Buffer, name string) {
diff --git a/bash_completions.md b/bash_completions.md
index e61a3a6..a82d5bb 100644
--- a/bash_completions.md
+++ b/bash_completions.md
@@ -1,206 +1,14 @@
-# Generating Bash Completions For Your Own cobra.Command
+# Generating Bash Completions For Your cobra.Command
 
-If you are using the generator you can create a completion command by running
+Please refer to [Shell Completions](shell_completions.md) for details.
 
-```bash
-cobra add completion
-```
-
-Update the help text show how to install the bash_completion Linux show here [Kubectl docs show mac options](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion)
-
-Writing the shell script to stdout allows the most flexible use.
-
-```go
-// completionCmd represents the completion command
-var completionCmd = &cobra.Command{
-	Use:   "completion",
-	Short: "Generates bash completion scripts",
-	Long: `To load completion run
-
-. <(bitbucket completion)
-
-To configure your bash shell to load completions for each session add to your bashrc
-
-# ~/.bashrc or ~/.profile
-. <(bitbucket completion)
-`,
-	Run: func(cmd *cobra.Command, args []string) {
-		rootCmd.GenBashCompletion(os.Stdout);
-	},
-}
-```
-
-**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script
-
-
-## Example from kubectl
-
-Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows:
-
-```go
-package main
-
-import (
-	"io/ioutil"
-	"os"
-
-	"k8s.io/kubernetes/pkg/kubectl/cmd"
-	"k8s.io/kubernetes/pkg/kubectl/cmd/util"
-)
-
-func main() {
-	kubectl := cmd.NewKubectlCommand(util.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
-	kubectl.GenBashCompletionFile("out.sh")
-}
-```
-
-`out.sh` will get you completions of subcommands and flags. Copy it to `/etc/bash_completion.d/` as described [here](https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) and reset your terminal to use autocompletion. If you make additional annotations to your code, you can get even more intelligent and flexible behavior.
-
-## Have the completions code complete your 'nouns'
-
-### Static completion of nouns
-
-This method allows you to provide a pre-defined list of completion choices for your nouns using the `validArgs` field.
-For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like:
-
-```go
-validArgs []string = { "pod", "node", "service", "replicationcontroller" }
-
-cmd := &cobra.Command{
-	Use:     "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
-	Short:   "Display one or many resources",
-	Long:    get_long,
-	Example: get_example,
-	Run: func(cmd *cobra.Command, args []string) {
-		err := RunGet(f, out, cmd, args)
-		util.CheckErr(err)
-	},
-	ValidArgs: validArgs,
-}
-```
-
-Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like
-
-```bash
-# kubectl get [tab][tab]
-node                 pod                    replicationcontroller  service
-```
-
-### Plural form and shortcuts for nouns
-
-If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
-
-```go
-argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
-
-cmd := &cobra.Command{
-    ...
-	ValidArgs:  validArgs,
-	ArgAliases: argAliases
-}
-```
-
-The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
-the completion algorithm if entered manually, e.g. in:
-
-```bash
-# kubectl get rc [tab][tab]
-backend        frontend       database 
-```
-
-Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns
-in this example again instead of the replication controllers.
-
-### Dynamic completion of nouns
-
-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.
+## Bash legacy dynamic completions
 
-**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.
-Simplified code from `helm status` looks like:
-
-```go
-cmd := &cobra.Command{
-	Use:   "status RELEASE_NAME",
-	Short: "Display the status of the named release",
-	Long:  status_long,
-	RunE: func(cmd *cobra.Command, args []string) {
-		RunGet(args[0])
-	},
-	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		if len(args) != 0 {
-			return nil, cobra.ShellCompDirectiveNoFileComp
-		}
-		return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp
-	},
-}
-```
-Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster.
-Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like
-
-```bash
-# helm status [tab][tab]
-harbor notary rook thanos
-```
-You may have noticed the use of `cobra.ShellCompDirective`.  These directives are bit fields allowing to control some shell completion behaviors for your particular completion.  You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp`
-```go
-// Indicates an error occurred and completions should be ignored.
-ShellCompDirectiveError
-// Indicates that the shell should not add a space after the completion,
-// even if there is a single completion provided.
-ShellCompDirectiveNoSpace
-// Indicates that the shell should not provide file completion even when
-// no completion is provided.
-// This currently does not work for zsh or bash < 4
-ShellCompDirectiveNoFileComp
-// Indicates that the shell will perform its default behavior after completions
-// have been provided (this implies !ShellCompDirectiveNoSpace && !ShellCompDirectiveNoFileComp).
-ShellCompDirectiveDefault
-```
+For backwards-compatibility, Cobra still supports its legacy dynamic completion solution (described below).  Unlike the `ValidArgsFunction` solution, the legacy solution will only work for Bash shell-completion and not for other shells. This legacy solution can be used along-side `ValidArgsFunction` and `RegisterFlagCompletionFunc()`, as long as both solutions are not used for the same command.  This provides a path to gradually migrate from the legacy solution to the new solution.
 
-When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line.  You therefore don't need to do this parsing yourself.  For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function.
+The legacy solution allows you to inject bash functions into the bash completion script.  Those bash functions are responsible for providing the completion choices for your own completions.
 
-##### Debugging
-
-Cobra achieves dynamic completions written in Go through the use of a hidden command called by the completion script.  To debug your Go completion code, you can call this hidden command directly:
-```bash
-# helm __complete status har<ENTER>
-harbor
-:4
-Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
-```
-***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command:
-```bash
-# helm __complete status ""<ENTER>
-harbor
-notary
-rook
-thanos
-:4
-Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
-```
-Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code.  You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code:
-```go
-// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
-// is set to a file path) and optionally prints to stderr.
-cobra.CompDebug(msg string, printToStdErr bool) {
-cobra.CompDebugln(msg string, printToStdErr bool)
-
-// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
-// is set to a file path) and to stderr.
-cobra.CompError(msg string)
-cobra.CompErrorln(msg string)
-```
-***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned above.
-
-#### 2. Custom completions of nouns written in Bash
-
-This method allows you to inject bash functions into the completion script.  Those bash functions are responsible for providing the completion choices for your own completions.
-
-Some more actual code that works in kubernetes:
+Some code that works in kubernetes:
 
 ```bash
 const (
@@ -253,93 +61,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
 
 The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__kubectl_custom_func()` (`__<command-use>_custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`.  `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`.  So it will call `__kubectl_parse_get pod`.  `__kubectl_parse_get` will actually call out to kubernetes and get any pods.  It will then set `COMPREPLY` to valid pods!
 
-## Mark flags as required
-
-Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab].  Marking a flag as 'Required' is incredibly easy.
-
-```go
-cmd.MarkFlagRequired("pod")
-cmd.MarkFlagRequired("container")
-```
-
-and you'll get something like
-
-```bash
-# kubectl exec [tab][tab][tab]
--c            --container=  -p            --pod=  
-```
-
-# Specify valid filename extensions for flags that take a filename
-
-In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
-
-```go
-	annotations := []string{"json", "yaml", "yml"}
-	annotation := make(map[string][]string)
-	annotation[cobra.BashCompFilenameExt] = annotations
-
-	flag := &pflag.Flag{
-		Name:        "filename",
-		Shorthand:   "f",
-		Usage:       usage,
-		Value:       value,
-		DefValue:    value.String(),
-		Annotations: annotation,
-	}
-	cmd.Flags().AddFlag(flag)
-```
-
-Now when you run a command with this filename flag you'll get something like
-
-```bash
-# kubectl create -f 
-test/                         example/                      rpmbuild/
-hello.yml                     test.json
-```
-
-So while there are many other files in the CWD it only shows me subdirs and those with valid extensions.
-
-# Specify custom flag completion
-
-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:
-
-```go
-flagName := "output"
-cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-	return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault
-})
-```
-Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated.  In our example this dynamic completion will give results like so:
-
-```bash
-# helm status --output [tab][tab]
-json table yaml
-```
-
-### Debugging
-
-You can also easily debug your Go completion code for flags:
-```bash
-# helm __complete status --output ""
-json
-table
-yaml
-:4
-Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
-```
-***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned in the above section.
-
-## 2. Custom completions of flags written in Bash
-
-Alternatively, you can use bash code for flag custom completion. Similar to the filename
-completion and filtering using `cobra.BashCompFilenameExt`, you can specify
-a custom flag completion bash function with `cobra.BashCompCustom`:
+Similarly, for flags:
 
 ```go
 	annotation := make(map[string][]string)
@@ -367,17 +89,3 @@ __kubectl_get_namespaces()
     fi
 }
 ```
-# Using bash aliases for commands
-
-You can also configure the `bash aliases` for the commands and they will also support completions.
-
-```bash
-alias aliasname=origcommand
-complete -o default -F __start_origcommand aliasname
-
-# and now when you run `aliasname` completion will make
-# suggestions as it did for `origcommand`.
-
-$) aliasname <tab><tab>
-completion     firstcommand   secondcommand
-```
diff --git a/custom_completions.go b/custom_completions.go
index 47cc9f5..c25c03e 100644
--- a/custom_completions.go
+++ b/custom_completions.go
@@ -1,7 +1,6 @@
 package cobra
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	"strings"
@@ -38,8 +37,29 @@ const (
 	// This currently does not work for zsh or bash < 4
 	ShellCompDirectiveNoFileComp
 
+	// ShellCompDirectiveFilterFileExt indicates that the provided completions
+	// should be used as file extension filters.
+	// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
+	// is a shortcut to using this directive explicitly.  The BashCompFilenameExt
+	// annotation can also be used to obtain the same behavior for flags.
+	ShellCompDirectiveFilterFileExt
+
+	// ShellCompDirectiveFilterDirs indicates that only directory names should
+	// be provided in file completion.  To request directory names within another
+	// directory, the returned completions should specify the directory within
+	// which to search.  The BashCompSubdirsInDir annotation can be used to
+	// obtain the same behavior but only for flags.
+	ShellCompDirectiveFilterDirs
+
+	// ===========================================================================
+
+	// All directives using iota should be above this one.
+	// For internal use.
+	shellCompDirectiveMaxValue
+
 	// ShellCompDirectiveDefault indicates to let the shell perform its default
 	// behavior after completions have been provided.
+	// This one must be last to avoid messing up the iota count.
 	ShellCompDirectiveDefault ShellCompDirective = 0
 )
 
@@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string {
 	if d&ShellCompDirectiveNoFileComp != 0 {
 		directives = append(directives, "ShellCompDirectiveNoFileComp")
 	}
+	if d&ShellCompDirectiveFilterFileExt != 0 {
+		directives = append(directives, "ShellCompDirectiveFilterFileExt")
+	}
+	if d&ShellCompDirectiveFilterDirs != 0 {
+		directives = append(directives, "ShellCompDirectiveFilterDirs")
+	}
 	if len(directives) == 0 {
 		directives = append(directives, "ShellCompDirectiveDefault")
 	}
 
-	if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+	if d >= shellCompDirectiveMaxValue {
 		return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
 	}
 	return strings.Join(directives, ", ")
@@ -105,11 +131,19 @@ func (c *Command) initCompleteCmd(args []string) {
 					// Remove any description that may be included following a tab character.
 					comp = strings.Split(comp, "\t")[0]
 				}
+
+				// Finally trim the completion.  This is especially important to get rid
+				// of a trailing tab when there are no description following it.
+				// For example, a sub-command without a description should not be completed
+				// with a tab at the end (or else zsh will show a -- following it
+				// although there is no description).
+				comp = strings.TrimSpace(comp)
+
 				// Print each possible completion to stdout for the completion script to consume.
 				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
 			}
 
-			if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+			if directive >= shellCompDirectiveMaxValue {
 				directive = ShellCompDirectiveDefault
 			}
 
@@ -136,8 +170,6 @@ func (c *Command) initCompleteCmd(args []string) {
 }
 
 func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
-	var completions []string
-
 	// The last argument, which is not completely typed by the user,
 	// should not be part of the list of arguments
 	toComplete := args[len(args)-1]
@@ -147,81 +179,158 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 	finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
 	if err != nil {
 		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
-		return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
+		return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
+	}
+
+	// Check if we are doing flag value completion before parsing the flags.
+	// This is important because if we are completing a flag value, we need to also
+	// remove the flag name argument from the list of finalArgs or else the parsing
+	// could fail due to an invalid value (incomplete) for the flag.
+	flag, finalArgs, toComplete, err := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
+	if err != nil {
+		// Error while attempting to parse flags
+		return finalCmd, []string{}, ShellCompDirectiveDefault, err
+	}
+
+	// Parse the flags early so we can check if required flags are set
+	if err = finalCmd.ParseFlags(finalArgs); err != nil {
+		return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
+	}
+
+	if flag != nil {
+		// Check if we are completing a flag value subject to annotations
+		if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
+			if len(validExts) != 0 {
+				// File completion filtered by extensions
+				return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
+			}
+
+			// The annotation requests simple file completion.  There is no reason to do
+			// that since it is the default behavior anyway.  Let's ignore this annotation
+			// in case the program also registered a completion function for this flag.
+			// Even though it is a mistake on the program's side, let's be nice when we can.
+		}
+
+		if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
+			if len(subDir) == 1 {
+				// Directory completion from within a directory
+				return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
+			}
+			// Directory completion
+			return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
+		}
 	}
 
 	// 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
+	// the flag name to be complete
+	if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
+		var completions []string
+
+		// First check for required flags
+		completions = completeRequireFlags(finalCmd, toComplete)
+
+		// If we have not found any required flags, only then can we show regular flags
+		if len(completions) == 0 {
+			doCompleteFlags := func(flag *pflag.Flag) {
+				if !flag.Changed ||
+					strings.Contains(flag.Value.Type(), "Slice") ||
+					strings.Contains(flag.Value.Type(), "Array") {
+					// If the flag is not already present, or if it can be specified multiple times (Array or Slice)
+					// we suggest it as a completion
+					completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+				}
 			}
+
+			// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
+			// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
+			// non-inherited flags.
+			finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+				doCompleteFlags(flag)
+			})
+			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+				doCompleteFlags(flag)
+			})
+		}
+
+		directive := ShellCompDirectiveNoFileComp
+		if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
+			// If there is a single completion, the shell usually adds a space
+			// after the completion.  We don't want that if the flag ends with an =
+			directive = ShellCompDirectiveNoSpace
 		}
 		return finalCmd, completions, directive, nil
 	}
 
-	var flag *pflag.Flag
+	// We only remove the flags from the arguments if DisableFlagParsing is not set.
+	// This is important for commands which have requested to do their own flag completion.
 	if !finalCmd.DisableFlagParsing {
-		// We only do flag completion if we are allowed to parse flags
-		// This is important for commands which have requested to do their own flag completion.
-		flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
-		if err != nil {
-			// Error while attempting to parse flags
-			return finalCmd, completions, ShellCompDirectiveDefault, err
-		}
+		finalArgs = finalCmd.Flags().Args()
 	}
 
+	var completions []string
+	directive := ShellCompDirectiveDefault
 	if flag == nil {
+		// Check if there are any local, non-persistent flags on the command-line
+		foundLocalNonPersistentFlag := false
+		localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
+		finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+			if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
+				foundLocalNonPersistentFlag = true
+			}
+		})
+
 		// Complete subcommand names, including the help command
-		for _, subCmd := range finalCmd.Commands() {
-			if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
-				if strings.HasPrefix(subCmd.Name(), toComplete) {
-					completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
+		if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
+			// We only complete sub-commands if:
+			// - there are no arguments on the command-line and
+			// - there are no local, non-peristent flag on the command-line
+			for _, subCmd := range finalCmd.Commands() {
+				if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
+					if strings.HasPrefix(subCmd.Name(), toComplete) {
+						completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
+					}
+					directive = ShellCompDirectiveNoFileComp
 				}
 			}
 		}
 
+		// Complete required flags even without the '-' prefix
+		completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
+
+		// Always complete ValidArgs, even if we are completing a subcommand name.
+		// This is for commands that have both subcommands and ValidArgs.
 		if len(finalCmd.ValidArgs) > 0 {
-			// 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 len(finalArgs) == 0 {
+				// ValidArgs are only for the first argument
+				for _, validArg := range finalCmd.ValidArgs {
+					if strings.HasPrefix(validArg, toComplete) {
+						completions = append(completions, validArg)
+					}
+				}
+				directive = ShellCompDirectiveNoFileComp
+
+				// If no completions were found within commands or ValidArgs,
+				// see if there are any ArgAliases that should be completed.
+				if len(completions) == 0 {
+					for _, argAlias := range finalCmd.ArgAliases {
+						if strings.HasPrefix(argAlias, toComplete) {
+							completions = append(completions, argAlias)
+						}
+					}
 				}
 			}
 
 			// 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
+			return finalCmd, completions, directive, nil
 		}
 
-		// Always let the logic continue so as to add any ValidArgsFunction completions,
+		// 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())
-	}
-
-	// We only remove the flags from the arguments if DisableFlagParsing is not set.
-	// This is important for commands which have requested to do their own flag completion.
-	if !finalCmd.DisableFlagParsing {
-		finalArgs = finalCmd.Flags().Args()
-	}
-
 	// Find the completion function for the flag or command
 	var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
 	if flag != nil {
@@ -229,14 +338,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 	} else {
 		completionFn = finalCmd.ValidArgsFunction
 	}
-	if completionFn == nil {
-		// Go custom completion not supported/needed for this flag or command
-		return finalCmd, completions, ShellCompDirectiveDefault, nil
+	if completionFn != nil {
+		// Go custom completion defined for this flag or command.
+		// Call the registered completion function to get the completions.
+		var comps []string
+		comps, directive = completionFn(finalCmd, finalArgs, toComplete)
+		completions = append(completions, comps...)
 	}
 
-	// Call the registered completion function to get the completions
-	comps, directive := completionFn(finalCmd, finalArgs, toComplete)
-	completions = append(completions, comps...)
 	return finalCmd, completions, directive, nil
 }
 
@@ -251,11 +360,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
 		// 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))
-		}
+		// Why suggest both long forms: --flag and --flag= ?
+		// This forces the user to *always* have to type either an = or a space after the flag name.
+		// Let's be nice and avoid making users have to do that.
+		// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
+		// The = form will still work, we just won't suggest it.
+		// This also makes the list of suggested flags shorter as we avoid all the = forms.
+		//
+		// if len(flag.NoOptDefVal) == 0 {
+		// 	// Flag requires a value, so it can be suffixed with =
+		// 	flagName += "="
+		// 	completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
+		// }
 	}
 
 	flagName = "-" + flag.Shorthand
@@ -266,17 +382,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
 	return completions
 }
 
+func completeRequireFlags(finalCmd *Command, toComplete string) []string {
+	var completions []string
+
+	doCompleteRequiredFlags := func(flag *pflag.Flag) {
+		if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
+			if !flag.Changed {
+				// If the flag is not already present, we suggest it as a completion
+				completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+			}
+		}
+	}
+
+	// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
+	// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
+	// non-inherited flags.
+	finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+		doCompleteRequiredFlags(flag)
+	})
+	finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+		doCompleteRequiredFlags(flag)
+	})
+
+	return completions
+}
+
 func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
+	if finalCmd.DisableFlagParsing {
+		// We only do flag completion if we are allowed to parse flags
+		// This is important for commands which have requested to do their own flag completion.
+		return nil, args, lastArg, nil
+	}
+
 	var flagName string
 	trimmedArgs := args
 	flagWithEqual := false
-	if isFlagArg(lastArg) {
+
+	// When doing completion of a flag name, as soon as an argument starts with
+	// a '-' we know it is a flag.  We cannot use isFlagArg() here as that function
+	// requires the flag name to be complete
+	if len(lastArg) > 0 && lastArg[0] == '-' {
 		if index := strings.Index(lastArg, "="); index >= 0 {
+			// Flag with an =
 			flagName = strings.TrimLeft(lastArg[:index], "-")
 			lastArg = lastArg[index+1:]
 			flagWithEqual = true
 		} else {
-			return nil, nil, "", errors.New("Unexpected completion request for flag")
+			// Normal flag completion
+			return nil, args, lastArg, nil
 		}
 	}
 
diff --git a/fish_completions.go b/fish_completions.go
index c83609c..52b89c6 100644
--- a/fish_completions.go
+++ b/fish_completions.go
@@ -37,6 +37,12 @@ function __%[1]s_perform_completion
     end
     __%[1]s_debug "emptyArg: $emptyArg"
 
+    if not type -q "$args[1]"
+        # This can happen when "complete --do-complete %[1]s" is called when running this script.
+        __%[1]s_debug "Cannot find $args[1]. No completions."
+        return
+    end
+
     set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg"
     __%[1]s_debug "Calling $requestComp"
 
@@ -71,7 +77,8 @@ function __%[1]s_prepare_completions
 
     # 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)
+        # Use the -c flag to allow for completion in the middle of the line
+        set __%[1]s_comp_commandLine (commandline -c)
     end
     __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
 
@@ -83,7 +90,7 @@ function __%[1]s_prepare_completions
         __%[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
+        return 1
     end
 
     set directive (string sub --start 2 $results[-1])
@@ -92,20 +99,35 @@ function __%[1]s_prepare_completions
     __%[1]s_debug "Completions are: $__%[1]s_comp_results"
     __%[1]s_debug "Directive is: $directive"
 
+    set shellCompDirectiveError %[3]d
+    set shellCompDirectiveNoSpace %[4]d
+    set shellCompDirectiveNoFileComp %[5]d
+    set shellCompDirectiveFilterFileExt %[6]d
+    set shellCompDirectiveFilterDirs %[7]d
+
     if test -z "$directive"
         set directive 0
     end
 
-    set compErr (math (math --scale 0 $directive / %[3]d) %% 2)
+    set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 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
+        return 1
     end
 
-    set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
-    set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
+    set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
+    set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
+    if test $filefilter -eq 1; or test $dirfilter -eq 1
+        __%[1]s_debug "File extension filtering or directory filtering not supported"
+        # Do full file completion instead
+        set --global __%[1]s_comp_do_file_comp 1
+        return 1
+    end
+
+    set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
+    set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
 
     __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
 
@@ -132,9 +154,13 @@ function __%[1]s_prepare_completions
     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?
+# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
+# so we can properly delete any completions provided by another script.
+# The space after the the program name is essential to trigger completion for the program
+# and not completion of the program name itself.
+complete --do-complete "%[1]s " &> /dev/null
+
+# Remove any pre-existing completions for the program since we will be handling all of them.
 complete -c %[1]s -e
 
 # The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
@@ -149,7 +175,9 @@ complete -c %[1]s -n 'set --query __%[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))
+`, name, compCmd,
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
 }
 
 // GenFishCompletion generates fish completion file and writes to the passed writer.
diff --git a/fish_completions.md b/fish_completions.md
index 6bfe5f8..19b2ed1 100644
--- a/fish_completions.md
+++ b/fish_completions.md
@@ -1,7 +1,4 @@
-## Generating Fish Completions for your own cobra.Command
+## Generating Fish Completions For Your 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.
+Please refer to [Shell Completions](shell_completions.md) for details.
 
-### Limitations
-
-* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not.
diff --git a/powershell_completions.md b/powershell_completions.md
index afed802..55f154a 100644
--- a/powershell_completions.md
+++ b/powershell_completions.md
@@ -2,6 +2,8 @@
 
 Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles.
 
+*Note*: PowerShell completions have not (yet?) been aligned to Cobra's generic shell completion support.  This implies the PowerShell completions are not as rich as for other shells (see [What's not yet supported](#whats-not-yet-supported)), and may behave slightly differently.  They are still very useful for PowerShell users.
+
 # What's supported
 
 - Completion for subcommands using their `.Short` description
diff --git a/shell_completions.go b/shell_completions.go
index ba0af9c..d99bf91 100644
--- a/shell_completions.go
+++ b/shell_completions.go
@@ -4,82 +4,81 @@ import (
 	"github.com/spf13/pflag"
 )
 
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
+// MarkFlagRequired instructs the various shell completion implementations to
+// prioritize the named flag when performing completion,
 // and causes your command to report an error if invoked without the flag.
 func (c *Command) MarkFlagRequired(name string) error {
 	return MarkFlagRequired(c.Flags(), name)
 }
 
-// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
+// MarkPersistentFlagRequired instructs the various shell completion implementations to
+// prioritize the named persistent flag when performing completion,
 // and causes your command to report an error if invoked without the flag.
 func (c *Command) MarkPersistentFlagRequired(name string) error {
 	return MarkFlagRequired(c.PersistentFlags(), name)
 }
 
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
+// MarkFlagRequired instructs the various shell completion implementations to
+// prioritize the named flag when performing completion,
 // and causes your command to report an error if invoked without the flag.
 func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
 	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
 }
 
-// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
+// MarkFlagFilename instructs the various shell completion implementations to
+// limit completions for the named flag to the specified file extensions.
 func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
 	return MarkFlagFilename(c.Flags(), name, extensions...)
 }
 
 // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
-// Generated bash autocompletion will call the bash function f for the flag.
+// The bash completion script will call the bash function f for the flag.
+//
+// This will only work for bash completion.
+// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
+// to register a Go function which will work across all shells.
 func (c *Command) MarkFlagCustom(name string, f string) error {
 	return MarkFlagCustom(c.Flags(), name, f)
 }
 
 // MarkPersistentFlagFilename instructs the various shell completion
-// implementations to limit completions for this persistent flag to the
-// specified extensions (patterns).
-//
-// Shell Completion compatibility matrix: bash, zsh
+// implementations to limit completions for the named persistent flag to the
+// specified file extensions.
 func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
 	return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
 }
 
 // MarkFlagFilename instructs the various shell completion implementations to
-// limit completions for this flag to the specified extensions (patterns).
-//
-// Shell Completion compatibility matrix: bash, zsh
+// limit completions for the named flag to the specified file extensions.
 func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
 	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
 }
 
-// MarkFlagCustom instructs the various shell completion implementations to
-// limit completions for this flag to the specified extensions (patterns).
+// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
+// The bash completion script will call the bash function f for the flag.
 //
-// Shell Completion compatibility matrix: bash, zsh
+// This will only work for bash completion.
+// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
+// to register a Go function which will work across all shells.
 func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
 	return flags.SetAnnotation(name, BashCompCustom, []string{f})
 }
 
 // MarkFlagDirname instructs the various shell completion implementations to
-// complete only directories with this named flag.
-//
-// Shell Completion compatibility matrix: zsh
+// limit completions for the named flag to directory names.
 func (c *Command) MarkFlagDirname(name string) error {
 	return MarkFlagDirname(c.Flags(), name)
 }
 
 // MarkPersistentFlagDirname instructs the various shell completion
-// implementations to complete only directories with this persistent named flag.
-//
-// Shell Completion compatibility matrix: zsh
+// implementations to limit completions for the named persistent flag to
+// directory names.
 func (c *Command) MarkPersistentFlagDirname(name string) error {
 	return MarkFlagDirname(c.PersistentFlags(), name)
 }
 
 // MarkFlagDirname instructs the various shell completion implementations to
-// complete only directories with this specified flag.
-//
-// Shell Completion compatibility matrix: zsh
+// limit completions for the named flag to directory names.
 func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
-	zshPattern := "-(/)"
-	return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern})
+	return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{})
 }
diff --git a/shell_completions.md b/shell_completions.md
new file mode 100644
index 0000000..e0a256d
--- /dev/null
+++ b/shell_completions.md
@@ -0,0 +1,429 @@
+# Generating shell completions
+
+Cobra can generate shell completions for multiple shells.
+The currently supported shells are:
+- Bash
+- Zsh
+- Fish
+- PowerShell
+
+If you are using the generator you can create a completion command by running
+
+```bash
+cobra add completion
+```
+and then modifying the generated `cmd/completion.go` file to look something like this
+(writing the shell script to stdout allows the most flexible use):
+
+```go
+var completionCmd = &cobra.Command{
+	Use:                   "completion [bash|zsh|fish|powershell]",
+	Short:                 "Generate completion script",
+	Long: `To load completions:
+
+Bash:
+
+$ source <(yourprogram completion bash)
+
+# To load completions for each session, execute once:
+Linux:
+  $ yourprogram completion bash > /etc/bash_completion.d/yourprogram
+MacOS:
+  $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram
+
+Zsh:
+
+$ source <(yourprogram completion zsh)
+
+# To load completions for each session, execute once:
+$ yourprogram completion zsh > "${fpath[1]}/_yourprogram"
+
+Fish:
+
+$ yourprogram completion fish | source
+
+# To load completions for each session, execute once:
+$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
+`,
+	DisableFlagsInUseLine: true,
+	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
+	Args:                  cobra.ExactValidArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		switch args[0] {
+		case "bash":
+			cmd.Root().GenBashCompletion(os.Stdout)
+		case "zsh":
+			cmd.Root().GenZshCompletion(os.Stdout)
+		case "fish":
+			cmd.Root().GenFishCompletion(os.Stdout, true)
+		case "powershell":
+			cmd.Root().GenPowerShellCompletion(os.Stdout)
+		}
+	},
+}
+```
+
+**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script so must be removed.
+
+# Customizing completions
+
+The generated completion scripts will automatically handle completing commands and flags.  However, you can make your completions much more powerful by providing information to complete your program's nouns and flag values.
+
+## Completion of nouns
+
+### Static completion of nouns
+
+Cobra allows you to provide a pre-defined list of completion choices for your nouns using the `ValidArgs` field.
+For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them.
+Some simplified code from `kubectl get` looks like:
+
+```go
+validArgs []string = { "pod", "node", "service", "replicationcontroller" }
+
+cmd := &cobra.Command{
+	Use:     "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
+	Short:   "Display one or many resources",
+	Long:    get_long,
+	Example: get_example,
+	Run: func(cmd *cobra.Command, args []string) {
+		err := RunGet(f, out, cmd, args)
+		util.CheckErr(err)
+	},
+	ValidArgs: validArgs,
+}
+```
+
+Notice we put the `ValidArgs` field on the `get` sub-command. Doing so will give results like:
+
+```bash
+$ kubectl get [tab][tab]
+node   pod   replicationcontroller   service
+```
+
+#### Aliases for nouns
+
+If your nouns have aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
+
+```go
+argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
+
+cmd := &cobra.Command{
+    ...
+	ValidArgs:  validArgs,
+	ArgAliases: argAliases
+}
+```
+
+The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
+the completion algorithm if entered manually, e.g. in:
+
+```bash
+$ kubectl get rc [tab][tab]
+backend        frontend       database 
+```
+
+Note that without declaring `rc` as an alias, the completion algorithm would not know to show the list of
+replication controllers following `rc`.
+
+### Dynamic completion of nouns
+
+In some cases it is not possible to provide a list of completions in advance.  Instead, the list of completions must be determined at execution-time. 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.
+Simplified code from `helm status` looks like:
+
+```go
+cmd := &cobra.Command{
+	Use:   "status RELEASE_NAME",
+	Short: "Display the status of the named release",
+	Long:  status_long,
+	RunE: func(cmd *cobra.Command, args []string) {
+		RunGet(args[0])
+	},
+	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		if len(args) != 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp
+	},
+}
+```
+Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster.
+Notice we put the `ValidArgsFunction` on the `status` sub-command. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like:
+
+```bash
+$ helm status [tab][tab]
+harbor notary rook thanos
+```
+You may have noticed the use of `cobra.ShellCompDirective`.  These directives are bit fields allowing to control some shell completion behaviors for your particular completion.  You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp`
+```go
+// Indicates that the shell will perform its default behavior after completions
+// have been provided (this implies none of the other directives).
+ShellCompDirectiveDefault
+
+// Indicates an error occurred and completions should be ignored.
+ShellCompDirectiveError
+
+// Indicates that the shell should not add a space after the completion,
+// even if there is a single completion provided.
+ShellCompDirectiveNoSpace
+
+// Indicates that the shell should not provide file completion even when
+// no completion is provided.
+ShellCompDirectiveNoFileComp
+
+// Indicates that the returned completions should be used as file extension filters.
+// For example, to complete only files of the form *.json or *.yaml:
+//    return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt
+// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename()
+// is a shortcut to using this directive explicitly.
+//
+ShellCompDirectiveFilterFileExt
+
+// Indicates that only directory names should be provided in file completion.
+// For example:
+//    return nil, ShellCompDirectiveFilterDirs
+// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly.
+//
+// To request directory names within another directory, the returned completions
+// should specify a single directory name within which to search. For example,
+// to complete directories within "themes/":
+//    return []string{"themes"}, ShellCompDirectiveFilterDirs
+//
+ShellCompDirectiveFilterDirs
+```
+
+***Note***: When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line.  You therefore don't need to do this parsing yourself.  For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function.
+
+#### Debugging
+
+Cobra achieves dynamic completion through the use of a hidden command called by the completion script.  To debug your Go completion code, you can call this hidden command directly:
+```bash
+$ helm __complete status har<ENTER>
+harbor
+:4
+Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
+```
+***Important:*** If the noun to complete is empty (when the user has not yet typed any letters of that noun), you must pass an empty parameter to the `__complete` command:
+```bash
+$ helm __complete status ""<ENTER>
+harbor
+notary
+rook
+thanos
+:4
+Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
+```
+Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code.  You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code:
+```go
+// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
+// is set to a file path) and optionally prints to stderr.
+cobra.CompDebug(msg string, printToStdErr bool) {
+cobra.CompDebugln(msg string, printToStdErr bool)
+
+// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
+// is set to a file path) and to stderr.
+cobra.CompError(msg string)
+cobra.CompErrorln(msg string)
+```
+***Important:*** You should **not** leave traces that print directly to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned above.
+
+## Completions for flags
+
+### Mark flags as required
+
+Most of the time completions will only show sub-commands. But if a flag is required to make a sub-command work, you probably want it to show up when the user types [tab][tab].  You can mark a flag as 'Required' like so:
+
+```go
+cmd.MarkFlagRequired("pod")
+cmd.MarkFlagRequired("container")
+```
+
+and you'll get something like
+
+```bash
+$ kubectl exec [tab][tab]
+-c            --container=  -p            --pod=  
+```
+
+### Specify dynamic flag completion
+
+As for nouns, Cobra provides a way of defining dynamic completion of flags.  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 using the `command.RegisterFlagCompletionFunc()` function.
+
+```go
+flagName := "output"
+cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault
+})
+```
+Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated.  In our example this dynamic completion will give results like so:
+
+```bash
+$ helm status --output [tab][tab]
+json table yaml
+```
+
+#### Debugging
+
+You can also easily debug your Go completion code for flags:
+```bash
+$ helm __complete status --output ""
+json
+table
+yaml
+:4
+Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
+```
+***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned further above.
+
+### Specify valid filename extensions for flags that take a filename
+
+To limit completions of flag values to file names with certain extensions you can either use the different `MarkFlagFilename()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterFileExt`, like so:
+```go
+flagName := "output"
+cmd.MarkFlagFilename(flagName, "yaml", "json")
+```
+or
+```go
+flagName := "output"
+cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt})
+```
+
+### Limit flag completions to directory names
+
+To limit completions of flag values to directory names you can either use the `MarkFlagDirname()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs`, like so:
+```go
+flagName := "output"
+cmd.MarkFlagDirname(flagName)
+```
+or
+```go
+flagName := "output"
+cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return nil, cobra.ShellCompDirectiveFilterDirs
+})
+```
+To limit completions of flag values to directory names *within another directory* you can use a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs` like so:
+```go
+flagName := "output"
+cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs
+})
+```
+### Descriptions for completions
+
+Both `zsh` and `fish` allow for descriptions to annotate completion choices.  For commands and flags, Cobra will provide the descriptions automatically, based on usage information.  For example, using zsh:
+```
+$ helm s[tab]
+search  -- search for a keyword in charts
+show    -- show information of a chart
+status  -- displays the status of the named release
+```
+while using fish:
+```
+$ helm s[tab]
+search  (search for a keyword in charts)  show  (show information of a chart)  status  (displays the status of the named release)
+```
+
+Cobra allows you to add annotations to your own completions.  Simply add the annotation text after each completion, following a `\t` separator.  This technique applies to completions returned by `ValidArgs`, `ValidArgsFunction` and `RegisterFlagCompletionFunc()`.  For example:
+```go
+ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return []string{"harbor\tAn image registry", "thanos\tLong-term metrics"}, cobra.ShellCompDirectiveNoFileComp
+}}
+```
+or
+```go
+ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"}
+```
+## Bash completions
+
+### Dependencies
+
+The bash completion script generated by Cobra requires the `bash_completion` package. You should update the help text of your completion command to show how to install the `bash_completion` package ([Kubectl docs](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion))
+
+### Aliases
+
+You can also configure `bash` aliases for your program and they will also support completions.
+
+```bash
+alias aliasname=origcommand
+complete -o default -F __start_origcommand aliasname
+
+# and now when you run `aliasname` completion will make
+# suggestions as it did for `origcommand`.
+
+$ aliasname <tab><tab>
+completion     firstcommand   secondcommand
+```
+### Bash legacy dynamic completions
+
+For backwards-compatibility, Cobra still supports its bash legacy dynamic completion solution.
+Please refer to [Bash Completions](bash_completions.md) for details.
+
+## Zsh completions
+
+Cobra supports native Zsh completion generated from the root `cobra.Command`.
+The generated completion script should be put somewhere in your `$fpath` and be named
+`_<yourProgram>`.
+
+Zsh supports descriptions for completions. Cobra will provide the description automatically,
+based on usage information. Cobra provides a way to completely disable such descriptions by
+using `GenZshCompletionNoDesc()` or `GenZshCompletionFileNoDesc()`. You can choose to make
+this a configurable option to your users.
+```
+# With descriptions
+$ helm s[tab]
+search  -- search for a keyword in charts
+show    -- show information of a chart
+status  -- displays the status of the named release
+
+# Without descriptions
+$ helm s[tab]
+search  show  status
+```
+*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`.
+
+### Limitations
+
+* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `zsh` (including the use of the `BashCompCustom` flag annotation).
+  * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`).
+* The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`.
+  * You should instead use `RegisterFlagCompletionFunc()`.
+
+### Zsh completions standardization
+
+Cobra 1.1 standardized its zsh completion support to align it with its other shell completions.  Although the API was kept backwards-compatible, some small changes in behavior were introduced.
+Please refer to [Zsh Completions](zsh_completions.md) for details.
+
+## Fish completions
+
+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.
+```
+# With descriptions
+$ helm s[tab]
+search  (search for a keyword in charts)  show  (show information of a chart)  status  (displays the status of the named release)
+
+# Without descriptions
+$ helm s[tab]
+search  show  status
+```
+*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`.
+
+### Limitations
+
+* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `fish` (including the use of the `BashCompCustom` flag annotation).
+  * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`).
+* The function `MarkFlagCustom()` is not supported and will be ignored for `fish`.
+  * You should instead use `RegisterFlagCompletionFunc()`.
+* The following flag completion annotations are not supported and will be ignored for `fish`:
+  * `BashCompFilenameExt` (filtering by file extension)
+  * `BashCompSubdirsInDir` (filtering by directory)
+* The functions corresponding to the above annotations are consequently not supported and will be ignored for `fish`:
+  * `MarkFlagFilename()` and `MarkPersistentFlagFilename()` (filtering by file extension)
+  * `MarkFlagDirname()` and `MarkPersistentFlagDirname()` (filtering by directory)
+* Similarly, the following completion directives are not supported and will be ignored for `fish`:
+  * `ShellCompDirectiveFilterFileExt` (filtering by file extension)
+  * `ShellCompDirectiveFilterDirs` (filtering by directory)
+
+## PowerShell completions
+
+Please refer to [PowerShell Completions](powershell_completions.md) for details.
diff --git a/zsh_completions.go b/zsh_completions.go
index 1275548..c25ce68 100644
--- a/zsh_completions.go
+++ b/zsh_completions.go
@@ -1,336 +1,235 @@
 package cobra
 
 import (
-	"encoding/json"
+	"bytes"
 	"fmt"
 	"io"
 	"os"
-	"sort"
-	"strings"
-	"text/template"
-
-	"github.com/spf13/pflag"
-)
-
-const (
-	zshCompArgumentAnnotation   = "cobra_annotations_zsh_completion_argument_annotation"
-	zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion"
-	zshCompArgumentWordComp     = "cobra_annotations_zsh_completion_argument_word_completion"
-	zshCompDirname              = "cobra_annotations_zsh_dirname"
 )
 
-var (
-	zshCompFuncMap = template.FuncMap{
-		"genZshFuncName":              zshCompGenFuncName,
-		"extractFlags":                zshCompExtractFlag,
-		"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
-		"extractArgsCompletions":      zshCompExtractArgumentCompletionHintsForRendering,
-	}
-	zshCompletionText = `
-{{/* should accept Command (that contains subcommands) as parameter */}}
-{{define "argumentsC" -}}
-{{ $cmdPath := genZshFuncName .}}
-function {{$cmdPath}} {
-  local -a commands
-
-  _arguments -C \{{- range extractFlags .}}
-    {{genFlagEntryForZshArguments .}} \{{- end}}
-    "1: :->cmnds" \
-    "*::arg:->args"
-
-  case $state in
-  cmnds)
-    commands=({{range .Commands}}{{if not .Hidden}}
-      "{{.Name}}:{{.Short}}"{{end}}{{end}}
-    )
-    _describe "command" commands
-    ;;
-  esac
-
-  case "$words[1]" in {{- range .Commands}}{{if not .Hidden}}
-  {{.Name}})
-    {{$cmdPath}}_{{.Name}}
-    ;;{{end}}{{end}}
-  esac
-}
-{{range .Commands}}{{if not .Hidden}}
-{{template "selectCmdTemplate" .}}
-{{- end}}{{end}}
-{{- end}}
-
-{{/* should accept Command without subcommands as parameter */}}
-{{define "arguments" -}}
-function {{genZshFuncName .}} {
-{{"  _arguments"}}{{range extractFlags .}} \
-    {{genFlagEntryForZshArguments . -}}
-{{end}}{{range extractArgsCompletions .}} \
-    {{.}}{{end}}
-}
-{{end}}
-
-{{/* dispatcher for commands with or without subcommands */}}
-{{define "selectCmdTemplate" -}}
-{{if .Hidden}}{{/* ignore hidden*/}}{{else -}}
-{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}}
-{{- end}}
-{{- end}}
-
-{{/* template entry point */}}
-{{define "Main" -}}
-#compdef _{{.Name}} {{.Name}}
-
-{{template "selectCmdTemplate" .}}
-{{end}}
-`
-)
-
-// zshCompArgsAnnotation is used to encode/decode zsh completion for
-// arguments to/from Command.Annotations.
-type zshCompArgsAnnotation map[int]zshCompArgHint
-
-type zshCompArgHint struct {
-	// Indicates the type of the completion to use. One of:
-	// zshCompArgumentFilenameComp or zshCompArgumentWordComp
-	Tipe string `json:"type"`
-
-	// A value for the type above (globs for file completion or words)
-	Options []string `json:"options"`
-}
-
-// GenZshCompletionFile generates zsh completion file.
+// GenZshCompletionFile generates zsh completion file including descriptions.
 func (c *Command) GenZshCompletionFile(filename string) error {
-	outFile, err := os.Create(filename)
-	if err != nil {
-		return err
-	}
-	defer outFile.Close()
-
-	return c.GenZshCompletion(outFile)
+	return c.genZshCompletionFile(filename, true)
 }
 
-// GenZshCompletion generates a zsh completion file and writes to the passed
-// writer. The completion always run on the root command regardless of the
-// command it was called from.
+// GenZshCompletion generates zsh completion file including descriptions
+// and writes it to the passed writer.
 func (c *Command) GenZshCompletion(w io.Writer) error {
-	tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
-	if err != nil {
-		return fmt.Errorf("error creating zsh completion template: %v", err)
-	}
-	return tmpl.Execute(w, c.Root())
-}
-
-// MarkZshCompPositionalArgumentFile marks the specified argument (first
-// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
-// optional - if not provided the completion will search for all files.
-func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
-	if argPosition < 1 {
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
-	}
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return err
-	}
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
-	}
-	annotation[argPosition] = zshCompArgHint{
-		Tipe:    zshCompArgumentFilenameComp,
-		Options: patterns,
-	}
-	return c.zshCompSetArgsAnnotations(annotation)
-}
-
-// MarkZshCompPositionalArgumentWords marks the specified positional argument
-// (first argument is 1) as completed by the provided words. At east one word
-// must be provided, spaces within words will be offered completion with
-// "word\ word".
-func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
-	if argPosition < 1 {
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
-	}
-	if len(words) == 0 {
-		return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
-	}
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return err
-	}
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
-	}
-	annotation[argPosition] = zshCompArgHint{
-		Tipe:    zshCompArgumentWordComp,
-		Options: words,
-	}
-	return c.zshCompSetArgsAnnotations(annotation)
-}
-
-func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
-	var result []string
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return nil, err
-	}
-	for k, v := range annotation {
-		s, err := zshCompRenderZshCompArgHint(k, v)
-		if err != nil {
-			return nil, err
-		}
-		result = append(result, s)
-	}
-	if len(c.ValidArgs) > 0 {
-		if _, positionOneExists := annotation[1]; !positionOneExists {
-			s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{
-				Tipe:    zshCompArgumentWordComp,
-				Options: c.ValidArgs,
-			})
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, s)
-		}
-	}
-	sort.Strings(result)
-	return result, nil
+	return c.genZshCompletion(w, true)
 }
 
-func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) {
-	switch t := z.Tipe; t {
-	case zshCompArgumentFilenameComp:
-		var globs []string
-		for _, g := range z.Options {
-			globs = append(globs, fmt.Sprintf(`-g "%s"`, g))
-		}
-		return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil
-	case zshCompArgumentWordComp:
-		var words []string
-		for _, w := range z.Options {
-			words = append(words, fmt.Sprintf("%q", w))
-		}
-		return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil
-	default:
-		return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t)
-	}
+// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
+func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
+	return c.genZshCompletionFile(filename, false)
 }
 
-func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool {
-	_, dup := annotation[position]
-	return dup
+// GenZshCompletionNoDesc generates zsh completion file without descriptions
+// and writes it to the passed writer.
+func (c *Command) GenZshCompletionNoDesc(w io.Writer) error {
+	return c.genZshCompletion(w, false)
 }
 
-func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) {
-	annotation := make(zshCompArgsAnnotation)
-	annotationString, ok := c.Annotations[zshCompArgumentAnnotation]
-	if !ok {
-		return annotation, nil
-	}
-	err := json.Unmarshal([]byte(annotationString), &annotation)
-	if err != nil {
-		return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err)
-	}
-	return annotation, nil
-}
-
-func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error {
-	jsn, err := json.Marshal(annotation)
-	if err != nil {
-		return fmt.Errorf("Error marshaling zsh argument annotation: %v", err)
-	}
-	if c.Annotations == nil {
-		c.Annotations = make(map[string]string)
-	}
-	c.Annotations[zshCompArgumentAnnotation] = string(jsn)
+// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
+// not consistent with Bash completion. It has therefore been disabled.
+// Instead, when no other completion is specified, file completion is done by
+// default for every argument. One can disable file completion on a per-argument
+// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
+// To achieve file extension filtering, one can use ValidArgsFunction and
+// ShellCompDirectiveFilterFileExt.
+//
+// Deprecated
+func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
 	return nil
 }
 
-func zshCompGenFuncName(c *Command) string {
-	if c.HasParent() {
-		return zshCompGenFuncName(c.Parent()) + "_" + c.Name()
-	}
-	return "_" + c.Name()
-}
-
-func zshCompExtractFlag(c *Command) []*pflag.Flag {
-	var flags []*pflag.Flag
-	c.LocalFlags().VisitAll(func(f *pflag.Flag) {
-		if !f.Hidden {
-			flags = append(flags, f)
-		}
-	})
-	c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
-		if !f.Hidden {
-			flags = append(flags, f)
-		}
-	})
-	return flags
-}
-
-// zshCompGenFlagEntryForArguments returns an entry that matches _arguments
-// zsh-completion parameters. It's too complicated to generate in a template.
-func zshCompGenFlagEntryForArguments(f *pflag.Flag) string {
-	if f.Name == "" || f.Shorthand == "" {
-		return zshCompGenFlagEntryForSingleOptionFlag(f)
-	}
-	return zshCompGenFlagEntryForMultiOptionFlag(f)
-}
-
-func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string {
-	var option, multiMark, extras string
-
-	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
-		multiMark = "*"
-	}
-
-	option = "--" + f.Name
-	if option == "--" {
-		option = "-" + f.Shorthand
-	}
-	extras = zshCompGenFlagEntryExtras(f)
-
-	return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras)
-}
-
-func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string {
-	var options, parenMultiMark, curlyMultiMark, extras string
-
-	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
-		parenMultiMark = "*"
-		curlyMultiMark = "\\*"
-	}
-
-	options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`,
-		parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name)
-	extras = zshCompGenFlagEntryExtras(f)
-
-	return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras)
+// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
+// been disabled.
+// To achieve the same behavior across all shells, one can use
+// ValidArgs (for the first argument only) or ValidArgsFunction for
+// any argument (can include the first one also).
+//
+// Deprecated
+func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
+	return nil
 }
 
-func zshCompGenFlagEntryExtras(f *pflag.Flag) string {
-	if f.NoOptDefVal != "" {
-		return ""
-	}
-
-	extras := ":" // allow options for flag (even without assistance)
-	for key, values := range f.Annotations {
-		switch key {
-		case zshCompDirname:
-			extras = fmt.Sprintf(":filename:_files -g %q", values[0])
-		case BashCompFilenameExt:
-			extras = ":filename:_files"
-			for _, pattern := range values {
-				extras = extras + fmt.Sprintf(` -g "%s"`, pattern)
-			}
-		}
+func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
+	outFile, err := os.Create(filename)
+	if err != nil {
+		return err
 	}
+	defer outFile.Close()
 
-	return extras
-}
-
-func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool {
-	return strings.Contains(f.Value.Type(), "Slice") ||
-		strings.Contains(f.Value.Type(), "Array")
-}
-
-func zshCompQuoteFlagDescription(s string) string {
-	return strings.Replace(s, "'", `'\''`, -1)
+	return c.genZshCompletion(outFile, includeDesc)
+}
+
+func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
+	buf := new(bytes.Buffer)
+	genZshComp(buf, c.Name(), includeDesc)
+	_, err := buf.WriteTo(w)
+	return err
+}
+
+func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) {
+	compCmd := ShellCompRequestCmd
+	if !includeDesc {
+		compCmd = ShellCompNoDescRequestCmd
+	}
+	buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s
+
+# zsh completion for %-36[1]s -*- shell-script -*-
+
+__%[1]s_debug()
+{
+    local file="$BASH_COMP_DEBUG_FILE"
+    if [[ -n ${file} ]]; then
+        echo "$*" >> "${file}"
+    fi
+}
+
+_%[1]s()
+{
+    local shellCompDirectiveError=%[3]d
+    local shellCompDirectiveNoSpace=%[4]d
+    local shellCompDirectiveNoFileComp=%[5]d
+    local shellCompDirectiveFilterFileExt=%[6]d
+    local shellCompDirectiveFilterDirs=%[7]d
+
+    local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
+    local -a completions
+
+    __%[1]s_debug "\n========= starting completion logic =========="
+    __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
+
+    # The user could have moved the cursor backwards on the command-line.
+    # We need to trigger completion from the $CURRENT location, so we need
+    # to truncate the command-line ($words) up to the $CURRENT location.
+    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
+    words=("${=words[1,CURRENT]}")
+    __%[1]s_debug "Truncated words[*]: ${words[*]},"
+
+    lastParam=${words[-1]}
+    lastChar=${lastParam[-1]}
+    __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
+
+    # For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>)
+    # completions must be prefixed with the flag
+    setopt local_options BASH_REMATCH
+    if [[ "${lastParam}" =~ '-.*=' ]]; then
+        # We are dealing with a flag with an =
+        flagPrefix="-P ${BASH_REMATCH}"
+    fi
+
+    # Prepare the command to obtain completions
+    requestComp="${words[1]} %[2]s ${words[2,-1]}"
+    if [ "${lastChar}" = "" ]; then
+        # If the last parameter is complete (there is a space following it)
+        # We add an extra empty parameter so we can indicate this to the go completion code.
+        __%[1]s_debug "Adding extra empty parameter"
+        requestComp="${requestComp} \"\""
+    fi
+
+    __%[1]s_debug "About to call: eval ${requestComp}"
+
+    # Use eval to handle any environment variables and such
+    out=$(eval ${requestComp} 2>/dev/null)
+    __%[1]s_debug "completion output: ${out}"
+
+    # Extract the directive integer following a : from the last line
+    local lastLine
+    while IFS='\n' read -r line; do
+        lastLine=${line}
+    done < <(printf "%%s\n" "${out[@]}")
+    __%[1]s_debug "last line: ${lastLine}"
+
+    if [ "${lastLine[1]}" = : ]; then
+        directive=${lastLine[2,-1]}
+        # Remove the directive including the : and the newline
+        local suffix
+        (( suffix=${#lastLine}+2))
+        out=${out[1,-$suffix]}
+    else
+        # There is no directive specified.  Leave $out as is.
+        __%[1]s_debug "No directive found.  Setting do default"
+        directive=0
+    fi
+
+    __%[1]s_debug "directive: ${directive}"
+    __%[1]s_debug "completions: ${out}"
+    __%[1]s_debug "flagPrefix: ${flagPrefix}"
+
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
+        __%[1]s_debug "Completion received error. Ignoring completions."
+        return
+    fi
+
+    compCount=0
+    while IFS='\n' read -r comp; do
+        if [ -n "$comp" ]; then
+            # If requested, completions are returned with a description.
+            # The description is preceded by a TAB character.
+            # For zsh's _describe, we need to use a : instead of a TAB.
+            # We first need to escape any : as part of the completion itself.
+            comp=${comp//:/\\:}
+
+            local tab=$(printf '\t')
+            comp=${comp//$tab/:}
+
+            ((compCount++))
+            __%[1]s_debug "Adding completion: ${comp}"
+            completions+=${comp}
+            lastComp=$comp
+        fi
+    done < <(printf "%%s\n" "${out[@]}")
+
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
+        # File extension filtering
+        local filteringCmd
+        filteringCmd='_files'
+        for filter in ${completions[@]}; do
+            if [ ${filter[1]} != '*' ]; then
+                # zsh requires a glob pattern to do file filtering
+                filter="\*.$filter"
+            fi
+            filteringCmd+=" -g $filter"
+        done
+        filteringCmd+=" ${flagPrefix}"
+
+        __%[1]s_debug "File filtering command: $filteringCmd"
+        _arguments '*:filename:'"$filteringCmd"
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
+        # File completion for directories only
+        local subDir
+        subdir="${completions[1]}"
+        if [ -n "$subdir" ]; then
+            __%[1]s_debug "Listing directories in $subdir"
+            pushd "${subdir}" >/dev/null 2>&1
+        else
+            __%[1]s_debug "Listing directories in ."
+        fi
+
+        _arguments '*:dirname:_files -/'" ${flagPrefix}"
+        if [ -n "$subdir" ]; then
+            popd >/dev/null 2>&1
+        fi
+    elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then
+        __%[1]s_debug "Activating nospace."
+        # We can use compadd here as there is no description when
+        # there is only one completion.
+        compadd -S '' "${lastComp}"
+    elif [ ${compCount} -eq 0 ]; then
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+            __%[1]s_debug "deactivating file completion"
+        else
+            # Perform file completion
+            __%[1]s_debug "activating file completion"
+            _arguments '*:filename:_files'" ${flagPrefix}"
+        fi
+    else
+        _describe "completions" completions $(echo $flagPrefix)
+    fi
+}
+`, name, compCmd,
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
 }
diff --git a/zsh_completions.md b/zsh_completions.md
index df9c2ea..f432dd8 100644
--- a/zsh_completions.md
+++ b/zsh_completions.md
@@ -1,39 +1,47 @@
-## Generating Zsh Completion for your cobra.Command
-
-Cobra supports native Zsh completion generated from the root `cobra.Command`.
-The generated completion script should be put somewhere in your `$fpath` named
-`_<YOUR COMMAND>`.
-
-### What's Supported
-
-* Completion for all non-hidden subcommands using their `.Short` description.
-* Completion for all non-hidden flags using the following rules:
-  * Filename completion works by marking the flag with `cmd.MarkFlagFilename...`
-    family of commands.
-  * The requirement for argument to the flag is decided by the `.NoOptDefVal`
-    flag value - if it's empty then completion will expect an argument.
-  * Flags of one of the various `*Array` and `*Slice` types supports multiple
-    specifications (with or without argument depending on the specific type).
-* Completion of positional arguments using the following rules:
-  * Argument position for all options below starts at `1`. If argument position
-    `0` is requested it will raise an error.
-  * Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob
-    patterns (e.g. `"*.log"`) are optional - if not specified it will offer to
-    complete all file types.
-  * Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for
-    completion. At least one word is required.
-  * It's possible to specify completion for some arguments and leave some
-    unspecified (e.g. offer words for second argument but nothing for first
-    argument). This will cause no completion for first argument but words
-    completion for second argument.
-  * If no argument completion was specified for 1st argument (but optionally was
-    specified for 2nd) and the command has `ValidArgs` it will be used as
-    completion options for 1st argument.
-  * Argument completions only offered for commands with no subcommands.
-
-### What's not yet Supported
-
-* Custom completion scripts are not supported yet (We should probably create zsh
-  specific one, doesn't make sense to re-use the bash one as the functions will
-  be different).
-* Whatever other feature you're looking for and doesn't exist :)
+## Generating Zsh Completion For Your cobra.Command
+
+Please refer to [Shell Completions](shell_completions.md) for details.
+
+## Zsh completions standardization
+
+Cobra 1.1 standardized its zsh completion support to align it with its other shell completions.  Although the API was kept backwards-compatible, some small changes in behavior were introduced.
+
+### Deprecation summary
+
+See further below for more details on these deprecations.
+
+* `cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` is no longer needed.  It is therefore **deprecated** and silently ignored.
+* `cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` is **deprecated** and silently ignored.
+  * Instead use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt`.
+* `cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored.
+  * Instead use `ValidArgsFunction`.
+
+### Behavioral changes
+
+**Noun completion**
+|Old behavior|New behavior|
+|---|---|
+|No file completion by default (opposite of bash)|File completion by default; use `ValidArgsFunction` with `ShellCompDirectiveNoFileComp` to turn off file completion on a per-argument basis|
+`cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` used to turn on file completion on a per-argument position basis|File completion for all arguments by default; `cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored|
+|`cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` used to turn on file completion **with glob filtering** on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored; use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt` for file **extension** filtering (not full glob filtering)|
+|`cmd.MarkZshCompPositionalArgumentWords(pos, words[])` used to provide completion choices on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored; use `ValidArgsFunction` to achieve the same behavior|
+
+**Flag-value completion**
+
+|Old behavior|New behavior|
+|---|---|
+|No file completion by default (opposite of bash)|File completion by default; use `RegisterFlagCompletionFunc()` with `ShellCompDirectiveNoFileComp` to turn off file completion|
+|`cmd.MarkFlagFilename(flag, []string{})` and similar used to turn on file completion|File completion by default; `cmd.MarkFlagFilename(flag, []string{})` no longer needed in this context and silently ignored|
+|`cmd.MarkFlagFilename(flag, glob[])`  used to turn on file completion **with glob filtering** (syntax of `[]string{"*.yaml", "*.yml"}` incompatible with bash)|Will continue to work, however, support for bash syntax is added and should be used instead so as to work for all shells (`[]string{"yaml", "yml"}`)|
+|`cmd.MarkFlagDirname(flag)` only completes directories (zsh-specific)|Has been added for all shells|
+|Completion of a flag name does not repeat, unless flag is of type `*Array` or `*Slice` (not supported by bash)|Retained for `zsh` and added to `fish`|
+|Completion of a flag name does not provide the `=` form (unlike bash)|Retained for `zsh` and added to `fish`|
+
+**Improvements**
+
+* Custom completion support (`ValidArgsFunction` and `RegisterFlagCompletionFunc()`)
+* File completion by default if no other completions found
+* Handling of required flags
+* File extension filtering no longer mutually exclusive with bash usage
+* Completion of directory names *within* another directory
+* Support for `=` form of flags


Test patch:
diff --git a/custom_completions_test.go b/custom_completions_test.go
index fe15263..66b3e63 100644
--- a/custom_completions_test.go
+++ b/custom_completions_test.go
@@ -34,6 +34,1106 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S
 	return completions, ShellCompDirectiveDefault
 }
 
+func TestCmdNameCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd1 := &Command{
+		Use:   "firstChild",
+		Short: "First command",
+		Run:   emptyRun,
+	}
+	childCmd2 := &Command{
+		Use: "secondChild",
+		Run: emptyRun,
+	}
+	hiddenCmd := &Command{
+		Use:    "testHidden",
+		Hidden: true, // Not completed
+		Run:    emptyRun,
+	}
+	deprecatedCmd := &Command{
+		Use:        "testDeprecated",
+		Deprecated: "deprecated", // Not completed
+		Run:        emptyRun,
+	}
+	aliasedCmd := &Command{
+		Use:     "aliased",
+		Short:   "A command with aliases",
+		Aliases: []string{"testAlias", "testSynonym"}, // Not completed
+		Run:     emptyRun,
+	}
+
+	rootCmd.AddCommand(childCmd1, childCmd2, hiddenCmd, deprecatedCmd, aliasedCmd)
+
+	// Test that sub-command names are completed
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"aliased",
+		"firstChild",
+		"help",
+		"secondChild",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that sub-command names are completed with prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "s")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"secondChild",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that even with no valid sub-command matches, hidden, deprecated and
+	// aliases are not completed
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "test")
+	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 that sub-command names are completed with description
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"aliased\tA command with aliases",
+		"firstChild\tFirst command",
+		"help\tHelp about any command",
+		"secondChild",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestNoCmdNameCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd1 := &Command{
+		Use:   "childCmd1",
+		Short: "First command",
+		Args:  MinimumNArgs(0),
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd1)
+	childCmd1.PersistentFlags().StringP("persistent", "p", "", "persistent flag")
+	persistentFlag := childCmd1.PersistentFlags().Lookup("persistent")
+	childCmd1.Flags().StringP("nonPersistent", "n", "", "non-persistent flag")
+	nonPersistentFlag := childCmd1.Flags().Lookup("nonPersistent")
+
+	childCmd2 := &Command{
+		Use: "childCmd2",
+		Run: emptyRun,
+	}
+	childCmd1.AddCommand(childCmd2)
+
+	// Test that sub-command names are not completed if there is an argument already
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "arg1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that sub-command names are not completed if a local non-persistent flag is present
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	nonPersistentFlag.Changed = false
+
+	expected = strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that sub-command names are not completed if a local non-persistent short flag is present
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	nonPersistentFlag.Changed = false
+
+	expected = strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that sub-command names are completed with a persistent flag
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--persistent", "value", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	persistentFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"childCmd2",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that sub-command names are completed with a persistent short flag
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-p", "value", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	persistentFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"childCmd2",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use:       "root",
+		ValidArgs: []string{"one", "two", "three"},
+		Args:      MinimumNArgs(1),
+	}
+
+	// Test that validArgs are completed
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		"three",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that validArgs are completed with prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "o")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"one",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that validArgs don't repeat
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "one", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsAndCmdCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use:       "root",
+		ValidArgs: []string{"one", "two"},
+		Run:       emptyRun,
+	}
+
+	childCmd := &Command{
+		Use: "thechild",
+		Run: emptyRun,
+	}
+
+	rootCmd.AddCommand(childCmd)
+
+	// Test that both sub-commands and validArgs are completed
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"help",
+		"thechild",
+		"one",
+		"two",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that both sub-commands and validArgs are completed with prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"thechild",
+		"two",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use:               "root",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+
+	childCmd := &Command{
+		Use:   "thechild",
+		Short: "The child command",
+		Run:   emptyRun,
+	}
+
+	rootCmd.AddCommand(childCmd)
+
+	// Test that both sub-commands and validArgsFunction are completed
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"help",
+		"thechild",
+		"one",
+		"two",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that both sub-commands and validArgs are completed with prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"thechild",
+		"two",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that both sub-commands and validArgs are completed with description
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"thechild\tThe child command",
+		"two\tThe second",
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagNameCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd := &Command{
+		Use: "childCmd",
+		Run: emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	rootCmd.Flags().IntP("first", "f", -1, "first flag")
+	rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
+	childCmd.Flags().String("subFlag", "", "sub flag")
+
+	// Test that flag names are not shown if the user has not given the '-' prefix
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"childCmd",
+		"help",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--first",
+		"-f",
+		"--second",
+		"-s",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed when a prefix is given
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--f")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--first",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed in a sub-cmd
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--second",
+		"-s",
+		"--subFlag",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd := &Command{
+		Use:   "childCmd",
+		Short: "first command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	rootCmd.Flags().IntP("first", "f", -1, "first flag")
+	rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
+	childCmd.Flags().String("subFlag", "", "sub flag")
+
+	// Test that flag names are not shown if the user has not given the '-' prefix
+	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"childCmd\tfirst command",
+		"help\tHelp about any command",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--first\tfirst flag",
+		"-f\tfirst flag",
+		"--second\tsecond flag",
+		"-s\tsecond flag",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed when a prefix is given
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--f")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--first\tfirst flag",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are completed in a sub-cmd
+	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "childCmd", "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--second\tsecond flag",
+		"-s\tsecond flag",
+		"--subFlag\tsub flag",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagNameCompletionRepeat(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd := &Command{
+		Use:   "childCmd",
+		Short: "first command",
+		Run:   emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	rootCmd.Flags().IntP("first", "f", -1, "first flag")
+	firstFlag := rootCmd.Flags().Lookup("first")
+	rootCmd.Flags().BoolP("second", "s", false, "second flag")
+	secondFlag := rootCmd.Flags().Lookup("second")
+	rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
+	arrayFlag := rootCmd.Flags().Lookup("array")
+	rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
+	sliceFlag := rootCmd.Flags().Lookup("slice")
+	rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
+	bsliceFlag := rootCmd.Flags().Lookup("bslice")
+
+	// Test that flag names are not repeated unless they are an array or slice
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	firstFlag.Changed = false
+
+	expected := strings.Join([]string{
+		"--array",
+		"--bslice",
+		"--second",
+		"--slice",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are not repeated unless they are an array or slice
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	firstFlag.Changed = false
+	secondFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"--array",
+		"--bslice",
+		"--slice",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are not repeated unless they are an array or slice
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	sliceFlag.Changed = false
+	arrayFlag.Changed = false
+	bsliceFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"--array",
+		"--bslice",
+		"--first",
+		"--second",
+		"--slice",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are not repeated unless they are an array or slice, using shortname
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	sliceFlag.Changed = false
+	arrayFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"--array",
+		"-a",
+		"--bslice",
+		"-b",
+		"--first",
+		"-f",
+		"--second",
+		"-s",
+		"--slice",
+		"-l",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	sliceFlag.Changed = false
+	arrayFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"-a",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestRequiredFlagNameCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use:       "root",
+		ValidArgs: []string{"realArg"},
+		Run:       emptyRun,
+	}
+	childCmd := &Command{
+		Use: "childCmd",
+		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+			return []string{"subArg"}, ShellCompDirectiveNoFileComp
+		},
+		Run: emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag")
+	rootCmd.MarkFlagRequired("requiredFlag")
+	requiredFlag := rootCmd.Flags().Lookup("requiredFlag")
+
+	rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent")
+	rootCmd.MarkPersistentFlagRequired("requiredPersistent")
+	requiredPersistent := rootCmd.PersistentFlags().Lookup("requiredPersistent")
+
+	rootCmd.Flags().StringP("release", "R", "", "Release name")
+
+	childCmd.Flags().BoolP("subRequired", "s", false, "sub required flag")
+	childCmd.MarkFlagRequired("subRequired")
+	childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag")
+
+	// Test that a required flag is suggested even without the - prefix
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"childCmd",
+		"help",
+		"--requiredFlag",
+		"-r",
+		"--requiredPersistent",
+		"-p",
+		"realArg",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that a required flag is suggested without other flags when using the '-' prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--requiredFlag",
+		"-r",
+		"--requiredPersistent",
+		"-p",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that if no required flag matches, the normal flags are suggested
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--relea")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--release",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test required flags for sub-commands
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--requiredPersistent",
+		"-p",
+		"--subRequired",
+		"-s",
+		"subArg",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--requiredPersistent",
+		"-p",
+		"--subRequired",
+		"-s",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "--subNot")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"--subNotRequired",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that when a required flag is present, it is not suggested anymore
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	requiredFlag.Changed = false
+
+	expected = strings.Join([]string{
+		"--requiredPersistent",
+		"-p",
+		"realArg",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that when a persistent required flag is present, it is not suggested anymore
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredPersistent", "1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flag for the next command
+	requiredPersistent.Changed = false
+
+	expected = strings.Join([]string{
+		"childCmd",
+		"help",
+		"--requiredFlag",
+		"-r",
+		"realArg",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that when all required flags are present, normal completion is done
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "--requiredPersistent", "1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	// Reset the flags for the next command
+	requiredFlag.Changed = false
+	requiredPersistent.Changed = false
+
+	expected = strings.Join([]string{
+		"realArg",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagFileExtFilterCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	// No extensions.  Should be ignored.
+	rootCmd.Flags().StringP("file", "f", "", "file flag")
+	rootCmd.MarkFlagFilename("file")
+
+	// Single extension
+	rootCmd.Flags().StringP("log", "l", "", "log flag")
+	rootCmd.MarkFlagFilename("log", "log")
+
+	// Multiple extensions
+	rootCmd.Flags().StringP("yaml", "y", "", "yaml flag")
+	rootCmd.MarkFlagFilename("yaml", "yaml", "yml")
+
+	// Directly using annotation
+	rootCmd.Flags().StringP("text", "t", "", "text flag")
+	rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"})
+
+	// Test that the completion logic returns the proper info for the completion
+	// script to handle the file filtering
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--file", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--log", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"log",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"yaml", "yml",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml=")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"yaml", "yml",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"yaml", "yml",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y=")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"yaml", "yml",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--text", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"txt",
+		":8",
+		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestFlagDirFilterCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+
+	// Filter directories
+	rootCmd.Flags().StringP("dir", "d", "", "dir flag")
+	rootCmd.MarkFlagDirname("dir")
+
+	// Filter directories within a directory
+	rootCmd.Flags().StringP("subdir", "s", "", "subdir")
+	rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"})
+
+	// Multiple directory specification get ignored
+	rootCmd.Flags().StringP("manydir", "m", "", "manydir")
+	rootCmd.Flags().SetAnnotation("manydir", BashCompSubdirsInDir, []string{"themes", "colors"})
+
+	// Test that the completion logic returns the proper info for the completion
+	// script to handle the directory filtering
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--dir", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-d", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"themes",
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir=")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"themes",
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"themes",
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s=")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"themes",
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--manydir", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":16",
+		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
 func TestValidArgsFuncSingleCmd(t *testing.T) {
 	rootCmd := &Command{
 		Use:               "root",
@@ -340,6 +1440,39 @@ func TestCompleteCmdInFishScript(t *testing.T) {
 	checkOmit(t, output, ShellCompNoDescRequestCmd)
 }
 
+func TestCompleteNoDesCmdInZshScript(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.GenZshCompletionNoDesc(buf)
+	output := buf.String()
+
+	check(t, output, ShellCompNoDescRequestCmd)
+}
+
+func TestCompleteCmdInZshScript(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.GenZshCompletion(buf)
+	output := buf.String()
+
+	check(t, output, ShellCompRequestCmd)
+	checkOmit(t, output, ShellCompNoDescRequestCmd)
+}
+
 func TestFlagCompletionInGo(t *testing.T) {
 	rootCmd := &Command{
 		Use: "root",
@@ -630,6 +1763,107 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) {
 	}
 }
 
+func TestValidArgsNotValidArgsFunc(t *testing.T) {
+	rootCmd := &Command{
+		Use:       "root",
+		ValidArgs: []string{"one", "two"},
+		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+			return []string{"three", "four"}, ShellCompDirectiveNoFileComp
+		},
+		Run: emptyRun,
+	}
+
+	// Test that if both ValidArgs and ValidArgsFunction are present
+	// only ValidArgs is considered
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestArgAliasesCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use:        "root",
+		Args:       OnlyValidArgs,
+		ValidArgs:  []string{"one", "two", "three"},
+		ArgAliases: []string{"un", "deux", "trois"},
+		Run:        emptyRun,
+	}
+
+	// Test that argaliases are not completed when there are validargs that match
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		"three",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that argaliases are not completed when there are validargs that match using a prefix
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two",
+		"three",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that argaliases are completed when there are no validargs that match
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "tr")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"trois",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
 func TestCompleteHelp(t *testing.T) {
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
 	child1Cmd := &Command{
@@ -658,8 +1892,8 @@ func TestCompleteHelp(t *testing.T) {
 		"child1",
 		"child2",
 		"help",
-		":0",
-		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
 
 	if output != expected {
 		t.Errorf("expected: %q, got: %q", expected, output)
diff --git a/zsh_completions_test.go b/zsh_completions_test.go
deleted file mode 100644
index e53fa88..0000000
--- a/zsh_completions_test.go
+++ /dev/null
@@ -1,475 +0,0 @@
-package cobra
-
-import (
-	"bytes"
-	"regexp"
-	"strings"
-	"testing"
-)
-
-func TestGenZshCompletion(t *testing.T) {
-	var debug bool
-	var option string
-
-	tcs := []struct {
-		name                string
-		root                *Command
-		expectedExpressions []string
-		invocationArgs      []string
-		skip                string
-	}{
-		{
-			name: "simple command",
-			root: func() *Command {
-				r := &Command{
-					Use:  "mycommand",
-					Long: "My Command long description",
-					Run:  emptyRun,
-				}
-				r.Flags().BoolVar(&debug, "debug", debug, "description")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`(?s)function _mycommand {\s+_arguments \\\s+'--debug\[description\]'.*--help.*}`,
-				"#compdef _mycommand mycommand",
-			},
-		},
-		{
-			name: "flags with both long and short flags",
-			root: func() *Command {
-				r := &Command{
-					Use:  "testcmd",
-					Long: "long description",
-					Run:  emptyRun,
-				}
-				r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'\(-d --debug\)'{-d,--debug}'\[debug description\]'`,
-			},
-		},
-		{
-			name: "command with subcommands and flags with values",
-			root: func() *Command {
-				r := &Command{
-					Use:  "rootcmd",
-					Long: "Long rootcmd description",
-				}
-				d := &Command{
-					Use:   "subcmd1",
-					Short: "Subcmd1 short description",
-					Run:   emptyRun,
-				}
-				e := &Command{
-					Use:  "subcmd2",
-					Long: "Subcmd2 short description",
-					Run:  emptyRun,
-				}
-				r.PersistentFlags().BoolVar(&debug, "debug", debug, "description")
-				d.Flags().StringVarP(&option, "option", "o", option, "option description")
-				r.AddCommand(d, e)
-				return r
-			}(),
-			expectedExpressions: []string{
-				`commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`,
-				`_arguments \\\n.*'--debug\[description]'`,
-				`_arguments -C \\\n.*'--debug\[description]'`,
-				`function _rootcmd_subcmd1 {`,
-				`function _rootcmd_subcmd1 {`,
-				`_arguments \\\n.*'\(-o --option\)'{-o,--option}'\[option description]:' \\\n`,
-			},
-		},
-		{
-			name: "filename completion with and without globs",
-			root: func() *Command {
-				var file string
-				r := &Command{
-					Use:   "mycmd",
-					Short: "my command short description",
-					Run:   emptyRun,
-				}
-				r.Flags().StringVarP(&file, "config", "c", file, "config file")
-				r.MarkFlagFilename("config")
-				r.Flags().String("output", "", "output file")
-				r.MarkFlagFilename("output", "*.log", "*.txt")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`\n +'\(-c --config\)'{-c,--config}'\[config file]:filename:_files'`,
-				`:_files -g "\*.log" -g "\*.txt"`,
-			},
-		},
-		{
-			name: "repeated variables both with and without value",
-			root: func() *Command {
-				r := genTestCommand("mycmd", true)
-				_ = r.Flags().BoolSliceP("debug", "d", []bool{}, "debug usage")
-				_ = r.Flags().StringArray("option", []string{}, "options")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'\*--option\[options]`,
-				`'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`,
-			},
-		},
-		{
-			name: "generated flags --help and --version should be created even when not executing root cmd",
-			root: func() *Command {
-				r := &Command{
-					Use:     "mycmd",
-					Short:   "mycmd short description",
-					Version: "myversion",
-				}
-				s := genTestCommand("sub1", true)
-				r.AddCommand(s)
-				return s
-			}(),
-			expectedExpressions: []string{
-				"--version",
-				"--help",
-			},
-			invocationArgs: []string{
-				"sub1",
-			},
-			skip: "--version and --help are currently not generated when not running on root command",
-		},
-		{
-			name: "zsh generation should run on root command",
-			root: func() *Command {
-				r := genTestCommand("root", false)
-				s := genTestCommand("sub1", true)
-				r.AddCommand(s)
-				return s
-			}(),
-			expectedExpressions: []string{
-				"function _root {",
-			},
-		},
-		{
-			name: "flag description with single quote (') shouldn't break quotes in completion file",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.Flags().Bool("private", false, "Don't show public info")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`--private\[Don'\\''t show public info]`,
-			},
-		},
-		{
-			name: "argument completion for file with and without patterns",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.MarkZshCompPositionalArgumentFile(1, "*.log")
-				r.MarkZshCompPositionalArgumentFile(2)
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'1: :_files -g "\*.log"' \\\n\s+'2: :_files`,
-			},
-		},
-		{
-			name: "argument zsh completion for words",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.MarkZshCompPositionalArgumentWords(1, "word1", "word2")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'1: :\("word1" "word2"\)`,
-			},
-		},
-		{
-			name: "argument completion for words with spaces",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.MarkZshCompPositionalArgumentWords(1, "single", "multiple words")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'1: :\("single" "multiple words"\)'`,
-			},
-		},
-		{
-			name: "argument completion when command has ValidArgs and no annotation for argument completion",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.ValidArgs = []string{"word1", "word2"}
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'1: :\("word1" "word2"\)'`,
-			},
-		},
-		{
-			name: "argument completion when command has ValidArgs and no annotation for argument at argPosition 1",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.ValidArgs = []string{"word1", "word2"}
-				r.MarkZshCompPositionalArgumentFile(2)
-				return r
-			}(),
-			expectedExpressions: []string{
-				`'1: :\("word1" "word2"\)' \\`,
-			},
-		},
-		{
-			name: "directory completion for flag",
-			root: func() *Command {
-				r := genTestCommand("root", true)
-				r.Flags().String("test", "", "test")
-				r.PersistentFlags().String("ptest", "", "ptest")
-				r.MarkFlagDirname("test")
-				r.MarkPersistentFlagDirname("ptest")
-				return r
-			}(),
-			expectedExpressions: []string{
-				`--test\[test]:filename:_files -g "-\(/\)"`,
-				`--ptest\[ptest]:filename:_files -g "-\(/\)"`,
-			},
-		},
-	}
-
-	for _, tc := range tcs {
-		t.Run(tc.name, func(t *testing.T) {
-			if tc.skip != "" {
-				t.Skip(tc.skip)
-			}
-			tc.root.Root().SetArgs(tc.invocationArgs)
-			tc.root.Execute()
-			buf := new(bytes.Buffer)
-			if err := tc.root.GenZshCompletion(buf); err != nil {
-				t.Error(err)
-			}
-			output := buf.Bytes()
-
-			for _, expr := range tc.expectedExpressions {
-				rgx, err := regexp.Compile(expr)
-				if err != nil {
-					t.Errorf("error compiling expression (%s): %v", expr, err)
-				}
-				if !rgx.Match(output) {
-					t.Errorf("expected completion (%s) to match '%s'", buf.String(), expr)
-				}
-			}
-		})
-	}
-}
-
-func TestGenZshCompletionHidden(t *testing.T) {
-	tcs := []struct {
-		name                string
-		root                *Command
-		expectedExpressions []string
-	}{
-		{
-			name: "hidden commands",
-			root: func() *Command {
-				r := &Command{
-					Use:   "main",
-					Short: "main short description",
-				}
-				s1 := &Command{
-					Use:    "sub1",
-					Hidden: true,
-					Run:    emptyRun,
-				}
-				s2 := &Command{
-					Use:   "sub2",
-					Short: "short sub2 description",
-					Run:   emptyRun,
-				}
-				r.AddCommand(s1, s2)
-
-				return r
-			}(),
-			expectedExpressions: []string{
-				"sub1",
-			},
-		},
-		{
-			name: "hidden flags",
-			root: func() *Command {
-				var hidden string
-				r := &Command{
-					Use:   "root",
-					Short: "root short description",
-					Run:   emptyRun,
-				}
-				r.Flags().StringVarP(&hidden, "hidden", "H", hidden, "hidden usage")
-				if err := r.Flags().MarkHidden("hidden"); err != nil {
-					t.Errorf("Error setting flag hidden: %v\n", err)
-				}
-				return r
-			}(),
-			expectedExpressions: []string{
-				"--hidden",
-			},
-		},
-	}
-
-	for _, tc := range tcs {
-		t.Run(tc.name, func(t *testing.T) {
-			tc.root.Execute()
-			buf := new(bytes.Buffer)
-			if err := tc.root.GenZshCompletion(buf); err != nil {
-				t.Error(err)
-			}
-			output := buf.String()
-
-			for _, expr := range tc.expectedExpressions {
-				if strings.Contains(output, expr) {
-					t.Errorf("Expected completion (%s) not to contain '%s' but it does", output, expr)
-				}
-			}
-		})
-	}
-}
-
-func TestMarkZshCompPositionalArgumentFile(t *testing.T) {
-	t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) {
-		c := &Command{}
-		if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil {
-			t.Errorf("Received error when we shouldn't have: %v\n", err)
-		}
-		if err := c.MarkZshCompPositionalArgumentFile(1); err == nil {
-			t.Error("Didn't receive an error when trying to overwrite argument position")
-		}
-	})
-
-	t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) {
-		c := &Command{}
-		err := c.MarkZshCompPositionalArgumentFile(0, "*")
-		if err == nil {
-			t.Fatal("Error was not thrown when indicating argument position 0")
-		}
-		if !strings.Contains(err.Error(), "position") {
-			t.Errorf("expected error message '%s' to contain 'position'", err.Error())
-		}
-	})
-}
-
-func TestMarkZshCompPositionalArgumentWords(t *testing.T) {
-	t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) {
-		c := &Command{}
-		if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil {
-			t.Errorf("Received error when we shouldn't have: %v\n", err)
-		}
-		if err := c.MarkZshCompPositionalArgumentWords(1, "hello"); err == nil {
-			t.Error("Didn't receive an error when trying to overwrite argument position")
-		}
-	})
-
-	t.Run("Doesn't allow calling without words", func(t *testing.T) {
-		c := &Command{}
-		if err := c.MarkZshCompPositionalArgumentWords(0); err == nil {
-			t.Error("Should not allow saving empty word list for annotation")
-		}
-	})
-
-	t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) {
-		c := &Command{}
-		err := c.MarkZshCompPositionalArgumentWords(0, "word")
-		if err == nil {
-			t.Fatal("Should not allow setting argument position less then 1")
-		}
-		if !strings.Contains(err.Error(), "position") {
-			t.Errorf("Expected error '%s' to contain 'position' but didn't", err.Error())
-		}
-	})
-}
-
-func BenchmarkMediumSizeConstruct(b *testing.B) {
-	root := constructLargeCommandHierarchy()
-	// if err := root.GenZshCompletionFile("_mycmd"); err != nil {
-	// 	b.Error(err)
-	// }
-
-	for i := 0; i < b.N; i++ {
-		buf := new(bytes.Buffer)
-		err := root.GenZshCompletion(buf)
-		if err != nil {
-			b.Error(err)
-		}
-	}
-}
-
-func TestExtractFlags(t *testing.T) {
-	var debug, cmdc, cmdd bool
-	c := &Command{
-		Use:  "cmdC",
-		Long: "Command C",
-	}
-	c.PersistentFlags().BoolVarP(&debug, "debug", "d", debug, "debug mode")
-	c.Flags().BoolVar(&cmdc, "cmd-c", cmdc, "Command C")
-	d := &Command{
-		Use:  "CmdD",
-		Long: "Command D",
-	}
-	d.Flags().BoolVar(&cmdd, "cmd-d", cmdd, "Command D")
-	c.AddCommand(d)
-
-	resC := zshCompExtractFlag(c)
-	resD := zshCompExtractFlag(d)
-
-	if len(resC) != 2 {
-		t.Errorf("expected Command C to return 2 flags, got %d", len(resC))
-	}
-	if len(resD) != 2 {
-		t.Errorf("expected Command D to return 2 flags, got %d", len(resD))
-	}
-}
-
-func constructLargeCommandHierarchy() *Command {
-	var config, st1, st2 string
-	var long, debug bool
-	var in1, in2 int
-	var verbose []bool
-
-	r := genTestCommand("mycmd", false)
-	r.PersistentFlags().StringVarP(&config, "config", "c", config, "config usage")
-	if err := r.MarkPersistentFlagFilename("config", "*"); err != nil {
-		panic(err)
-	}
-	s1 := genTestCommand("sub1", true)
-	s1.Flags().BoolVar(&long, "long", long, "long description")
-	s1.Flags().BoolSliceVar(&verbose, "verbose", verbose, "verbose description")
-	s1.Flags().StringArray("option", []string{}, "various options")
-	s2 := genTestCommand("sub2", true)
-	s2.PersistentFlags().BoolVar(&debug, "debug", debug, "debug description")
-	s3 := genTestCommand("sub3", true)
-	s3.Hidden = true
-	s1_1 := genTestCommand("sub1sub1", true)
-	s1_1.Flags().StringVar(&st1, "st1", st1, "st1 description")
-	s1_1.Flags().StringVar(&st2, "st2", st2, "st2 description")
-	s1_2 := genTestCommand("sub1sub2", true)
-	s1_3 := genTestCommand("sub1sub3", true)
-	s1_3.Flags().IntVar(&in1, "int1", in1, "int1 description")
-	s1_3.Flags().IntVar(&in2, "int2", in2, "int2 description")
-	s1_3.Flags().StringArrayP("option", "O", []string{}, "more options")
-	s2_1 := genTestCommand("sub2sub1", true)
-	s2_2 := genTestCommand("sub2sub2", true)
-	s2_3 := genTestCommand("sub2sub3", true)
-	s2_4 := genTestCommand("sub2sub4", true)
-	s2_5 := genTestCommand("sub2sub5", true)
-
-	s1.AddCommand(s1_1, s1_2, s1_3)
-	s2.AddCommand(s2_1, s2_2, s2_3, s2_4, s2_5)
-	r.AddCommand(s1, s2, s3)
-	r.Execute()
-	return r
-}
-
-func genTestCommand(name string, withRun bool) *Command {
-	r := &Command{
-		Use:   name,
-		Short: name + " short description",
-		Long:  "Long description for " + name,
-	}
-	if withRun {
-		r.Run = emptyRun
-	}
-
-	return r
-}