Repository: rsteube/carapace
Pull request #146 (2020-12-14 16:35:33)

added callback for ActionFiles and ActionDirectories


Patch:
diff --git a/action.go b/action.go
index b2cb1bb..7c01e47 100644
--- a/action.go
+++ b/action.go
@@ -1,6 +1,8 @@
 package carapace
 
 import (
+	"io/ioutil"
+	"path/filepath"
 	"strings"
 
 	"github.com/rsteube/carapace/internal/bash"
@@ -184,7 +186,7 @@ func ActionDirectories() Action {
 		powershell: func() string { return powershell.ActionDirectories() },
 		xonsh:      func() string { return xonsh.ActionDirectories() },
 		zsh:        func() string { return zsh.ActionDirectories() },
-		// TODO add Callback so that the action can be used in ActionMultiParts as well
+		callback:   func(args []string) Action { return actionPath("", true) },
 	}
 }
 
@@ -197,7 +199,30 @@ func ActionFiles(suffix string) Action {
 		powershell: func() string { return powershell.ActionFiles(suffix) },
 		xonsh:      func() string { return xonsh.ActionFiles(suffix) },
 		zsh:        func() string { return zsh.ActionFiles("*" + suffix) },
-		// TODO add Callback so that the action can be used in ActionMultiParts as well
+		callback:   func(args []string) Action { return actionPath(suffix, false) },
+	}
+}
+
+func actionPath(fileSuffix string, dirOnly bool) Action {
+	folder := filepath.Dir(CallbackValue)
+	if files, err := ioutil.ReadDir(folder); err != nil {
+		return ActionMessage(err.Error())
+	} else {
+		if folder == "." {
+			folder = ""
+		} else if !strings.HasSuffix(folder, "/") {
+			folder = folder + "/"
+		}
+
+		vals := make([]string, len(files))
+		for index, file := range files {
+			if file.IsDir() {
+				vals[index] = folder + file.Name() + "/"
+			} else if !dirOnly && strings.HasSuffix(file.Name(), fileSuffix) {
+				vals[index] = folder + file.Name()
+			}
+		}
+		return ActionValues(vals...)
 	}
 }
 
diff --git a/docs/src/carapace/action/actionMultiParts.md b/docs/src/carapace/action/actionMultiParts.md
index 220aeb6..15fa0b6 100644
--- a/docs/src/carapace/action/actionMultiParts.md
+++ b/docs/src/carapace/action/actionMultiParts.md
@@ -1,6 +1,6 @@
 # ActionMultiParts
 
-[ActionMultiParts](https://pkg.go.dev/github.com/rsteube/carapace#ActionMultiParts) is a [callback action](./actionCallback.md) where parts of an argument can be completed separately (e.g. `user:group` from chown). Divider can be empty as well, but note that bash and fish will add the space suffix for anything other than `/=@:.,` (it still works, but after each selection backspace is needed to continue the completion).
+[ActionMultiParts] is a [callback action](./actionCallback.md) where parts of an argument can be completed separately (e.g. `user:group` from chown). Divider can be empty as well, but note that bash and fish will add the space suffix for anything other than `/=@:.,` (it still works, but after each selection backspace is needed to continue the completion).
 
 ```go
 carapace.ActionMultiParts(":", func(args []string, parts []string) carapace.Action {
@@ -14,3 +14,45 @@ carapace.ActionMultiParts(":", func(args []string, parts []string) carapace.Acti
 	}
 })
 ```
+
+- [`carapace.CallbackValue`] is updated during execution to only contain the currently completed part
+- values **must not** contain the separator as a simple `strings.Split()` is used to separate the parts
+- it is however **allowed as suffix** to enable fluent tab completion (like `/` for a directory)
+
+> There are still some [issues](https://github.com/rsteube/carapace/issues?q=is%3Aissue+is%3Aopen+ActionMultiParts+) with this so a couple of edge cases might not work
+
+## Nesting
+
+[ActionMultiParts] can be nested as well, e.g. completing multiple `KEY=VALUE` pairs separated by `,`.
+
+```go
+carapace.ActionMultiParts(",", func(args, entries []string) carapace.Action {
+	return carapace.ActionMultiParts("=", func(args, parts []string) carapace.Action {
+		switch len(parts) {
+		case 0:
+			keys := make([]string, len(entries))
+			for index, entry := range entries {
+				keys[index] = strings.Split(entry, "=")[0]
+			}
+			return carapace.ActionValues("FILE", "DIRECTORY", "VALUE").Invoke(args).Filter(keys).Suffix("=").ToA()
+		case 1:
+			switch parts[0] {
+			case "FILE":
+				return carapace.ActionFiles("")
+			case "DIRECTORY":
+				return carapace.ActionDirectories()
+			case "VALUE":
+				return carapace.ActionValues("one", "two", "three")
+			default:
+				return carapace.ActionValues()
+
+			}
+		default:
+			return carapace.ActionValues()
+		}
+	})
+}),
+```
+
+[`carapace.CallbackValue`]:https://pkg.go.dev/github.com/rsteube/carapace#pkg-variables
+[ActionMultiParts]:https://pkg.go.dev/github.com/rsteube/carapace#ActionMultiParts
diff --git a/example/cmd/multiparts.go b/example/cmd/multiparts.go
index 1819ee7..6ec129e 100644
--- a/example/cmd/multiparts.go
+++ b/example/cmd/multiparts.go
@@ -37,16 +37,28 @@ func init() {
 		}),
 	})
 
-	carapace.Gen(multipartsCmd).PositionalCompletion(ActionMultipartsTest(","))
-
-	carapace.Gen(multipartsCmd).PositionalAnyCompletion(
-		carapace.ActionMultiParts(",", func(args, parts []string) carapace.Action {
+	carapace.Gen(multipartsCmd).PositionalCompletion(
+		carapace.ActionMultiParts(",", func(args, entries []string) carapace.Action {
 			return carapace.ActionMultiParts("=", func(args, parts []string) carapace.Action {
 				switch len(parts) {
 				case 0:
-					return carapace.ActionValues("a", "b", "c")
+					keys := make([]string, len(entries))
+					for index, entry := range entries {
+						keys[index] = strings.Split(entry, "=")[0]
+					}
+					return carapace.ActionValues("FILE", "DIRECTORY", "VALUE").Invoke(args).Filter(keys).Suffix("=").ToA()
 				case 1:
-					return carapace.ActionValues("one", "two", "three")
+					switch parts[0] {
+					case "FILE":
+						return carapace.ActionFiles("")
+					case "DIRECTORY":
+						return carapace.ActionDirectories()
+					case "VALUE":
+						return carapace.ActionValues("one", "two", "three")
+					default:
+						return carapace.ActionValues()
+
+					}
 				default:
 					return carapace.ActionValues()
 				}


Test patch:
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 9b45504..ff96c49 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -445,8 +445,6 @@ edit:complex-candidate 'invalid' &display='invalid' }]
     ]
     arg-handlers = [
       [_]{ _example_callback '_example__multiparts#1' }
-      [_]{ _example_callback '_example__multiparts#0' }
-      ...
     ]
     subargs = $arg[(subindex multiparts):] 
     if (> (count $subargs) 0) {
@@ -1341,8 +1339,7 @@ function _example__multiparts {
     "--equals[multiparts with = as divider]: :{_example_callback '_example__multiparts##equals'}" \
     "--none[multiparts without divider]: :{_example_callback '_example__multiparts##none'}" \
     "--slash[multiparts with / as divider]: :{_example_callback '_example__multiparts##slash'}" \
-    "1: :{_example_callback '_example__multiparts#1'}" \
-    "*: :{_example_callback '_example__multiparts#0'}"
+    "1: :{_example_callback '_example__multiparts#1'}"
 }
 compquote '' 2>/dev/null && _example
 compdef _example example