Repository: rsteube/carapace
Pull request #920 (2023-09-25 14:55:58)

cobra bridge
related https://github.com/spf13/cobra/pull/1943
related https://github.com/spf13/cobra/issues/2019



Patch:
diff --git a/.gitignore b/.gitignore
index a4aea7d..592ae4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ caraparse/caraparse
 docs/book
 example/cmd/_test_files/*.txt
 example/example
+example-nonposix/example-nonposix
 integration.cov
 unit.cov
 .vscode
diff --git a/carapace.go b/carapace.go
index 736cfc4..900691b 100644
--- a/carapace.go
+++ b/carapace.go
@@ -57,7 +57,7 @@ func (c Carapace) PositionalCompletion(action ...Action) {
 
 // PositionalAnyCompletion defines completion for any positional arguments not already defined.
 func (c Carapace) PositionalAnyCompletion(action Action) {
-	storage.get(c.cmd).positionalAny = action
+	storage.get(c.cmd).positionalAny = &action
 }
 
 // DashCompletion defines completion for positional arguments after dash (`--`) using a list of Actions.
@@ -67,7 +67,7 @@ func (c Carapace) DashCompletion(action ...Action) {
 
 // DashAnyCompletion defines completion for any positional arguments after dash (`--`) not already defined.
 func (c Carapace) DashAnyCompletion(action Action) {
-	storage.get(c.cmd).dashAny = action
+	storage.get(c.cmd).dashAny = &action
 }
 
 // FlagCompletion defines completion for flags using a map consisting of name and Action.
diff --git a/compat.go b/compat.go
index 800116f..44eddcb 100644
--- a/compat.go
+++ b/compat.go
@@ -2,6 +2,7 @@ package carapace
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
@@ -10,7 +11,10 @@ import (
 func registerValidArgsFunction(cmd *cobra.Command) {
 	if cmd.ValidArgsFunction == nil {
 		cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			action := storage.getPositional(cmd, len(args)).Invoke(Context{Args: args, Value: toComplete})
+			action := Action{}.Invoke(Context{Args: args, Value: toComplete}) // TODO just IvokedAction{} ok?
+			if storage.hasPositional(cmd, len(args)) {
+				action = storage.getPositional(cmd, len(args)).Invoke(Context{Args: args, Value: toComplete})
+			}
 			return cobraValuesFor(action), cobraDirectiveFor(action)
 		}
 	}
@@ -18,9 +22,16 @@ func registerValidArgsFunction(cmd *cobra.Command) {
 
 func registerFlagCompletion(cmd *cobra.Command) {
 	cmd.LocalFlags().VisitAll(func(f *pflag.Flag) {
+		if !storage.hasFlag(cmd, f.Name) {
+			return // skip if not defined in carapace
+		}
+		if _, ok := cmd.GetFlagCompletionFunc(f.Name); ok {
+			return // skip if already defined in cobra
+		}
+
 		err := cmd.RegisterFlagCompletionFunc(f.Name, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 			a := storage.getFlag(cmd, f.Name)
-			action := a.Invoke(Context{Args: args, Value: toComplete})
+			action := a.Invoke(Context{Args: args, Value: toComplete}) // TODO cmd might differ for persistentflags and either way args or cmd will be wrong
 			return cobraValuesFor(action), cobraDirectiveFor(action)
 		})
 		if err != nil {
@@ -51,3 +62,48 @@ func cobraDirectiveFor(action InvokedAction) cobra.ShellCompDirective {
 	}
 	return directive
 }
+
+type compDirective cobra.ShellCompDirective
+
+func (d compDirective) matches(cobraDirective cobra.ShellCompDirective) bool {
+	return d&compDirective(cobraDirective) != 0
+}
+
+func (d compDirective) ToA(values ...string) Action {
+	var action Action
+	switch {
+	case d.matches(cobra.ShellCompDirectiveError):
+		return ActionMessage("an error occured")
+	case d.matches(cobra.ShellCompDirectiveFilterDirs):
+		switch len(values) {
+		case 0:
+			action = ActionDirectories()
+		default:
+			action = ActionDirectories().Chdir(values[0])
+		}
+	case d.matches(cobra.ShellCompDirectiveFilterFileExt):
+		extensions := make([]string, 0)
+		for _, v := range values {
+			extensions = append(extensions, "."+v)
+		}
+		return ActionFiles(extensions...)
+	case len(values) == 0 && !d.matches(cobra.ShellCompDirectiveNoFileComp):
+		action = ActionFiles()
+	default:
+		vals := make([]string, 0)
+		for _, v := range values {
+			if splitted := strings.SplitN(v, "\t", 2); len(splitted) == 2 {
+				vals = append(vals, splitted[0], splitted[1])
+			} else {
+				vals = append(vals, splitted[0], "")
+			}
+		}
+		action = ActionValuesDescribed(vals...)
+	}
+
+	if d.matches(cobra.ShellCompDirectiveNoSpace) {
+		action = action.NoSpace()
+	}
+
+	return action
+}
diff --git a/context.go b/context.go
index bdf02b4..2eed94a 100644
--- a/context.go
+++ b/context.go
@@ -12,6 +12,7 @@ import (
 	"github.com/rsteube/carapace/pkg/util"
 	"github.com/rsteube/carapace/third_party/github.com/drone/envsubst"
 	"github.com/rsteube/carapace/third_party/golang.org/x/sys/execabs"
+	"github.com/spf13/cobra"
 )
 
 // Context provides information during completion.
@@ -28,6 +29,7 @@ type Context struct {
 	Dir string
 
 	mockedReplies map[string]string
+	cmd           *cobra.Command // needed for ActionCobra
 }
 
 // NewContext creates a new context for given arguments.
diff --git a/defaultActions.go b/defaultActions.go
index e2c9a45..2d1df6a 100644
--- a/defaultActions.go
+++ b/defaultActions.go
@@ -474,7 +474,11 @@ func ActionPositional(cmd *cobra.Command) Action {
 		c.Args = cmd.Flags().Args()
 		entry := storage.get(cmd)
 
-		a := entry.positionalAny
+		var a Action
+		if entry.positionalAny != nil {
+			a = *entry.positionalAny
+		}
+
 		if index := len(c.Args); index < len(entry.positional) {
 			a = entry.positional[len(c.Args)]
 		}
@@ -514,3 +518,18 @@ func ActionCommands(cmd *cobra.Command) Action {
 		return batch.ToA()
 	})
 }
+
+// ActionCora bridges given cobra completion function.
+func ActionCobra(f func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)) Action {
+	return ActionCallback(func(c Context) Action {
+		switch {
+		case f == nil:
+			return ActionValues()
+		case c.cmd == nil: // ensure cmd is never nil even if context does not contain one
+			LOG.Print("cmd is nil [ActionCobra]")
+			c.cmd = &cobra.Command{Use: "_carapace_actioncobra", Hidden: true, Deprecated: "dummy command for ActionCobra"}
+		}
+		values, directive := f(c.cmd, c.cmd.Flags().Args(), c.Value)
+		return compDirective(directive).ToA(values...)
+	})
+}
diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md
index ae439ba..6f943bf 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)
+    - [ActionCobra](./carapace/defaultActions/actionCobra.md)
     - [ActionCommands](./carapace/defaultActions/actionCommands.md)
     - [ActionDirectories](./carapace/defaultActions/actionDirectories.md)
     - [ActionExecCommand](./carapace/defaultActions/actionExecCommand.md)
diff --git a/docs/src/carapace/defaultActions/actionCobra.cast b/docs/src/carapace/defaultActions/actionCobra.cast
new file mode 100644
index 0000000..6450c46
--- /dev/null
+++ b/docs/src/carapace/defaultActions/actionCobra.cast
@@ -0,0 +1,50 @@
+{"version": 2, "width": 108, "height": 24, "timestamp": 1701100000, "env": {"SHELL": "elvish", "TERM": "tmux-256color"}}
+[0.122617, "o", "\u001b[?7h\u001b[7m⏎\u001b[m                                                                                                           \r \r\u001b[?7l\u001b[?2004h\u001b[?25l\r???>                                                                                                   ???> \r\u001b[5C\u001b[?25h"]
+[0.123322, "o", "\u001b[?25l\r\u001b[5C\u001b[K\r\u001b[5C\u001b[?25h"]
+[0.139511, "o", "\u001b[?25l\r\r\u001b[5C\u001b[?25h"]
+[0.139635, "o", "\u001b[?25l\r\u001b[K\r\n\u001b[0;1;36mcarapace/example\u001b[0;m on \u001b[0;1;35m cobra-bridge\u001b[0;m \u001b[0;1;31m[$!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.21.4 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \r\u001b[6C\u001b[?25h"]
+[0.456695, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;32me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[0.457047, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.457406, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.476703, "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.476774, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.661672, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;31mex\u001b[0;m\r\u001b[8C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[0.756942, "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.757869, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[0.758057, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[0.915507, "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"]
+[0.972819, "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.088957, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[11C\u001b[0;31ml\u001b[0;m\r\u001b[12C\u001b[?25h"]
+[1.242635, "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\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[13C\u001b[?25h"]
+[1.318984, "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.421313, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[14Ca\r\u001b[15C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[15C\u001b[?25h"]
+[1.565644, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[15Cc\r\u001b[16C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[16C\u001b[?25h"]
+[1.789857, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[16Ct\r\u001b[17C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[17C\u001b[?25h"]
+[1.861419, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[17Ci\r\u001b[18C\u001b[?25h"]
+[1.861572, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[18C\u001b[?25h"]
+[1.953279, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[18Co\r\u001b[19C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[19C\u001b[?25h"]
+[2.001547, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[19Cn\r\u001b[20C\u001b[?25h"]
+[2.001701, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[20C\u001b[?25h"]
+[2.133981, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[20C \r\u001b[21C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[21C\u001b[?25h"]
+[2.297596, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[21C-\r\u001b[22C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[22C\u001b[?25h"]
+[2.449595, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[22C-\r\u001b[23C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[23C\u001b[?25h"]
+[2.665291, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[23Cc\r\u001b[24C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[24C\u001b[?25h"]
+[2.769879, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[24Co\r\u001b[25C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[25C\u001b[?25h"]
+[2.920473, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[21C\u001b[K\u001b[0;4m--cobra \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7;34m--cobra\u001b[0;2;7m (ActionCobra())\u001b[0;m  \u001b[0;34m--commands\u001b[0;2m (ActionCommands())\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[3.679034, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[21C\u001b[K--cobra \r\n\u001b[J\u001b[A\r\u001b[29C\u001b[?25h"]
+[4.167667, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[29C\u001b[0;4mone\r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7mone\u001b[0;m  two\u001b[1A\r\u001b[22C\u001b[?25h"]
+[5.054262, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[29C\u001b[K\u001b[0;4mtwo\r\n\r\n\u001b[0;m\u001b[Kone  \u001b[0;7mtwo\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[6.629692, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[29C\u001b[Ktwo\r\n\u001b[J\u001b[A\r\u001b[32C\u001b[?25h"]
+[6.629826, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[32C\u001b[?25h"]
+[7.230671, "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"]
+[7.231617, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[7.25196, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[7.471051, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;32me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[7.471209, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[7.684061, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;31mex\u001b[0;m\r\u001b[8C\u001b[?25h"]
+[7.684762, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[7.688565, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[7.688621, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[7.816432, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[8C\u001b[0;31mi\u001b[0;m\r\u001b[9C\u001b[?25h"]
+[7.907227, "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"]
+[8.0253, "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/actionCobra.md b/docs/src/carapace/defaultActions/actionCobra.md
new file mode 100644
index 0000000..ffed651
--- /dev/null
+++ b/docs/src/carapace/defaultActions/actionCobra.md
@@ -0,0 +1,13 @@
+# ActionCobra
+
+[`ActionCobra`] bridges given cobra completion function.
+
+```go
+carapace.ActionCobra(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return []string{"one", "two"}, cobra.ShellCompDirectiveNoSpace
+})
+```
+
+![](./actionCobra.cast)
+
+[`ActionCobra`]:https://pkg.go.dev/github.com/rsteube/carapace#ActionCobra
diff --git a/example/cmd/action.go b/example/cmd/action.go
index 95684c8..eb70cc7 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("cobra", "", "ActionCobra()")
 	actionCmd.Flags().String("commands", "", "ActionCommands()")
 	actionCmd.Flags().String("directories", "", "ActionDirectories()")
 	actionCmd.Flags().String("execcommand", "", "ActionExecCommand()")
@@ -49,6 +50,9 @@ func init() {
 			}
 			return carapace.ActionMessage("values flag is not set")
 		}),
+		"cobra": carapace.ActionCobra(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return []string{"one", "two"}, cobra.ShellCompDirectiveNoSpace
+		}),
 		"commands":    carapace.ActionCommands(rootCmd).Split(),
 		"directories": carapace.ActionDirectories(),
 		"execcommand": carapace.ActionExecCommand("git", "remote")(func(output []byte) carapace.Action {
diff --git a/example/cmd/compat.go b/example/cmd/compat.go
new file mode 100644
index 0000000..c6d7d28
--- /dev/null
+++ b/example/cmd/compat.go
@@ -0,0 +1,87 @@
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/rsteube/carapace"
+	"github.com/spf13/cobra"
+)
+
+var compatCmd = &cobra.Command{
+	Use:   "compat",
+	Short: "",
+	Run:   func(cmd *cobra.Command, args []string) {},
+}
+
+func init() {
+	carapace.Gen(compatCmd).Standalone()
+
+	compatCmd.Flags()
+
+	compatCmd.Flags().String("error", "", "ShellCompDirectiveError")
+	compatCmd.Flags().String("nospace", "", "ShellCompDirectiveNoSpace")
+	compatCmd.Flags().String("nofilecomp", "", "ShellCompDirectiveNoFileComp")
+	compatCmd.Flags().String("filterfileext", "", "ShellCompDirectiveFilterFileExt")
+	compatCmd.Flags().String("filterdirs", "", "ShellCompDirectiveFilterDirs")
+	compatCmd.Flags().String("filterdirs-chdir", "", "ShellCompDirectiveFilterDirs")
+	compatCmd.Flags().String("keeporder", "", "ShellCompDirectiveKeepOrder")
+	compatCmd.Flags().String("default", "", "ShellCompDirectiveDefault")
+
+	compatCmd.Flags().String("unset", "", "no completions defined")
+	compatCmd.PersistentFlags().String("persistent-compat", "", "persistent flag defined with cobra")
+
+	rootCmd.AddCommand(compatCmd)
+
+	_ = compatCmd.RegisterFlagCompletionFunc("error", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return nil, cobra.ShellCompDirectiveError
+	})
+	_ = compatCmd.RegisterFlagCompletionFunc("nospace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return []string{"one", "two"}, cobra.ShellCompDirectiveNoSpace
+	})
+	_ = compatCmd.RegisterFlagCompletionFunc("nofilecomp", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return nil, cobra.ShellCompDirectiveNoFileComp
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("filterfileext", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return []string{"mod", "sum"}, cobra.ShellCompDirectiveFilterFileExt
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("filterdirs", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return nil, cobra.ShellCompDirectiveFilterDirs
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("filterdirs-chdir", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return []string{"subdir"}, cobra.ShellCompDirectiveFilterDirs
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("keeporder", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return []string{"one", "three", "two"}, cobra.ShellCompDirectiveKeepOrder
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("default", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return nil, cobra.ShellCompDirectiveDefault
+	})
+
+	_ = compatCmd.RegisterFlagCompletionFunc("persistent-compat", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return []string{
+			fmt.Sprintf("args: %#v toComplete: %#v", args, toComplete),
+			"alternative",
+		}, cobra.ShellCompDirectiveNoFileComp
+	})
+
+	compatCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		switch len(args) {
+		case 0:
+			return []string{"p1", "positional1"}, cobra.ShellCompDirectiveDefault
+		case 1:
+			return nil, cobra.ShellCompDirectiveDefault
+		case 2:
+			return []string{
+				fmt.Sprintf("args: %#v toComplete: %#v", args, toComplete),
+				"alternative",
+			}, cobra.ShellCompDirectiveNoFileComp
+		default:
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+	}
+}
diff --git a/example/cmd/compat_sub.go b/example/cmd/compat_sub.go
new file mode 100644
index 0000000..ac3d3ef
--- /dev/null
+++ b/example/cmd/compat_sub.go
@@ -0,0 +1,18 @@
+package cmd
+
+import (
+	"github.com/rsteube/carapace"
+	"github.com/spf13/cobra"
+)
+
+var compat_subCmd = &cobra.Command{
+	Use:   "sub",
+	Short: "",
+	Run:   func(cmd *cobra.Command, args []string) {},
+}
+
+func init() {
+	carapace.Gen(compat_subCmd).Standalone()
+
+	compatCmd.AddCommand(compat_subCmd)
+}
diff --git a/go.work.sum b/go.work.sum
index 0eead84..8464a61 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -1,4 +1,5 @@
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/rsteube/carapace-pflag v0.0.4 h1:Onb0cLNLxg1xJr2EsMlBldAI5KkybrvZ89b5cRElZXI=
 github.com/rsteube/carapace-pflag v0.0.4/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/rsteube/carapace-pflag v0.0.5 h1:QQC0KnthHMayHsX7B7DxqOkr0B6JSIM0glB+KrSTruU=
@@ -7,6 +8,6 @@ github.com/rsteube/carapace-pflag v0.1.0 h1:CPJRlj3jbyOnxuMf5pdrM76hEwdQ0STDDmkA
 github.com/rsteube/carapace-pflag v0.1.0/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/rsteube/carapace-shlex v0.0.1 h1:8uvsc+ISKw7uoITSp92nNisFUOulYMz+Uu7N5nbHTiM=
 github.com/rsteube/carapace-shlex v0.0.1/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQMATzDMYQQybo3o=
-github.com/rsteube/carapace-shlex v0.0.4 h1:3GVn8PaM2RCxPTAiwVy9vDQI8Mi7DqrbdpDgf5ZzQmY=
-github.com/rsteube/carapace-shlex v0.0.4/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQMATzDMYQQybo3o=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
diff --git a/storage.go b/storage.go
index 082d986..b937885 100644
--- a/storage.go
+++ b/storage.go
@@ -17,9 +17,9 @@ type entry struct {
 	flag          ActionMap
 	flagMutex     sync.RWMutex
 	positional    []Action
-	positionalAny Action
+	positionalAny *Action
 	dash          []Action
-	dashAny       Action
+	dashAny       *Action
 	preinvoke     func(cmd *cobra.Command, flag *pflag.Flag, action Action) Action
 	prerun        func(cmd *cobra.Command, args []string)
 	bridged       bool
@@ -72,6 +72,18 @@ func (s _storage) bridge(cmd *cobra.Command) {
 	}
 }
 
+func (s _storage) hasFlag(cmd *cobra.Command, name string) bool {
+	if flag := cmd.LocalFlags().Lookup(name); flag == nil && cmd.HasParent() {
+		return s.hasFlag(cmd.Parent(), name)
+	} else {
+		entry := s.get(cmd)
+		entry.flagMutex.RLock()
+		defer entry.flagMutex.RUnlock()
+		_, ok := entry.flag[name]
+		return ok
+	}
+}
+
 func (s _storage) getFlag(cmd *cobra.Command, name string) Action {
 	if flag := cmd.LocalFlags().Lookup(name); flag == nil && cmd.HasParent() {
 		return s.getFlag(cmd.Parent(), name)
@@ -79,7 +91,15 @@ func (s _storage) getFlag(cmd *cobra.Command, name string) Action {
 		entry := s.get(cmd)
 		entry.flagMutex.RLock()
 		defer entry.flagMutex.RUnlock()
-		a := s.preinvoke(cmd, flag, entry.flag[name])
+
+		flagAction, ok := entry.flag[name]
+		if !ok {
+			if f, ok := cmd.GetFlagCompletionFunc(name); ok {
+				flagAction = ActionCobra(f)
+			}
+		}
+
+		a := s.preinvoke(cmd, flag, flagAction)
 
 		return ActionCallback(func(c Context) Action { // TODO verify order of execution is correct
 			invoked := a.Invoke(c)
@@ -112,6 +132,24 @@ func (s _storage) preinvoke(cmd *cobra.Command, flag *pflag.Flag, action Action)
 	return a
 }
 
+func (s _storage) hasPositional(cmd *cobra.Command, index int) bool {
+	entry := s.get(cmd)
+	isDash := common.IsDash(cmd)
+
+	// TODO fallback to cobra defined completion if exists
+
+	switch {
+	case !isDash && len(entry.positional) > index:
+		return true
+	case !isDash:
+		return entry.positionalAny != nil
+	case len(entry.dash) > index:
+		return true
+	default:
+		return entry.dashAny != nil
+	}
+}
+
 func (s _storage) getPositional(cmd *cobra.Command, index int) Action {
 	entry := s.get(cmd)
 	isDash := common.IsDash(cmd)
@@ -119,14 +157,23 @@ func (s _storage) getPositional(cmd *cobra.Command, index int) Action {
 	var a Action
 	switch {
 	case !isDash && len(entry.positional) > index:
-		a = s.preinvoke(cmd, nil, entry.positional[index])
+		a = entry.positional[index]
 	case !isDash:
-		a = s.preinvoke(cmd, nil, entry.positionalAny)
+		if entry.positionalAny != nil {
+			a = *entry.positionalAny
+		} else {
+			a = ActionCobra(cmd.ValidArgsFunction)
+		}
 	case len(entry.dash) > index:
-		a = s.preinvoke(cmd, nil, entry.dash[index])
+		a = entry.dash[index]
 	default:
-		a = s.preinvoke(cmd, nil, entry.dashAny)
+		if entry.dashAny != nil {
+			a = *entry.dashAny
+		} else {
+			a = ActionCobra(cmd.ValidArgsFunction)
+		}
 	}
+	a = s.preinvoke(cmd, nil, a)
 
 	return ActionCallback(func(c Context) Action {
 		invoked := a.Invoke(c)
diff --git a/traverse.go b/traverse.go
index 2a54477..eb8a747 100644
--- a/traverse.go
+++ b/traverse.go
@@ -26,6 +26,7 @@ func traverse(cmd *cobra.Command, args []string) (Action, Context) {
 	fs := pflagfork.FlagSet{FlagSet: cmd.Flags()}
 
 	context := NewContext(args...)
+	context.cmd = cmd
 loop:
 	for i, arg := range context.Args {
 		switch {


Test patch:
diff --git a/compat_test.go b/compat_test.go
index fa55901..0e92024 100644
--- a/compat_test.go
+++ b/compat_test.go
@@ -1,7 +1,7 @@
 package carapace
 
 import (
-	"io/ioutil"
+	"io"
 	"os"
 	"strings"
 	"testing"
@@ -54,7 +54,7 @@ func TestRegisterFlagCompletion(t *testing.T) {
 	_ = cmd.Execute()
 
 	w.Close()
-	out, _ := ioutil.ReadAll(r)
+	out, _ := io.ReadAll(r)
 	os.Stdout = rescueStdout
 
 	if lines := strings.Split(string(out), "\n"); lines[0] != "1\tone" {
diff --git a/example/cmd/action_test.go b/example/cmd/action_test.go
index b280690..0d88dfd 100644
--- a/example/cmd/action_test.go
+++ b/example/cmd/action_test.go
@@ -24,6 +24,13 @@ func TestAction(t *testing.T) {
 			Expect(carapace.ActionMessage("values flag is not set").
 				Usage("ActionCallback()"))
 
+		s.Run("action", "--cobra", "").
+			Expect(carapace.ActionValues(
+				"one",
+				"two",
+			).NoSpace().
+				Usage("ActionCobra()"))
+
 		s.Run("action", "--commands", "s").
 			Expect(carapace.ActionValuesDescribed(
 				"special", "",
diff --git a/example/cmd/compat_sub_test.go b/example/cmd/compat_sub_test.go
new file mode 100644
index 0000000..9fa70c2
--- /dev/null
+++ b/example/cmd/compat_sub_test.go
@@ -0,0 +1,30 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/rsteube/carapace"
+	"github.com/rsteube/carapace/pkg/sandbox"
+)
+
+func TestCompatPersistent(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Run("compat", "sub", "--persistent-compat", "").
+			Expect(carapace.ActionValues(
+				`args: []string(nil) toComplete: ""`,
+				"alternative",
+			).Usage("persistent flag defined with cobra"))
+
+		s.Run("compat", "sub", "one", "--persistent-compat", "").
+			Expect(carapace.ActionValues(
+				`args: []string{"one"} toComplete: ""`,
+				"alternative",
+			).Usage("persistent flag defined with cobra"))
+
+		s.Run("compat", "sub", "one", "two", "--persistent-compat", "a").
+			Expect(carapace.ActionValues(
+				`args: []string{"one", "two"} toComplete: "a"`,
+				"alternative",
+			).Usage("persistent flag defined with cobra"))
+	})
+}
diff --git a/example/cmd/compat_test.go b/example/cmd/compat_test.go
new file mode 100644
index 0000000..8c57936
--- /dev/null
+++ b/example/cmd/compat_test.go
@@ -0,0 +1,124 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/rsteube/carapace"
+	"github.com/rsteube/carapace/pkg/sandbox"
+	"github.com/rsteube/carapace/pkg/style"
+)
+
+func TestCompat(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Files(
+			"subdir/file1.txt", "",
+			"subdir/subdir2/file2.txt", "",
+			"go.mod", "",
+			"go.sum", "",
+			"README.md", "",
+		)
+
+		s.Run("compat", "--error", "").
+			Expect(carapace.ActionMessage("an error occured").
+				Usage("ShellCompDirectiveError"))
+
+		s.Run("compat", "--nospace", "").
+			Expect(carapace.ActionValues(
+				"one",
+				"two",
+			).NoSpace().
+				Usage("ShellCompDirectiveNoSpace"))
+
+		s.Run("compat", "--nofilecomp", "").
+			Expect(carapace.ActionValues().
+				Usage("ShellCompDirectiveNoFileComp"))
+
+		s.Run("compat", "--filterfileext", "").
+			Expect(carapace.ActionValues(
+				"subdir/",
+				"go.mod",
+				"go.sum",
+			).NoSpace('/').
+				Tag("files").
+				StyleF(style.ForPath).
+				Usage("ShellCompDirectiveFilterFileExt"))
+
+		s.Run("compat", "--filterdirs", "").
+			Expect(carapace.ActionValues(
+				"subdir/",
+			).NoSpace('/').
+				Tag("directories").
+				StyleF(style.ForPath).
+				Usage("ShellCompDirectiveFilterDirs"))
+
+		s.Run("compat", "--filterdirs-chdir", "").
+			Expect(carapace.ActionValues(
+				"subdir2/",
+			).NoSpace('/').
+				Tag("directories").
+				StyleF(style.ForPathExt).
+				Usage("ShellCompDirectiveFilterDirs"))
+
+		s.Run("compat", "--keeporder", "").
+			Expect(carapace.ActionValues(
+				"one",
+				"two",
+				"three",
+			).Usage("ShellCompDirectiveKeepOrder"))
+
+		s.Run("compat", "--default", "").
+			Expect(carapace.ActionValues(
+				"subdir/",
+				"go.mod",
+				"go.sum",
+				"README.md",
+			).NoSpace('/').
+				Tag("files").
+				StyleF(style.ForPath).
+				Usage("ShellCompDirectiveDefault"))
+
+		s.Run("compat", "--unset", "").
+			Expect(carapace.ActionValues().
+				Usage("no completions defined"))
+
+		s.Run("compat", "").
+			Expect(carapace.Batch(
+				carapace.ActionValues(
+					"p1",
+					"positional1",
+				),
+				carapace.ActionValues(
+					"sub",
+				).Tag("commands"),
+			).ToA().Usage(""))
+
+		s.Run("compat", "positional1", "").
+			Expect(carapace.ActionValues(
+				"subdir/",
+				"go.mod",
+				"go.sum",
+				"README.md",
+			).NoSpace('/').
+				StyleF(style.ForPath).
+				Tag("files").
+				Usage(""))
+
+		s.Run("compat", "positional1", "main.go", "").
+			Expect(carapace.ActionValues(
+				`args: []string{"positional1", "main.go"} toComplete: ""`,
+				"alternative",
+			).Usage(""))
+
+		s.Run("compat", "positional1", "main.go", "a").
+			Expect(carapace.ActionValues(
+				`args: []string{"positional1", "main.go"} toComplete: "a"`,
+				"alternative",
+			).Usage(""))
+
+		s.Run("compat", "--nospace", "one", "positional1", "--", "main.go", "a").
+			Expect(carapace.ActionValues(
+				`args: []string{"positional1", "main.go"} toComplete: "a"`,
+				"alternative",
+			).Usage(""))
+	})
+}
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 1233971..cafbe98 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -73,6 +73,7 @@ func TestRoot(t *testing.T) {
 				).Style(style.Magenta).Tag("plugin commands"),
 				carapace.ActionValuesDescribed(
 					"chain", "shorthand chain",
+					"compat", "",
 					"completion", "Generate the autocompletion script for the specified shell",
 					"group", "group example",
 					"help", "Help about any command",