Repository: rsteube/carapace
Pull request #556 (2022-08-28 11:12:21)

zsh: fix named directories
related #313
related rsteube/carapace-bin#1277


Patch:
diff --git a/context.go b/context.go
index baa3c11..3bc527e 100644
--- a/context.go
+++ b/context.go
@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/rsteube/carapace/internal/shell/zsh"
 	"github.com/rsteube/carapace/third_party/github.com/drone/envsubst"
 	"github.com/rsteube/carapace/third_party/golang.org/x/sys/execabs"
 )
@@ -66,25 +67,15 @@ func (c Context) Command(name string, arg ...string) *execabs.Cmd {
 
 func expandHome(s string) (string, error) {
 	if strings.HasPrefix(s, "~") {
-		// TODO use env from context
-		if hash, ok := os.LookupEnv("CARAPACE_ZSH_HASH"); ok { // support zsh static named directories
-			for _, line := range strings.Split(hash, "\n") {
-				if splitted := strings.SplitN(line, "=", 2); len(splitted) == 2 {
-					if staticNamedDirectory := fmt.Sprintf("~%v/", splitted[0]); strings.HasPrefix(s, staticNamedDirectory) {
-						if !strings.HasSuffix(splitted[1], "/") {
-							splitted[1] = splitted[1] + "/"
-						}
-						return strings.Replace(s, staticNamedDirectory, splitted[1], 1), nil
-					}
-				}
-			}
+		if zsh.NamedDirectories.Matches(s) {
+			return zsh.NamedDirectories.Replace(s), nil
 		}
 
 		home, err := os.UserHomeDir() // TODO duplicated code
 		if err != nil {
 			return "", err
 		}
-		s = strings.Replace(s, "~", home+"/", 1)
+		s = strings.Replace(s, "~/", home+"/", 1)
 	}
 	return s, nil
 }
diff --git a/internal/shell/zsh/action.go b/internal/shell/zsh/action.go
index 2688ddf..a1ada13 100644
--- a/internal/shell/zsh/action.go
+++ b/internal/shell/zsh/action.go
@@ -41,7 +41,7 @@ var quoter = strings.NewReplacer(
 )
 
 func quoteValue(s string) string {
-	if strings.HasPrefix(s, "~") {
+	if strings.HasPrefix(s, "~/") || NamedDirectories.Matches(s) {
 		return "~" + quoter.Replace(strings.TrimPrefix(s, "~")) // assume file path expansion
 	}
 	return quoter.Replace(s)
diff --git a/internal/shell/zsh/namedDirectory.go b/internal/shell/zsh/namedDirectory.go
new file mode 100644
index 0000000..590cadf
--- /dev/null
+++ b/internal/shell/zsh/namedDirectory.go
@@ -0,0 +1,44 @@
+package zsh
+
+import (
+	"os"
+	"strings"
+)
+
+type namedDirectories map[string]string
+
+// NamedDirectories provides rudimentary named directory support as these aren't expanded by zsh in the `${words}` provided to the compdef function
+var NamedDirectories = make(namedDirectories)
+
+func (nd *namedDirectories) match(s string) string {
+	if strings.HasPrefix(s, "~") && !strings.HasPrefix(s, "~/") && strings.Contains(s, "/") {
+		return NamedDirectories[strings.SplitN(s, "/", 2)[0][1:]]
+	}
+	return ""
+}
+
+// Matches checks if given string has a known named directory prefix
+func (nd *namedDirectories) Matches(s string) bool {
+	return nd.match(s) != ""
+}
+
+// Replace replaces a known named directory prefix with the actual folder
+func (nd *namedDirectories) Replace(s string) string {
+	if match := nd.match(s); match != "" {
+		if !strings.HasSuffix(match, "/") {
+			match = match + "/"
+		}
+		return match + strings.SplitN(s, "/", 2)[1]
+	}
+	return s
+}
+
+func init() {
+	if hashDirs, ok := os.LookupEnv("CARAPACE_ZSH_HASH_DIRS"); ok {
+		for _, line := range strings.Split(hashDirs, "\n") {
+			if splitted := strings.SplitN(line, "=", 2); len(splitted) == 2 {
+				NamedDirectories[splitted[0]] = splitted[1]
+			}
+		}
+	}
+}
diff --git a/internal/shell/zsh/snippet.go b/internal/shell/zsh/snippet.go
index 371c29e..ccfe92d 100644
--- a/internal/shell/zsh/snippet.go
+++ b/internal/shell/zsh/snippet.go
@@ -17,13 +17,13 @@ function _%v_completion {
   # shellcheck disable=SC2207,SC2086,SC2154
   if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words}"''" | CARAPACE_ZSH_HASH="$(hash -d)" xargs %v _carapace zsh ))
+    local lines=($(echo ${words}"''" | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh ))
   elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words} | sed "s/\$/'/" | CARAPACE_ZSH_HASH="$(hash -d)" xargs %v _carapace zsh))
+    local lines=($(echo ${words} | sed "s/\$/'/" | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh))
   else
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words} | sed 's/$/"/'  | CARAPACE_ZSH_HASH="$(hash -d)" xargs %v _carapace zsh))
+    local lines=($(echo ${words} | sed 's/$/"/' | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh))
   fi
 
   export ZLS_COLOURS="${lines[1]}"


Test patch:
diff --git a/context_test.go b/context_test.go
index 18d35af..5052fe0 100644
--- a/context_test.go
+++ b/context_test.go
@@ -43,7 +43,7 @@ func TestContextAbs(t *testing.T) {
 		"/dir", "./.file", "/dir/.file",
 		"", "/dir/", "/dir/",
 		"/dir/", "", "/dir/",
-		"~", "file", home("file"),
+		"~/", "file", home("file"),
 		"", "/", "/",
 		"", ".hidden", wd(".hidden"),
 		"", "./", wd("")+"/",
diff --git a/example/cmd/_test/zsh.sh b/example/cmd/_test/zsh.sh
index 2bf133c..48be4b4 100644
--- a/example/cmd/_test/zsh.sh
+++ b/example/cmd/_test/zsh.sh
@@ -5,13 +5,13 @@ function _example_completion {
   # shellcheck disable=SC2207,SC2086,SC2154
   if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words}"''" | CARAPACE_ZSH_HASH="$(hash -d)" xargs example _carapace zsh ))
+    local lines=($(echo ${words}"''" | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs example _carapace zsh ))
   elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words} | sed "s/\$/'/" | CARAPACE_ZSH_HASH="$(hash -d)" xargs example _carapace zsh))
+    local lines=($(echo ${words} | sed "s/\$/'/" | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs example _carapace zsh))
   else
     # shellcheck disable=SC2207,SC2086
-    local lines=($(echo ${words} | sed 's/$/"/'  | CARAPACE_ZSH_HASH="$(hash -d)" xargs example _carapace zsh))
+    local lines=($(echo ${words} | sed 's/$/"/' | CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs example _carapace zsh))
   fi
 
   export ZLS_COLOURS="${lines[1]}"