From 440c33b3b36310e321102bd78d99ec020084b7b6 Mon Sep 17 00:00:00 2001 From: Harald Albers <github@albersweb.de> Date: Sun, 19 Jan 2025 22:32:32 +0000 Subject: [PATCH 1/4] The default ShellCompDirective can be configured --- completions.go | 5 +++- completions_test.go | 42 ++++++++++++++++++++++++++++++ site/content/completions/_index.md | 23 ++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/completions.go b/completions.go index 0862d3f6..7c501fd9 100644 --- a/completions.go +++ b/completions.go @@ -115,6 +115,9 @@ type CompletionOptions struct { DisableDescriptions bool // HiddenDefaultCmd makes the default 'completion' command hidden HiddenDefaultCmd bool + // DefaultShellCompDirective sets the ShellCompDirective that is returned + // if no special directive can be determined + DefaultShellCompDirective ShellCompDirective } // NoFileCompletions can be used to disable file completion for commands that should @@ -451,7 +454,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi return finalCmd, completions, directive, nil } } else { - directive = ShellCompDirectiveDefault + directive = finalCmd.CompletionOptions.DefaultShellCompDirective if flag == nil { foundLocalNonPersistentFlag := false // If TraverseChildren is true on the root command we don't check for diff --git a/completions_test.go b/completions_test.go index a8f378eb..19c577a4 100644 --- a/completions_test.go +++ b/completions_test.go @@ -3742,3 +3742,45 @@ func TestDisableDescriptions(t *testing.T) { }) } } + +func TestCustomDefaultShellCompDirective(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.Flags().String("string", "", "test string flag") + // use ShellCompDirectiveNoFileComp instead of the default, which is ShellCompDirectiveDefault + rootCmd.CompletionOptions.DefaultShellCompDirective = ShellCompDirectiveNoFileComp + + testCases := []struct { + desc string + args []string + }{ + { + "args completion with custom ShellCompDirective", + []string{""}, + }, + { + "flag completion with custom ShellCompDirective", + []string{"--string", ""}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + args := []string{ShellCompNoDescRequestCmd} + args = append(args, tc.args...) + + output, err := executeCommand(rootCmd, args...) + + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + }) + } +} diff --git a/site/content/completions/_index.md b/site/content/completions/_index.md index 02257ade..afab3f6d 100644 --- a/site/content/completions/_index.md +++ b/site/content/completions/_index.md @@ -304,6 +304,29 @@ $ helm status --output [tab][tab] json table yaml ``` +#### Change the default ShellCompDirective + +If Cobra cannot determine a special `ShellCompDirective` during flag parsing, +it will return `ShellCompDirectiveDefault`, which will invoke the shell's filename completion. +This is handy for flags that accept filenames, as they do not require a `FlagCompletionFunc`. + +For other flags where no meaningful completion can be provided, this requires extra work: +You have to register a `FlagCompletionFunc` just to get rid of file completion. + +If you find yourself registering lots of handlers like + +```go +cmd.RegisterFlagCompletionFunc("flag-name", cobra.NoFileCompletions) +``` + +you can change the default `ShellCompDirective` to `ShellCompDirectiveNoFileComp`: + +```go +cmd.CompletionOptions.DefaultShellCompDirective = ShellCompDirectiveNoFileComp +``` + +Keep in mind that from now on you have to register handlers for every filename flag. + #### Debugging You can also easily debug your Go completion code for flags: From 33e3ac194f892d993afde4f5c5076334839ec976 Mon Sep 17 00:00:00 2001 From: Harald Albers <github@albersweb.de> Date: Mon, 20 Jan 2025 08:17:22 +0000 Subject: [PATCH 2/4] Be more explicit about the default ShellCompDirective --- completions.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/completions.go b/completions.go index 7c501fd9..0c5c2ec7 100644 --- a/completions.go +++ b/completions.go @@ -454,7 +454,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi return finalCmd, completions, directive, nil } } else { - directive = finalCmd.CompletionOptions.DefaultShellCompDirective + customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective + if customShellCompDirective != ShellCompDirectiveDefault { + directive = customShellCompDirective + } else { + directive = ShellCompDirectiveDefault + } + if flag == nil { foundLocalNonPersistentFlag := false // If TraverseChildren is true on the root command we don't check for From b4f26aebb91f267bd595ade69839ac998f2e1dce Mon Sep 17 00:00:00 2001 From: Harald Albers <albers@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:10:52 +0100 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- completions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/completions.go b/completions.go index 0c5c2ec7..b404322f 100644 --- a/completions.go +++ b/completions.go @@ -454,11 +454,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi return finalCmd, completions, directive, nil } } else { + directive = ShellCompDirectiveDefault + customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective if customShellCompDirective != ShellCompDirectiveDefault { directive = customShellCompDirective - } else { - directive = ShellCompDirectiveDefault } if flag == nil { From 5df541d5ef7032016e75b948e32ba33756433536 Mon Sep 17 00:00:00 2001 From: Harald Albers <github@albersweb.de> Date: Sun, 26 Jan 2025 22:36:57 +0000 Subject: [PATCH 4/4] Resolve gofmt error --- completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions.go b/completions.go index b404322f..390996d4 100644 --- a/completions.go +++ b/completions.go @@ -455,7 +455,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } } else { directive = ShellCompDirectiveDefault - + customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective if customShellCompDirective != ShellCompDirectiveDefault { directive = customShellCompDirective