Add completion for help command (#1136)

* Don't exclude 'help' from bash completions

Fixes #1000.

* Provide completion for the help command

1- Show 'help' as a possible completion
2- Provide completions for the help command itself

Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>

Co-authored-by: Zaven Muradyan <voithos@google.com>
This commit is contained in:
Marc Khouzam 2020-06-16 16:49:26 -04:00 committed by GitHub
parent ed7b60e298
commit 04318720db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 6 deletions

View File

@ -389,7 +389,7 @@ fi
func writeCommands(buf *bytes.Buffer, cmd *Command) { func writeCommands(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(" commands=()\n") buf.WriteString(" commands=()\n")
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() && c != cmd.helpCommand {
continue continue
} }
buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
@ -582,7 +582,7 @@ func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
func gen(buf *bytes.Buffer, cmd *Command) { func gen(buf *bytes.Buffer, cmd *Command) {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() && c != cmd.helpCommand {
continue continue
} }
gen(buf, c) gen(buf, c)

View File

@ -1056,7 +1056,25 @@ func (c *Command) InitDefaultHelpCmd() {
Short: "Help about any command", Short: "Help about any command",
Long: `Help provides help for any command in the application. Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`, Simply type ` + c.Name() + ` help [path to command] for full details.`,
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
var completions []string
cmd, _, e := c.Root().Find(args)
if e != nil {
return nil, ShellCompDirectiveNoFileComp
}
if cmd == nil {
// Root help command.
cmd = c.Root()
}
for _, subCmd := range cmd.Commands() {
if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
if strings.HasPrefix(subCmd.Name(), toComplete) {
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
}
}
}
return completions, ShellCompDirectiveNoFileComp
},
Run: func(c *Command, args []string) { Run: func(c *Command, args []string) {
cmd, _, e := c.Root().Find(args) cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil { if cmd == nil || e != nil {

View File

@ -183,10 +183,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
} }
if flag == nil { if flag == nil {
// Complete subcommand names // Complete subcommand names, including the help command
for _, subCmd := range finalCmd.Commands() { for _, subCmd := range finalCmd.Commands() {
if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) { if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) if strings.HasPrefix(subCmd.Name(), toComplete) {
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
}
} }
} }

View File

@ -629,3 +629,71 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) {
t.Errorf("expected: %q, got: %q", expected, output) t.Errorf("expected: %q, got: %q", expected, output)
} }
} }
func TestCompleteHelp(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
child1Cmd := &Command{
Use: "child1",
Run: emptyRun,
}
child2Cmd := &Command{
Use: "child2",
Run: emptyRun,
}
rootCmd.AddCommand(child1Cmd, child2Cmd)
child3Cmd := &Command{
Use: "child3",
Run: emptyRun,
}
child1Cmd.AddCommand(child3Cmd)
// Test that completion includes the help command
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"child1",
"child2",
"help",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test sub-commands are completed on first level of help command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"child1",
"child2",
"help", // "<program> help help" is a valid command, so should be completed
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test sub-commands are completed on first level of help command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "child1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"child3",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}