diff options
author | Robin Jarry <robin@jarry.cc> | 2024-10-12 00:21:44 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2024-10-23 10:22:51 +0200 |
commit | 3c9ad93801ce9bb71d76fa398d7d94f1afd2c2cb (patch) | |
tree | dcc715e090fedf65e2b0f800d4d969b4606f7a5c | |
parent | 63db443949b92eed5f5d2ded5f430bda96312b43 (diff) | |
download | aerc-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>
34 files changed, 145 insertions, 88 deletions
diff --git a/app/aerc.go b/app/aerc.go index 107fc1f3..72a60562 100644 --- a/app/aerc.go +++ b/app/aerc.go @@ -11,7 +11,7 @@ import ( "time" "unicode" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rockorager/vaxis" "github.com/ProtonMail/go-crypto/openpgp" "github.com/emersion/go-message/mail" @@ -29,7 +29,7 @@ type Aerc struct { accounts map[string]*AccountView cmd func(string, *config.AccountConfig, *models.MessageInfo) error cmdHistory lib.History - complete func(cmd string) ([]string, string) + complete func(cmd string) ([]opt.Completion, string) focused ui.Interactive grid *ui.Grid simulating int @@ -54,7 +54,7 @@ type Choice struct { func (aerc *Aerc) Init( crypto crypto.Provider, cmd func(string, *config.AccountConfig, *models.MessageInfo) error, - complete func(cmd string) ([]string, string), cmdHistory lib.History, + complete func(cmd string) ([]opt.Completion, string), cmdHistory lib.History, deferLoop chan struct{}, ) { tabs := ui.NewTabs(func(d ui.Drawable) *config.UIConfig { @@ -317,7 +317,7 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) { aerc.simulating -= 1 if exline, ok := aerc.focused.(*ExLine); ok { // we are still focused on the exline, turn on tab complete - exline.TabComplete(func(cmd string) ([]string, string) { + exline.TabComplete(func(cmd string) ([]opt.Completion, string) { return aerc.complete(cmd) }) if complete { @@ -633,14 +633,12 @@ func (aerc *Aerc) focus(item ui.Interactive) { func (aerc *Aerc) BeginExCommand(cmd string) { previous := aerc.focused - var tabComplete func(string) ([]string, string) + var tabComplete func(string) ([]opt.Completion, string) if aerc.simulating != 0 { // Don't try to draw completions for simulated events tabComplete = nil } else { - tabComplete = func(cmd string) ([]string, string) { - return aerc.complete(cmd) - } + tabComplete = aerc.complete } exline := NewExLine(cmd, func(cmd string) { err := aerc.cmd(cmd, nil, nil) @@ -673,7 +671,7 @@ func (aerc *Aerc) RegisterPrompt(prompt string, cmd string) { if err != nil { aerc.PushError(err.Error()) } - }, func(cmd string) ([]string, string) { + }, func(cmd string) ([]opt.Completion, string) { return nil, "" // TODO: completions }) aerc.prompts.Push(p) @@ -700,7 +698,7 @@ func (aerc *Aerc) RegisterChoices(choices []Choice) { if err != nil { aerc.PushError(err.Error()) } - }, func(cmd string) ([]string, string) { + }, func(cmd string) ([]opt.Completion, string) { return nil, "" // TODO: completions }) aerc.prompts.Push(p) @@ -10,6 +10,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" + "git.sr.ht/~rjarry/go-opt/v2" "github.com/ProtonMail/go-crypto/openpgp" ) @@ -18,7 +19,7 @@ var aerc Aerc func Init( crypto crypto.Provider, cmd func(string, *config.AccountConfig, *models.MessageInfo) error, - complete func(cmd string) ([]string, string), history lib.History, + complete func(cmd string) ([]opt.Completion, string), history lib.History, deferLoop chan struct{}, ) { aerc.Init(crypto, cmd, complete, history, deferLoop) diff --git a/app/exline.go b/app/exline.go index e8b0069e..5f8e4280 100644 --- a/app/exline.go +++ b/app/exline.go @@ -4,19 +4,20 @@ import ( "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib/ui" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rockorager/vaxis" ) type ExLine struct { commit func(cmd string) finish func() - tabcomplete func(cmd string) ([]string, string) + tabcomplete func(cmd string) ([]opt.Completion, string) cmdHistory lib.History input *ui.TextInput } func NewExLine(cmd string, commit func(cmd string), finish func(), - tabcomplete func(cmd string) ([]string, string), + tabcomplete func(cmd string) ([]opt.Completion, string), cmdHistory lib.History, ) *ExLine { input := ui.NewTextInput("", config.Ui).Prompt(":").Set(cmd) @@ -38,7 +39,7 @@ func NewExLine(cmd string, commit func(cmd string), finish func(), return exline } -func (x *ExLine) TabComplete(tabComplete func(string) ([]string, string)) { +func (x *ExLine) TabComplete(tabComplete func(string) ([]opt.Completion, string)) { x.input.TabComplete( tabComplete, config.Ui.CompletionDelay, @@ -48,7 +49,7 @@ func (x *ExLine) TabComplete(tabComplete func(string) ([]string, string)) { } func NewPrompt(prompt string, commit func(text string), - tabcomplete func(cmd string) ([]string, string), + tabcomplete func(cmd string) ([]opt.Completion, string), ) *ExLine { input := ui.NewTextInput("", config.Ui).Prompt(prompt) if config.Ui.CompletionPopovers { diff --git a/app/msgviewer.go b/app/msgviewer.go index 0af87e5e..0c2c1bef 100644 --- a/app/msgviewer.go +++ b/app/msgviewer.go @@ -23,7 +23,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/parse" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/models" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rockorager/vaxis" "git.sr.ht/~rockorager/vaxis/widgets/align" diff --git a/commands/account/cf.go b/commands/account/cf.go index 8d7d27ae..2c2cee53 100644 --- a/commands/account/cf.go +++ b/commands/account/cf.go @@ -10,7 +10,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type ChangeFolder struct { diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go index 13310665..d6a6f9f3 100644 --- a/commands/account/mkdir.go +++ b/commands/account/mkdir.go @@ -7,7 +7,7 @@ import ( "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type MakeDir struct { diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go index ff1463b6..3c5b24d7 100644 --- a/commands/account/rmdir.go +++ b/commands/account/rmdir.go @@ -9,7 +9,7 @@ import ( "git.sr.ht/~rjarry/aerc/commands" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type RemoveDir struct { diff --git a/commands/commands.go b/commands/commands.go index 27e50bcf..e87cd802 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -9,7 +9,7 @@ import ( "strings" "unicode" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/config" @@ -272,7 +272,7 @@ func GetTemplateCompletion( // GetCompletions returns the completion options and the command prefix func GetCompletions( cmd Command, args *opt.Args, -) (options []string, prefix string) { +) (options []opt.Completion, prefix string) { // copy zeroed struct tmp := reflect.New(reflect.TypeOf(cmd)).Interface().(Command) s, err := args.ArgSafe(0) diff --git a/commands/completion_helpers.go b/commands/completion_helpers.go index 92c33bea..8d293a32 100644 --- a/commands/completion_helpers.go +++ b/commands/completion_helpers.go @@ -32,7 +32,7 @@ func GetAddress(search string) []string { if cmpl != nil { addrList, _ := cmpl.ForHeader("to")(search) for _, full := range addrList { - addr, err := mail.ParseAddress(full) + addr, err := mail.ParseAddress(full.Value) if err != nil { continue } diff --git a/commands/compose/send.go b/commands/compose/send.go index aefeaa3c..1e30bd0e 100644 --- a/commands/compose/send.go +++ b/commands/compose/send.go @@ -19,6 +19,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/send" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" + "git.sr.ht/~rjarry/go-opt/v2" "github.com/emersion/go-message/mail" ) @@ -142,12 +143,13 @@ func (s Send) Execute(args []string) error { from, rcpts, tab.Name, s.CopyTo, s.Archive, copyToReplied) } - }, func(cmd string) ([]string, string) { + }, func(cmd string) ([]opt.Completion, string) { + var comps []opt.Completion if cmd == "" { - return []string{"y", "n"}, "" + comps = append(comps, opt.Completion{Value: "y"}) + comps = append(comps, opt.Completion{Value: "n"}) } - - return nil, "" + return comps, "" }, ) diff --git a/commands/menu.go b/commands/menu.go index 6ebba81f..08eec9cb 100644 --- a/commands/menu.go +++ b/commands/menu.go @@ -12,7 +12,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/log" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/models" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type Menu struct { diff --git a/commands/patch/find.go b/commands/patch/find.go index a508551d..10b96ef2 100644 --- a/commands/patch/find.go +++ b/commands/patch/find.go @@ -11,7 +11,7 @@ import ( "git.sr.ht/~rjarry/aerc/commands/account" "git.sr.ht/~rjarry/aerc/lib/pama" "git.sr.ht/~rjarry/aerc/lib/pama/models" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type Find struct { diff --git a/commands/patch/list.go b/commands/patch/list.go index 0451605b..b05a9bf3 100644 --- a/commands/patch/list.go +++ b/commands/patch/list.go @@ -13,7 +13,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/pama" "git.sr.ht/~rjarry/aerc/lib/pama/models" "git.sr.ht/~rjarry/aerc/lib/ui" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rockorager/vaxis" ) diff --git a/commands/patch/patch.go b/commands/patch/patch.go index 25d7850a..15fb35f4 100644 --- a/commands/patch/patch.go +++ b/commands/patch/patch.go @@ -5,7 +5,7 @@ import ( "fmt" "git.sr.ht/~rjarry/aerc/commands" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) var subCommands map[string]commands.Command @@ -68,7 +68,11 @@ func (p *Patch) CompleteSubArgs(arg string) []string { } // prepend arbitrary string to arg to work with sub-commands options, _ := commands.GetCompletions(p.SubCmd, opt.LexArgs("a "+arg)) - return options + completions := make([]string, 0, len(options)) + for _, o := range options { + completions = append(completions, o.Value) + } + return completions } func (p Patch) Execute(args []string) error { diff --git a/commands/prompt.go b/commands/prompt.go index 9ab2aac1..fe6632e9 100644 --- a/commands/prompt.go +++ b/commands/prompt.go @@ -1,7 +1,7 @@ package commands import ( - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rjarry/aerc/app" ) diff --git a/commands/util.go b/commands/util.go index bb20a204..8c1e497d 100644 --- a/commands/util.go +++ b/commands/util.go @@ -18,7 +18,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/xdg" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rockorager/vaxis" ) diff --git a/completer/completer.go b/completer/completer.go index 5a5dea10..c5b61528 100644 --- a/completer/completer.go +++ b/completer/completer.go @@ -13,7 +13,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/format" "git.sr.ht/~rjarry/aerc/lib/log" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) // A Completer is used to autocomplete text inputs based on the configured @@ -31,7 +31,7 @@ type Completer struct { // A CompleteFunc accepts a string to be completed and returns a slice of // completions candidates with a prefix to prepend to the chosen candidate -type CompleteFunc func(string) ([]string, string) +type CompleteFunc func(string) ([]opt.Completion, string) // New creates a new Completer with the specified address book command. func New(addressBookCmd string, errHandler func(error)) *Completer { @@ -51,11 +51,11 @@ func (c *Completer) ForHeader(h string) CompleteFunc { return nil } // wrap completeAddress in an error handler - return func(s string) ([]string, string) { + return func(s string) ([]opt.Completion, string) { completions, prefix, err := c.completeAddress(s) if err != nil { c.handleErr(err) - return []string{}, "" + return []opt.Completion{}, "" } return completions, prefix } @@ -80,7 +80,7 @@ var tooManyLines = fmt.Errorf("returned more than %d lines", maxCompletionLines) // completeAddress uses the configured address book completion command to fetch // completions for the specified string, returning a slice of completions and // a prefix to be prepended to the selected completion, or an error. -func (c *Completer) completeAddress(s string) ([]string, string, error) { +func (c *Completer) completeAddress(s string) ([]opt.Completion, string, error) { prefix, candidate := c.parseAddress(s) cmd, err := c.getAddressCmd(candidate) if err != nil { @@ -156,9 +156,9 @@ func (c *Completer) getAddressCmd(s string) (*exec.Cmd, error) { // must consist of tab-delimited fields. Only the first field (the email // address field) is required, the second field (the contact name) is optional, // and subsequent fields are ignored. -func readCompletions(r io.Reader) ([]string, error) { +func readCompletions(r io.Reader) ([]opt.Completion, error) { buf := bufio.NewReader(r) - completions := []string{} + var completions []opt.Completion for i := 0; i < maxCompletionLines; i++ { line, err := buf.ReadString('\n') if errors.Is(err, io.EOF) { @@ -180,7 +180,9 @@ func readCompletions(r io.Reader) ([]string, error) { if len(parts) > 1 { addr.Name = strings.TrimSpace(parts[1]) } - completions = append(completions, format.AddressForHumans(addr)) + completions = append(completions, opt.Completion{ + Value: format.AddressForHumans(addr), + }) } return completions, tooManyLines } diff --git a/config/style.go b/config/style.go index a8f17e8d..c81e7204 100644 --- a/config/style.go +++ b/config/style.go @@ -54,6 +54,7 @@ const ( STYLE_PART_MIMETYPE STYLE_COMPLETION_DEFAULT + STYLE_COMPLETION_DESCRIPTION STYLE_COMPLETION_GUTTER STYLE_COMPLETION_PILL @@ -105,9 +106,10 @@ var StyleNames = map[string]StyleObject{ "part_filename": STYLE_PART_FILENAME, "part_mimetype": STYLE_PART_MIMETYPE, - "completion_default": STYLE_COMPLETION_DEFAULT, - "completion_gutter": STYLE_COMPLETION_GUTTER, - "completion_pill": STYLE_COMPLETION_PILL, + "completion_default": STYLE_COMPLETION_DEFAULT, + "completion_description": STYLE_COMPLETION_DESCRIPTION, + "completion_gutter": STYLE_COMPLETION_GUTTER, + "completion_pill": STYLE_COMPLETION_PILL, "tab": STYLE_TAB, "stack": STYLE_STACK, @@ -337,9 +339,11 @@ selector_chooser.bold = true selector_focused.bold = true selector_focused.bg = 12 selector_focused.fg = 15 +completion_*.bg = 8 completion_pill.bg = 12 -completion_default.bg = 8 completion_default.fg = 15 +completion_description.fg = 15 +completion_description.dim = true ` func NewStyleSet() StyleSet { diff --git a/doc/aerc-stylesets.7.scd b/doc/aerc-stylesets.7.scd index ad05d69c..2a311db2 100644 --- a/doc/aerc-stylesets.7.scd +++ b/doc/aerc-stylesets.7.scd @@ -135,6 +135,8 @@ styling. : Attachment/part MIME type in the part switcher. | *completion_default* : The default style for the completion engine. +| *completion_description* +: Completion item descriptions. | *completion_gutter* : The completion gutter. | *completion_pill* @@ -384,9 +386,11 @@ selector_chooser.bold = true selector_focused.bold = true selector_focused.bg = 12 selector_focused.fg = 15 +completion_*.bg = 8 completion_pill.bg = 12 -completion_default.bg = 8 completion_default.fg = 15 +completion_description.fg = 15 +completion_description.dim = true [viewer] url.underline = true @@ -3,7 +3,7 @@ module git.sr.ht/~rjarry/aerc go 1.21 require ( - git.sr.ht/~rjarry/go-opt v1.4.0 + git.sr.ht/~rjarry/go-opt/v2 v2.0.1 git.sr.ht/~rockorager/go-jmap v0.5.0 git.sr.ht/~rockorager/vaxis v0.10.3 github.com/ProtonMail/go-crypto v1.0.0 @@ -1,5 +1,5 @@ -git.sr.ht/~rjarry/go-opt v1.4.0 h1:YHUKRXOuoy6d57Jt2b0DRSLbxq0mz5biq+6P/npFK5s= -git.sr.ht/~rjarry/go-opt v1.4.0/go.mod h1:oEPZUTJKGn1FVye0znaLoeskE/QTuyoJw5q+fjusdM4= +git.sr.ht/~rjarry/go-opt/v2 v2.0.1 h1:rNag0btxzpPN9FOPEqJfmFY70R9Zqf7M1lbNdy6+jvM= +git.sr.ht/~rjarry/go-opt/v2 v2.0.1/go.mod h1:ZIcXh1fUrJEE5bdfaOpx5Uk9YURsimePQ7JJpitDZq4= git.sr.ht/~rockorager/go-jmap v0.5.0 h1:Xs8NeqpA631HUz4uIe6V+0CpWt6b+nnHF7S14U2BVPA= git.sr.ht/~rockorager/go-jmap v0.5.0/go.mod h1:aOTCtwpZSINpDDSOkLGpHU0Kbbm5lcSDMcobX3ZtOjY= git.sr.ht/~rockorager/vaxis v0.10.3 h1:r9oHYKPfItWh05SQE/UJ4wDrmWRY9MDJKtA9HEl5nBI= @@ -77,6 +77,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -167,6 +168,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -174,6 +176,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 } @@ -11,7 +11,7 @@ import ( "sync" "time" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands" @@ -50,24 +50,36 @@ func execCommand( return err } -func getCompletions(cmdline string) ([]string, string) { +func getCompletions(cmdline string) ([]opt.Completion, string) { // complete template terms if options, prefix, ok := commands.GetTemplateCompletion(cmdline); ok { sort.Strings(options) - return options, prefix + completions := make([]opt.Completion, 0, len(options)) + for _, o := range options { + completions = append(completions, opt.Completion{ + Value: o, + Description: "Template", + }) + } + return completions, prefix } args := opt.LexArgs(cmdline) if args.Count() < 2 && args.TrailingSpace() == "" { // complete command names - var completions []string + var completions []opt.Completion for _, name := range commands.ActiveCommandNames() { if strings.HasPrefix(name, cmdline) { - completions = append(completions, name+" ") + completions = append(completions, opt.Completion{ + Value: name + " ", + Description: "", + }) } } - sort.Strings(completions) + sort.Slice(completions, func(i, j int) bool { + return completions[i].Value < completions[j].Value + }) return completions, "" } diff --git a/stylesets/blue b/stylesets/blue index 4fffca0a..7c1169fb 100644 --- a/stylesets/blue +++ b/stylesets/blue @@ -43,7 +43,6 @@ part_*.selected.fg=#ffffff part_*.selected.bg=#005f87 part_filename.selected.bold=true -completion_pill.reverse=true selector_focused.bold=true selector_focused.bg=#005f87 selector_focused.fg=white @@ -54,7 +53,9 @@ default.selected.bold=true default.selected.fg=white default.selected.bg=#005f87 +completion_pill.reverse=true completion_default.selected.bg=#005f87 +completion_description.dim=true [viewer] *.default=true diff --git a/stylesets/default b/stylesets/default index dd5f4a8d..22ea1701 100644 --- a/stylesets/default +++ b/stylesets/default @@ -48,9 +48,11 @@ #selector_focused.bg = 12 #selector_focused.fg = 15 +#completion_*.bg = 8 #completion_pill.bg = 12 -#completion_default.bg = 8 #completion_default.fg = 15 +#completion_description.fg = 15 +#completion_description.dim = true #[viewer] # Uncomment these two lines to reset all attributes in the [viewer] section. diff --git a/stylesets/monochrome b/stylesets/monochrome index 42d5352d..00b7f51b 100644 --- a/stylesets/monochrome +++ b/stylesets/monochrome @@ -30,6 +30,7 @@ part_filename.selected.bold = true selector_focused.reverse = true selector_chooser.bold = true +completion_description.dim = true [viewer] *.default = true diff --git a/stylesets/nord b/stylesets/nord index 3d1d76a3..d8388b94 100644 --- a/stylesets/nord +++ b/stylesets/nord @@ -18,6 +18,7 @@ statusline_default.reverse=true statusline_error.reverse=true completion_pill.reverse=true +completion_description.dim=true border.fg = #49576b diff --git a/stylesets/pink b/stylesets/pink index 87428874..65ef2fe2 100644 --- a/stylesets/pink +++ b/stylesets/pink @@ -43,7 +43,6 @@ part_*.selected.fg=#ffffff part_*.selected.bg=#de4e85 part_filename.selected.bold=true -completion_pill.reverse=true selector_focused.bold=true selector_focused.bg=#de4e85 selector_focused.fg=white @@ -54,7 +53,9 @@ default.selected.bold=true default.selected.fg=white default.selected.bg=#de4e85 +completion_pill.reverse=true completion_default.selected.bg=#de4e85 +completion_description.dim=true [viewer] *.default=true diff --git a/stylesets/solarized b/stylesets/solarized index 29717c72..9308d785 100644 --- a/stylesets/solarized +++ b/stylesets/solarized @@ -8,6 +8,7 @@ *error.bold=true border.reverse=true completion_pill.reverse=true +completion_description.dim=true error.fg=#dc322f # red header.bold=true selector_chooser.bold=true diff --git a/worker/imap/search.go b/worker/imap/search.go index 970d4db6..4bcff69a 100644 --- a/worker/imap/search.go +++ b/worker/imap/search.go @@ -6,7 +6,7 @@ import ( "github.com/emersion/go-imap" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) func translateSearch(c *types.SearchCriteria) *imap.SearchCriteria { diff --git a/worker/lib/search.go b/worker/lib/search.go index b98e2bbb..9715c4a1 100644 --- a/worker/lib/search.go +++ b/worker/lib/search.go @@ -10,7 +10,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/rfc822" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) func Search(messages []rfc822.RawMessage, criteria *types.SearchCriteria) ([]models.UID, error) { diff --git a/worker/notmuch/search.go b/worker/notmuch/search.go index a84711eb..ddbb13cd 100644 --- a/worker/notmuch/search.go +++ b/worker/notmuch/search.go @@ -9,7 +9,7 @@ import ( "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/go-opt/v2" ) type queryBuilder struct { |