Repository: rsteube/carapace (go)

Issue:
global `ActionMap` caching / completion initialization
I have a json that holds all the information necessary to compose completion. Launching the command that generates that json takes up to 3 seconds in extreme cases (yeah, bad).

```json
[{"cell":"std","organelles":[{"clade":"runnables","organelle":"cli","readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/cli/Readme.md","targets":[{"actions":[{"description":"exec this target","name":"run"}],"deps":[],"description":"A tui for projects that conform to Standard","name":"default","readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/cli/default.md"}]},{"clade":"functions","organelle":"lib","readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/lib/Readme.md","targets":[{"actions":[],"deps":[],"description":"n/a","name":"fromMakesWith","readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/lib/fromMakesWith.md"},{"actions":[],"deps":[],"description":"n/a","name":"mkShell","readme":""}]},{"clade":"functions","organelle":"devshellProfiles","readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/devshellProfiles/Readme.md","targets":[{"actions":[],"deps":[],"description":"n/a","namestd/devshellProfiles/default.md"}]},{"clade":"devshells","organelle":"devshells","readme":"","targets":[{"actions":[{"description":"enter this devshell","name":"enter"}],"deps":[],"description":"n/a","name":"default","readme":""}]},{}],"readme":"/nix/store/0v43f20l1n9i2gcwrx7k7aab2wfz9qlg-cells/std/Readme.md"}]
```

After transforming this into an `ActionMap` of  `carapace.ActionValues()` I want to cache the entire `ActionMap` for ~30 sec, so that subsequent completions during the same completion "session" don't take 3 seconds each.

What can I do?

Pull request description:
ToMultiParts: fix display
fix #532

Patch:
diff --git a/invokedAction.go b/invokedAction.go
index ca3d7e1..d856fc4 100644
--- a/invokedAction.go
+++ b/invokedAction.go
@@ -1,6 +1,8 @@
 package carapace
 
 import (
+	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/rsteube/carapace/internal/common"
@@ -88,37 +90,53 @@ func (a InvokedAction) ToA() Action {
 	return a.Action
 }
 
-// ToMultiPartsA create an ActionMultiParts from values with given divider
+// 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(divider ...string) Action {
+func (a InvokedAction) ToMultiPartsA(dividers ...string) Action {
 	return ActionCallback(func(c Context) Action {
-		for _, d := range divider {
-			a = a.toMultiPartsA(d).Invoke(c)
+		_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, "|"))
+			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
+			}
+
 		}
-		return a.ToA()
-	})
-}
 
-func (a InvokedAction) toMultiPartsA(divider string) Action {
-	return ActionMultiParts(divider, func(c Context) Action {
 		uniqueVals := make(map[string]common.RawValue)
 		for _, val := range a.rawValues {
-			if strings.HasPrefix(val.Value, strings.Join(c.Parts, divider)) {
-				if splitted := strings.Split(val.Value, divider); len(splitted) > len(c.Parts) {
-					if len(splitted) == len(c.Parts)+1 {
-						v := splitted[len(c.Parts)]
+			if strings.HasPrefix(val.Value, c.CallbackValue) {
+				if splitted := _split(val.Value); len(splitted) >= len(splittedCV) {
+					v := strings.Join(splitted[:len(splittedCV)], "")
+					d := splitted[len(splittedCV)-1]
+
+					if len(splitted) == len(splittedCV) {
 						uniqueVals[v] = common.RawValue{
 							Value:       v,
-							Display:     v,
+							Display:     d,
 							Description: val.Description,
 							Style:       val.Style,
 						}
 					} else {
-						v := splitted[len(c.Parts)] + divider
 						uniqueVals[v] = common.RawValue{
 							Value:       v,
-							Display:     v,
+							Display:     d,
 							Description: "",
 							Style:       "",
 						}


Test patch:
diff --git a/action_test.go b/action_test.go
index 02611c1..19be7ca 100644
--- a/action_test.go
+++ b/action_test.go
@@ -138,7 +138,6 @@ func TestActionDirectories(t *testing.T) {
 
 	assertEqual(t,
 		ActionStyledValues(
-			"_test/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
 			"cmd/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
 		).noSpace(true).Invoke(Context{}).Prefix("example/"),
 		ActionDirectories().Invoke(Context{CallbackValue: "example/cm"}),
diff --git a/invokedAction_test.go b/invokedAction_test.go
new file mode 100644
index 0000000..2d13772
--- /dev/null
+++ b/invokedAction_test.go
@@ -0,0 +1,39 @@
+package carapace
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/rsteube/carapace/pkg/style"
+)
+
+func TestToMultiParts(t *testing.T) {
+	_test := func(cv, expected string, delimiter ...string) {
+		a := ActionStyledValuesDescribed(
+			"A/a:1", "one", style.Green,
+			"A/a:2", "two", style.BgBlue,
+			"A/b:3", "three", style.Red,
+			"B/b:4", "four", style.Magenta,
+			"B/c:5:1", "fiftyone", style.Black,
+			"B/c:5:2/z", "fiftytwo", style.Yellow,
+			"B/c/", "withsuffix", style.Underlined,
+		)
+		a = a.Invoke(Context{}).ToMultiPartsA(delimiter...)
+		if actual := a.Invoke(Context{CallbackValue: cv}).value("export", cv); !strings.Contains(actual, expected) {
+			t.Errorf("expected '%v' in '%v' for '%v'", expected, actual, cv)
+		}
+	}
+
+	_test("A/a:1", `{"Value":"A/a:1","Display":"1","Description":"one","Style":"green"}`, "/", ":")
+	_test("A/a:1", `{"Value":"A/a:1","Display":"1","Description":"one","Style":"green"}`, ":", "/")
+	_test("A/a:1", `{"Value":"A/a:1","Display":"a:1","Description":"one","Style":"green"}`, "/")
+	_test("A", `{"Value":"A/","Display":"A/","Description":"","Style":""}`, "/", ":")
+	_test("A", `{"Value":"A/","Display":"A/","Description":"","Style":""}`, "/")
+	_test("", `{"Value":"A/","Display":"A/","Description":"","Style":""}`, "/")
+	_test("A/", `{"Value":"A/a:","Display":"a:","Description":"","Style":""}`, "/", ":")
+	_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/","Description":"","Style":""}`, "/")
+	_test("B/c:5", `{"Value":"B/c:5:","Display":"5:","Description":"","Style":""}`, "/", ":")
+	_test("B/c:5", `{"Value":"B/c:5:","Display":"5:","Description":"","Style":""}`, ":", "/")
+}