Repository: rsteube/carapace
Pull request #628 (2022-12-02 22:45:02)

zsh: arrange values using _describe
related #584


Patch:
diff --git a/example/cmd/action.go b/example/cmd/action.go
index cab47f1..a254a1b 100644
--- a/example/cmd/action.go
+++ b/example/cmd/action.go
@@ -78,7 +78,7 @@ func init() {
 		"multiparts": carapace.ActionMultiParts(":", func(c carapace.Context) carapace.Action {
 			switch len(c.Parts) {
 			case 0:
-				return carapace.ActionValues("userA", "UserB").Invoke(c).Suffix(":").ToA()
+				return carapace.ActionValues("userA", "userB").Invoke(c).Suffix(":").ToA()
 			case 1:
 				return carapace.ActionValues("groupA", "groupB")
 			default:
@@ -120,6 +120,7 @@ func init() {
 			"first", "description of first", style.Blink,
 			"second", "description of second", style.Of("color210", style.Underlined),
 			"third", "description of third", style.Of("#112233", style.Italic),
+			"thirdalias", "description of third", style.BgBrightMagenta,
 		),
 		"values": carapace.ActionValues("first", "second", "third"),
 		"values-described": carapace.ActionValuesDescribed(
diff --git a/internal/shell/zsh/action.go b/internal/shell/zsh/action.go
index 86494ec..fda374c 100644
--- a/internal/shell/zsh/action.go
+++ b/internal/shell/zsh/action.go
@@ -5,8 +5,6 @@ import (
 	"strings"
 
 	"github.com/rsteube/carapace/internal/common"
-	"github.com/rsteube/carapace/pkg/style"
-	"github.com/rsteube/carapace/third_party/github.com/elves/elvish/pkg/ui"
 )
 
 var sanitizer = strings.NewReplacer(
@@ -38,6 +36,7 @@ var quoter = strings.NewReplacer(
 	`*`, `\*`,
 	`\`, `\\`,
 	`~`, `\~`,
+	`:`, `\:`,
 )
 
 func quoteValue(s string) string {
@@ -56,17 +55,6 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
 		}
 	}
 
-	valueStyle := "default"
-	if s := style.Carapace.Value; s != "" && ui.ParseStyling(s) != nil {
-		valueStyle = s
-	}
-
-	descriptionStyle := "default"
-	if s := style.Carapace.Description; s != "" && ui.ParseStyling(s) != nil {
-		descriptionStyle = s
-	}
-
-	zstyles := make([]string, 0)
 	vals := make([]string, len(values))
 	for index, val := range values {
 		val.Value = sanitizer.Replace(val.Value)
@@ -77,40 +65,11 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
 		val.Display = sanitizer.Replace(val.Display)
 		val.Description = sanitizer.Replace(val.Description)
 
-		if val.Style == "" || ui.ParseStyling(val.Style) == nil {
-			val.Style = valueStyle
-		}
-
 		if strings.TrimSpace(val.Description) == "" {
 			vals[index] = fmt.Sprintf("%v\t%v", val.Value, val.Display)
-			zstyles = append(zstyles, formatZstyle(fmt.Sprintf("(%v)()", zstyleQuoter.Replace(val.Display)), val.Style, descriptionStyle))
 		} else {
-			vals[index] = fmt.Sprintf("%v\t%v%v-- %v", val.Value, val.Display, strings.Repeat(" ", maxLength-len(val.Display)+1), val.TrimmedDescription())
-			zstyles = append(zstyles, formatZstyle(fmt.Sprintf("(%v)(%v)(-- %v)", zstyleQuoter.Replace(val.Display), strings.Repeat(" ", maxLength-len(val.Display)+1), zstyleQuoter.Replace(val.TrimmedDescription())), val.Style, descriptionStyle))
+			vals[index] = fmt.Sprintf("%v\t%v:%v", val.Value, val.Display, val.TrimmedDescription())
 		}
 	}
-
-	if len(zstyles) > 1000 { // TODO disable styling for large amount of values (bad performance)
-		zstyles = make([]string, 0)
-	}
-
-	return fmt.Sprintf(":%v\n%v\n%v", strings.Join(zstyles, ":"), message{meta}.format(), strings.Join(vals, "\n"))
-}
-
-var zstyleQuoter = strings.NewReplacer(
-	"#", `\#`,
-	"*", `\*`,
-	"(", `\(`,
-	")", `\)`,
-	"[", `\[`,
-	"]", `\]`,
-	"|", `\|`,
-	"~", `\~`,
-)
-
-// 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, _styleValue, _styleDescription string) string {
-	return fmt.Sprintf("=(#b)%v=0=%v=%v=%v", s, style.SGR(_styleValue), style.SGR(_styleDescription+" bg-default"), style.SGR(_styleDescription))
+	return fmt.Sprintf(":%v\n%v\n%v", zstyles{values}.Format(), message{meta}.Format(), strings.Join(vals, "\n"))
 }
diff --git a/internal/shell/zsh/message.go b/internal/shell/zsh/message.go
index fefeb50..ce7f70b 100644
--- a/internal/shell/zsh/message.go
+++ b/internal/shell/zsh/message.go
@@ -12,7 +12,7 @@ type message struct {
 	common.Meta
 }
 
-func (m message) format() string {
+func (m message) Format() string {
 	formatted := make([]string, 0)
 	for _, message := range m.Messages.Get() {
 		formatted = append(formatted, m.formatMessage(message, style.Carapace.Error))
diff --git a/internal/shell/zsh/snippet.go b/internal/shell/zsh/snippet.go
index e9f5a70..0542948 100644
--- a/internal/shell/zsh/snippet.go
+++ b/internal/shell/zsh/snippet.go
@@ -37,7 +37,8 @@ function _%v_completion {
   # shellcheck disable=SC2034,2206
   local displays=(${lines##*$'\t'})
 
-  compadd -Q -S '' -a -d displays -- vals
+  local compOpts=( -qS "" -Q )
+  _describe -t "sometag" "sometag" displays vals "${compOpts[@]}"
 }
 compquote '' 2>/dev/null && _%v_completion
 compdef _%v_completion %v
diff --git a/internal/shell/zsh/zstyle.go b/internal/shell/zsh/zstyle.go
new file mode 100644
index 0000000..816e02c
--- /dev/null
+++ b/internal/shell/zsh/zstyle.go
@@ -0,0 +1,72 @@
+package zsh
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/rsteube/carapace/internal/common"
+	"github.com/rsteube/carapace/pkg/style"
+	"github.com/rsteube/carapace/third_party/github.com/elves/elvish/pkg/ui"
+)
+
+type zstyles struct {
+	rawValues common.RawValues
+}
+
+func (z zstyles) descriptionSGR() string {
+	if s := style.Carapace.Description; s != "" && ui.ParseStyling(s) != nil {
+		return style.SGR(s)
+	}
+	return style.SGR(style.Default)
+}
+
+func (z zstyles) valueSGR(val common.RawValue) string {
+	if val.Style != "" && ui.ParseStyling(val.Style) != nil {
+		return style.SGR(val.Style)
+	}
+
+	if ui.ParseStyling(style.Carapace.Value) != nil {
+		return style.SGR(style.Carapace.Value)
+	}
+	return style.SGR(style.Default)
+
+}
+
+func (z zstyles) hasAliases() bool {
+	descriptions := make(map[string]bool)
+	for _, val := range z.rawValues {
+		if _, exists := descriptions[val.Description]; exists {
+			return true
+		}
+		descriptions[val.Description] = true
+	}
+	return false
+}
+
+func (z zstyles) Format() string {
+	replacer := strings.NewReplacer(
+		"#", `\#`,
+		"*", `\*`,
+		"(", `\(`,
+		")", `\)`,
+		"[", `\[`,
+		"]", `\]`,
+		"|", `\|`,
+		"~", `\~`,
+	)
+
+	// TODO this might need to be handled differently regarding tags/groups (e.g. done for each tag)
+	pattern := "=(#b)(%v)( * -- *)=0=%v=%v" // match value with description
+	if z.hasAliases() {                     // different behaviour in `_describe` when values are on the same line
+		pattern = "=(#b)(%v)()=0=%v=%v" // only match value
+	}
+
+	formatted := make([]string, 0)
+	if len(z.rawValues) < 1000 { // disable styling for large amount of values (bad performance)
+		for _, val := range z.rawValues {
+			formatted = append(formatted, fmt.Sprintf(pattern, replacer.Replace(val.Display), z.valueSGR(val), z.descriptionSGR()))
+		}
+	}
+	formatted = append(formatted, fmt.Sprintf("=(#b)(%v)=0=%v", "-- *", z.descriptionSGR()))
+	return strings.Join(formatted, ":")
+}


Test patch:
diff --git a/example/cmd/_test/zsh.sh b/example/cmd/_test/zsh.sh
index 0a4a45f..bfc53cd 100644
--- a/example/cmd/_test/zsh.sh
+++ b/example/cmd/_test/zsh.sh
@@ -25,7 +25,8 @@ function _example_completion {
   # shellcheck disable=SC2034,2206
   local displays=(${lines##*$'\t'})
 
-  compadd -Q -S '' -a -d displays -- vals
+  local compOpts=( -qS "" -Q )
+  _describe -t "sometag" "sometag" displays vals "${compOpts[@]}"
 }
 compquote '' 2>/dev/null && _example_completion
 compdef _example_completion example