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

Custom autocompletion after double dash (--) #1877

Open
richard-kramer opened this issue Dec 12, 2022 · 6 comments · May be fixed by #2259
Open

Custom autocompletion after double dash (--) #1877

richard-kramer opened this issue Dec 12, 2022 · 6 comments · May be fixed by #2259

Comments

@richard-kramer
Copy link

richard-kramer commented Dec 12, 2022

I'm currently trying out the cobra framework and stumbled upon an issue with dynamic autocompletion and cmd.ArgsLenAtDash().

I'm trying to provide autocompletion for a predefined set of possible positional parameters until a -- is added. Everything after that should be passed down to another command (or otherwise processed differently to all args before the double dash) and should provide different autocompletion results.

Unfortunately, because of the added -- here, the result of cmd.ArgsLenAtDash() inside ValidArgsFunction is always at least 0, even when there is no -- in my command. This makes it impossible to detect, if a -- hasn't been added to the arguments yet. As soon as I add another argument behind the --, cmd.ArgsLenAtDash() returns the correct result.

Now I'm not sure, if this is simply not a supported use case, if I'm missing something or if this is actually a bug.

Example Code

This is an example cmd/test.go file inside an initialized cobra project, added to the root command.

// cmd/test.go

// …

var validArgs = []string{"foo", "bar", "baz"}

// TestCmd represents the test command
var TestCmd = &cobra.Command{
  Use:   "test",
  ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    cobra.CompDebugln(fmt.Sprint(cmd.ArgsLenAtDash()), true) // => always at least `0`, even when it should be `-1`

    possibleArgs := append(validArgs, "--")

    if cmd.ArgsLenAtDash() < 0 {
      return possibleArgs, cobra.ShellCompDirectiveNoFileComp
    }

    return nil, cobra.ShellCompDirectiveDefault
  },
  Run: func(cmd *cobra.Command, args []string) {
    cmdArgsEnd := len(args)
    if cmd.ArgsLenAtDash() > -1 {
      cmdArgsEnd = cmd.ArgsLenAtDash()
    }

    fmt.Println("test called")
    fmt.Println()
    fmt.Printf("args: %+q\n", args[:cmdArgsEnd])
    fmt.Printf("ArgsLenAtDash: %d\n", cmd.ArgsLenAtDash())
    fmt.Printf("args after --: %+q\n", args[cmdArgsEnd:])
  },
}

// …

I'm expecting to get completions like these:

go run ./main.go __complete test ""
# >
# [Debug] -1
# foo
# bar
# baz
# --
# :4
# Completion ended with directive: ShellCompDirectiveNoFileComp
go run ./main.go __complete test foo ""
# >
# [Debug] -1
# foo
# bar
# baz
# --
# :4
# Completion ended with directive: ShellCompDirectiveNoFileComp
go run ./main.go __complete test foo -- ""
# >
# [Debug] 1
# :0
# Completion ended with directive: ShellCompDirectiveDefault
go run ./main.go __complete test foo -- bar ""
# >
# [Debug] 1
# :0
# Completion ended with directive: ShellCompDirectiveDefault

But I'm always getting the last return values:

go run ./main.go __complete test ""
# >
# [Debug] 0
# :0
# Completion ended with directive: ShellCompDirectiveDefault
go run ./main.go __complete test foo ""
# >
# [Debug] 1
# :0
# Completion ended with directive: ShellCompDirectiveDefault

My goal is to add different autocompletion for possible arguments after the --, but the only issue with that is the correct detection of when the -- has been added.

@marckhouzam
Copy link
Collaborator

Thanks @richard-kramer for the report.
This looks like a bug. When we temporarily add a -- to the args as you pointed to, at

_ = finalCmd.ParseFlags(append(finalArgs, "--"))

we are modifying the state of the real -- counter and that value is kept. We should either reset the value or use a dummy flagset to avoid this.

cc @Luap99

@Luap99
Copy link
Contributor

Luap99 commented Dec 13, 2022

see 5d46ac9
The only reason why I did this is because the interspersed option is not exported by pflag, so the only way to know whenever it was set is by trying with such logic.

If we add a GetInterspersed() function in pflag we could just check

if !finalCmd.Flags().GetInterspersed() ||  finalCmd.ArgsLenAtDash() >= 0 { // no flag completion }

That would also be much better since we only would parse once and avoid other potential side effects like this one.
Unfortunately it looks like pflag is pretty much unmaintained so changes of getting such a small function merged is unlikely.

@richard-kramer
Copy link
Author

Do you know any workaround to accomplish this, if pflag cannot be changed? I'm actually a bit at a loss, because I think I tried to work with all options I have access to, but I might also be overlooking something and am not ready to give up yet.

@Luap99
Copy link
Contributor

Luap99 commented Dec 13, 2022

The ugly workaround would be to parse the args all by yourself. You could check if os.Args contains -- to see if it was really set on the cli.

In any case someone should open a PR with such change against this repo. Maybe some of the active maintainers here have some know how to get stuff merged there?

Otherwise as @marckhouzam said we could fix it here by using a dummy flag set for this check.

@richard-kramer
Copy link
Author

richard-kramer commented Dec 13, 2022

Oops, I was absolutely not aware of os.Args (working with go since a week 😅). I guess it works for now and is actually not as ugly as I thought it would get. Thank you.

Current workaround using golang.org/x/exp/slices:

// cmd/test.go

// …

import (
  // …
  "os"
  "github.com/spf13/cobra"
  "golang.org/x/exp/slices"
  // …
)

var validArgs = []string{"foo", "bar", "baz"}

var TestCmd = &cobra.Command{
  // …
  ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    possibleArgs := append(validArgs, "--")

    if !slices.Contains(os.Args, "--") {
      return possibleArgs, cobra.ShellCompDirectiveNoFileComp
    }

    return nil, cobra.ShellCompDirectiveDefault
  }
  // …
}

// …

@github-actions
Copy link

The Cobra project currently lacks enough contributors to adequately respond to all issues. This bot triages issues and PRs according to the following rules:

  • After 60d of inactivity, lifecycle/stale is applied. - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the issue is closed.
    You can:
  • Make a comment to remove the stale label and show your support. The 60 days reset. - If an issue has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants