aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2024-10-12 00:21:44 +0200
committerRobin Jarry <robin@jarry.cc>2024-10-23 10:22:51 +0200
commit3c9ad93801ce9bb71d76fa398d7d94f1afd2c2cb (patch)
treedcc715e090fedf65e2b0f800d4d969b4606f7a5c /lib
parent63db443949b92eed5f5d2ded5f430bda96312b43 (diff)
downloadaerc-3c9ad93801ce9bb71d76fa398d7d94f1afd2c2cb.tar.gz
completion: display descriptions next to choices
Use go-opt v2 new completion API which returns items descriptions along with their text values. Display the descriptions after the items separated by two spaces. Wrap the descriptions in parentheses to better indicate that they are not part of the completion choices. Limit the description length to 80 characters to avoid display issues. Add a new style object completion_description in stylesets. By default, the object will be rendered with a dimmed terminal attribute. Update all stylesets and documentation accordingly. Implements: https://todo.sr.ht/~rjarry/aerc/271 Link: https://git.sr.ht/~rjarry/go-opt/commit/ebeb82538395a Changelog-added: Command completion now displays descriptions next to completion items. Changelog-added: New `completion_description` style object in style sets used for rendering completion item descriptions. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bojan Gabric <bojan@bojangabric.com> Tested-by: Jason Cox <me@jasoncarloscox.com> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/open.go2
-rw-r--r--lib/send/sendmail.go2
-rw-r--r--lib/ui/textinput.go69
3 files changed, 46 insertions, 27 deletions
diff --git a/lib/open.go b/lib/open.go
index 5ca819e0..edbc7ff6 100644
--- a/lib/open.go
+++ b/lib/open.go
@@ -6,7 +6,7 @@ import (
"runtime"
"strings"
- "git.sr.ht/~rjarry/go-opt"
+ "git.sr.ht/~rjarry/go-opt/v2"
"github.com/danwakefield/fnmatch"
"git.sr.ht/~rjarry/aerc/config"
diff --git a/lib/send/sendmail.go b/lib/send/sendmail.go
index b267721e..9d98cf8f 100644
--- a/lib/send/sendmail.go
+++ b/lib/send/sendmail.go
@@ -6,7 +6,7 @@ import (
"net/url"
"os/exec"
- "git.sr.ht/~rjarry/go-opt"
+ "git.sr.ht/~rjarry/go-opt/v2"
"github.com/emersion/go-message/mail"
"github.com/pkg/errors"
)
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 6c4551b3..d52859b7 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -10,6 +10,7 @@ import (
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib/log"
+ "git.sr.ht/~rjarry/go-opt/v2"
"git.sr.ht/~rockorager/vaxis"
)
@@ -27,8 +28,8 @@ type TextInput struct {
text []vaxis.Character
change []func(ti *TextInput)
focusLost []func(ti *TextInput)
- tabcomplete func(s string) ([]string, string)
- completions []string
+ tabcomplete func(s string) ([]opt.Completion, string)
+ completions []opt.Completion
prefix string
completeIndex int
completeDelay time.Duration
@@ -62,7 +63,7 @@ func (ti *TextInput) Prompt(prompt string) *TextInput {
}
func (ti *TextInput) TabComplete(
- tabcomplete func(s string) ([]string, string),
+ tabcomplete func(s string) ([]opt.Completion, string),
d time.Duration, minChars int, key *config.KeyStroke,
) *TextInput {
ti.tabcomplete = tabcomplete
@@ -142,8 +143,24 @@ func (ti *TextInput) drawPopover(ctx *Context) {
if len(ti.completions) == 0 {
return
}
- cmp := &completions{ti: ti}
- width := maxLen(ti.completions) + 3
+
+ valWidth := 0
+ descWidth := 0
+ for _, c := range ti.completions {
+ valWidth = max(valWidth, runewidth.StringWidth(unquote(c.Value)))
+ descWidth = max(descWidth, runewidth.StringWidth(c.Description))
+ }
+ descWidth = min(descWidth, 80)
+ // one space padding
+ width := 1 + valWidth
+ if descWidth != 0 {
+ // two spaces padding + parentheses
+ width += 2 + descWidth + 2
+ }
+ // one space padding + gutter
+ width += 2
+
+ cmp := &completions{ti: ti, valWidth: valWidth, descWidth: descWidth}
height := len(ti.completions)
pos := len(ti.prefix) - ti.scroll
@@ -275,7 +292,7 @@ func (ti *TextInput) backspace() {
func (ti *TextInput) executeCompletion() {
if len(ti.completions) > 0 {
- ti.Set(ti.prefix + ti.completions[ti.completeIndex] + ti.StringRight())
+ ti.Set(ti.prefix + ti.completions[ti.completeIndex].Value + ti.StringRight())
}
}
@@ -402,7 +419,9 @@ func (ti *TextInput) Event(event vaxis.Event) bool {
}
type completions struct {
- ti *TextInput
+ ti *TextInput
+ valWidth int
+ descWidth int
}
func unquote(s string) string {
@@ -412,22 +431,13 @@ func unquote(s string) string {
return s
}
-func maxLen(ss []string) int {
- max := 0
- for _, s := range ss {
- l := runewidth.StringWidth(unquote(s))
- if l > max {
- max = l
- }
- }
- return max
-}
-
func (c *completions) Draw(ctx *Context) {
bg := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_DEFAULT)
+ bgDesc := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_DESCRIPTION)
gutter := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_GUTTER)
pill := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_PILL)
sel := c.ti.uiConfig.GetStyleSelected(config.STYLE_COMPLETION_DEFAULT)
+ selDesc := c.ti.uiConfig.GetStyleSelected(config.STYLE_COMPLETION_DESCRIPTION)
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', bg)
@@ -445,11 +455,20 @@ func (c *completions) Draw(ctx *Context) {
if idx > endIdx {
continue
}
+ val := runewidth.FillRight(unquote(opt.Value), c.valWidth)
+ desc := opt.Description
+ if desc != "" {
+ if runewidth.StringWidth(desc) > c.descWidth {
+ desc = runewidth.Truncate(desc, c.descWidth, "…")
+ }
+ desc = " " + runewidth.FillRight("("+desc+")", c.descWidth+2)
+ }
if c.index() == idx {
- ctx.Fill(0, idx-startIdx, ctx.Width(), 1, ' ', sel)
- ctx.Printf(0, idx-startIdx, sel, " %s ", unquote(opt))
+ n := ctx.Printf(0, idx-startIdx, sel, " %s", val)
+ ctx.Printf(n, idx-startIdx, selDesc, "%s ", desc)
} else {
- ctx.Printf(0, idx-startIdx, bg, " %s ", unquote(opt))
+ n := ctx.Printf(0, idx-startIdx, bg, " %s", val)
+ ctx.Printf(n, idx-startIdx, bgDesc, "%s ", desc)
}
}
@@ -548,23 +567,23 @@ func (c *completions) stem(stem string) {
c.ti.index = len(vaxis.Characters(c.ti.prefix + stem))
}
-func findStem(words []string) string {
+func findStem(words []opt.Completion) string {
if len(words) == 0 {
return ""
}
if len(words) == 1 {
- return words[0]
+ return words[0].Value
}
var stem string
stemLen := 1
- firstWord := []rune(words[0])
+ firstWord := []rune(words[0].Value)
for {
if len(firstWord) < stemLen {
return stem
}
var r rune = firstWord[stemLen-1]
for _, word := range words[1:] {
- runes := []rune(word)
+ runes := []rune(word.Value)
if len(runes) < stemLen {
return stem
}