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

The default ShellCompDirective can be configured #2221

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

albers
Copy link

@albers albers commented Jan 19, 2025

Closes #2209.
Is this implementation acceptable?

/cc @thaJeztah

@CLAassistant
Copy link

CLAassistant commented Jan 19, 2025

CLA assistant check
All committers have signed the CLA.

completions.go Outdated
@@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code then rely on the fact the default value of this variable is 0 and then matches ShellCompDirectiveDefault

It's a bit tricky and invisible, I'm not fan of this.

Could you make it more explicit?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, PTAL.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit tricky and invisible, I'm not fan of this.

Agreed but the explicit code just feels "wrong" to me. I mean:

directive = ShellCompDirectiveDefault

customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective
if customShellCompDirective != ShellCompDirectiveDefault {
	directive = customShellCompDirective
}

when I read the if statement, I immediately wonder why we have this effectively non-useful if statement.
Furthermore, if we were to change the first line and use a different directive, it would require to change the if statement, which seems unnecessarily brittle.
Finally, looking at the if statement, one still wonders what happens when the new option is not set by the program and will need to realize it is set by the compiler.

How going back to the original one-line but adding a comment above it:
// If not explicitly set by the program, the default directive will be [ShellCompDirectiveDefault]

completions.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@marckhouzam marckhouzam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @albers.
We need to settle on the API (see in-line comment) and then we'll be good.

```

Keep in mind that from now on you have to register handlers for every filename flag.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do think of the slightly reworked:

#### 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` to `ShellCompDirectiveNoFileComp`:

```go
rootCmd.CompletionOptions.DefaultShellCompDirective = 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))
```

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much better than my version, thank you very much.
I'll incorporate it as soon as the API discussion is settled, because depending on its outcome, rootCmd might be changed to cmd in the second code block.

@@ -452,6 +455,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
}
} else {
directive = ShellCompDirectiveDefault

customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to settle on what would be the best API for this new option.
Where do we expect the developer to set CompletionOptions.DefaultShellCompDirective?

  1. Do they set it on the root command to set it globally for all the commands and flags of the program? Note that all other completion directives need to be set on the root command
  2. Or should they set it on individual commands and have that option only affect that command and its flags? So, in practice, probably having to set it on many commands.
  3. Set it on a individual commands but have the option recursively affect the command and its flags as well as all its sub-commands and their flags? Here a dev could set the option on the rootCmd and it will affect the entire program, or they could set it only on one or more sub-trees of the command tree.

You've implemented option 2, and I wanted to ask if this was a conscious decision?

Whatever we settle on, I suggest making it clear on the comment above the definition of the new option, and also in the documenation.

Copy link
Author

@albers albers Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marckhouzam
It was a conscious decision.
Changing the default ShellCompDirective has massive side effects (effectively inverting the logic when and when not to write boilerplate handlers). I prefer an explicit opt-in for this change on the command level that does not propagate to child commands. This allows gradually introducing this new setting in just the places where it makes sense.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a good point. But I can't help imagining a developer trying to use this option and being annoyed at having to set it on every single command; some programs have quite a lot of them; I feel like someone will come back and ask for another option to set a "global" default directive 😄 .

If we implement option 3 (affect command and its sub-commands), and a dev only wants to set the option for a single command, they could achieve this by setting the option on the specific command, and then setting the option to ShellCompDirectiveDefault on the first level children of that command. How awkward would that seem to you as an API?

I want to get this as best we can because once it is released, we will not be able to change it (except by introducing another completion option).

@thaJeztah Feel free to give your opinion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ccoVeille If you have an opinion, don't hesitate.
Or anyone else for that matter 😄
This is a community project.

completions.go Outdated
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit tricky and invisible, I'm not fan of this.

Agreed but the explicit code just feels "wrong" to me. I mean:

directive = ShellCompDirectiveDefault

customShellCompDirective := finalCmd.CompletionOptions.DefaultShellCompDirective
if customShellCompDirective != ShellCompDirectiveDefault {
	directive = customShellCompDirective
}

when I read the if statement, I immediately wonder why we have this effectively non-useful if statement.
Furthermore, if we were to change the first line and use a different directive, it would require to change the if statement, which seems unnecessarily brittle.
Finally, looking at the if statement, one still wonders what happens when the new option is not set by the program and will need to realize it is set by the compiler.

How going back to the original one-line but adding a comment above it:
// If not explicitly set by the program, the default directive will be [ShellCompDirectiveDefault]

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 this pull request may close these issues.

Feature request: Set a default completion function
4 participants