Repository: rsteube/carapace (go)

Issue:
context.Args contains curently completed word
```sh
git tag <TAB>
```

`context.Args` contains the current callback value (`[]string{""}`) but should be empty.


Pull request:
fix context
fix #298


Patch:
diff --git a/carapace.go b/carapace.go
index 47ddf81..daf903d 100644
--- a/carapace.go
+++ b/carapace.go
@@ -133,18 +133,10 @@ func addCompletionCommand(cmd *cobra.Command) {
 				}
 			} else {
 				if len(args) == 1 {
-					switch args[0] {
-					case "debug":
-						// TODO broken, not indexed by uid anymore
-						for uid, action := range storage {
-							fmt.Printf("%v:\t%v\n", uid, action)
-						}
-					default:
-						if s, err := Gen(cmd).Snippet(args[0]); err != nil {
-							fmt.Fprintln(io.MultiWriter(os.Stderr, logger.Writer()), err.Error())
-						} else {
-							fmt.Fprintln(io.MultiWriter(os.Stdout, logger.Writer()), s)
-						}
+					if s, err := Gen(cmd).Snippet(args[0]); err != nil {
+						fmt.Fprintln(io.MultiWriter(os.Stderr, logger.Writer()), err.Error())
+					} else {
+						fmt.Fprintln(io.MultiWriter(os.Stdout, logger.Writer()), s)
 					}
 				} else {
 					shell := args[0]
@@ -177,13 +169,16 @@ func addCompletionCommand(cmd *cobra.Command) {
 							} else { // complete flagnames
 								targetAction = actionFlags(targetCmd)
 							}
-						} else if targetCmd.HasAvailableSubCommands() && len(targetArgs) <= 1 {
-							subcommandA := actionSubcommands(targetCmd)
-							a := findAction(targetCmd, targetArgs)
-							subcommandA = a.Invoke(context).Merge(subcommandA.Invoke(context)).ToA()
-							targetAction = subcommandA
 						} else {
+							if len(context.Args) > 0 {
+								context.Args = context.Args[:len(context.Args)-1] // current word being completed is a positional so remove it from context.Args
+							}
+
 							targetAction = findAction(targetCmd, targetArgs)
+							if targetCmd.HasAvailableSubCommands() && len(targetArgs) <= 1 {
+								subcommandA := actionSubcommands(targetCmd).Invoke(context)
+								targetAction = targetAction.Invoke(context).Merge(subcommandA).ToA()
+							}
 						}
 						fmt.Fprintln(io.MultiWriter(os.Stdout, logger.Writer()), targetAction.Invoke(context).value(shell, context.CallbackValue))
 					default:


Test patch:
diff --git a/carapace_test.go b/carapace_test.go
new file mode 100644
index 0000000..5845b47
--- /dev/null
+++ b/carapace_test.go
@@ -0,0 +1,136 @@
+package carapace
+
+import (
+	"encoding/json"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/rsteube/carapace/internal/assert"
+	"github.com/spf13/cobra"
+)
+
+func execCompletion(args ...string) (context Context) {
+	rootCmd := &cobra.Command{
+		Use: "root",
+		Run: func(cmd *cobra.Command, args []string) {},
+	}
+	rootCmd.Flags().String("multiparts", "", "")
+
+	Gen(rootCmd).FlagCompletion(ActionMap{
+		"multiparts": ActionMultiParts(",", func(c Context) Action {
+			context = c
+			return ActionValues()
+		}),
+	})
+
+	Gen(rootCmd).PositionalAnyCompletion(
+		ActionMultiParts(":", func(c Context) Action {
+			context = c
+			return ActionValues()
+		}),
+	)
+
+	subCmd := &cobra.Command{
+		Use: "sub",
+		Run: func(cmd *cobra.Command, args []string) {},
+	}
+
+	Gen(subCmd).PositionalAnyCompletion(
+		ActionCallback(func(c Context) Action {
+			context = c
+			return ActionValues()
+		}),
+	)
+
+	rootCmd.AddCommand(subCmd)
+
+	os.Args = append([]string{"root", "_carapace", "elvish", "_", "root"}, args...)
+	rootCmd.Execute()
+	return
+}
+
+func testContext(t *testing.T, expected Context, args ...string) {
+	t.Run(strings.Join(args, " "), func(t *testing.T) {
+		null, _ := os.Open(os.DevNull)
+		defer null.Close()
+
+		sOut := os.Stdout
+		sErr := os.Stderr
+
+		os.Stdout = null
+		os.Stderr = null
+		actual := execCompletion(args...)
+		os.Stdout = sOut
+		os.Stderr = sErr
+
+		e, _ := json.Marshal(expected)
+		a, _ := json.Marshal(actual)
+		assert.Equal(t, string(e), string(a))
+	})
+}
+
+func TestContext(t *testing.T) {
+	testContext(t, Context{
+		CallbackValue: "",
+		Args:          []string{},
+		Parts:         []string{},
+	},
+		"")
+
+	testContext(t, Context{
+		CallbackValue: "",
+		Args:          []string{"pos1"},
+		Parts:         []string{},
+	},
+		"pos1", "")
+
+	testContext(t, Context{
+		CallbackValue: "po",
+		Args:          []string{"pos1", "pos2"},
+		Parts:         []string{},
+	},
+		"pos1", "pos2", "po")
+
+	testContext(t, Context{
+		CallbackValue: "",
+		Args:          []string{},
+		Parts:         []string{},
+	},
+		"--multiparts", "")
+
+	testContext(t, Context{
+		CallbackValue: "fir",
+		Args:          []string{},
+		Parts:         []string{},
+	},
+		"--multiparts", "fir")
+
+	testContext(t, Context{
+		CallbackValue: "seco",
+		Args:          []string{"pos1"},
+		Parts:         []string{"first"},
+	},
+		"pos1", "--multiparts", "first,seco")
+
+	testContext(t, Context{
+		CallbackValue: "pos",
+		Args:          []string{},
+		Parts:         []string{},
+	},
+		"pos")
+
+	testContext(t, Context{
+		CallbackValue: "sec",
+		Args:          []string{},
+		Parts:         []string{"first"},
+	},
+		"first:sec")
+
+	testContext(t, Context{
+		CallbackValue: "thi",
+		Args:          []string{"first:second"},
+		Parts:         []string{},
+	},
+		"first:second", "thi")
+}