From 04318720db1743b8488c86b2f7dca6d9663cb2f2 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 16 Jun 2020 16:49:26 -0400 Subject: [PATCH] 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 Co-authored-by: Zaven Muradyan --- bash_completions.go | 4 +-- command.go | 20 ++++++++++- custom_completions.go | 8 +++-- custom_completions_test.go | 68 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index d8341cc4..f5623993 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -389,7 +389,7 @@ fi func writeCommands(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" commands=()\n") for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() && c != cmd.helpCommand { continue } 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) { for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() && c != cmd.helpCommand { continue } gen(buf, c) diff --git a/command.go b/command.go index 5b81f61d..5f1caccc 100644 --- a/command.go +++ b/command.go @@ -1056,7 +1056,25 @@ func (c *Command) InitDefaultHelpCmd() { Short: "Help about any command", Long: `Help provides help for any command in the application. 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) { cmd, _, e := c.Root().Find(args) if cmd == nil || e != nil { diff --git a/custom_completions.go b/custom_completions.go index ba57327c..47cc9f5a 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -183,10 +183,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } if flag == nil { - // Complete subcommand names + // Complete subcommand names, including the help command for _, subCmd := range finalCmd.Commands() { - if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) { - completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { + if strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } } } diff --git a/custom_completions_test.go b/custom_completions_test.go index 86154839..fe15263c 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -629,3 +629,71 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { 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", // " 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) + } +}