From 84a1965f142cf6e48f2dd3aeb3b2aeaafd393551 Mon Sep 17 00:00:00 2001 From: Harald Albers <github@albersweb.de> Date: Tue, 4 Feb 2025 20:41:38 +0000 Subject: [PATCH 1/2] The default ShellCompDirective can be customized. --- completions.go | 15 ++++++++ completions_test.go | 57 ++++++++++++++++++++++++++++++ site/content/completions/_index.md | 28 +++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/completions.go b/completions.go index a1752f76..0733c5bb 100644 --- a/completions.go +++ b/completions.go @@ -115,6 +115,13 @@ 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 +} + +func (receiver *CompletionOptions) SetDefaultShellCompDirective(directive ShellCompDirective) { + receiver.DefaultShellCompDirective = &directive } // Completion is a string that can be used for completions @@ -480,6 +487,14 @@ func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCo } } else { directive = ShellCompDirectiveDefault + // check current and parent commands for a custom DefaultShellCompDirective + for cmd := finalCmd; cmd != nil; cmd = cmd.parent { + if cmd.CompletionOptions.DefaultShellCompDirective != nil { + directive = *cmd.CompletionOptions.DefaultShellCompDirective + break + } + } + 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 89da3d50..894a09f1 100644 --- a/completions_test.go +++ b/completions_test.go @@ -4016,3 +4016,60 @@ func TestInitDefaultCompletionCmd(t *testing.T) { }) } } + +func TestCustomDefaultShellCompDirective(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.PersistentFlags().String("string", "", "test string flag") + // use ShellCompDirectiveNoFileComp instead of the default, which is ShellCompDirectiveDefault. + rootCmd.CompletionOptions.SetDefaultShellCompDirective(ShellCompDirectiveNoFileComp) + + // child1 inherits the custom DefaultShellCompDirective. + childCmd1 := &Command{Use: "child1", Run: emptyRun} + // child2 resets the custom DefaultShellCompDirective to the default value. + childCmd2 := &Command{Use: "child2", Run: emptyRun} + childCmd2.CompletionOptions.SetDefaultShellCompDirective(ShellCompDirectiveDefault) + + rootCmd.AddCommand(childCmd1, childCmd2) + + testCases := []struct { + desc string + args []string + expectedDirective string + }{ + { + "flag completion on root command with custom DefaultShellCompDirective", + []string{"--string", ""}, + "ShellCompDirectiveNoFileComp", + }, + { + "flag completion on subcommand with inherited custom DefaultShellCompDirective", + []string{"child1", "--string", ""}, + "ShellCompDirectiveNoFileComp", + }, + { + "flag completion on subcommand with reset DefaultShellCompDirective", + []string{"child2", "--string", ""}, + "ShellCompDirectiveDefault", + }, + } + + 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) + } + + outputWords := strings.Split(strings.TrimSpace(output), " ") + directive := outputWords[len(outputWords)-1] + + if directive != tc.expectedDirective { + t.Errorf("expected: %q, got: %q", tc.expectedDirective, directive) + } + }) + } +} diff --git a/site/content/completions/_index.md b/site/content/completions/_index.md index 8445850a..2595a178 100644 --- a/site/content/completions/_index.md +++ b/site/content/completions/_index.md @@ -305,6 +305,34 @@ $ helm status --output [tab][tab] json table yaml ``` +#### Change the default ShellCompDirective + +When no completion function is registered for a leaf command or for a flag, Cobra will +automatically use `ShellCompDirectiveDefault`, which will invoke the shell's filename completion. +This implies that when file completion does not apply to a leaf command or to a flag (the command +or flag does not operate on a filename), turning off file completion requires you to register a +completion function for that command/flag. +For example: + +```go +cmd.RegisterFlagCompletionFunc("flag-name", cobra.NoFileCompletions) +``` + +If you find that there are more situations where file completion should be turned off than +when it is applicable, you can change the default `ShellCompDirective` for a command +and its subcommands to `ShellCompDirectiveNoFileComp`: + +```go +cmd.CompletionOptions.SetDefaultShellCompDirective(ShellCompDirectiveNoFileComp) +``` + +If doing so, keep in mind that you should instead register a completion function for leaf commands or +flags where file completion is applicable. For example: + +```go +cmd.RegisterFlagCompletionFunc("flag-name", cobra.FixedCompletions(nil, ShellCompDirectiveDefault)) +``` + #### Debugging You can also easily debug your Go completion code for flags: From 6ad4cd6904cb4ea79cced0ae5a6298721258a73d Mon Sep 17 00:00:00 2001 From: Harald Albers <github@albersweb.de> Date: Thu, 6 Mar 2025 12:26:08 +0000 Subject: [PATCH 2/2] Improve documentation of DefaultShellCompDirective --- site/content/completions/_index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/content/completions/_index.md b/site/content/completions/_index.md index 2595a178..0115910f 100644 --- a/site/content/completions/_index.md +++ b/site/content/completions/_index.md @@ -319,7 +319,7 @@ cmd.RegisterFlagCompletionFunc("flag-name", cobra.NoFileCompletions) ``` If you find that there are more situations where file completion should be turned off than -when it is applicable, you can change the default `ShellCompDirective` for a command +when it is applicable, you can recursively change the default `ShellCompDirective` for a command and its subcommands to `ShellCompDirectiveNoFileComp`: ```go @@ -333,6 +333,8 @@ flags where file completion is applicable. For example: cmd.RegisterFlagCompletionFunc("flag-name", cobra.FixedCompletions(nil, ShellCompDirectiveDefault)) ``` +To change the default directive for the entire program, set the DefaultShellCompDirective on the root command. + #### Debugging You can also easily debug your Go completion code for flags: