diff --git a/defaultActions.go b/defaultActions.go index 75799e386..daf47dc19 100644 --- a/defaultActions.go +++ b/defaultActions.go @@ -11,6 +11,7 @@ import ( "github.com/rsteube/carapace/internal/common" "github.com/rsteube/carapace/internal/config" + "github.com/rsteube/carapace/internal/env" "github.com/rsteube/carapace/internal/export" "github.com/rsteube/carapace/internal/man" "github.com/rsteube/carapace/pkg/match" @@ -480,3 +481,36 @@ func ActionPositional(cmd *cobra.Command) Action { return a.Invoke(c).ToA() }) } + +// ActionCommands completes (sub)commands of given command. +// `Context.Args` is used to traverse the command tree further down - use `Action.Shift` to avoid this. +// +// carapace.Gen(helpCmd).PositionalAnyCompletion( +// carapace.ActionCommands(rootCmd), +// ) +func ActionCommands(cmd *cobra.Command) Action { + return ActionCallback(func(c Context) Action { + if len(c.Args) > 0 { + for _, subCommand := range cmd.Commands() { + for _, name := range append(subCommand.Aliases, subCommand.Name()) { + if name == c.Args[0] { // cmd.Find is too lenient + return ActionCommands(subCommand).Shift(1) + } + } + } + return ActionMessage("unknown subcommand %#v for %#v", c.Args[0], cmd.Name()) + } + + batch := Batch() + for _, subcommand := range cmd.Commands() { + if (!subcommand.Hidden || env.Hidden()) && subcommand.Deprecated == "" { + group := common.Group{Cmd: subcommand} + batch = append(batch, ActionStyledValuesDescribed(subcommand.Name(), subcommand.Short, group.Style()).Tag(group.Tag())) + for _, alias := range subcommand.Aliases { + batch = append(batch, ActionStyledValuesDescribed(alias, subcommand.Short, group.Style()).Tag(group.Tag())) + } + } + } + return batch.ToA() + }) +} diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 062a6dc6e..ae439ba81 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -55,6 +55,7 @@ - [ToMultiPartsA](./carapace/invokedAction/toMultiPartsA.md) - [DefaultActions](./carapace/defaultActions.md) - [ActionCallback](./carapace/defaultActions/actionCallback.md) + - [ActionCommands](./carapace/defaultActions/actionCommands.md) - [ActionDirectories](./carapace/defaultActions/actionDirectories.md) - [ActionExecCommand](./carapace/defaultActions/actionExecCommand.md) - [ActionExecCommandE](./carapace/defaultActions/actionExecCommandE.md) diff --git a/docs/src/carapace/defaultActions/actionCommands.md b/docs/src/carapace/defaultActions/actionCommands.md new file mode 100644 index 000000000..4d91e9b5e --- /dev/null +++ b/docs/src/carapace/defaultActions/actionCommands.md @@ -0,0 +1 @@ +# ActionCommands diff --git a/example/cmd/action.go b/example/cmd/action.go index bd7acc9e0..95684c846 100644 --- a/example/cmd/action.go +++ b/example/cmd/action.go @@ -21,6 +21,7 @@ func init() { rootCmd.AddCommand(actionCmd) actionCmd.Flags().String("callback", "", "ActionCallback()") + actionCmd.Flags().String("commands", "", "ActionCommands()") actionCmd.Flags().String("directories", "", "ActionDirectories()") actionCmd.Flags().String("execcommand", "", "ActionExecCommand()") actionCmd.Flags().String("execcommandE", "", "ActionExecCommand()") @@ -48,6 +49,7 @@ func init() { } return carapace.ActionMessage("values flag is not set") }), + "commands": carapace.ActionCommands(rootCmd).Split(), "directories": carapace.ActionDirectories(), "execcommand": carapace.ActionExecCommand("git", "remote")(func(output []byte) carapace.Action { lines := strings.Split(string(output), "\n") diff --git a/example/cmd/subcommand.go b/example/cmd/subcommand.go new file mode 100644 index 000000000..a8b8d6dfc --- /dev/null +++ b/example/cmd/subcommand.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +var subcommandCmd = &cobra.Command{ + Use: "subcommand", + Short: "subcommand example", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(subcommandCmd).Standalone() + + subcommandCmd.AddGroup( + &cobra.Group{ID: "group", Title: ""}, + ) + + rootCmd.AddCommand(subcommandCmd) +} diff --git a/example/cmd/subcommand_alias.go b/example/cmd/subcommand_alias.go new file mode 100644 index 000000000..5f706f4b7 --- /dev/null +++ b/example/cmd/subcommand_alias.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +var subcommand_aliasCmd = &cobra.Command{ + Use: "alias", + Short: "subcommand with alias", + Aliases: []string{"a1", "a2"}, + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(subcommand_aliasCmd).Standalone() + + subcommandCmd.AddCommand(subcommand_aliasCmd) +} diff --git a/example/cmd/subcommand_group.go b/example/cmd/subcommand_group.go new file mode 100644 index 000000000..f0b6285ad --- /dev/null +++ b/example/cmd/subcommand_group.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +var subcommand_groupCmd = &cobra.Command{ + Use: "group", + Short: "subcommand with group", + GroupID: "group", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(subcommand_groupCmd).Standalone() + + subcommandCmd.AddCommand(subcommand_groupCmd) +} diff --git a/example/cmd/subcommand_hidden.go b/example/cmd/subcommand_hidden.go new file mode 100644 index 000000000..0478b8f62 --- /dev/null +++ b/example/cmd/subcommand_hidden.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +var subcommand_hiddenCmd = &cobra.Command{ + Use: "hidden", + Short: "hidden subcommand", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(subcommand_hiddenCmd).Standalone() + + subcommandCmd.AddCommand(subcommand_hiddenCmd) +} diff --git a/example/cmd/subcommand_hidden_visible.go b/example/cmd/subcommand_hidden_visible.go new file mode 100644 index 000000000..a6433694b --- /dev/null +++ b/example/cmd/subcommand_hidden_visible.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/spf13/cobra" +) + +var subcommand_hidden_visibleCmd = &cobra.Command{ + Use: "visible", + Short: "visible subcommand of a hidden command", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(subcommand_hidden_visibleCmd).Standalone() + + + subcommand_hiddenCmd.AddCommand(subcommand_hidden_visibleCmd) +} diff --git a/internalActions.go b/internalActions.go index 3a52e660a..e5b753aea 100644 --- a/internalActions.go +++ b/internalActions.go @@ -6,7 +6,6 @@ import ( "path/filepath" "strings" - "github.com/rsteube/carapace/internal/common" "github.com/rsteube/carapace/internal/env" "github.com/rsteube/carapace/internal/pflagfork" "github.com/rsteube/carapace/pkg/style" @@ -133,22 +132,6 @@ func actionFlags(cmd *cobra.Command) Action { }).Tag("flags") } -func actionSubcommands(cmd *cobra.Command) Action { - return ActionCallback(func(c Context) Action { - batch := Batch() - for _, subcommand := range cmd.Commands() { - if (!subcommand.Hidden || env.Hidden()) && subcommand.Deprecated == "" { - group := common.Group{Cmd: subcommand} - batch = append(batch, ActionStyledValuesDescribed(subcommand.Name(), subcommand.Short, group.Style()).Tag(group.Tag())) - for _, alias := range subcommand.Aliases { - batch = append(batch, ActionStyledValuesDescribed(alias, subcommand.Short, group.Style()).Tag(group.Tag())) - } - } - } - return batch.ToA() - }) -} - func initHelpCompletion(cmd *cobra.Command) { helpCmd, _, err := cmd.Find([]string{"help"}) if err != nil { @@ -162,12 +145,6 @@ func initHelpCompletion(cmd *cobra.Command) { } Gen(helpCmd).PositionalAnyCompletion( - ActionCallback(func(c Context) Action { - lastCmd, _, err := cmd.Find(c.Args) - if err != nil { - return ActionMessage(err.Error()) - } - return actionSubcommands(lastCmd) - }), + ActionCommands(cmd), ) } diff --git a/traverse.go b/traverse.go index 900b63844..2a5447718 100644 --- a/traverse.go +++ b/traverse.go @@ -10,20 +10,20 @@ import ( "github.com/spf13/cobra" ) -func traverse(c *cobra.Command, args []string) (Action, Context) { - LOG.Printf("traverse called for %#v with args %#v\n", c.Name(), args) - storage.preRun(c, args) +func traverse(cmd *cobra.Command, args []string) (Action, Context) { + LOG.Printf("traverse called for %#v with args %#v\n", cmd.Name(), args) + storage.preRun(cmd, args) if env.Lenient() { LOG.Printf("allowing unknown flags") - c.FParseErrWhitelist.UnknownFlags = true + cmd.FParseErrWhitelist.UnknownFlags = true } inArgs := []string{} // args consumed by current command inPositionals := []string{} // positionals consumed by current command var inFlag *pflagfork.Flag // last encountered flag that still expects arguments - c.LocalFlags() // TODO force c.mergePersistentFlags() which is missing from c.Flags() - fs := pflagfork.FlagSet{FlagSet: c.Flags()} + cmd.LocalFlags() // TODO force c.mergePersistentFlags() which is missing from c.Flags() + fs := pflagfork.FlagSet{FlagSet: cmd.Flags()} context := NewContext(args...) loop: @@ -47,7 +47,7 @@ loop: break loop // flag - case !c.DisableFlagParsing && strings.HasPrefix(arg, "-") && (fs.IsInterspersed() || len(inPositionals) == 0): + case !cmd.DisableFlagParsing && strings.HasPrefix(arg, "-") && (fs.IsInterspersed() || len(inPositionals) == 0): LOG.Printf("arg %#v is a flag\n", arg) inArgs = append(inArgs, arg) inFlag = fs.LookupArg(arg) @@ -58,22 +58,22 @@ loop: continue // subcommand - case subcommand(c, arg) != nil: + case subcommand(cmd, arg) != nil: LOG.Printf("arg %#v is a subcommand\n", arg) switch { - case c.DisableFlagParsing: - LOG.Printf("flag parsing disabled for %#v\n", c.Name()) + case cmd.DisableFlagParsing: + LOG.Printf("flag parsing disabled for %#v\n", cmd.Name()) default: - LOG.Printf("parsing flags for %#v with args %#v\n", c.Name(), inArgs) - if err := c.ParseFlags(inArgs); err != nil { + LOG.Printf("parsing flags for %#v with args %#v\n", cmd.Name(), inArgs) + if err := cmd.ParseFlags(inArgs); err != nil { return ActionMessage(err.Error()), context } - context.Args = c.Flags().Args() + context.Args = cmd.Flags().Args() } - return traverse(subcommand(c, arg), args[i+1:]) + return traverse(subcommand(cmd, arg), args[i+1:]) // positional default: @@ -105,34 +105,34 @@ loop: // TODO duplicated code switch { - case c.DisableFlagParsing: - LOG.Printf("flag parsing is disabled for %#v\n", c.Name()) + case cmd.DisableFlagParsing: + LOG.Printf("flag parsing is disabled for %#v\n", cmd.Name()) default: - LOG.Printf("parsing flags for %#v with args %#v\n", c.Name(), toParse) - if err := c.ParseFlags(toParse); err != nil { + LOG.Printf("parsing flags for %#v with args %#v\n", cmd.Name(), toParse) + if err := cmd.ParseFlags(toParse); err != nil { return ActionMessage(err.Error()), context } - context.Args = c.Flags().Args() + context.Args = cmd.Flags().Args() } switch { // dash argument - case common.IsDash(c): + case common.IsDash(cmd): LOG.Printf("completing dash for arg %#v\n", context.Value) - context.Args = c.Flags().Args()[c.ArgsLenAtDash():] + context.Args = cmd.Flags().Args()[cmd.ArgsLenAtDash():] LOG.Printf("context: %#v\n", context.Args) - return storage.getPositional(c, len(context.Args)), context + return storage.getPositional(cmd, len(context.Args)), context // flag argument case inFlag != nil && inFlag.Consumes(context.Value): LOG.Printf("completing flag argument of %#v for arg %#v\n", inFlag.Name, context.Value) context.Parts = inFlag.Args - return storage.getFlag(c, inFlag.Name), context + return storage.getFlag(cmd, inFlag.Name), context // flag - case !c.DisableFlagParsing && strings.HasPrefix(context.Value, "-") && (fs.IsInterspersed() || len(inPositionals) == 0): + case !cmd.DisableFlagParsing && strings.HasPrefix(context.Value, "-") && (fs.IsInterspersed() || len(inPositionals) == 0): if f := fs.LookupArg(context.Value); f != nil && len(f.Args) > 0 { LOG.Printf("completing optional flag argument for arg %#v with prefix %#v\n", context.Value, f.Prefix) @@ -140,21 +140,21 @@ loop: case "bool": return ActionValues("true", "false").StyleF(style.ForKeyword).Usage(f.Usage).Prefix(f.Prefix), context default: - return storage.getFlag(c, f.Name).Prefix(f.Prefix), context + return storage.getFlag(cmd, f.Name).Prefix(f.Prefix), context } } else if f != nil && fs.IsPosix() && !strings.HasPrefix(context.Value, "--") && !f.IsOptarg() && f.Prefix == context.Value { LOG.Printf("completing attached flag argument for arg %#v with prefix %#v\n", context.Value, f.Prefix) - return storage.getFlag(c, f.Name).Prefix(f.Prefix), context + return storage.getFlag(cmd, f.Name).Prefix(f.Prefix), context } LOG.Printf("completing flags for arg %#v\n", context.Value) - return actionFlags(c), context + return actionFlags(cmd), context // positional or subcommand default: LOG.Printf("completing positionals and subcommands for arg %#v\n", context.Value) - batch := Batch(storage.getPositional(c, len(context.Args))) - if c.HasAvailableSubCommands() && len(context.Args) == 0 { - batch = append(batch, actionSubcommands(c)) + batch := Batch(storage.getPositional(cmd, len(context.Args))) + if cmd.HasAvailableSubCommands() && len(context.Args) == 0 { + batch = append(batch, ActionCommands(cmd)) } return batch.ToA(), context }