Repository: rsteube/carapace
Pull request #928 (2023-10-03 11:00:46)

expose ActionCommands
related #888


Patch:
diff --git a/defaultActions.go b/defaultActions.go
index 75799e3..e2c9a45 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 062a6dc..ae439ba 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.cast b/docs/src/carapace/defaultActions/actionCommands.cast
new file mode 100644
index 0000000..f9190f8
--- /dev/null
+++ b/docs/src/carapace/defaultActions/actionCommands.cast
@@ -0,0 +1,92 @@
+{"version": 2, "width": 137, "height": 41, "timestamp": 1696332546, "env": {"SHELL": "elvish", "TERM": "tmux-256color"}}
+[0.094236, "o", "\u001b[?7h\u001b[7m⏎\u001b[m                                                                                                                                        \r \r\u001b[?7l\u001b[?2004h"]
+[0.094797, "o", "\u001b[?25l\r???>                                                                                                                                ???> \r\u001b[5C\u001b[?25h\u001b[?25l\r\u001b[5C\u001b[K\r\u001b[5C\u001b[?25h"]
+[0.108616, "o", "\u001b[?25l\r\r\u001b[5C\u001b[?25h"]
+[0.108696, "o", "\u001b[?25l\r\u001b[K\r\n\u001b[0;1;36mcarapace\u001b[0;m on \u001b[0;1;35m expose-actioncommands\u001b[0;m \u001b[0;1;31m[!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.21.1 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \r\u001b[6C\u001b[?25h"]
+[0.600642, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[0.600799, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.6021, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.603258, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.617333, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.617468, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.820823, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h"]
+[0.92734, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexa\u001b[0;m\r\u001b[9C\u001b[?25h"]
+[0.927892, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[1.041719, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;31mexam\u001b[0;m\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[1.109002, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[10C\u001b[0;31mp\u001b[0;m\r\u001b[11C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[11C\u001b[?25h"]
+[1.209395, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[11C\u001b[0;31ml\u001b[0;m\r\u001b[12C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[12C\u001b[?25h"]
+[1.310631, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexample\u001b[0;m\r\u001b[13C\u001b[?25h"]
+[1.310753, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[13C\u001b[?25h"]
+[1.393126, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[13C \r\u001b[14C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[14C\u001b[?25h"]
+[1.564969, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[14C\u001b[0;4maction \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7;34maction\u001b[0;2;7m (action example)                                                \r\n\u001b[0;34malias\u001b[0;2m (action example)                                                 \r\n\u001b[0;mchain\u001b[0;2m (shorthand chain)                                                \r\n\u001b[0;mcompletion\u001b[0;2m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;34mflag\u001b[0;2m (flag example)                                                    \r\n\u001b[0;mgroup\u001b[0;2m (group example)                                                  \r\n\u001b[0;mhelp\u001b[0;2m (Help about any command)                                          \r\n\u001b[0;minterspersed\u001b[0;2m (interspersed example)                                    \r\n\u001b[0;33mmodifier\u001b[0;2m (modifier example)                                            \r\n\u001b[0;mmultiparts\u001b[0;2m (multiparts example)                                        \r\n\u001b[0;35mplugin\u001b[0;2m (dynamic plugin command)                                        \r\n\u001b[0;mspecial                                                                \r\nsubcommand\u001b[0;2m (subcommand example)                                        \u001b[0;m\u001b[13A\r\u001b[22C\u001b[?25h"]
+[1.565805, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b[13A\r\u001b[22C\u001b[?25h"]
+[2.387067, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[14C\u001b[K\u001b[0;4mchain \r\n\u001b[22C\u001b[0;mh\r\n\u001b[K\u001b[0;7mchain\u001b[0;2;7m (shorthand chain)                                                \r\n\u001b[0;m\u001b[Kcompletion\u001b[0;2m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;m\u001b[Khelp\u001b[0;2m (Help about any command)                                          \u001b[0;m\r\n\u001b[J\u001b[A\u001b[3A\r\u001b[23C\u001b[?25h"]
+[2.467791, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[15C\u001b[K\u001b[0;4mompletion \r\n\u001b[23C\u001b[0;me\r\n\u001b[1C\u001b[K\u001b[0;7mompletion\u001b[0;2;7m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;m\u001b[Khelp\u001b[0;2m (Help about any command)                                          \u001b[0;m\r\n\u001b[J\u001b[A\u001b[2A\r\u001b[24C\u001b[?25h"]
+[2.467925, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\r\n\r\n\r\n\u001b[2A\r\u001b[24C\u001b[?25h"]
+[3.031697, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[14C\u001b[K\u001b[0;4mhelp \r\n\r\n\u001b[0;m\u001b[Kcompletion\u001b[0;2m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;m\u001b[K\u001b[0;7mhelp\u001b[0;2;7m (Help about any command)                                          \u001b[0;m\u001b[2A\r\u001b[24C\u001b[?25h"]
+[3.266605, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[14C\u001b[Khelp \r\n\u001b[J\u001b[A\r\u001b[19C\u001b[?25h"]
+[3.779308, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[19C\u001b[0;4maction \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7;34maction\u001b[0;2;7m (action example)                                                \r\n\u001b[0;34malias\u001b[0;2m (action example)                                                 \r\n\u001b[0;mchain\u001b[0;2m (shorthand chain)                                                \r\n\u001b[0;mcompletion\u001b[0;2m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;34mflag\u001b[0;2m (flag example)                                                    \r\n\u001b[0;mgroup\u001b[0;2m (group example)                                                  \r\n\u001b[0;mhelp\u001b[0;2m (Help about any command)                                          \r\n\u001b[0;minterspersed\u001b[0;2m (interspersed example)                                    \r\n\u001b[0;33mmodifier\u001b[0;2m (modifier example)                                            \r\n\u001b[0;mmultiparts\u001b[0;2m (multiparts example)                                        \r\n\u001b[0;35mplugin\u001b[0;2m (dynamic plugin command)                                        \r\n\u001b[0;mspecial                                                                \r\nsubcommand\u001b[0;2m (subcommand example)                                        \u001b[0;m\u001b[13A\r\u001b[22C\u001b[?25h"]
+[3.780158, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b[13A\r\u001b[22C\u001b[?25h"]
+[4.80428, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[20C\u001b[K\u001b[0;4mlias \r\n\u001b[22C\u001b[0;ms\r\n\u001b[1C\u001b[K\u001b[0;7;34mlias\u001b[0;2;7m (action example)                                                 \r\n\u001b[0;m\u001b[Kchain\u001b[0;2m (shorthand chain)                                                \r\n\u001b[1C\u001b[0;m\u001b[Kompletion\u001b[0;2m (Generate the autocompletion script for the specified shell)\r\n\u001b[0;m\u001b[Kinterspersed\u001b[0;2m (interspersed example)                                    \r\n\u001b[0;m\u001b[Kmultiparts\u001b[0;2m (multiparts example)                                        \r\n\u001b[0;m\u001b[Kspecial                                                                \r\n\u001b[Ksubcommand\u001b[0;2m (subcommand example)                                        \u001b[0;m\r\n\u001b[J\u001b[A\u001b[7A\r\u001b[23C\u001b[?25h"]
+[4.955258, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[19C\u001b[K\u001b[0;4msubcommand \r\n\u001b[23C\u001b[0;mu\r\n\u001b[K\u001b[0;7msubcommand\u001b[0;2;7m (subcommand example)\u001b[0;m\r\n\u001b[J\u001b[A\u001b[1A\r\u001b[24C\u001b[?25h\u001b[?25l\u001b[3A\r\r\n\r\n\r\n\r\n\u001b[1A\r\u001b[24C\u001b[?25h"]
+[5.617643, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[19C\u001b[Ksubcommand \r\n\u001b[J\u001b[A\r\u001b[30C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[30C\u001b[?25h"]
+[6.588664, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[30C\u001b[0;4ma1 \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7ma1\u001b[0;2;7m (subcommand with alias)\u001b[0;m  a2\u001b[0;2m (subcommand with alias)\u001b[0;m  alias\u001b[0;2m (subcommand with alias)\u001b[0;m  \u001b[0;34mgroup\u001b[0;2m (subcommand with group)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[7.575675, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[31C\u001b[K\u001b[0;4m2 \r\n\r\n\u001b[0;m\u001b[Ka1\u001b[0;2m (subcommand with alias)\u001b[0;m  \u001b[0;7ma2\u001b[0;2;7m (subcommand with alias)\u001b[0;m  alias\u001b[0;2m (subcommand with alias)\u001b[0;m  \u001b[0;34mgroup\u001b[0;2m (subcommand with group)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[7.738443, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[31C\u001b[K\u001b[0;4mlias \r\n\r\n\u001b[28C\u001b[0;m\u001b[Ka2\u001b[0;2m (subcommand with alias)\u001b[0;m  \u001b[0;7malias\u001b[0;2;7m (subcommand with alias)\u001b[0;m  \u001b[0;34mgroup\u001b[0;2m (subcommand with group)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[7.876879, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[30C\u001b[K\u001b[0;4mgroup \r\n\r\n\u001b[56C\u001b[0;m\u001b[Kalias\u001b[0;2m (subcommand with alias)\u001b[0;m  \u001b[0;7;34mgroup\u001b[0;2;7m (subcommand with group)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[8.139634, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[30C\u001b[Kgroup \r\n\u001b[J\u001b[A\r\u001b[36C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[36C\u001b[?25h"]
+[8.345816, "o", "\u001b[?25l\u001b[2A\r\u001b[0;2musage: \u001b[0;mhelp [command]\u001b[K\r\n\u001b[0;31merror:\u001b[0;m no candidates\u001b[K\r\n\u001b[K\r\n\u001b[K\u001b[0;1;36mcarapace\u001b[0;m on \u001b[0;1;35m expose-actioncommands\u001b[0;m \u001b[0;1;31m[!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.21.1 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \u001b[0;32mexample\u001b[0;m help subcommand group \r\u001b[36C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[36C\u001b[?25h"]
+[9.182447, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[35C\u001b[K\r\u001b[35C\u001b[?25h"]
+[9.310809, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[34C\u001b[K\r\u001b[34C\u001b[?25h"]
+[9.463969, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[33C\u001b[K\r\u001b[33C\u001b[?25h"]
+[9.611221, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32C\u001b[K\r\u001b[32C\u001b[?25h"]
+[9.779251, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[31C\u001b[K\r\u001b[31C\u001b[?25h"]
+[9.779495, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[31C\u001b[?25h"]
+[9.781015, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[31C\u001b[?25h"]
+[9.781267, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[31C\u001b[?25h"]
+[9.966292, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[30C\u001b[K\r\u001b[30C\u001b[?25h"]
+[10.210334, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[30Ch\r\u001b[31C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[31C\u001b[?25h"]
+[10.296325, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[31Ci\r\u001b[32C\u001b[?25h"]
+[10.460204, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32Cd\r\u001b[33C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[33C\u001b[?25h"]
+[10.640842, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[33Cd\r\u001b[34C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[34C\u001b[?25h"]
+[10.812994, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[34Ce\r\u001b[35C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[35C\u001b[?25h"]
+[10.935553, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[35Cn\r\u001b[36C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[36C\u001b[?25h"]
+[11.069793, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[36C \r\u001b[37C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[37C\u001b[?25h"]
+[11.221802, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[37Cvisible \r\u001b[45C\u001b[?25h"]
+[12.050845, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[44C\u001b[K\r\u001b[44C\u001b[?25h"]
+[12.651453, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[43C\u001b[K\r\u001b[43C\u001b[?25h"]
+[12.691589, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[42C\u001b[K\r\u001b[42C\u001b[?25h"]
+[12.731299, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[41C\u001b[K\r\u001b[41C\u001b[?25h"]
+[12.771711, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[40C\u001b[K\r\u001b[40C\u001b[?25h"]
+[12.810859, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[39C\u001b[K\r\u001b[39C\u001b[?25h"]
+[12.851552, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[38C\u001b[K\r\u001b[38C\u001b[?25h"]
+[12.890893, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[37C\u001b[K\r\u001b[37C\u001b[?25h"]
+[12.931143, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[36C\u001b[K\r\u001b[36C\u001b[?25h"]
+[12.970876, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[35C\u001b[K\r\u001b[35C\u001b[?25h"]
+[13.010886, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[34C\u001b[K\r\u001b[34C\u001b[?25h"]
+[13.212033, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[33C\u001b[K\r\u001b[33C\u001b[?25h"]
+[13.383856, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32C\u001b[K\r\u001b[32C\u001b[?25h"]
+[13.549261, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[31C\u001b[K\r\u001b[31C\u001b[?25h"]
+[13.688414, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[30C\u001b[K\r\u001b[30C\u001b[?25h"]
+[13.999022, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[30Cu\r\u001b[31C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[31C\u001b[?25h"]
+[14.16606, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[31Cn\r\u001b[32C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h"]
+[14.166814, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h"]
+[14.167524, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h"]
+[14.167796, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h"]
+[14.378884, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32Ck\r\u001b[33C\u001b[?25h"]
+[14.516593, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[33Cn\r\u001b[34C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[34C\u001b[?25h"]
+[14.618915, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[34Co\r\u001b[35C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[35C\u001b[?25h"]
+[14.748914, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[35Cw\r\u001b[36C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[36C\u001b[?25h"]
+[14.863251, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[36Cn\r\u001b[37C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[37C\u001b[?25h"]
+[14.969171, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[37C \r\u001b[38C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[38C\u001b[?25h"]
+[15.260303, "o", "\u001b[?25l\u001b[2A\r\u001b[0;31merror: \u001b[0;munknown subcommand \"unknown\" for \"subcommand\"\u001b[K\r\n\u001b[0;2musage: \u001b[0;mhelp [command]\u001b[K\r\n\u001b[0;31merror:\u001b[0;m no candidates\u001b[K\r\n\u001b[K\r\n\u001b[K\u001b[0;1;36mcarapace\u001b[0;m on \u001b[0;1;35m expose-actioncommands\u001b[0;m \u001b[0;1;31m[!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.21.1 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \u001b[0;32mexample\u001b[0;m help subcommand unknown \r\u001b[38C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[38C\u001b[?25h"]
+[17.583113, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\r\u001b[6C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[17.583276, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[17.584402, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[17.608591, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[17.608683, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[17.882376, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[18.144484, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[18.299889, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[8C\u001b[0;31mi\u001b[0;m\r\u001b[9C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[18.394181, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexit\u001b[0;m\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[18.501852, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\n\r\u001b[?25h\u001b[?7h\u001b[?2004l\r"]
diff --git a/docs/src/carapace/defaultActions/actionCommands.md b/docs/src/carapace/defaultActions/actionCommands.md
new file mode 100644
index 0000000..b078bc2
--- /dev/null
+++ b/docs/src/carapace/defaultActions/actionCommands.md
@@ -0,0 +1,17 @@
+# ActionCommands
+
+[`ActionCommands`] completes (sub)commands of given command.
+
+> `Context.Args` is used to traverse the command tree further down.
+> Use [Shift](../action/shift.md) to avoid this.
+
+
+```go
+carapace.Gen(helpCmd).PositionalAnyCompletion(
+	carapace.ActionCommands(cmd),
+)
+```
+
+![](./actionCommands.cast)
+
+[`ActionCommands`]:https://pkg.go.dev/github.com/rsteube/carapace#ActionCommands
diff --git a/example/cmd/action.go b/example/cmd/action.go
index bd7acc9..95684c8 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 0000000..a8b8d6d
--- /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 0000000..5f706f4
--- /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 0000000..f0b6285
--- /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 0000000..0478b8f
--- /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 0000000..e506069
--- /dev/null
+++ b/example/cmd/subcommand_hidden_visible.go
@@ -0,0 +1,18 @@
+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 3a52e66..e5b753a 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 5f4c485..2a54477 100644
--- a/traverse.go
+++ b/traverse.go
@@ -154,7 +154,7 @@ loop:
 		LOG.Printf("completing positionals and subcommands for arg %#v\n", context.Value)
 		batch := Batch(storage.getPositional(cmd, len(context.Args)))
 		if cmd.HasAvailableSubCommands() && len(context.Args) == 0 {
-			batch = append(batch, actionSubcommands(cmd))
+			batch = append(batch, ActionCommands(cmd))
 		}
 		return batch.ToA(), context
 	}


Test patch:
diff --git a/example/cmd/action_test.go b/example/cmd/action_test.go
index 4058c7a..b280690 100644
--- a/example/cmd/action_test.go
+++ b/example/cmd/action_test.go
@@ -24,6 +24,44 @@ func TestAction(t *testing.T) {
 			Expect(carapace.ActionMessage("values flag is not set").
 				Usage("ActionCallback()"))
 
+		s.Run("action", "--commands", "s").
+			Expect(carapace.ActionValuesDescribed(
+				"special", "",
+				"subcommand", "subcommand example",
+			).Suffix(" ").
+				NoSpace().
+				Tag("other commands").
+				Usage("ActionCommands()"))
+
+		s.Run("action", "--commands", "subcommand ").
+			Expect(carapace.Batch(
+				carapace.ActionValuesDescribed(
+					"a1", "subcommand with alias",
+					"a2", "subcommand with alias",
+					"alias", "subcommand with alias",
+				).Tag("other commands"),
+				carapace.ActionValuesDescribed(
+					"group", "subcommand with group",
+				).Style(style.Blue).Tag("group commands"),
+			).ToA().
+				Prefix("subcommand ").
+				Suffix(" ").
+				NoSpace().
+				Usage("ActionCommands()"))
+
+		s.Run("action", "--commands", "subcommand unknown ").
+			Expect(carapace.ActionMessage(`unknown subcommand "unknown" for "subcommand"`).NoSpace().
+				Usage("ActionCommands()"))
+
+		s.Run("action", "--commands", "subcommand hidden ").
+			Expect(carapace.ActionValuesDescribed(
+				"visible", "visible subcommand of a hidden command",
+			).Prefix("subcommand hidden ").
+				Suffix(" ").
+				NoSpace().
+				Tag("commands").
+				Usage("ActionCommands()"))
+
 		s.Run("action", "--values", "first", "--callback", "").
 			Expect(carapace.ActionMessage("values flag is set to: 'first'").
 				Usage("ActionCallback()"))
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 214f680..1233971 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -79,6 +79,7 @@ func TestRoot(t *testing.T) {
 					"interspersed", "interspersed example",
 					"multiparts", "multiparts example",
 					"special", "",
+					"subcommand", "subcommand example",
 				).Tag("other commands"),
 			).ToA())