Repository: rsteube/carapace (go)

Issue:
`()` file completion broken
Files containing `()` are currently truncated: `file()name -> file`

```go
10b4875b54ad7da4f2b6d6e10a7e73a2e219431a is the first bad commit
commit 10b4875b54ad7da4f2b6d6e10a7e73a2e219431a
Author: rsteube <rsteube@users.noreply.github.com>
Date:   Fri May 27 10:23:09 2022 +0200

    ToMultiParts: fix display

 action_test.go        |  1 -
 invokedAction.go      | 50 ++++++++++++++++++++++++++++++++++----------------
 invokedAction_test.go | 39 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 17 deletions(-)
 create mode 100644 invokedAction_test.go
bisect found first bad commit
```

caused-by: #534 

Pull request description:
ToMultiParts: fix divider with more than one character
fix #554 

Patch:
diff --git a/example/cmd/modifier.go b/example/cmd/modifier.go
index 03ac0e5..809379f 100644
--- a/example/cmd/modifier.go
+++ b/example/cmd/modifier.go
@@ -17,6 +17,7 @@ var modifierCmd = &cobra.Command{
 func init() {
 	modifierCmd.Flags().String("batch", "", "Batch()")
 	modifierCmd.Flags().String("timeout", "", "Timeout()")
+	modifierCmd.Flags().String("tomultiparts", "", "ToMultiPartsA()")
 	modifierCmd.Flags().String("usage", "", "Usage()")
 
 	rootCmd.AddCommand(modifierCmd)
@@ -57,7 +58,16 @@ func init() {
 				return carapace.ActionValues()
 			}
 		}),
-
+		"tomultiparts": carapace.ActionCallback(func(c carapace.Context) carapace.Action {
+			return carapace.ActionValuesDescribed(
+				"1=1==1/1", "one",
+				"1=1==1/2", "two",
+				"1=1==2/1", "three",
+				"1=1==2/2", "four",
+				"1=2==1/1", "five",
+				"2=1==1/1", "six",
+			).Invoke(c).ToMultiPartsA("==", "=", "/")
+		}),
 		"usage": carapace.ActionValues().Usage("explicit flag usage"),
 	})
 
diff --git a/invokedAction.go b/invokedAction.go
index 2a90b21..8b11468 100644
--- a/invokedAction.go
+++ b/invokedAction.go
@@ -1,8 +1,6 @@
 package carapace
 
 import (
-	"fmt"
-	"regexp"
 	"strings"
 
 	"github.com/rsteube/carapace/internal/common"
@@ -67,40 +65,34 @@ func (a InvokedAction) ToA() Action {
 	return a.Action
 }
 
+func tokenize(s string, dividers ...string) []string {
+	if len(dividers) == 0 {
+		return []string{s}
+	}
+
+	result := make([]string, 0)
+	for _, word := range strings.SplitAfter(s, dividers[0]) {
+		tokens := tokenize(strings.TrimSuffix(word, dividers[0]), dividers[1:]...)
+		if len(tokens) > 0 && strings.HasSuffix(word, dividers[0]) {
+			tokens[len(tokens)-1] = tokens[len(tokens)-1] + dividers[0]
+		}
+		result = append(result, tokens...)
+	}
+	return result
+}
+
 // ToMultiPartsA create an ActionMultiParts from values with given dividers
 //
 //	a := carapace.ActionValues("A/B/C", "A/C", "B/C", "C").Invoke(c)
 //	b := a.ToMultiPartsA("/") // completes segments separately (first one is ["A/", "B/", "C"])
 func (a InvokedAction) ToMultiPartsA(dividers ...string) Action {
 	return ActionCallback(func(c Context) Action {
-		_split := func() func(s string) []string {
-			quotedDividiers := make([]string, 0)
-			for _, d := range dividers {
-				quotedDividiers = append(quotedDividiers, regexp.QuoteMeta(d))
-			}
-			f := fmt.Sprintf("([^%v]*(%v)?)", strings.Join(quotedDividiers, "|"), strings.Join(quotedDividiers, "|")) // TODO quickfix - this is wrong (fails for dividers longer than one character) an might need a reverse lookahead for character sequence
-			r := regexp.MustCompile(f)
-			return func(s string) []string {
-				if matches := r.FindAllString(s, -1); matches != nil {
-					return matches
-				}
-				return []string{}
-			}
-		}()
-
-		splittedCV := _split(c.CallbackValue)
-		for _, d := range dividers {
-			if strings.HasSuffix(c.CallbackValue, d) {
-				splittedCV = append(splittedCV, "")
-				break
-			}
-
-		}
+		splittedCV := tokenize(c.CallbackValue, dividers...)
 
 		uniqueVals := make(map[string]common.RawValue)
 		for _, val := range a.rawValues {
 			if strings.HasPrefix(val.Value, c.CallbackValue) {
-				if splitted := _split(val.Value); len(splitted) >= len(splittedCV) {
+				if splitted := tokenize(val.Value, dividers...); len(splitted) >= len(splittedCV) {
 					v := strings.Join(splitted[:len(splittedCV)], "")
 					d := splitted[len(splittedCV)-1]
 


Test patch:
diff --git a/example/cmd/modifier_test.go b/example/cmd/modifier_test.go
index bbde9d8..81787f2 100644
--- a/example/cmd/modifier_test.go
+++ b/example/cmd/modifier_test.go
@@ -58,3 +58,32 @@ func TestChdir(t *testing.T) {
 				Tag("files"))
 	})
 }
+
+func TestToMultiPartsA(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Run("modifier", "--tomultiparts", "").
+			Expect(carapace.ActionValues("1=", "2=").
+				NoSpace('/', '=').
+				Usage("ToMultiPartsA()"))
+
+		s.Run("modifier", "--tomultiparts", "1=").
+			Expect(carapace.ActionValues("1==", "2==").
+				Prefix("1=").
+				NoSpace('/', '=').
+				Usage("ToMultiPartsA()"))
+
+		s.Run("modifier", "--tomultiparts", "1=1==").
+			Expect(carapace.ActionValues("1/", "2/").
+				Prefix("1=1==").
+				NoSpace('/', '=').
+				Usage("ToMultiPartsA()"))
+
+		s.Run("modifier", "--tomultiparts", "1=1==1/").
+			Expect(carapace.ActionValuesDescribed(
+				"1", "one",
+				"2", "two").
+				Prefix("1=1==1/").
+				NoSpace('/', '=').
+				Usage("ToMultiPartsA()"))
+	})
+}
diff --git a/invokedAction_test.go b/invokedAction_test.go
index a8cb84b..372c9bc 100644
--- a/invokedAction_test.go
+++ b/invokedAction_test.go
@@ -33,7 +33,6 @@ func TestToMultiParts(t *testing.T) {
 	_test("", `{"value":"A/","display":"A/"}`, "/")
 	_test("A/", `{"value":"A/a:","display":"a:"}`, "/", ":")
 	_test("A/", `{"value":"A/a:1","display":"a:1","description":"one","style":"green"}`, "/")
-	_test("B/", `{"value":"B/c/","display":"c/","description":"withsuffix","style":"underlined"}`, "/")
 	_test("B/c:5", `{"value":"B/c:5:2/","display":"c:5:2/"}`, "/")
 	_test("B/c:5", `{"value":"B/c:5:","display":"5:"}`, "/", ":")
 	_test("B/c:5", `{"value":"B/c:5:","display":"5:"}`, ":", "/")