Skip to content

Commit

Permalink
custom comp: do not complete flags after args when interspersed is false
Browse files Browse the repository at this point in the history
If the interspersed option is set false and one arg is already set all
following arguments are counted as arg and not parsed as flags. Because
of that we should not offer flag completion. The same applies to arguments
followed after `--`.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
  • Loading branch information
Luap99 committed Jan 2, 2021
1 parent a4ab3fa commit ae890af
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 3 deletions.
20 changes: 17 additions & 3 deletions custom_completions.go
Expand Up @@ -206,12 +206,26 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
return finalCmd, []string{}, ShellCompDirectiveDefault, err
}

// Check if interspersed is false or -- was set on a previous arg.
// This works by counting the arguments. Normally -- is not counted as arg but
// if -- was already set or interspersed is false and there is already one arg than
// the extra added -- is counted as arg.
flagCompletion := true
_ = finalCmd.ParseFlags(append(finalArgs, "--"))
newArgCount := finalCmd.Flags().NArg()

// 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 {
realArgCount := finalCmd.Flags().NArg()
if newArgCount > realArgCount {
// don't do flag completion (see above)
flagCompletion = false
}

if flag != nil && flagCompletion {
// Check if we are completing a flag value subject to annotations
if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
if len(validExts) != 0 {
Expand All @@ -238,7 +252,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// 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 name to be complete
if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
var completions []string

// First check for required flags
Expand Down Expand Up @@ -284,7 +298,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi

var completions []string
directive := ShellCompDirectiveDefault
if flag == nil {
if flag == nil && flagCompletion {
foundLocalNonPersistentFlag := false
// If TraverseChildren is true on the root command we don't check for
// local flags because we can use a local flag on a parent command
Expand Down
101 changes: 101 additions & 0 deletions custom_completions_test.go
Expand Up @@ -1697,6 +1697,107 @@ func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) {
}
}

func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
ValidArgsFunction: validArgsFunc,
}
rootCmd.Flags().Bool("bool", false, "test bool flag")
rootCmd.Flags().String("string", "", "test string flag")

// Test flag completion with no argument
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

expected := strings.Join([]string{
"--bool\ttest bool flag",
"--string\ttest string flag",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that no flags are completed after the -- arg
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--", "-")
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 no flags are completed after the -- arg with a flag set
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--bool", "--", "-")
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)
}

// set Interspersed to false which means that no flags should be completed after the fist arg
rootCmd.Flags().SetInterspersed(false)

// Test that no flags are completed after the fist arg
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "arg", "--")
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 no flags are completed after the fist arg with a flag set
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--string", "t", "arg", "--")
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)
}

// Check that args are still completed after --
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

expected = strings.Join([]string{
"one\tThe first",
"two\tThe second",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}

func TestFlagCompletionInGoWithDesc(t *testing.T) {
rootCmd := &Command{
Use: "root",
Expand Down

0 comments on commit ae890af

Please sign in to comment.