Repository: rsteube/carapace (go)

Issue:
zsh: color support in tabular completion
`compadd -l` (list view) supports ansi escape sequences but they are stripped in `compadd` (tabular view).

While it is possible achieve this using `list-colors` with matchers for each completion value this is not a great solution.

```sh
#compdef example
function _example_completion {
  local vals=(default red)
  local displays=(default $'\033[31mred\033[0m')

    compadd -l -d displays -a -- vals
    compadd -d displays -a -- vals
}
compquote '' 2>/dev/null && _example_completion
compdef _example_completion example
```

related #457 
related #448 

Pull request:
zsh tabular color
fix #458


Patch:
diff --git a/internal/shell/zsh/action.go b/internal/shell/zsh/action.go
index 9441f1e..77ff31f 100644
--- a/internal/shell/zsh/action.go
+++ b/internal/shell/zsh/action.go
@@ -2,6 +2,7 @@ package zsh
 
 import (
 	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/rsteube/carapace/internal/common"
@@ -31,6 +32,7 @@ func ActionRawValues(currentWord string, nospace bool, values common.RawValues)
 		}
 	}
 
+	zstyles := make([]string, 0)
 	vals := make([]string, len(filtered))
 	for index, val := range filtered {
 		val.Value = sanitizer.Replace(val.Value)
@@ -40,16 +42,117 @@ func ActionRawValues(currentWord string, nospace bool, values common.RawValues)
 		val.Display = sanitizer.Replace(val.Display)
 		val.Description = sanitizer.Replace(val.Description)
 
+		if zstyle := formatZstyle(val.Display, val.Style); zstyle != "" {
+			zstyles = append(zstyles, zstyle)
+		}
+
 		if strings.TrimSpace(val.Description) == "" {
-			if hasDescriptions {
-				vals[index] = fmt.Sprintf("%v\t%v", val.Value, style.FormatAnsi(val.Display, val.Style))
-			} else {
-				// TODO compadd strips display values of ansi escape codes during tabular completion (so leave that out that for now)
-				vals[index] = fmt.Sprintf("%v\t%v", val.Value, val.Display)
-			}
+			vals[index] = fmt.Sprintf("%v\t%v", val.Value, val.Display)
 		} else {
-			vals[index] = fmt.Sprintf("%v\t%v\002 %v-- %v", val.Value, style.FormatAnsi(val.Display, val.Style), strings.Repeat(" ", maxLength-len(val.Display)), val.TrimmedDescription())
+			vals[index] = fmt.Sprintf("%v\t%v\002 %v-- %v", val.Value, val.Display, strings.Repeat(" ", maxLength-len(val.Display)), val.TrimmedDescription())
 		}
 	}
-	return strings.Join(vals, "\n")
+	return fmt.Sprintf(":%v\n%v", strings.Join(zstyles, ":"), strings.Join(vals, "\n")) // first line is intentionally never empty (single `:`) for snippet
+}
+
+// formatZstyle creates a zstyle matcher for given display stings.
+// `compadd -l` (one per line) accepts ansi escape sequences in display value but it seems in tabular view these are removed.
+// To ease matching in list mode, the display values have a hidden `\002` suffix.
+func formatZstyle(s, _style string) string {
+	result := make([]string, 0)
+
+	reColor256 := regexp.MustCompile(`^color(?P<number>\d+)$`)
+	for _, word := range strings.Split(_style, " ") {
+		switch word {
+		case style.Black:
+			result = append(result, "30")
+		case style.Red:
+			result = append(result, "31")
+		case style.Green:
+			result = append(result, "32")
+		case style.Yellow:
+			result = append(result, "33")
+		case style.Blue:
+			result = append(result, "34")
+		case style.Magenta:
+			result = append(result, "35")
+		case style.Cyan:
+			result = append(result, "36")
+		case style.White:
+			result = append(result, "37")
+
+		case style.BrightBlack:
+			result = append(result, "90")
+		case style.BrightRed:
+			result = append(result, "91")
+		case style.BrightGreen:
+			result = append(result, "92")
+		case style.BrightYellow:
+			result = append(result, "93")
+		case style.BrightBlue:
+			result = append(result, "94")
+		case style.BrightMagenta:
+			result = append(result, "95")
+		case style.BrightCyan:
+			result = append(result, "96")
+		case style.BrightWhite:
+			result = append(result, "97")
+
+		case style.BgBlack:
+			result = append(result, "40")
+		case style.BgRed:
+			result = append(result, "41")
+		case style.BgGreen:
+			result = append(result, "42")
+		case style.BgYellow:
+			result = append(result, "43")
+		case style.BgBlue:
+			result = append(result, "44")
+		case style.BgMagenta:
+			result = append(result, "45")
+		case style.BgCyan:
+			result = append(result, "46")
+		case style.BgWhite:
+			result = append(result, "47")
+
+		case style.BgBrightBlack:
+			result = append(result, "100")
+		case style.BgBrightRed:
+			result = append(result, "101")
+		case style.BgBrightGreen:
+			result = append(result, "102")
+		case style.BgBrightYellow:
+			result = append(result, "103")
+		case style.BgBrightBlue:
+			result = append(result, "104")
+		case style.BgBrightMagenta:
+			result = append(result, "105")
+		case style.BgBrightCyan:
+			result = append(result, "106")
+		case style.BgBrightWhite:
+			result = append(result, "107")
+
+		case style.Bold:
+			result = append(result, "1")
+		case style.Dim:
+			result = append(result, "2")
+		case style.Italic:
+			result = append(result, "3")
+		case style.Underlined:
+			result = append(result, "4")
+		case style.Blink:
+			result = append(result, "5")
+		case style.Inverse:
+			result = append(result, "7")
+		default:
+			if reColor256.MatchString(word) {
+				result = append(result, fmt.Sprintf("38;5;%v", reColor256.FindStringSubmatch(word)[1]))
+			}
+		}
+	}
+
+	if len(result) > 0 {
+		return fmt.Sprintf("=(#b)(%v)((\002*|))=0=%v", strings.Replace(regexp.QuoteMeta(s), "#", `\#`, -1), strings.Join(result, ";"))
+	}
+	return ""
 }
diff --git a/internal/shell/zsh/snippet.go b/internal/shell/zsh/snippet.go
index be9cd8b..5ad022b 100644
--- a/internal/shell/zsh/snippet.go
+++ b/internal/shell/zsh/snippet.go
@@ -17,29 +17,36 @@ function _%v_completion {
   # shellcheck disable=SC2207,SC2086,SC2154
   if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words}"''" | xargs %v _carapace zsh ))
+    local lines=($(echo ${words}"''" | xargs %v _carapace zsh ))
   elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words} | sed "s/\$/'/" | xargs %v _carapace zsh))
+    local lines=($(echo ${words} | sed "s/\$/'/" | xargs %v _carapace zsh))
   else
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words} | sed 's/$/"/'  | xargs %v _carapace zsh))
+    local lines=($(echo ${words} | sed 's/$/"/'  | xargs %v _carapace zsh))
   fi
 
+  export ZLS_COLOURS="${lines[1]}"
+  
+  # shellcheck disable=SC2034,2206
+  lines=(${lines[@]:1})
+
   # shellcheck disable=SC2034,2206
-  local vals=(${c%%%%$'\t'*})
+  local vals=(${lines%%%%$'\t'*})
   # shellcheck disable=SC2034,2206
-  local displays=(${c##*$'\t'})
+  local displays=(${lines##*$'\t'})
 
   local suffix=' '
   [[ ${vals[1]} == *$'\001' ]] && suffix=''
   # shellcheck disable=SC2034,2206
   vals=(${vals%%%%$'\001'*})
 
-  if [[ ${displays[*]} == *$'\002'* ]]; then # with descriptions
-    compadd -l -S "${suffix}" -d displays -a -- vals
+  if [[ ${displays[*]} == *$'\002'" "* ]]; then # with descriptions
+      compadd -l -S "${suffix}" -d displays -a -- vals
   else
-    compadd -S "${suffix}" -d displays -a -- vals
+      # shellcheck disable=SC2034,2206
+      displays=(${displays%%%%$'\002'*})
+      compadd -S "${suffix}" -d displays -a -- vals
   fi
 }
 compquote '' 2>/dev/null && _%v_completion
diff --git a/pkg/style/style.go b/pkg/style/style.go
index 65a25ad..f860d5f 100644
--- a/pkg/style/style.go
+++ b/pkg/style/style.go
@@ -29,6 +29,7 @@ var (
 	BrightBlue    string = "bright-blue"
 	BrightMagenta string = "bright-magenta"
 	BrightCyan    string = "bright-cyan"
+	BrightWhite   string = "bright-white"
 
 	BgBlack   string = "bg-black"
 	BgRed     string = "bg-red"


Test patch:
diff --git a/example/cmd/_test/zsh.sh b/example/cmd/_test/zsh.sh
index 9f594bd..05eebe5 100644
--- a/example/cmd/_test/zsh.sh
+++ b/example/cmd/_test/zsh.sh
@@ -5,29 +5,36 @@ function _example_completion {
   # shellcheck disable=SC2207,SC2086,SC2154
   if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words}"''" | xargs example _carapace zsh ))
+    local lines=($(echo ${words}"''" | xargs example _carapace zsh ))
   elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words} | sed "s/\$/'/" | xargs example _carapace zsh))
+    local lines=($(echo ${words} | sed "s/\$/'/" | xargs example _carapace zsh))
   else
     # shellcheck disable=SC2207,SC2086
-    local c=($(echo ${words} | sed 's/$/"/'  | xargs example _carapace zsh))
+    local lines=($(echo ${words} | sed 's/$/"/'  | xargs example _carapace zsh))
   fi
 
+  export ZLS_COLOURS="${lines[1]}"
+  
+  # shellcheck disable=SC2034,2206
+  lines=(${lines[@]:1})
+
   # shellcheck disable=SC2034,2206
-  local vals=(${c%%$'\t'*})
+  local vals=(${lines%%$'\t'*})
   # shellcheck disable=SC2034,2206
-  local displays=(${c##*$'\t'})
+  local displays=(${lines##*$'\t'})
 
   local suffix=' '
   [[ ${vals[1]} == *$'\001' ]] && suffix=''
   # shellcheck disable=SC2034,2206
   vals=(${vals%%$'\001'*})
 
-  if [[ ${displays[*]} == *$'\002'* ]]; then # with descriptions
-    compadd -l -S "${suffix}" -d displays -a -- vals
+  if [[ ${displays[*]} == *$'\002'" "* ]]; then # with descriptions
+      compadd -l -S "${suffix}" -d displays -a -- vals
   else
-    compadd -S "${suffix}" -d displays -a -- vals
+      # shellcheck disable=SC2034,2206
+      displays=(${displays%%$'\002'*})
+      compadd -S "${suffix}" -d displays -a -- vals
   fi
 }
 compquote '' 2>/dev/null && _example_completion