Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix zsh completion not working on the first time in a shell session #1237

Merged
merged 1 commit into from
Oct 4, 2020
Merged

Fix zsh completion not working on the first time in a shell session #1237

merged 1 commit into from
Oct 4, 2020

Conversation

midchildan
Copy link
Contributor

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
Copy link

CLAassistant commented Sep 25, 2020

CLA assistant check
All committers have signed the CLA.

@marckhouzam
Copy link
Collaborator

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
Copy link
Contributor Author

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:

_%[1]s()

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
Copy link
Contributor Author

midchildan commented Sep 25, 2020

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 for the first time.

@midchildan
Copy link
Contributor Author

midchildan commented Sep 25, 2020

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/Functions.html#Autoloading-Functions
http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Autoloaded-files

@marckhouzam
Copy link
Collaborator

@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
Copy link
Collaborator

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
Copy link
Contributor Author

I vaguely recall there was a way to detect if the running code is in completion context, but I'm not certain.

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 commit is a fix for this problem.
@midchildan
Copy link
Contributor Author

midchildan commented Sep 25, 2020

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:

% f() { f() { echo "real f is called" }; if [ "$funcstack[1]" = "f" ]; then f; fi }; f
real f is called
% source <(echo 'f() { echo "real f is called" }; if [ "$funcstack[1]" = "f" ]; then f; fi')

@marckhouzam
Copy link
Collaborator

marckhouzam commented Sep 25, 2020

@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
Copy link
Contributor Author

I was able to source this script without problems, though.

#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

@midchildan
Copy link
Contributor Author

midchildan commented Sep 25, 2020

I looked into helm completion zsh, but it looks like helm isn't using cobra's completion generator. https://github.com/helm/helm/blob/e454e6a9102009562fa8814b39125673ddad05b7/cmd/helm/completion.go#L147

@marckhouzam
Copy link
Collaborator

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
Copy link
Contributor Author

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.

Copy link
Collaborator

@marckhouzam marckhouzam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally feel this follows the proper zsh completion approach, while still protecting users that want to use source as best we can.

Thanks @midchildan.

@jharshman what do you think?

@jharshman jharshman self-assigned this Oct 4, 2020
@jharshman jharshman added the kind/feature A feature request for cobra; new or enhanced behavior label Oct 4, 2020
Copy link
Collaborator

@jharshman jharshman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @midchildan and @marckhouzam.
This change LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A feature request for cobra; new or enhanced behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants