Repository: spf13/cobra
Pull request #1237 (2020-09-25 15:30:46)

Fix zsh completion not working on the first time in a shell session
The zsh completion script output by cobra is a stub completion function which replaces itself with the actual completion function. This technique enables cobra to define helper functions without splitting the completion script into multiple files.  However, the current implementation forgets to call the actual completion function at the end of the stub function, meaning that completion won't work the first time it's invoked in a shell session. This PR is a fix for this problem.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/cobra?pullRequest=1237) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/cobra?pullRequest=1237) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/cobra?pullRequest=1237) it.</sub>
marckhouzam: Hi @midchildan 

> The zsh completion script output by cobra is a stub completion function which replaces itself with the actual completion function. 

Actually, the completion script is the one that generates the completions.  It does not replace itself with anything.

> However, the current implementation forgets to call the actual completion function at the end of the stub function, meaning that completion won't work the first time it's invoked in a shell session.

Could you clarify the steps to cause this problem?  The script generated by Cobra should work right away if you put it in a directory that is on $fpath
midchildan: AFAIK the completion script provided by cobra is an autoloaded function, meaning that it's a lazy-loaded function whose contents are read from the corresponding file in `fpath`. cobra's completion function redefines itself upon first invocation by defining a function with the exact same name as itself in the code below:

https://github.com/spf13/cobra/blob/0bc8bfbe596beeea88b39beb0fca86b3d7d10de6/zsh_completions.go#L90

This redefined completion function would be called when the user invokes the completion a second time, but it won't work on initial invocation because this redefined function isn't called anywhere in the initial function.
midchildan: This problem can be seen with the GitHub CLI, which uses cobra. Although it uses cobra `1.0.0` and the code is quite different from `master`, it suffers from the same problem. You have to press `TAB` twice to trigger completion the for first time.
midchildan: Just for reference,

Example completion script from zsh-completions which does something similar:
https://github.com/zsh-users/zsh-completions/blob/master/src/_cheat

Relevant documentation on autoloaded functions:
http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Autoloaded-files
marckhouzam: @midchildan thank you for the explanation.  I see you are correct.  I learn new things every day 😄 

On a fresh zsh shell where the script is part of my `$fpath`, I can see:
```
$ declare -f _helm
_helm () {
	# undefined
	builtin autoload -XUz
}
```
Which does cause the need to press `<TAB>` twice for the very first completions.
The first tab will trigger the `_helm()` function above to load the real `_helm()` function but that's it, and the second tab will trigger the real `_helm()` function to provide the completions.

With your PR, the first tab press will trigger the above `_helm()` function to load the Cobra script AND that script will immediately call the new `_helm()` function to provide the completions.
marckhouzam: Ah but this causes a limitation in the fact that the Cobra script can no longer be sourced.

Some programs choose to append the (adapted) following line when generating the Cobra script,
```
compdef _program program
```
 to allow users to do something like:
```
source <(program completion zsh)
```
With this PR, it won't work anymore and complain that the `_program` function cannot be called outside of a completion function:
```
$ source <(./helm completion zsh)
_arguments:comparguments:325: can only be called from completion function
```
midchildan: I vaguely recall there was a way to detect if the running code is in completion context, but I'm not certain.
midchildan: I couldn't find it, so I might've been confusing it with something else. I took an alternative approach and decided to check the function call stack instead. Here's a quick demonstration to show that it works:

```console
% f() { f() { echo "real f is called" }; if [ "$funcstack[1]" != "f" ]; then f; fi }; f
real f is called
% source <(echo 'if [ "$funcstack[1]" = "f" ]; then f; fi')
```
marckhouzam: @midchildan I thought you had it!  But this case fails:
```
$ ./helm completion zsh _helm
$ source _helm
_arguments:comparguments:325: can only be called from completion function
```
Is there a simple way to include this case?  If not, maybe it's still ok...
midchildan: I was able to source this script without problems, though.

<details>

```zsh
#compdef _gh gh


function _gh {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    '--version[Show gh version]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "alias:Create command shortcuts"
      "api:Make an authenticated GitHub API request"
      "auth:Login, logout, and refresh your authentication"
      "completion:Generate shell completion scripts"
      "config:Manage configuration for gh"
      "gist:Create gists"
      "help:Help about any command"
      "issue:Manage issues"
      "pr:Manage pull requests"
      "release:Manage GitHub releases"
      "repo:Create, clone, fork, and view repositories"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  alias)
    _gh_alias
    ;;
  api)
    _gh_api
    ;;
  auth)
    _gh_auth
    ;;
  completion)
    _gh_completion
    ;;
  config)
    _gh_config
    ;;
  gist)
    _gh_gist
    ;;
  help)
    _gh_help
    ;;
  issue)
    _gh_issue
    ;;
  pr)
    _gh_pr
    ;;
  release)
    _gh_release
    ;;
  repo)
    _gh_repo
    ;;
  esac
}


function _gh_alias {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "delete:Delete an alias"
      "list:List your aliases"
      "set:Create a shortcut for a gh command"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  delete)
    _gh_alias_delete
    ;;
  list)
    _gh_alias_list
    ;;
  set)
    _gh_alias_set
    ;;
  esac
}

function _gh_alias_delete {
  _arguments \
    '--help[Show help for command]'
}

function _gh_alias_list {
  _arguments \
    '--help[Show help for command]'
}

function _gh_alias_set {
  _arguments \
    '(-s --shell)'{-s,--shell}'[Declare an alias to be passed through a shell interpreter]' \
    '--help[Show help for command]'
}

function _gh_api {
  _arguments \
    '(*-F *--field)'{\*-F,\*--field}'[Add a parameter of inferred type]:' \
    '(*-H *--header)'{\*-H,\*--header}'[Add an additional HTTP request header]:' \
    '(-i --include)'{-i,--include}'[Include HTTP response headers in the output]' \
    '--input[The file to use as body for the HTTP request]:' \
    '(-X --method)'{-X,--method}'[The HTTP method for the request]:' \
    '--paginate[Make additional HTTP requests to fetch all pages of results]' \
    '(*-f *--raw-field)'{\*-f,\*--raw-field}'[Add a string parameter]:' \
    '--silent[Do not print the response body]' \
    '--help[Show help for command]'
}


function _gh_auth {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "login:Authenticate with a GitHub host"
      "logout:Log out of a GitHub host"
      "refresh:Refresh stored authentication credentials"
      "status:View authentication status"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  login)
    _gh_auth_login
    ;;
  logout)
    _gh_auth_logout
    ;;
  refresh)
    _gh_auth_refresh
    ;;
  status)
    _gh_auth_status
    ;;
  esac
}

function _gh_auth_login {
  _arguments \
    '(-h --hostname)'{-h,--hostname}'[The hostname of the GitHub instance to authenticate with]:' \
    '(-w --web)'{-w,--web}'[Open a browser to authenticate]' \
    '--with-token[Read token from standard input]' \
    '--help[Show help for command]'
}

function _gh_auth_logout {
  _arguments \
    '(-h --hostname)'{-h,--hostname}'[The hostname of the GitHub instance to log out of]:' \
    '--help[Show help for command]'
}

function _gh_auth_refresh {
  _arguments \
    '(-h --hostname)'{-h,--hostname}'[The GitHub host to use for authentication]:' \
    '(*-s *--scopes)'{\*-s,\*--scopes}'[Additional authentication scopes for gh to have]:' \
    '--help[Show help for command]'
}

function _gh_auth_status {
  _arguments \
    '(-h --hostname)'{-h,--hostname}'[Check a specific hostname'\''s auth status]:' \
    '--help[Show help for command]'
}

function _gh_completion {
  _arguments \
    '(-s --shell)'{-s,--shell}'[Shell type: {bash|zsh|fish|powershell}]:' \
    '--help[Show help for command]'
}


function _gh_config {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "get:Print the value of a given configuration key"
      "set:Update configuration with a value for the given key"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  get)
    _gh_config_get
    ;;
  set)
    _gh_config_set
    ;;
  esac
}

function _gh_config_get {
  _arguments \
    '(-h --host)'{-h,--host}'[Get per-host setting]:' \
    '--help[Show help for command]'
}

function _gh_config_set {
  _arguments \
    '(-h --host)'{-h,--host}'[Set per-host setting]:' \
    '--help[Show help for command]'
}


function _gh_gist {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "create:Create a new gist"
      "edit:Edit one of your gists"
      "list:List your gists"
      "view:View a gist"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  create)
    _gh_gist_create
    ;;
  edit)
    _gh_gist_edit
    ;;
  list)
    _gh_gist_list
    ;;
  view)
    _gh_gist_view
    ;;
  esac
}

function _gh_gist_create {
  _arguments \
    '(-d --desc)'{-d,--desc}'[A description for this gist]:' \
    '(-f --filename)'{-f,--filename}'[Provide a filename to be used when reading from STDIN]:' \
    '(-p --public)'{-p,--public}'[List the gist publicly (default: private)]' \
    '--help[Show help for command]'
}

function _gh_gist_edit {
  _arguments \
    '(-f --filename)'{-f,--filename}'[a specific file to edit]:' \
    '--help[Show help for command]'
}

function _gh_gist_list {
  _arguments \
    '(-L --limit)'{-L,--limit}'[Maximum number of gists to fetch]:' \
    '--public[Show only public gists]' \
    '--secret[Show only secret gists]' \
    '--help[Show help for command]'
}

function _gh_gist_view {
  _arguments \
    '(-f --filename)'{-f,--filename}'[display a single file of the gist]:' \
    '(-r --raw)'{-r,--raw}'[do not try and render markdown]' \
    '(-w --web)'{-w,--web}'[open gist in browser]' \
    '--help[Show help for command]'
}

function _gh_help {
  _arguments \
    '--help[Show help for command]'
}


function _gh_issue {
  local -a commands

  _arguments -C \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:' \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "close:Close issue"
      "create:Create a new issue"
      "list:List and filter issues in this repository"
      "reopen:Reopen issue"
      "status:Show status of relevant issues"
      "view:View an issue"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  close)
    _gh_issue_close
    ;;
  create)
    _gh_issue_create
    ;;
  list)
    _gh_issue_list
    ;;
  reopen)
    _gh_issue_reopen
    ;;
  status)
    _gh_issue_status
    ;;
  view)
    _gh_issue_view
    ;;
  esac
}

function _gh_issue_close {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_issue_create {
  _arguments \
    '(*-a *--assignee)'{\*-a,\*--assignee}'[Assign people by their `login`]:' \
    '(-b --body)'{-b,--body}'[Supply a body. Will prompt for one otherwise.]:' \
    '(*-l *--label)'{\*-l,\*--label}'[Add labels by `name`]:' \
    '(-m --milestone)'{-m,--milestone}'[Add the issue to a milestone by `name`]:' \
    '(*-p *--project)'{\*-p,\*--project}'[Add the issue to projects by `name`]:' \
    '(-t --title)'{-t,--title}'[Supply a title. Will prompt for one otherwise.]:' \
    '(-w --web)'{-w,--web}'[Open the browser to create an issue]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_issue_list {
  _arguments \
    '(-a --assignee)'{-a,--assignee}'[Filter by assignee]:' \
    '(-A --author)'{-A,--author}'[Filter by author]:' \
    '(*-l *--label)'{\*-l,\*--label}'[Filter by labels]:' \
    '(-L --limit)'{-L,--limit}'[Maximum number of issues to fetch]:' \
    '--mention[Filter by mention]:' \
    '(-m --milestone)'{-m,--milestone}'[Filter by milestone `number` or `title`]:' \
    '(-s --state)'{-s,--state}'[Filter by state: {open|closed|all}]:' \
    '(-w --web)'{-w,--web}'[Open the browser to list the issue(s)]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_issue_reopen {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_issue_status {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_issue_view {
  _arguments \
    '(-w --web)'{-w,--web}'[Open an issue in the browser]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}


function _gh_pr {
  local -a commands

  _arguments -C \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:' \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "checkout:Check out a pull request in git"
      "checks:Show CI status for a single pull request"
      "close:Close a pull request"
      "create:Create a pull request"
      "diff:View changes in a pull request"
      "list:List and filter pull requests in this repository"
      "merge:Merge a pull request"
      "ready:Mark a pull request as ready for review"
      "reopen:Reopen a pull request"
      "review:Add a review to a pull request"
      "status:Show status of relevant pull requests"
      "view:View a pull request"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  checkout)
    _gh_pr_checkout
    ;;
  checks)
    _gh_pr_checks
    ;;
  close)
    _gh_pr_close
    ;;
  create)
    _gh_pr_create
    ;;
  diff)
    _gh_pr_diff
    ;;
  list)
    _gh_pr_list
    ;;
  merge)
    _gh_pr_merge
    ;;
  ready)
    _gh_pr_ready
    ;;
  reopen)
    _gh_pr_reopen
    ;;
  review)
    _gh_pr_review
    ;;
  status)
    _gh_pr_status
    ;;
  view)
    _gh_pr_view
    ;;
  esac
}

function _gh_pr_checkout {
  _arguments \
    '--recurse-submodules[Update all active submodules (recursively)]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_checks {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_close {
  _arguments \
    '(-d --delete-branch)'{-d,--delete-branch}'[Delete the local and remote branch after close]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_create {
  _arguments \
    '(*-a *--assignee)'{\*-a,\*--assignee}'[Assign people by their `login`]:' \
    '(-B --base)'{-B,--base}'[The `branch` into which you want your code merged]:' \
    '(-b --body)'{-b,--body}'[Body for the pull request]:' \
    '(-d --draft)'{-d,--draft}'[Mark pull request as a draft]' \
    '(-f --fill)'{-f,--fill}'[Do not prompt for title/body and just use commit info]' \
    '(-H --head)'{-H,--head}'[The `branch` that contains commits for your pull request (default: current branch)]:' \
    '(*-l *--label)'{\*-l,\*--label}'[Add labels by `name`]:' \
    '(-m --milestone)'{-m,--milestone}'[Add the pull request to a milestone by `name`]:' \
    '(*-p *--project)'{\*-p,\*--project}'[Add the pull request to projects by `name`]:' \
    '(*-r *--reviewer)'{\*-r,\*--reviewer}'[Request reviews from people by their `login`]:' \
    '(-t --title)'{-t,--title}'[Title for the pull request]:' \
    '(-w --web)'{-w,--web}'[Open the web browser to create a pull request]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_diff {
  _arguments \
    '--color[Use color in diff output: {always|never|auto}]:' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_list {
  _arguments \
    '(-a --assignee)'{-a,--assignee}'[Filter by assignee]:' \
    '(-B --base)'{-B,--base}'[Filter by base branch]:' \
    '(*-l *--label)'{\*-l,\*--label}'[Filter by labels]:' \
    '(-L --limit)'{-L,--limit}'[Maximum number of items to fetch]:' \
    '(-s --state)'{-s,--state}'[Filter by state: {open|closed|merged|all}]:' \
    '(-w --web)'{-w,--web}'[Open the browser to list the pull requests]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_merge {
  _arguments \
    '(-d --delete-branch)'{-d,--delete-branch}'[Delete the local and remote branch after merge]' \
    '(-m --merge)'{-m,--merge}'[Merge the commits with the base branch]' \
    '(-r --rebase)'{-r,--rebase}'[Rebase the commits onto the base branch]' \
    '(-s --squash)'{-s,--squash}'[Squash the commits into one commit and merge it into the base branch]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_ready {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_reopen {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_review {
  _arguments \
    '(-a --approve)'{-a,--approve}'[Approve pull request]' \
    '(-b --body)'{-b,--body}'[Specify the body of a review]:' \
    '(-c --comment)'{-c,--comment}'[Comment on a pull request]' \
    '(-r --request-changes)'{-r,--request-changes}'[Request changes on a pull request]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_status {
  _arguments \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_pr_view {
  _arguments \
    '(-w --web)'{-w,--web}'[Open a pull request in the browser]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}


function _gh_release {
  local -a commands

  _arguments -C \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:' \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "create:Create a new release"
      "delete:Delete a release"
      "download:Download release assets"
      "list:List releases in a repository"
      "upload:Upload assets to a release"
      "view:View information about a release"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  create)
    _gh_release_create
    ;;
  delete)
    _gh_release_delete
    ;;
  download)
    _gh_release_download
    ;;
  list)
    _gh_release_list
    ;;
  upload)
    _gh_release_upload
    ;;
  view)
    _gh_release_view
    ;;
  esac
}

function _gh_release_create {
  _arguments \
    '(-d --draft)'{-d,--draft}'[Save the release as a draft instead of publishing it]' \
    '(-n --notes)'{-n,--notes}'[Release notes]:' \
    '(-F --notes-file)'{-F,--notes-file}'[Read release notes from `file`]:' \
    '(-p --prerelease)'{-p,--prerelease}'[Mark the release as a prerelease]' \
    '--target[Target `branch` or commit SHA (default: main branch)]:' \
    '(-t --title)'{-t,--title}'[Release title]:' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_release_delete {
  _arguments \
    '(-y --yes)'{-y,--yes}'[Skip the confirmation prompt]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_release_download {
  _arguments \
    '(-D --dir)'{-D,--dir}'[The directory to download files into]:' \
    '(*-p *--pattern)'{\*-p,\*--pattern}'[Download only assets that match a glob pattern]:' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_release_list {
  _arguments \
    '(-L --limit)'{-L,--limit}'[Maximum number of items to fetch]:' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_release_upload {
  _arguments \
    '--clobber[Overwrite existing assets of the same name]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}

function _gh_release_view {
  _arguments \
    '(-w --web)'{-w,--web}'[Open the release in the browser]' \
    '--help[Show help for command]' \
    '(-R --repo)'{-R,--repo}'[Select another repository using the `OWNER/REPO` format]:'
}


function _gh_repo {
  local -a commands

  _arguments -C \
    '--help[Show help for command]' \
    "1: :->cmnds" \
    "*::arg:->args"

  case $state in
  cmnds)
    commands=(
      "clone:Clone a repository locally"
      "create:Create a new repository"
      "fork:Create a fork of a repository"
      "view:View a repository"
    )
    _describe "command" commands
    ;;
  esac

  case "$words[1]" in
  clone)
    _gh_repo_clone
    ;;
  create)
    _gh_repo_create
    ;;
  fork)
    _gh_repo_fork
    ;;
  view)
    _gh_repo_view
    ;;
  esac
}

function _gh_repo_clone {
  _arguments \
    '--help[Show help for command]'
}

function _gh_repo_create {
  _arguments \
    '(-y --confirm)'{-y,--confirm}'[Confirm the submission directly]' \
    '(-d --description)'{-d,--description}'[Description of repository]:' \
    '--enable-issues[Enable issues in the new repository]' \
    '--enable-wiki[Enable wiki in the new repository]' \
    '(-h --homepage)'{-h,--homepage}'[Repository home page URL]:' \
    '--internal[Make the new repository internal]' \
    '--private[Make the new repository private]' \
    '--public[Make the new repository public]' \
    '(-t --team)'{-t,--team}'[The name of the organization team to be granted access]:' \
    '(-p --template)'{-p,--template}'[Make the new repository based on a template repository]:' \
    '--help[Show help for command]'
}

function _gh_repo_fork {
  _arguments \
    '--clone[Clone the fork {true|false}]' \
    '--remote[Add remote for fork {true|false}]' \
    '--help[Show help for command]'
}

function _gh_repo_view {
  _arguments \
    '(-w --web)'{-w,--web}'[Open a repository in the browser]' \
    '--help[Show help for command]'
}

if [ "$funcstack[1]" = "_gh" ]; then
	_gh
fi
```
</details>
midchildan: I looked into `helm completion zsh`, but it looks like helm isn't using cobra's completion generator.
marckhouzam: If you dump the `_gh` script to a file called `_gh` and then source that file, it will fail.  But we could consider that an unsupported case.

As for helm, it's waiting for the next Cobra release so it can use the new zsh completion.  I'm currently testing with a local branch pointing to your PR and calling the new code.
midchildan: It seems that I wasn't able to replicate the failure because I was using an older version of zsh. I got the same error when I tested using a newer version.

> If you dump the _gh script to a file called _gh and then source that file, it will fail. But we could consider that an unsupported case.

I personally think it's fine to not support this use case, because the only time you'd want to `source` or `eval` a completion script is when it's being generated each time. If it's on-disk, it would make more sense to place it in `fpath`. But I don't know how people actually use cobra, so I may well be wrong.

Patch:
diff --git a/README.md b/README.md
index dd937e5..b8cda7a 100644
--- a/README.md
+++ b/README.md
@@ -177,7 +177,7 @@ var rootCmd = &cobra.Command{
 
 func Execute() {
   if err := rootCmd.Execute(); err != nil {
-    fmt.Println(err)
+    fmt.Fprintln(os.Stderr, err)
     os.Exit(1)
   }
 }
diff --git a/command.go b/command.go
index 27d39d5..77b399e 100644
--- a/command.go
+++ b/command.go
@@ -367,7 +367,7 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
 		c.mergePersistentFlags()
 		err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
 		if err != nil {
-			c.Println(err)
+			c.PrintErrln(err)
 		}
 		return err
 	}
@@ -395,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
 		// See https://github.com/spf13/cobra/issues/1002
 		err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
 		if err != nil {
-			c.Println(err)
+			c.PrintErrln(err)
 		}
 	}
 }
@@ -938,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 			c = cmd
 		}
 		if !c.SilenceErrors {
-			c.Println("Error:", err.Error())
-			c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
+			c.PrintErrln("Error:", err.Error())
+			c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
 		}
 		return c, err
 	}
@@ -967,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 		// If root command has SilentErrors flagged,
 		// all subcommands should respect it
 		if !cmd.SilenceErrors && !c.SilenceErrors {
-			c.Println("Error:", err.Error())
+			c.PrintErrln("Error:", err.Error())
 		}
 
 		// If root command has SilentUsage flagged,
@@ -1209,12 +1209,12 @@ func (c *Command) PrintErr(i ...interface{}) {
 
 // PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set.
 func (c *Command) PrintErrln(i ...interface{}) {
-	c.Print(fmt.Sprintln(i...))
+	c.PrintErr(fmt.Sprintln(i...))
 }
 
 // PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set.
 func (c *Command) PrintErrf(format string, i ...interface{}) {
-	c.Print(fmt.Sprintf(format, i...))
+	c.PrintErr(fmt.Sprintf(format, i...))
 }
 
 // CommandPath returns the full path to this command.
diff --git a/zsh_completions.go b/zsh_completions.go
index c25ce68..92a7039 100644
--- a/zsh_completions.go
+++ b/zsh_completions.go
@@ -229,6 +229,11 @@ _%[1]s()
         _describe "completions" completions $(echo $flagPrefix)
     fi
 }
+
+# don't run the completion function when being source-ed or eval-ed
+if [ "$funcstack[1]" = "_%[1]s" ]; then
+	_%[1]s
+fi
 `, name, compCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
 		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))


Test patch:
diff --git a/command_test.go b/command_test.go
index 16cc41b..3a47a81 100644
--- a/command_test.go
+++ b/command_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"reflect"
 	"strings"
@@ -1636,6 +1637,47 @@ func TestUsageStringRedirected(t *testing.T) {
 	}
 }
 
+func TestCommandPrintRedirection(t *testing.T) {
+	errBuff, outBuff := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
+	root := &Command{
+		Run: func(cmd *Command, args []string) {
+
+			cmd.PrintErr("PrintErr")
+			cmd.PrintErrln("PrintErr", "line")
+			cmd.PrintErrf("PrintEr%s", "r")
+
+			cmd.Print("Print")
+			cmd.Println("Print", "line")
+			cmd.Printf("Prin%s", "t")
+		},
+	}
+
+	root.SetErr(errBuff)
+	root.SetOut(outBuff)
+
+	if err := root.Execute(); err != nil {
+		t.Error(err)
+	}
+
+	gotErrBytes, err := ioutil.ReadAll(errBuff)
+	if err != nil {
+		t.Error(err)
+	}
+
+	gotOutBytes, err := ioutil.ReadAll(outBuff)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if wantErr := []byte("PrintErrPrintErr line\nPrintErr"); !bytes.Equal(gotErrBytes, wantErr) {
+		t.Errorf("got: '%s' want: '%s'", gotErrBytes, wantErr)
+	}
+
+	if wantOut := []byte("PrintPrint line\nPrint"); !bytes.Equal(gotOutBytes, wantOut) {
+		t.Errorf("got: '%s' want: '%s'", gotOutBytes, wantOut)
+	}
+}
+
 func TestFlagErrorFunc(t *testing.T) {
 	c := &Command{Use: "c", Run: emptyRun}