urfave / cli

A simple, fast, and fun package for building command line apps in Go
https://cli.urfave.org
MIT License
21.9k stars 1.69k forks source link

Shell completion requires double TAB on first usage #1874

Open bartekpacia opened 3 months ago

bartekpacia commented 3 months ago

My urfave/cli version is

v2.27.1

Checklist

Dependency Management

My project is using Go modules.

Describe the bug

TL;DR After shell startup, I have to click tab twice for completions to show up.

I noticed that the provided zsh completion script doesn't work as expected when it is placed in the conventional directory for shell completion function /opt/homebrew/share/zsh/site-functions (My zsh is installed through Homebrew, to keep it up-to-date).

To reproduce expected behavior

All code is available in a public repository of mine.

  1. Clone it

  2. Build executable:

    go build -o emu cmd/emu/main.go
  3. Source shell completion:

    source <(cat autocomplete/zsh_autocomplete)
  4. Run ./emu followed by <SPACE> and <TAB>, and notice that shell completions show up just fine:

    Video demo:

    https://github.com/urfave/cli/assets/40357511/997f2cf9-e5e7-4b9b-96a8-6227071a2730

To reproduce wrong behavior

Do step 1 and 2 from above, but instead of sourcing the completion script directly, put it into the conventional location for shell completion scripts:

cp ./autocomplete/zsh_autocomplete ./autocomplete/_emu
cp ./autocomplete/_emu /opt/homebrew/share/zsh/site-functions

This is for macOS with Homebrew-installed zsh. For Linux it's probably gonna be sth like /usr/local/share/zsh/site-functions.

Now reload shell so it picks up new completions from that directory:

exec zsh

[!NOTE]

At this point, you can run which _emu and see it output something like this:

$ which _emu
_emu () {
  # undefined
  builtin autoload -XUz /opt/homebrew/share/zsh/site-functions
}

which is strange and I don't understand it. It's as if it was not initialized?

Now, try to trigger shell completion by typing this in the cloned project:

./emu <TAB>

and notice no shell completions show up. But if you click <TAB> second time, they will show up.

This video demonstrates this behavior:

https://github.com/urfave/cli/assets/40357511/4d820f16-3dd3-4fa8-89d7-7c7077f71ee9

Notice that at the end of the video I clicked <TAB> but it didn't do anything.

But interesting thing is that after the first <TAB>, but before the second <TAB>, the output of which _emu changes:

New which _emu output ``` $ which _emu _emu () { _cli_zsh_autocomplete () { local -a opts local cur cur=${words[-1]} if [[ "$cur" == "-"* ]] then opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]] then _describe 'values' opts else _files fi } compdef _cli_zsh_autocomplete emu } ```

Expected behavior

I expect shell completion suggestions to be shown immediately on first TAB.

Additional context

I noticed this only occurs with urfave/cli.

It does not occur with zsh completions generated by Cobra.

I did some googling and found

Want to fix this yourself?

I don't think I have enough CLI completion expertise and experience. But I have time, if someone could guide me into some resources, I'd be happy to try!

Also, thanks a ton for this wonderful library, I truly love it as it's much more lightweight than Cobra IMHO. If this small issue was fixed, I'd be on cloud 9.

Run go version and paste its output here

go version go1.22.1 darwin/arm64

Run go env and paste its output here

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/bartek/Library/Caches/go-build'
GOENV='/Users/bartek/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/bartek/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/bartek/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.22.1/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.22.1/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.1'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/Users/bartek/projects/emu/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/r6/5z0bypqn4cb8p369zlrfhw3c0000gn/T/go-build1695057898=/tmp/go-build -gno-record-gcc-switches -fno-common'
bartekpacia commented 3 months ago

I did some digging and noticed commit e66017d73a69165ac6216f85dffed3d2cc78c68c

This bug does not occur with commits before e66017d

Completion script not exhibiting the bug (before e66017d) ```zsh #compdef emu local -a opts local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]]; then _describe 'values' opts else _files fi ```
Completion script exhibiting the bug ```zsh #compdef emu _cli_zsh_autocomplete() { local -a opts local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]]; then _describe 'values' opts else _files fi } compdef _cli_zsh_autocomplete emu ```
dearchap commented 3 months ago

@bartekpacia I'm not a zsh expert. Is there a way you can fix it ?

bartekpacia commented 3 months ago

Yes, I have fixed it (see this comment for a version of completion script that works fine).

My suggestion is to eliminate the $PROG variable from completion scripts. Instead, the docs should tell users to copy and modify their completion scripts.

For example, I have a program called emu. Therefore I have to change $PROG to emu, resulting in:

#compdef emu

local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
  opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
  opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi

if [[ "${opts[1]}" != "" ]]; then
  _describe 'values' opts
else
  _files
fi

What do you think? I would be happy to update the docs accordingly.

Note that these completion scripts are for v2. For v3, --generate-bash-completion must be replaced with --generate-shell-completion.

dearchap commented 3 months ago

Yeah go ahead.

bartekpacia commented 2 months ago

The even better version of the script above. The advantage is that it can be sourceed, which makes testing easier (no need to copy to /opt/homebrew/share/zsh/site-functions):

. ./autocomplete/zsh_autocomplete
#compdef emu
compdef _emu emu

_emu() {
    local -a opts
    local cur
    cur=${words[-1]}
    if [[ "$cur" == "-"* ]]; then
        opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
    else
        opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
    fi

    if [[ "${opts[1]}" != "" ]]; then
        _describe 'values' opts
    else
        _files
    fi
}

# don't run the completion function when being source-ed or eval-ed
if [ "$funcstack[1]" = "_emu" ]; then
    _emu
fi