~rjarry/aerc-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
5 4

[PATCH aerc 1/3] completion: display descriptions next to choices

Details
Message ID
<20241001130716.345110-4-robin@jarry.cc>
DKIM signature
pass
Download raw message
Patch: +145 -88
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: New `completion_description` style object in style
 sets used for rendering completion item descriptions.
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 app/aerc.go                    | 18 ++++-----
 app/app.go                     |  3 +-
 app/exline.go                  |  9 +++--
 app/msgviewer.go               |  2 +-
 commands/account/cf.go         |  2 +-
 commands/account/mkdir.go      |  2 +-
 commands/account/rmdir.go      |  2 +-
 commands/commands.go           |  4 +-
 commands/completion_helpers.go |  2 +-
 commands/compose/send.go       | 10 +++--
 commands/menu.go               |  2 +-
 commands/patch/find.go         |  2 +-
 commands/patch/list.go         |  2 +-
 commands/patch/patch.go        |  8 +++-
 commands/prompt.go             |  2 +-
 commands/util.go               |  2 +-
 completer/completer.go         | 18 +++++----
 config/style.go                | 12 ++++--
 doc/aerc-stylesets.7.scd       |  6 ++-
 go.mod                         |  2 +-
 go.sum                         |  7 +++-
 lib/open.go                    |  2 +-
 lib/send/sendmail.go           |  2 +-
 lib/ui/textinput.go            | 69 ++++++++++++++++++++++------------
 main.go                        | 24 +++++++++---
 stylesets/blue                 |  3 +-
 stylesets/default              |  4 +-
 stylesets/monochrome           |  1 +
 stylesets/nord                 |  1 +
 stylesets/pink                 |  3 +-
 stylesets/solarized            |  1 +
 worker/imap/search.go          |  2 +-
 worker/lib/search.go           |  2 +-
 worker/notmuch/search.go       |  2 +-
 34 files changed, 145 insertions(+), 88 deletions(-)

diff --git a/app/aerc.go b/app/aerc.go
index beef632801c7..52d77628d764 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 {
@@ -318,7 +318,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 {
@@ -627,14 +627,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)
@@ -667,7 +665,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)
@@ -694,7 +692,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)
diff --git a/app/app.go b/app/app.go
index 37444c8b77c4..071aac744214 100644
--- a/app/app.go
+++ b/app/app.go
@@ -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 e8b0069ea7f0..5f8e4280e53b 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 0af87e5e8edd..0c2c1bef0f86 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 8d7d27aeba5d..2c2cee53cf8d 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 1331066569b2..d6a6f9f39305 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 ff1463b67f5d..3c5b24d7c8bb 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 27e50bcf1690..e87cd80226e7 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 92c33bea8c68..8d293a328bc5 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 73672fedad12..0c23aa95cff0 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"
)

@@ -137,12 +138,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 6ebba81f3ac1..08eec9cbf526 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 a508551da792..10b96ef29a23 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 0451605b5fa6..b05a9bf3f69a 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 25d7850aab86..15fb35f4b651 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 9ab2aac1eacb..fe6632e96bf9 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 bb20a20417f6..8c1e497dfdbc 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 5a5dea10cbb0..c5b61528adb3 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 a8f17e8db5c0..c81e720466e3 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 ad05d69cf2ae..2a311db23276 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
diff --git a/go.mod b/go.mod
index d34a80cab8b3..9f82c8e37381 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 607f91b7b9c3..6b6ca80616da 100644
--- a/go.sum
+++ b/go.sum
@@ -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 5ca819e044fe..edbc7ff6990c 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 b267721e25e5..9d98cf8f7b02 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 6c4551b3ff09..d52859b7bd00 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
			}
diff --git a/main.go b/main.go
index efdbf6535271..9f105ba597bf 100644
--- a/main.go
+++ b/main.go
@@ -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"
@@ -49,24 +49,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 4fffca0afb51..7c1169fb1046 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 dd5f4a8df22f..22ea1701ff32 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 42d5352d6c86..00b7f51bc1b9 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 3d1d76a3d88f..d8388b9442a4 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 8742887482d8..65ef2fe28b08 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 29717c725419..9308d78590b0 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 970d4db6d961..4bcff69aaef1 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 b98e2bbb05ea..9715c4a17f09 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 a84711eb3928..ddbb13cd2b76 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 {
-- 
2.46.2

[PATCH aerc 2/3] completion: add commands descriptions

Details
Message ID
<20241001130716.345110-5-robin@jarry.cc>
In-Reply-To
<20241001130716.345110-4-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Patch: +383 -14
Update the Command interface to include a Description() method.
Implement the method for all commands using short descriptions inspired
from the aerc(1) man page.

Return the description values along with command names so that they can
be displayed in completion choices.

Implements: https://todo.sr.ht/~rjarry/aerc/271
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 commands/account/align.go                  |  4 ++++
 commands/account/cf.go                     |  4 ++++
 commands/account/check-mail.go             |  4 ++++
 commands/account/clear.go                  |  4 ++++
 commands/account/compose.go                |  4 ++++
 commands/account/connection.go             |  4 ++++
 commands/account/expand-folder.go          |  4 ++++
 commands/account/export-mbox.go            |  4 ++++
 commands/account/import-mbox.go            |  4 ++++
 commands/account/mkdir.go                  |  4 ++++
 commands/account/next-folder.go            |  4 ++++
 commands/account/next-result.go            |  4 ++++
 commands/account/next.go                   |  4 ++++
 commands/account/query.go                  |  4 ++++
 commands/account/recover.go                |  4 ++++
 commands/account/rmdir.go                  |  4 ++++
 commands/account/search.go                 |  4 ++++
 commands/account/select.go                 |  4 ++++
 commands/account/sort.go                   |  4 ++++
 commands/account/split.go                  |  4 ++++
 commands/account/view.go                   |  4 ++++
 commands/cd.go                             |  4 ++++
 commands/choose.go                         |  4 ++++
 commands/close.go                          |  4 ++++
 commands/commands.go                       |  7 +++++++
 commands/compose/abort.go                  |  4 ++++
 commands/compose/attach-key.go             |  4 ++++
 commands/compose/attach.go                 |  4 ++++
 commands/compose/cc-bcc.go                 |  4 ++++
 commands/compose/detach.go                 |  4 ++++
 commands/compose/edit.go                   |  4 ++++
 commands/compose/encrypt.go                |  4 ++++
 commands/compose/header.go                 |  4 ++++
 commands/compose/multipart.go              |  4 ++++
 commands/compose/next-field.go             |  4 ++++
 commands/compose/postpone.go               |  4 ++++
 commands/compose/send.go                   |  4 ++++
 commands/compose/sign.go                   |  4 ++++
 commands/compose/switch.go                 |  4 ++++
 commands/ct.go                             |  4 ++++
 commands/echo.go                           |  4 ++++
 commands/eml.go                            |  4 ++++
 commands/exec.go                           |  4 ++++
 commands/help.go                           |  4 ++++
 commands/menu.go                           |  4 ++++
 commands/move-tab.go                       |  4 ++++
 commands/msg/archive.go                    |  4 ++++
 commands/msg/bounce.go                     |  4 ++++
 commands/msg/copy.go                       |  4 ++++
 commands/msg/delete.go                     |  4 ++++
 commands/msg/envelope.go                   |  4 ++++
 commands/msg/fold.go                       |  4 ++++
 commands/msg/forward.go                    |  4 ++++
 commands/msg/invite.go                     |  4 ++++
 commands/msg/mark.go                       |  4 ++++
 commands/msg/modify-labels.go              |  4 ++++
 commands/msg/move.go                       |  4 ++++
 commands/msg/pipe.go                       |  4 ++++
 commands/msg/read.go                       |  4 ++++
 commands/msg/recall.go                     |  4 ++++
 commands/msg/reply.go                      |  4 ++++
 commands/msg/toggle-thread-context.go      |  4 ++++
 commands/msg/toggle-threads.go             |  4 ++++
 commands/msg/unsubscribe.go                |  4 ++++
 commands/msgview/next-part.go              |  4 ++++
 commands/msgview/open-link.go              |  4 ++++
 commands/msgview/open.go                   |  8 ++++----
 commands/msgview/save.go                   |  8 ++++----
 commands/msgview/toggle-headers.go         |  4 ++++
 commands/msgview/toggle-key-passthrough.go |  4 ++++
 commands/new-account.go                    |  4 ++++
 commands/next-tab.go                       |  4 ++++
 commands/patch/apply.go                    |  4 ++++
 commands/patch/cd.go                       |  4 ++++
 commands/patch/drop.go                     |  4 ++++
 commands/patch/find.go                     |  4 ++++
 commands/patch/init.go                     |  4 ++++
 commands/patch/list.go                     |  4 ++++
 commands/patch/patch.go                    |  4 ++++
 commands/patch/rebase.go                   |  4 ++++
 commands/patch/switch.go                   |  4 ++++
 commands/patch/term.go                     |  4 ++++
 commands/patch/unlink.go                   |  4 ++++
 commands/pin-tab.go                        |  4 ++++
 commands/prompt.go                         |  4 ++++
 commands/pwd.go                            |  4 ++++
 commands/quit.go                           |  4 ++++
 commands/redraw.go                         |  4 ++++
 commands/reload.go                         |  4 ++++
 commands/send-keys.go                      |  4 ++++
 commands/suspend.go                        |  4 ++++
 commands/term.go                           |  4 ++++
 commands/z.go                              |  4 ++++
 main.go                                    | 14 ++++++++------
 94 files changed, 383 insertions(+), 14 deletions(-)

diff --git a/commands/account/align.go b/commands/account/align.go
index e4c1379a2b95..83bb5b64745a 100644
--- a/commands/account/align.go
+++ b/commands/account/align.go
@@ -16,6 +16,10 @@ func init() {
	commands.Register(Align{})
}

func (Align) Description() string {
	return "Align the message list view."
}

var posNames []string = []string{"top", "center", "bottom"}

func (a *Align) ParsePos(arg string) error {
diff --git a/commands/account/cf.go b/commands/account/cf.go
index 2c2cee53cf8d..3cce37a38261 100644
--- a/commands/account/cf.go
+++ b/commands/account/cf.go
@@ -22,6 +22,10 @@ func init() {
	commands.Register(ChangeFolder{})
}

func (ChangeFolder) Description() string {
	return "Change the folder shown in the message list."
}

func (ChangeFolder) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/check-mail.go b/commands/account/check-mail.go
index d82fa042e864..507fdc38b80a 100644
--- a/commands/account/check-mail.go
+++ b/commands/account/check-mail.go
@@ -13,6 +13,10 @@ func init() {
	commands.Register(CheckMail{})
}

func (CheckMail) Description() string {
	return "Check for new mail on the selected account."
}

func (CheckMail) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/clear.go b/commands/account/clear.go
index ec033c46faad..e16b563a4500 100644
--- a/commands/account/clear.go
+++ b/commands/account/clear.go
@@ -16,6 +16,10 @@ func init() {
	commands.Register(Clear{})
}

func (Clear) Description() string {
	return "Clear the current search or filter criteria."
}

func (Clear) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/compose.go b/commands/account/compose.go
index c9e3637989f6..9eeb339b01c7 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -27,6 +27,10 @@ func init() {
	commands.Register(Compose{})
}

func (Compose) Description() string {
	return "Open the compose window to write a new email."
}

func (Compose) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/connection.go b/commands/account/connection.go
index 126c79f1207c..7ea1ef0b7665 100644
--- a/commands/account/connection.go
+++ b/commands/account/connection.go
@@ -15,6 +15,10 @@ func init() {
	commands.Register(Connection{})
}

func (Connection) Description() string {
	return "Disconnect or reconnect the current account."
}

func (Connection) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/expand-folder.go b/commands/account/expand-folder.go
index 8eafa0ed5df4..8b1a8c665a81 100644
--- a/commands/account/expand-folder.go
+++ b/commands/account/expand-folder.go
@@ -13,6 +13,10 @@ func init() {
	commands.Register(ExpandCollapseFolder{})
}

func (ExpandCollapseFolder) Description() string {
	return "Expand or collapse the current folder."
}

func (ExpandCollapseFolder) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go
index 619c24a26821..fed8568e61fe 100644
--- a/commands/account/export-mbox.go
+++ b/commands/account/export-mbox.go
@@ -26,6 +26,10 @@ func init() {
	commands.Register(ExportMbox{})
}

func (ExportMbox) Description() string {
	return "Export messages in the current folder to an mbox file."
}

func (ExportMbox) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/import-mbox.go b/commands/account/import-mbox.go
index cdf99c96314c..946098e5bfc8 100644
--- a/commands/account/import-mbox.go
+++ b/commands/account/import-mbox.go
@@ -26,6 +26,10 @@ func init() {
	commands.Register(ImportMbox{})
}

func (ImportMbox) Description() string {
	return "Import all messages from an mbox file to the current folder."
}

func (ImportMbox) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go
index d6a6f9f39305..cf8763be5686 100644
--- a/commands/account/mkdir.go
+++ b/commands/account/mkdir.go
@@ -18,6 +18,10 @@ func init() {
	commands.Register(MakeDir{})
}

func (MakeDir) Description() string {
	return "Create and change to a new folder."
}

func (MakeDir) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/next-folder.go b/commands/account/next-folder.go
index 369249ce0064..eb075315fa5b 100644
--- a/commands/account/next-folder.go
+++ b/commands/account/next-folder.go
@@ -15,6 +15,10 @@ func init() {
	commands.Register(NextPrevFolder{})
}

func (NextPrevFolder) Description() string {
	return "Cycle to the next or previous folder shown in the sidebar."
}

func (NextPrevFolder) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/next-result.go b/commands/account/next-result.go
index e49884cf7375..0fb7d0a1d5ec 100644
--- a/commands/account/next-result.go
+++ b/commands/account/next-result.go
@@ -14,6 +14,10 @@ func init() {
	commands.Register(NextPrevResult{})
}

func (NextPrevResult) Description() string {
	return "Select the next or previous search result."
}

func (NextPrevResult) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/next.go b/commands/account/next.go
index 926243163942..5454f1e528c9 100644
--- a/commands/account/next.go
+++ b/commands/account/next.go
@@ -23,6 +23,10 @@ func init() {
	commands.Register(NextPrevMsg{})
}

func (NextPrevMsg) Description() string {
	return "Select the next or previous message in the message list."
}

func (NextPrevMsg) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/account/query.go b/commands/account/query.go
index 65b5e06384d2..da8a8c0667e0 100644
--- a/commands/account/query.go
+++ b/commands/account/query.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(Query{})
}

func (Query) Description() string {
	return "Create a virtual folder using the specified notmuch query."
}

func (Query) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/recover.go b/commands/account/recover.go
index fb08f646e0bc..625ac122e910 100644
--- a/commands/account/recover.go
+++ b/commands/account/recover.go
@@ -23,6 +23,10 @@ func init() {
	commands.Register(Recover{})
}

func (Recover) Description() string {
	return "Resume composing a message that was not sent nor postponed."
}

func (Recover) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go
index 3c5b24d7c8bb..852d717b0a08 100644
--- a/commands/account/rmdir.go
+++ b/commands/account/rmdir.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(RemoveDir{})
}

func (RemoveDir) Description() string {
	return "Remove folder."
}

func (RemoveDir) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/search.go b/commands/account/search.go
index 14aa367b392c..5dde33222f73 100644
--- a/commands/account/search.go
+++ b/commands/account/search.go
@@ -39,6 +39,10 @@ func init() {
	commands.Register(SearchFilter{})
}

func (SearchFilter) Description() string {
	return "Search or filter the current folder."
}

func (SearchFilter) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/select.go b/commands/account/select.go
index f990a1a15416..dda76af47d01 100644
--- a/commands/account/select.go
+++ b/commands/account/select.go
@@ -15,6 +15,10 @@ func init() {
	commands.Register(SelectMessage{})
}

func (SelectMessage) Description() string {
	return "Select the <N>th message in the message list."
}

func (SelectMessage) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/sort.go b/commands/account/sort.go
index b41d49208be6..16060aa00297 100644
--- a/commands/account/sort.go
+++ b/commands/account/sort.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(Sort{})
}

func (Sort) Description() string {
	return "Sort the message list by the given criteria."
}

func (Sort) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/split.go b/commands/account/split.go
index f866af78f976..efa89fc76c61 100644
--- a/commands/account/split.go
+++ b/commands/account/split.go
@@ -18,6 +18,10 @@ func init() {
	commands.Register(Split{})
}

func (Split) Description() string {
	return "Split the message list with a preview pane."
}

func (Split) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/account/view.go b/commands/account/view.go
index 5f5bd289cc5e..0e3ca09c5a92 100644
--- a/commands/account/view.go
+++ b/commands/account/view.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(ViewMessage{})
}

func (ViewMessage) Description() string {
	return "View the selected message in a new tab."
}

func (ViewMessage) Context() commands.CommandContext {
	return commands.MESSAGE_LIST
}
diff --git a/commands/cd.go b/commands/cd.go
index 1d71882f4901..27333807c334 100644
--- a/commands/cd.go
+++ b/commands/cd.go
@@ -18,6 +18,10 @@ func init() {
	Register(ChangeDirectory{})
}

func (ChangeDirectory) Description() string {
	return "Change aerc's current working directory."
}

func (ChangeDirectory) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/choose.go b/commands/choose.go
index 4ecff0eac710..6c3906c4eb9c 100644
--- a/commands/choose.go
+++ b/commands/choose.go
@@ -14,6 +14,10 @@ func init() {
	Register(Choose{})
}

func (Choose) Description() string {
	return "Prompt to choose from various options."
}

func (Choose) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/close.go b/commands/close.go
index c74a705e95ae..177c54a73389 100644
--- a/commands/close.go
+++ b/commands/close.go
@@ -10,6 +10,10 @@ func init() {
	Register(Close{})
}

func (Close) Description() string {
	return "Close the focused tab."
}

func (Close) Context() CommandContext {
	return MESSAGE_VIEWER | TERMINAL
}
diff --git a/commands/commands.go b/commands/commands.go
index e87cd80226e7..a06e8ef966b2 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -53,6 +53,7 @@ func CurrentContext() CommandContext {
}

type Command interface {
	Description() string
	Context() CommandContext
	Aliases() []string
	Execute([]string) error
@@ -75,8 +76,14 @@ func Register(cmd Command) {
func ActiveCommands() []Command {
	var cmds []Command
	context := CurrentContext()
	seen := make(map[reflect.Type]bool)

	for _, cmd := range allCommands {
		t := reflect.TypeOf(cmd)
		if seen[t] {
			continue
		}
		seen[t] = true
		if cmd.Context()&context != 0 {
			cmds = append(cmds, cmd)
		}
diff --git a/commands/compose/abort.go b/commands/compose/abort.go
index f446c306eec1..a6d50e4e8ac4 100644
--- a/commands/compose/abort.go
+++ b/commands/compose/abort.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(Abort{})
}

func (Abort) Description() string {
	return "Close the composer without sending."
}

func (Abort) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/attach-key.go b/commands/compose/attach-key.go
index 047b9c08f0d2..0030e7ed88cc 100644
--- a/commands/compose/attach-key.go
+++ b/commands/compose/attach-key.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(AttachKey{})
}

func (AttachKey) Description() string {
	return "Attach the public key of the current account."
}

func (AttachKey) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index c86b0f4f97dd..f811f5a857f2 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -31,6 +31,10 @@ func init() {
	commands.Register(Attach{})
}

func (Attach) Description() string {
	return "Attach the file at the given path to the email."
}

func (Attach) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/cc-bcc.go b/commands/compose/cc-bcc.go
index 92f792f1cbb1..a3c1c188c656 100644
--- a/commands/compose/cc-bcc.go
+++ b/commands/compose/cc-bcc.go
@@ -13,6 +13,10 @@ func init() {
	commands.Register(CC{})
}

func (CC) Description() string {
	return "Add the given address(es) to the Cc or Bcc header."
}

func (CC) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/detach.go b/commands/compose/detach.go
index 69f8538d2c19..ddf2e24f84e4 100644
--- a/commands/compose/detach.go
+++ b/commands/compose/detach.go
@@ -18,6 +18,10 @@ func init() {
	commands.Register(Detach{})
}

func (Detach) Description() string {
	return "Detach the file with the given path from the composed email."
}

func (Detach) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/edit.go b/commands/compose/edit.go
index 45c55046bb3e..f02848b7659a 100644
--- a/commands/compose/edit.go
+++ b/commands/compose/edit.go
@@ -17,6 +17,10 @@ func init() {
	commands.Register(Edit{})
}

func (Edit) Description() string {
	return "(Re-)open text editor to edit the message in progress."
}

func (Edit) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/encrypt.go b/commands/compose/encrypt.go
index 5e52f821773a..1e6be8150702 100644
--- a/commands/compose/encrypt.go
+++ b/commands/compose/encrypt.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(Encrypt{})
}

func (Encrypt) Description() string {
	return "Toggle encryption of the message to all recipients."
}

func (Encrypt) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/header.go b/commands/compose/header.go
index 839896c3a36a..64c1c37d7b9f 100644
--- a/commands/compose/header.go
+++ b/commands/compose/header.go
@@ -29,6 +29,10 @@ func init() {
	commands.Register(Header{})
}

func (Header) Description() string {
	return "Add or remove the specified email header."
}

func (Header) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/multipart.go b/commands/compose/multipart.go
index a38e0a6e9fc5..87f4d0300c1d 100644
--- a/commands/compose/multipart.go
+++ b/commands/compose/multipart.go
@@ -17,6 +17,10 @@ func init() {
	commands.Register(Multipart{})
}

func (Multipart) Description() string {
	return "Convert the message to multipart with the given mime-type part."
}

func (Multipart) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/next-field.go b/commands/compose/next-field.go
index 6c96cd9458b8..e21a8f8b433a 100644
--- a/commands/compose/next-field.go
+++ b/commands/compose/next-field.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(NextPrevField{})
}

func (NextPrevField) Description() string {
	return "Cycle between header input fields."
}

func (NextPrevField) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
index d5b3ed2fe5b5..3dd555548a26 100644
--- a/commands/compose/postpone.go
+++ b/commands/compose/postpone.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(Postpone{})
}

func (Postpone) Description() string {
	return "Save the current state of the message to the postpone folder."
}

func (Postpone) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/send.go b/commands/compose/send.go
index 0c23aa95cff0..ffb6ab460031 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -35,6 +35,10 @@ func init() {
	commands.Register(Send{})
}

func (Send) Description() string {
	return "Send the message using the configured outgoing transport."
}

func (Send) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/sign.go b/commands/compose/sign.go
index 692ef108c797..3f9cacffd1f1 100644
--- a/commands/compose/sign.go
+++ b/commands/compose/sign.go
@@ -13,6 +13,10 @@ func init() {
	commands.Register(Sign{})
}

func (Sign) Description() string {
	return "Sign the message using the account default key."
}

func (Sign) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/compose/switch.go b/commands/compose/switch.go
index c9d1013c207d..4bcfc136c7c1 100644
--- a/commands/compose/switch.go
+++ b/commands/compose/switch.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(SwitchAccount{})
}

func (SwitchAccount) Description() string {
	return "Change composing from the specified account."
}

func (SwitchAccount) Context() commands.CommandContext {
	return commands.COMPOSE
}
diff --git a/commands/ct.go b/commands/ct.go
index 4f908fcf99ef..be3d4e982d7d 100644
--- a/commands/ct.go
+++ b/commands/ct.go
@@ -16,6 +16,10 @@ func init() {
	Register(ChangeTab{})
}

func (ChangeTab) Description() string {
	return "Change the focus to the specified tab."
}

func (ChangeTab) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/echo.go b/commands/echo.go
index ebe997da5384..819b30d63523 100644
--- a/commands/echo.go
+++ b/commands/echo.go
@@ -12,6 +12,10 @@ func init() {
	Register(Echo{})
}

func (Echo) Description() string {
	return "Print text after template expansion."
}

func (Echo) Aliases() []string {
	return []string{"echo"}
}
diff --git a/commands/eml.go b/commands/eml.go
index fa1267295057..d4323a29b17c 100644
--- a/commands/eml.go
+++ b/commands/eml.go
@@ -19,6 +19,10 @@ func init() {
	Register(Eml{})
}

func (Eml) Description() string {
	return "Open an eml file into the message viewer."
}

func (Eml) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/exec.go b/commands/exec.go
index ac33cf21b7c7..5b7d7f69d490 100644
--- a/commands/exec.go
+++ b/commands/exec.go
@@ -18,6 +18,10 @@ func init() {
	Register(ExecCmd{})
}

func (ExecCmd) Description() string {
	return "Execute an arbitrary command in the background."
}

func (ExecCmd) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/help.go b/commands/help.go
index 079b2fd5479e..dab0af1a6d69 100644
--- a/commands/help.go
+++ b/commands/help.go
@@ -32,6 +32,10 @@ func init() {
	Register(Help{})
}

func (Help) Description() string {
	return "Display one of aerc's man pages in the embedded terminal."
}

func (Help) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/menu.go b/commands/menu.go
index 08eec9cbf526..4db1a1d72982 100644
--- a/commands/menu.go
+++ b/commands/menu.go
@@ -28,6 +28,10 @@ func init() {
	Register(Menu{})
}

func (Menu) Description() string {
	return "Open a popover dialog."
}

func (Menu) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/move-tab.go b/commands/move-tab.go
index c8e1b99afd8c..a1ebfc0d5e86 100644
--- a/commands/move-tab.go
+++ b/commands/move-tab.go
@@ -16,6 +16,10 @@ func init() {
	Register(MoveTab{})
}

func (MoveTab) Description() string {
	return "Move the selected tab to the given index."
}

func (MoveTab) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/msg/archive.go b/commands/msg/archive.go
index c262de41c798..0b4b82587c7d 100644
--- a/commands/msg/archive.go
+++ b/commands/msg/archive.go
@@ -51,6 +51,10 @@ func init() {
	commands.Register(Archive{})
}

func (Archive) Description() string {
	return "Move the selected message to the archive."
}

func (Archive) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/bounce.go b/commands/msg/bounce.go
index 1460951dfabb..0c0452b4e12d 100644
--- a/commands/msg/bounce.go
+++ b/commands/msg/bounce.go
@@ -27,6 +27,10 @@ func init() {
	commands.Register(Bounce{})
}

func (Bounce) Description() string {
	return "Re-send the selected message(s) to the specified addresses."
}

func (Bounce) Aliases() []string {
	return []string{"bounce", "resend"}
}
diff --git a/commands/msg/copy.go b/commands/msg/copy.go
index 52a6ea6c2888..126dbd8ee576 100644
--- a/commands/msg/copy.go
+++ b/commands/msg/copy.go
@@ -28,6 +28,10 @@ func init() {
	commands.Register(Copy{})
}

func (Copy) Description() string {
	return "Copy the selected message(s) to the specified folder."
}

func (Copy) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/delete.go b/commands/msg/delete.go
index 2e41504a83f2..4999045e9aaa 100644
--- a/commands/msg/delete.go
+++ b/commands/msg/delete.go
@@ -21,6 +21,10 @@ func init() {
	commands.Register(Delete{})
}

func (Delete) Description() string {
	return "Delete the selected message(s)."
}

func (Delete) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/envelope.go b/commands/msg/envelope.go
index 35243509ed99..09e7fa1950f8 100644
--- a/commands/msg/envelope.go
+++ b/commands/msg/envelope.go
@@ -22,6 +22,10 @@ func init() {
	commands.Register(Envelope{})
}

func (Envelope) Description() string {
	return "Open the message envelope in a dialog popup."
}

func (Envelope) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/fold.go b/commands/msg/fold.go
index 7942e9651e03..d3ec243810a7 100644
--- a/commands/msg/fold.go
+++ b/commands/msg/fold.go
@@ -16,6 +16,10 @@ func init() {
	commands.Register(Fold{})
}

func (Fold) Description() string {
	return "Collapse or expand the thread children of the selected message."
}

func (Fold) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index d030636eb1e8..3bfad8cfcd48 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -37,6 +37,10 @@ func init() {
	commands.Register(forward{})
}

func (forward) Description() string {
	return "Open the composer to forward the selected message to another recipient."
}

func (forward) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/invite.go b/commands/msg/invite.go
index 63ad2d3ab94e..53e5ad8f9cf8 100644
--- a/commands/msg/invite.go
+++ b/commands/msg/invite.go
@@ -25,6 +25,10 @@ func init() {
	commands.Register(invite{})
}

func (invite) Description() string {
	return "Accept or decline a meeting invitation."
}

func (invite) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/mark.go b/commands/msg/mark.go
index 42110038c98f..6c36dcd711bf 100644
--- a/commands/msg/mark.go
+++ b/commands/msg/mark.go
@@ -19,6 +19,10 @@ func init() {
	commands.Register(Mark{})
}

func (Mark) Description() string {
	return "Mark, unmark or remark messages."
}

func (Mark) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/modify-labels.go b/commands/msg/modify-labels.go
index 90536b914002..e317440c2517 100644
--- a/commands/msg/modify-labels.go
+++ b/commands/msg/modify-labels.go
@@ -16,6 +16,10 @@ func init() {
	commands.Register(ModifyLabels{})
}

func (ModifyLabels) Description() string {
	return "Modify message labels."
}

func (ModifyLabels) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/move.go b/commands/msg/move.go
index 8e548bfe9c0e..63473b1378d0 100644
--- a/commands/msg/move.go
+++ b/commands/msg/move.go
@@ -27,6 +27,10 @@ func init() {
	commands.Register(Move{})
}

func (Move) Description() string {
	return "Move the selected message(s) to the specified folder."
}

func (Move) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go
index 9764de823118..f66e7dcaf03e 100644
--- a/commands/msg/pipe.go
+++ b/commands/msg/pipe.go
@@ -33,6 +33,10 @@ func init() {
	commands.Register(Pipe{})
}

func (Pipe) Description() string {
	return "Pipe the selected message(s) into the given shell command."
}

func (Pipe) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/read.go b/commands/msg/read.go
index 686c1a9ee109..e548c12b0653 100644
--- a/commands/msg/read.go
+++ b/commands/msg/read.go
@@ -23,6 +23,10 @@ func init() {
	commands.Register(FlagMsg{})
}

func (FlagMsg) Description() string {
	return "Set or unset a flag on the marked or selected messages."
}

func (FlagMsg) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index 155664142e63..5611f2891e3a 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -29,6 +29,10 @@ func init() {
	commands.Register(Recall{})
}

func (Recall) Description() string {
	return "Open a postponed message for re-editing."
}

func (Recall) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 2654514e09f5..5f1c8e32026b 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -36,6 +36,10 @@ func init() {
	commands.Register(reply{})
}

func (reply) Description() string {
	return "Open the composer to reply to the selected message."
}

func (reply) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/toggle-thread-context.go b/commands/msg/toggle-thread-context.go
index 83dc87bdc1ce..fed87bad1e0f 100644
--- a/commands/msg/toggle-thread-context.go
+++ b/commands/msg/toggle-thread-context.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(ToggleThreadContext{})
}

func (ToggleThreadContext) Description() string {
	return "Show/hide message thread context."
}

func (ToggleThreadContext) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/toggle-threads.go b/commands/msg/toggle-threads.go
index 66fa594d7cb5..b8ebc15a5160 100644
--- a/commands/msg/toggle-threads.go
+++ b/commands/msg/toggle-threads.go
@@ -12,6 +12,10 @@ func init() {
	commands.Register(ToggleThreads{})
}

func (ToggleThreads) Description() string {
	return "Toggle between message threading and the normal message list."
}

func (ToggleThreads) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go
index 62f33f4df787..46b86f306f41 100644
--- a/commands/msg/unsubscribe.go
+++ b/commands/msg/unsubscribe.go
@@ -27,6 +27,10 @@ func init() {
	commands.Register(Unsubscribe{})
}

func (Unsubscribe) Description() string {
	return "Attempt to automatically unsubscribe from mailing lists."
}

func (Unsubscribe) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/msgview/next-part.go b/commands/msgview/next-part.go
index b5375d756fc7..5f1a8fb62919 100644
--- a/commands/msgview/next-part.go
+++ b/commands/msgview/next-part.go
@@ -13,6 +13,10 @@ func init() {
	commands.Register(NextPrevPart{})
}

func (NextPrevPart) Description() string {
	return "Cycle between message parts being shown."
}

func (NextPrevPart) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}
diff --git a/commands/msgview/open-link.go b/commands/msgview/open-link.go
index 9f02f795b360..00cc9314ae7d 100644
--- a/commands/msgview/open-link.go
+++ b/commands/msgview/open-link.go
@@ -19,6 +19,10 @@ func init() {
	commands.Register(OpenLink{})
}

func (OpenLink) Description() string {
	return "Open the specified URL with an external program."
}

func (OpenLink) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}
diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index 1361b15ef922..7b23423d8582 100644
--- a/commands/msgview/open.go
+++ b/commands/msgview/open.go
@@ -22,12 +22,12 @@ func init() {
	commands.Register(Open{})
}

func (Open) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
func (Open) Description() string {
	return "Save the current message part to a temporary file, then open it."
}

func (Open) Options() string {
	return "d"
func (Open) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}

func (Open) Aliases() []string {
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 1a219e93d20b..0310ed0ec93e 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.go
@@ -29,12 +29,12 @@ func init() {
	commands.Register(Save{})
}

func (Save) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
func (Save) Description() string {
	return "Save the current message part to the given path."
}

func (Save) Options() string {
	return "fpaA"
func (Save) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}

func (Save) Aliases() []string {
diff --git a/commands/msgview/toggle-headers.go b/commands/msgview/toggle-headers.go
index 201fce90d5e3..c2b4e8f76b45 100644
--- a/commands/msgview/toggle-headers.go
+++ b/commands/msgview/toggle-headers.go
@@ -11,6 +11,10 @@ func init() {
	commands.Register(ToggleHeaders{})
}

func (ToggleHeaders) Description() string {
	return "Toggle the visibility of message headers."
}

func (ToggleHeaders) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}
diff --git a/commands/msgview/toggle-key-passthrough.go b/commands/msgview/toggle-key-passthrough.go
index 1b3f86285eaf..c972d18a9290 100644
--- a/commands/msgview/toggle-key-passthrough.go
+++ b/commands/msgview/toggle-key-passthrough.go
@@ -12,6 +12,10 @@ func init() {
	commands.Register(ToggleKeyPassthrough{})
}

func (ToggleKeyPassthrough) Description() string {
	return "Enter or exit the passthrough key bindings context."
}

func (ToggleKeyPassthrough) Context() commands.CommandContext {
	return commands.MESSAGE_VIEWER
}
diff --git a/commands/new-account.go b/commands/new-account.go
index 5a5499df9c42..9792d7e1409e 100644
--- a/commands/new-account.go
+++ b/commands/new-account.go
@@ -12,6 +12,10 @@ func init() {
	Register(NewAccount{})
}

func (NewAccount) Description() string {
	return "Start the new account wizard."
}

func (NewAccount) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/next-tab.go b/commands/next-tab.go
index 12f659c99f42..466c4ff08a02 100644
--- a/commands/next-tab.go
+++ b/commands/next-tab.go
@@ -12,6 +12,10 @@ func init() {
	Register(NextPrevTab{})
}

func (NextPrevTab) Description() string {
	return "Cycle to the previous or next tab."
}

func (NextPrevTab) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/patch/apply.go b/commands/patch/apply.go
index 6554ad8b2ed9..12f5b3f8ac95 100644
--- a/commands/patch/apply.go
+++ b/commands/patch/apply.go
@@ -24,6 +24,10 @@ func init() {
	register(Apply{})
}

func (Apply) Description() string {
	return "Apply the selected message(s) to the current project."
}

func (Apply) Context() commands.CommandContext {
	return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
}
diff --git a/commands/patch/cd.go b/commands/patch/cd.go
index bbca1ebaa62c..70e92412764e 100644
--- a/commands/patch/cd.go
+++ b/commands/patch/cd.go
@@ -16,6 +16,10 @@ func init() {
	register(Cd{})
}

func (Cd) Description() string {
	return "Change aerc's working directory to the current project."
}

func (Cd) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/drop.go b/commands/patch/drop.go
index a888568a5c2a..dbb1fb079feb 100644
--- a/commands/patch/drop.go
+++ b/commands/patch/drop.go
@@ -18,6 +18,10 @@ func init() {
	register(Drop{})
}

func (Drop) Description() string {
	return "Drop a patch from the repository."
}

func (Drop) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/find.go b/commands/patch/find.go
index 10b96ef29a23..a74f926edb6f 100644
--- a/commands/patch/find.go
+++ b/commands/patch/find.go
@@ -23,6 +23,10 @@ func init() {
	register(Find{})
}

func (Find) Description() string {
	return "Search for applied patches."
}

func (Find) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/init.go b/commands/patch/init.go
index de0c6e45d499..b7316ee44a72 100644
--- a/commands/patch/init.go
+++ b/commands/patch/init.go
@@ -18,6 +18,10 @@ func init() {
	register(Init{})
}

func (Init) Description() string {
	return "Create a new project."
}

func (Init) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/list.go b/commands/patch/list.go
index b05a9bf3f69a..a9c68e4d0a7b 100644
--- a/commands/patch/list.go
+++ b/commands/patch/list.go
@@ -25,6 +25,10 @@ func init() {
	register(List{})
}

func (List) Description() string {
	return "List the current project with the tracked patch sets."
}

func (List) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/patch.go b/commands/patch/patch.go
index 15fb35f4b651..0e19d888fa0d 100644
--- a/commands/patch/patch.go
+++ b/commands/patch/patch.go
@@ -31,6 +31,10 @@ func init() {
	commands.Register(Patch{})
}

func (Patch) Description() string {
	return "Local patch management commands."
}

func (Patch) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/rebase.go b/commands/patch/rebase.go
index e99cc275b42d..26b82f6d53d0 100644
--- a/commands/patch/rebase.go
+++ b/commands/patch/rebase.go
@@ -28,6 +28,10 @@ func init() {
	register(Rebase{})
}

func (Rebase) Description() string {
	return "Rebase the patch data."
}

func (Rebase) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/switch.go b/commands/patch/switch.go
index dc9138d8a1db..9eb26ef29c95 100644
--- a/commands/patch/switch.go
+++ b/commands/patch/switch.go
@@ -18,6 +18,10 @@ func init() {
	register(Switch{})
}

func (Switch) Description() string {
	return "Switch context to the specified project."
}

func (Switch) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/term.go b/commands/patch/term.go
index f9f79be1bc49..c81f6995b2dd 100644
--- a/commands/patch/term.go
+++ b/commands/patch/term.go
@@ -13,6 +13,10 @@ func init() {
	register(Term{})
}

func (Term) Description() string {
	return "Open a shell or run a command in the current project's directory."
}

func (Term) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/patch/unlink.go b/commands/patch/unlink.go
index 2d24d25b247c..da21c15ee1f6 100644
--- a/commands/patch/unlink.go
+++ b/commands/patch/unlink.go
@@ -18,6 +18,10 @@ func init() {
	register(Unlink{})
}

func (Unlink) Description() string {
	return "Delete all patch tracking data for the specified project."
}

func (Unlink) Context() commands.CommandContext {
	return commands.GLOBAL
}
diff --git a/commands/pin-tab.go b/commands/pin-tab.go
index aaac356d4f0d..8ec6d24e59c9 100644
--- a/commands/pin-tab.go
+++ b/commands/pin-tab.go
@@ -10,6 +10,10 @@ func init() {
	Register(PinTab{})
}

func (PinTab) Description() string {
	return "Move the current tab to the left and mark it as pinned."
}

func (PinTab) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/prompt.go b/commands/prompt.go
index fe6632e96bf9..dc965dd07bab 100644
--- a/commands/prompt.go
+++ b/commands/prompt.go
@@ -15,6 +15,10 @@ func init() {
	Register(Prompt{})
}

func (Prompt) Description() string {
	return "Prompt for user input and execute a command."
}

func (Prompt) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/pwd.go b/commands/pwd.go
index fb1d04f1aa44..e60326e50a0d 100644
--- a/commands/pwd.go
+++ b/commands/pwd.go
@@ -13,6 +13,10 @@ func init() {
	Register(PrintWorkDir{})
}

func (PrintWorkDir) Description() string {
	return "Display aerc's current working directory."
}

func (PrintWorkDir) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/quit.go b/commands/quit.go
index e17200ef0dca..8e828823ca54 100644
--- a/commands/quit.go
+++ b/commands/quit.go
@@ -14,6 +14,10 @@ func init() {
	Register(Quit{})
}

func (Quit) Description() string {
	return "Exit aerc."
}

func (Quit) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/redraw.go b/commands/redraw.go
index b43e45e39516..be25d5bdb5fb 100644
--- a/commands/redraw.go
+++ b/commands/redraw.go
@@ -8,6 +8,10 @@ func init() {
	Register(Redraw{})
}

func (Redraw) Description() string {
	return "Force a full redraw of the screen."
}

func (Redraw) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/reload.go b/commands/reload.go
index ecda1387c91d..e09290c1e1b0 100644
--- a/commands/reload.go
+++ b/commands/reload.go
@@ -19,6 +19,10 @@ func init() {
	Register(Reload{})
}

func (Reload) Description() string {
	return "Hot-reload configuration files."
}

func (r *Reload) CompleteStyle(s string) []string {
	var files []string
	for _, dir := range config.Ui.StyleSetDirs {
diff --git a/commands/send-keys.go b/commands/send-keys.go
index a61bfcf2c3ee..a49f4f1cd81c 100644
--- a/commands/send-keys.go
+++ b/commands/send-keys.go
@@ -15,6 +15,10 @@ func init() {
	Register(SendKeys{})
}

func (SendKeys) Description() string {
	return "Send keystrokes to the currently visible terminal."
}

func (SendKeys) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/suspend.go b/commands/suspend.go
index 52198774e6d5..dc2d6b5af6b2 100644
--- a/commands/suspend.go
+++ b/commands/suspend.go
@@ -8,6 +8,10 @@ func init() {
	Register(Suspend{})
}

func (Suspend) Description() string {
	return "Suspend the aerc process."
}

func (Suspend) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/term.go b/commands/term.go
index e8b1919dd1cb..e10f3e653e40 100644
--- a/commands/term.go
+++ b/commands/term.go
@@ -18,6 +18,10 @@ func init() {
	Register(Term{})
}

func (Term) Description() string {
	return "Open a new terminal tab."
}

func (Term) Context() CommandContext {
	return GLOBAL
}
diff --git a/commands/z.go b/commands/z.go
index ef26ae9c5708..493e36ff55fe 100644
--- a/commands/z.go
+++ b/commands/z.go
@@ -34,6 +34,10 @@ func init() {
	}
}

func (Zoxide) Description() string {
	return "Change aerc's current working directory using zoxide."
}

func (Zoxide) Context() CommandContext {
	return GLOBAL
}
diff --git a/main.go b/main.go
index 9f105ba597bf..c4878a52399f 100644
--- a/main.go
+++ b/main.go
@@ -68,12 +68,14 @@ func getCompletions(cmdline string) ([]opt.Completion, string) {
	if args.Count() < 2 && args.TrailingSpace() == "" {
		// complete command names
		var completions []opt.Completion
		for _, name := range commands.ActiveCommandNames() {
			if strings.HasPrefix(name, cmdline) {
				completions = append(completions, opt.Completion{
					Value:       name + " ",
					Description: "",
				})
		for _, cmd := range commands.ActiveCommands() {
			for _, alias := range cmd.Aliases() {
				if strings.HasPrefix(alias, cmdline) {
					completions = append(completions, opt.Completion{
						Value:       alias + " ",
						Description: cmd.Description(),
					})
				}
			}
		}
		sort.Slice(completions, func(i, j int) bool {
-- 
2.46.2

[PATCH aerc 3/3] completion: add command option descriptions

Details
Message ID
<20241001130716.345110-6-robin@jarry.cc>
In-Reply-To
<20241001130716.345110-4-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Patch: +122 -122
Add `desc:""` struct field tags in all command arguments where it makes
sense.

The description values will be returned along with completion choices.

Implements: https://todo.sr.ht/~rjarry/aerc/271
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 commands/account/align.go     |  2 +-
 commands/account/cf.go        |  2 +-
 commands/account/clear.go     |  2 +-
 commands/account/compose.go   |  8 ++++----
 commands/account/query.go     |  6 +++---
 commands/account/recover.go   |  6 +++---
 commands/account/rmdir.go     |  2 +-
 commands/account/search.go    | 24 ++++++++++++------------
 commands/account/sort.go      |  4 ++--
 commands/account/view.go      |  4 ++--
 commands/compose/attach.go    |  4 ++--
 commands/compose/edit.go      |  4 ++--
 commands/compose/header.go    |  4 ++--
 commands/compose/multipart.go |  4 ++--
 commands/compose/postpone.go  |  2 +-
 commands/compose/send.go      |  8 ++++----
 commands/compose/switch.go    |  6 +++---
 commands/ct.go                |  2 +-
 commands/help.go              |  2 +-
 commands/menu.go              | 10 +++++-----
 commands/msg/archive.go       |  4 ++--
 commands/msg/bounce.go        |  2 +-
 commands/msg/copy.go          |  8 ++++----
 commands/msg/delete.go        |  2 +-
 commands/msg/envelope.go      |  4 ++--
 commands/msg/fold.go          |  4 ++--
 commands/msg/forward.go       | 10 +++++-----
 commands/msg/invite.go        |  4 ++--
 commands/msg/mark.go          | 10 +++++-----
 commands/msg/modify-labels.go |  2 +-
 commands/msg/move.go          |  6 +++---
 commands/msg/pipe.go          | 10 +++++-----
 commands/msg/read.go          |  8 ++++----
 commands/msg/recall.go        |  6 +++---
 commands/msg/reply.go         | 14 +++++++-------
 commands/msg/unsubscribe.go   |  4 ++--
 commands/msgview/open.go      |  2 +-
 commands/msgview/save.go      |  8 ++++----
 commands/new-account.go       |  2 +-
 commands/patch/apply.go       |  4 ++--
 commands/patch/drop.go        |  2 +-
 commands/patch/find.go        |  4 ++--
 commands/patch/init.go        |  2 +-
 commands/patch/list.go        |  2 +-
 commands/patch/patch.go       |  2 +-
 commands/patch/switch.go      |  2 +-
 commands/patch/unlink.go      |  2 +-
 commands/quit.go              |  2 +-
 commands/reload.go            |  6 +++---
 49 files changed, 122 insertions(+), 122 deletions(-)

diff --git a/commands/account/align.go b/commands/account/align.go
index 83bb5b64745a..2c65b556166d 100644
--- a/commands/account/align.go
+++ b/commands/account/align.go
@@ -9,7 +9,7 @@ import (
)

type Align struct {
	Pos app.AlignPosition `opt:"pos" metavar:"top|center|bottom" action:"ParsePos" complete:"CompletePos"`
	Pos app.AlignPosition `opt:"pos" metavar:"top|center|bottom" action:"ParsePos" complete:"CompletePos" desc:"Position."`
}

func init() {
diff --git a/commands/account/cf.go b/commands/account/cf.go
index 3cce37a38261..81d6c9848cd5 100644
--- a/commands/account/cf.go
+++ b/commands/account/cf.go
@@ -14,7 +14,7 @@ import (
)

type ChangeFolder struct {
	Account bool   `opt:"-a"`
	Account bool   `opt:"-a" desc:"Change to specified account."`
	Folder  string `opt:"..." complete:"CompleteFolderAndNotmuch"`
}

diff --git a/commands/account/clear.go b/commands/account/clear.go
index e16b563a4500..d45c52011fa5 100644
--- a/commands/account/clear.go
+++ b/commands/account/clear.go
@@ -9,7 +9,7 @@ import (
)

type Clear struct {
	Selected bool `opt:"-s"`
	Selected bool `opt:"-s" desc:"Select first message after clearing."`
}

func init() {
diff --git a/commands/account/compose.go b/commands/account/compose.go
index 9eeb339b01c7..5e5d3e0fdce2 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -16,10 +16,10 @@ import (
)

type Compose struct {
	Headers  string `opt:"-H" action:"ParseHeader"`
	Template string `opt:"-T" complete:"CompleteTemplate"`
	Edit     bool   `opt:"-e"`
	NoEdit   bool   `opt:"-E"`
	Headers  string `opt:"-H" action:"ParseHeader" desc:"Add the specified header to the message."`
	Template string `opt:"-T" complete:"CompleteTemplate" desc:"Template name."`
	Edit     bool   `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit   bool   `opt:"-E" desc:"Force [compose].edit-headers = false."`
	Body     string `opt:"..." required:"false"`
}

diff --git a/commands/account/query.go b/commands/account/query.go
index da8a8c0667e0..f7a5c3433299 100644
--- a/commands/account/query.go
+++ b/commands/account/query.go
@@ -11,9 +11,9 @@ import (
)

type Query struct {
	Account string `opt:"-a" complete:"CompleteAccount"`
	Name    string `opt:"-n"`
	Force   bool   `opt:"-f"`
	Account string `opt:"-a" complete:"CompleteAccount" desc:""`
	Name    string `opt:"-n" desc:"Force name of virtual folder."`
	Force   bool   `opt:"-f" desc:"Replace existing query if any."`
	Query   string `opt:"..." complete:"CompleteNotmuch"`
}

diff --git a/commands/account/recover.go b/commands/account/recover.go
index 625ac122e910..57d9cf1c565d 100644
--- a/commands/account/recover.go
+++ b/commands/account/recover.go
@@ -13,9 +13,9 @@ import (
)

type Recover struct {
	Force  bool   `opt:"-f"`
	Edit   bool   `opt:"-e"`
	NoEdit bool   `opt:"-E"`
	Force  bool   `opt:"-f" desc:"Delete recovered file after opening the composer."`
	Edit   bool   `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit bool   `opt:"-E" desc:"Force [compose].edit-headers = false."`
	File   string `opt:"file" complete:"CompleteFile"`
}

diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go
index 852d717b0a08..51fd08dbdc04 100644
--- a/commands/account/rmdir.go
+++ b/commands/account/rmdir.go
@@ -13,7 +13,7 @@ import (
)

type RemoveDir struct {
	Force  bool   `opt:"-f"`
	Force  bool   `opt:"-f" desc:"Remove the directory even if it contains messages."`
	Folder string `opt:"folder" complete:"CompleteFolder" required:"false"`
}

diff --git a/commands/account/search.go b/commands/account/search.go
index 5dde33222f73..4891aa0a06c4 100644
--- a/commands/account/search.go
+++ b/commands/account/search.go
@@ -19,18 +19,18 @@ import (
)

type SearchFilter struct {
	Read         bool                 `opt:"-r" action:"ParseRead"`
	Unread       bool                 `opt:"-u" action:"ParseUnread"`
	Body         bool                 `opt:"-b"`
	All          bool                 `opt:"-a"`
	UseExtension bool                 `opt:"-e"`
	Headers      textproto.MIMEHeader `opt:"-H" action:"ParseHeader" metavar:"<header>:<value>"`
	WithFlags    models.Flags         `opt:"-x" action:"ParseFlag" complete:"CompleteFlag"`
	WithoutFlags models.Flags         `opt:"-X" action:"ParseNotFlag" complete:"CompleteFlag"`
	To           []string             `opt:"-t" action:"ParseTo" complete:"CompleteAddress"`
	From         []string             `opt:"-f" action:"ParseFrom" complete:"CompleteAddress"`
	Cc           []string             `opt:"-c" action:"ParseCc" complete:"CompleteAddress"`
	StartDate    time.Time            `opt:"-d" action:"ParseDate" complete:"CompleteDate"`
	Read         bool                 `opt:"-r" action:"ParseRead" desc:"Search for read messages."`
	Unread       bool                 `opt:"-u" action:"ParseUnread" desc:"Search for unread messages."`
	Body         bool                 `opt:"-b" desc:"Search in the body of the messages."`
	All          bool                 `opt:"-a" desc:"Search in the entire text of the messages."`
	UseExtension bool                 `opt:"-e" desc:"Use custom search backend extension."`
	Headers      textproto.MIMEHeader `opt:"-H" action:"ParseHeader" metavar:"<header>:<value>" desc:"Search for messages with the specified header."`
	WithFlags    models.Flags         `opt:"-x" action:"ParseFlag" complete:"CompleteFlag" desc:"Search messages with specified flag."`
	WithoutFlags models.Flags         `opt:"-X" action:"ParseNotFlag" complete:"CompleteFlag" desc:"Search messages without specified flag."`
	To           []string             `opt:"-t" action:"ParseTo" complete:"CompleteAddress" desc:"Search for messages To:<address>."`
	From         []string             `opt:"-f" action:"ParseFrom" complete:"CompleteAddress" desc:"Search for messages From:<address>."`
	Cc           []string             `opt:"-c" action:"ParseCc" complete:"CompleteAddress" desc:"Search for messages Cc:<address>."`
	StartDate    time.Time            `opt:"-d" action:"ParseDate" complete:"CompleteDate" desc:"Search for messages within a particular date range."`
	EndDate      time.Time
	Terms        string `opt:"..." required:"false" complete:"CompleteTerms"`
}
diff --git a/commands/account/sort.go b/commands/account/sort.go
index 16060aa00297..b381548cfac4 100644
--- a/commands/account/sort.go
+++ b/commands/account/sort.go
@@ -13,8 +13,8 @@ import (
type Sort struct {
	Unused struct{} `opt:"-"`
	// these fields are only used for completion
	Reverse  bool     `opt:"-r"`
	Criteria []string `opt:"criteria" complete:"CompleteCriteria"`
	Reverse  bool     `opt:"-r" desc:"Sort in the reverse order."`
	Criteria []string `opt:"criteria" complete:"CompleteCriteria" desc:"Sort criterion."`
}

func init() {
diff --git a/commands/account/view.go b/commands/account/view.go
index 0e3ca09c5a92..72f62b5173fa 100644
--- a/commands/account/view.go
+++ b/commands/account/view.go
@@ -13,8 +13,8 @@ import (
)

type ViewMessage struct {
	Peek       bool `opt:"-p"`
	Background bool `opt:"-b"`
	Peek       bool `opt:"-p" desc:"Peek message without marking it as read."`
	Background bool `opt:"-b" desc:"Open message in a background tab."`
}

func init() {
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index f811f5a857f2..24597c1ad94f 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -21,8 +21,8 @@ import (
)

type Attach struct {
	Menu bool   `opt:"-m"`
	Name string `opt:"-r"`
	Menu bool   `opt:"-m" desc:"Select files from file-picker-cmd."`
	Name string `opt:"-r" desc:"<name> <cmd...>: Generate attachment from command output."`
	Path string `opt:"path" required:"false" complete:"CompletePath"`
	Args string `opt:"..." required:"false"`
}
diff --git a/commands/compose/edit.go b/commands/compose/edit.go
index f02848b7659a..117afe320d9d 100644
--- a/commands/compose/edit.go
+++ b/commands/compose/edit.go
@@ -9,8 +9,8 @@ import (
)

type Edit struct {
	Edit   bool `opt:"-e"`
	NoEdit bool `opt:"-E"`
	Edit   bool `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit bool `opt:"-E" desc:"Force [compose].edit-headers = false."`
}

func init() {
diff --git a/commands/compose/header.go b/commands/compose/header.go
index 64c1c37d7b9f..60ee050f3417 100644
--- a/commands/compose/header.go
+++ b/commands/compose/header.go
@@ -9,8 +9,8 @@ import (
)

type Header struct {
	Force  bool   `opt:"-f"`
	Remove bool   `opt:"-d"`
	Force  bool   `opt:"-f" desc:"Overwrite any existing header."`
	Remove bool   `opt:"-d" desc:"Remove the header instead of adding it."`
	Name   string `opt:"name" complete:"CompleteHeaders"`
	Value  string `opt:"..." required:"false"`
}
diff --git a/commands/compose/multipart.go b/commands/compose/multipart.go
index 87f4d0300c1d..8d952a7f869c 100644
--- a/commands/compose/multipart.go
+++ b/commands/compose/multipart.go
@@ -9,7 +9,7 @@ import (
)

type Multipart struct {
	Remove bool   `opt:"-d"`
	Remove bool   `opt:"-d" desc:"Remove the specified mime/type."`
	Mime   string `opt:"mime" metavar:"<mime/type>" complete:"CompleteMime"`
}

@@ -18,7 +18,7 @@ func init() {
}

func (Multipart) Description() string {
	return "Convert the message to multipart with the given mime-type part."
	return "Convert the message to multipart with the given mime/type part."
}

func (Multipart) Context() commands.CommandContext {
diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
index 3dd555548a26..7aba6b188e55 100644
--- a/commands/compose/postpone.go
+++ b/commands/compose/postpone.go
@@ -14,7 +14,7 @@ import (
)

type Postpone struct {
	Folder string `opt:"-t" complete:"CompleteFolder"`
	Folder string `opt:"-t" complete:"CompleteFolder" desc:"Override the target folder."`
}

func init() {
diff --git a/commands/compose/send.go b/commands/compose/send.go
index ffb6ab460031..c895cc32a7df 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -24,11 +24,11 @@ import (
)

type Send struct {
	Archive string `opt:"-a" action:"ParseArchive" metavar:"flat|year|month" complete:"CompleteArchive"`
	CopyTo  string `opt:"-t" complete:"CompleteFolders"`
	Archive string `opt:"-a" action:"ParseArchive" metavar:"flat|year|month" complete:"CompleteArchive" desc:"Archive the message being replied to."`
	CopyTo  string `opt:"-t" complete:"CompleteFolders" desc:"Override the Copy-To folder."`

	CopyToReplied   bool `opt:"-r"`
	NoCopyToReplied bool `opt:"-R"`
	CopyToReplied   bool `opt:"-r" desc:"Save sent message to current folder."`
	NoCopyToReplied bool `opt:"-R" desc:"Do not save sent message to current folder."`
}

func init() {
diff --git a/commands/compose/switch.go b/commands/compose/switch.go
index 4bcfc136c7c1..55dc86e25b1d 100644
--- a/commands/compose/switch.go
+++ b/commands/compose/switch.go
@@ -12,9 +12,9 @@ type AccountSwitcher interface {
}

type SwitchAccount struct {
	Next    bool   `opt:"-n"`
	Prev    bool   `opt:"-p"`
	Account string `opt:"account" required:"false" complete:"CompleteAccount"`
	Prev    bool   `opt:"-p" desc:"Switch to previous account."`
	Next    bool   `opt:"-n" desc:"Switch to next account."`
	Account string `opt:"account" required:"false" complete:"CompleteAccount" desc:"Account name."`
}

func init() {
diff --git a/commands/ct.go b/commands/ct.go
index be3d4e982d7d..1ac4641bf5bc 100644
--- a/commands/ct.go
+++ b/commands/ct.go
@@ -9,7 +9,7 @@ import (
)

type ChangeTab struct {
	Tab string `opt:"tab" complete:"CompleteTab"`
	Tab string `opt:"tab" complete:"CompleteTab" desc:"Tab name."`
}

func init() {
diff --git a/commands/help.go b/commands/help.go
index dab0af1a6d69..30688c5a06ca 100644
--- a/commands/help.go
+++ b/commands/help.go
@@ -7,7 +7,7 @@ import (
)

type Help struct {
	Topic string `opt:"topic" action:"ParseTopic" default:"aerc" complete:"CompleteTopic"`
	Topic string `opt:"topic" action:"ParseTopic" default:"aerc" complete:"CompleteTopic" desc:"Help topic."`
}

var pages = []string{
diff --git a/commands/menu.go b/commands/menu.go
index 4db1a1d72982..3db866e16248 100644
--- a/commands/menu.go
+++ b/commands/menu.go
@@ -16,11 +16,11 @@ import (
)

type Menu struct {
	ErrExit     bool   `opt:"-e"`
	Background  bool   `opt:"-b"`
	Accounts    bool   `opt:"-a"`
	Directories bool   `opt:"-d"`
	Command     string `opt:"-c"`
	ErrExit     bool   `opt:"-e" desc:"Stop executing commands on the first error."`
	Background  bool   `opt:"-b" desc:"Do NOT spawn the popover dialog."`
	Accounts    bool   `opt:"-a" desc:"Feed command with account names."`
	Directories bool   `opt:"-d" desc:"Feed command with folder names."`
	Command     string `opt:"-c" desc:"Override [general].default-menu-cmd."`
	Xargs       string `opt:"..." complete:"CompleteXargs"`
}

diff --git a/commands/msg/archive.go b/commands/msg/archive.go
index 0b4b82587c7d..819fb21b6a4e 100644
--- a/commands/msg/archive.go
+++ b/commands/msg/archive.go
@@ -22,8 +22,8 @@ const (
var ARCHIVE_TYPES = []string{ARCHIVE_FLAT, ARCHIVE_YEAR, ARCHIVE_MONTH}

type Archive struct {
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
	Type              string                   `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType"`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS" desc:"Multi-file archiving strategy."`
	Type              string                   `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType" desc:"Archive scheme."`
}

func (a *Archive) ParseMFS(arg string) error {
diff --git a/commands/msg/bounce.go b/commands/msg/bounce.go
index 0c0452b4e12d..5920cf9451fd 100644
--- a/commands/msg/bounce.go
+++ b/commands/msg/bounce.go
@@ -19,7 +19,7 @@ import (
)

type Bounce struct {
	Account string   `opt:"-A" complete:"CompleteAccount"`
	Account string   `opt:"-A" complete:"CompleteAccount" desc:"Account from which to re-send the message."`
	To      []string `opt:"..." required:"true" complete:"CompleteTo"`
}

diff --git a/commands/msg/copy.go b/commands/msg/copy.go
index 126dbd8ee576..f20fd040326b 100644
--- a/commands/msg/copy.go
+++ b/commands/msg/copy.go
@@ -17,10 +17,10 @@ import (
)

type Copy struct {
	CreateFolders     bool                     `opt:"-p"`
	Decrypt           bool                     `opt:"-d"`
	Account           string                   `opt:"-a" complete:"CompleteAccount"`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
	CreateFolders     bool                     `opt:"-p" desc:"Create folder if it does not exist."`
	Decrypt           bool                     `opt:"-d" desc:"Decrypt the message before copying."`
	Account           string                   `opt:"-a" complete:"CompleteAccount" desc:"Copy to the specified account."`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS" desc:"Multi-file strategy."`
	Folder            string                   `opt:"folder" complete:"CompleteFolder"`
}

diff --git a/commands/msg/delete.go b/commands/msg/delete.go
index 4999045e9aaa..dcfc5ea1752e 100644
--- a/commands/msg/delete.go
+++ b/commands/msg/delete.go
@@ -14,7 +14,7 @@ import (
)

type Delete struct {
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS" desc:"Multi-file strategy."`
}

func init() {
diff --git a/commands/msg/envelope.go b/commands/msg/envelope.go
index 09e7fa1950f8..d064e43c8563 100644
--- a/commands/msg/envelope.go
+++ b/commands/msg/envelope.go
@@ -14,8 +14,8 @@ import (
)

type Envelope struct {
	Header bool   `opt:"-h"`
	Format string `opt:"-s" default:"%-20.20s: %s"`
	Header bool   `opt:"-h" desc:"Show all header fields."`
	Format string `opt:"-s" default:"%-20.20s: %s" desc:"Format specifier."`
}

func init() {
diff --git a/commands/msg/fold.go b/commands/msg/fold.go
index d3ec243810a7..5f2b4b095df0 100644
--- a/commands/msg/fold.go
+++ b/commands/msg/fold.go
@@ -8,8 +8,8 @@ import (
)

type Fold struct {
	All    bool `opt:"-a"`
	Toggle bool `opt:"-t"`
	All    bool `opt:"-a" desc:"Fold/unfold all threads."`
	Toggle bool `opt:"-t" desc:"Toggle between folded/unfolded."`
}

func init() {
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index 3bfad8cfcd48..8325f2f40f89 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -25,11 +25,11 @@ import (
)

type forward struct {
	AttachAll  bool     `opt:"-A"`
	AttachFull bool     `opt:"-F"`
	Edit       bool     `opt:"-e"`
	NoEdit     bool     `opt:"-E"`
	Template   string   `opt:"-T" complete:"CompleteTemplate"`
	AttachAll  bool     `opt:"-A" desc:"Forward the message and all attachments."`
	AttachFull bool     `opt:"-F" desc:"Forward the full message as an RFC 2822 attachment."`
	Edit       bool     `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit     bool     `opt:"-E" desc:"Force [compose].edit-headers = false."`
	Template   string   `opt:"-T" complete:"CompleteTemplate" desc:"Template name."`
	To         []string `opt:"..." required:"false" complete:"CompleteTo"`
}

diff --git a/commands/msg/invite.go b/commands/msg/invite.go
index 53e5ad8f9cf8..15afbc336b89 100644
--- a/commands/msg/invite.go
+++ b/commands/msg/invite.go
@@ -17,8 +17,8 @@ import (
)

type invite struct {
	Edit   bool `opt:"-e"`
	NoEdit bool `opt:"-E"`
	Edit   bool `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit bool `opt:"-E" desc:"Force [compose].edit-headers = false."`
}

func init() {
diff --git a/commands/msg/mark.go b/commands/msg/mark.go
index 6c36dcd711bf..ff793465d468 100644
--- a/commands/msg/mark.go
+++ b/commands/msg/mark.go
@@ -8,11 +8,11 @@ import (
)

type Mark struct {
	All         bool `opt:"-a" aliases:"mark,unmark"`
	Toggle      bool `opt:"-t" aliases:"mark,unmark"`
	Visual      bool `opt:"-v" aliases:"mark,unmark"`
	VisualClear bool `opt:"-V" aliases:"mark,unmark"`
	Thread      bool `opt:"-T" aliases:"mark,unmark"`
	All         bool `opt:"-a" aliases:"mark,unmark" desc:"Mark all messages in current folder."`
	Toggle      bool `opt:"-t" aliases:"mark,unmark" desc:"Toggle the marked state."`
	Visual      bool `opt:"-v" aliases:"mark,unmark" desc:"Enter / leave visual mark mode."`
	VisualClear bool `opt:"-V" aliases:"mark,unmark" desc:"Same as -v but does not clear existing selection."`
	Thread      bool `opt:"-T" aliases:"mark,unmark" desc:"Mark the selected thread."`
}

func init() {
diff --git a/commands/msg/modify-labels.go b/commands/msg/modify-labels.go
index e317440c2517..f89d84a699ba 100644
--- a/commands/msg/modify-labels.go
+++ b/commands/msg/modify-labels.go
@@ -9,7 +9,7 @@ import (
)

type ModifyLabels struct {
	Labels []string `opt:"..." metavar:"[+-]<label>" complete:"CompleteLabels"`
	Labels []string `opt:"..." metavar:"[+-]<label>" complete:"CompleteLabels" desc:"Message label."`
}

func init() {
diff --git a/commands/msg/move.go b/commands/msg/move.go
index 63473b1378d0..9959fd392b15 100644
--- a/commands/msg/move.go
+++ b/commands/msg/move.go
@@ -17,9 +17,9 @@ import (
)

type Move struct {
	CreateFolders     bool                     `opt:"-p"`
	Account           string                   `opt:"-a" complete:"CompleteAccount"`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
	CreateFolders     bool                     `opt:"-p" desc:"Create missing folders if required."`
	Account           string                   `opt:"-a" complete:"CompleteAccount" desc:"Move to specified account."`
	MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS" desc:"Multi-file strategy."`
	Folder            string                   `opt:"folder" complete:"CompleteFolder"`
}

diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go
index f66e7dcaf03e..7a630b553594 100644
--- a/commands/msg/pipe.go
+++ b/commands/msg/pipe.go
@@ -21,11 +21,11 @@ import (
)

type Pipe struct {
	Background bool   `opt:"-b"`
	Silent     bool   `opt:"-s"`
	Full       bool   `opt:"-m"`
	Decrypt    bool   `opt:"-d"`
	Part       bool   `opt:"-p"`
	Background bool   `opt:"-b" desc:"Run the command in the background."`
	Silent     bool   `opt:"-s" desc:"Silently close the terminal tab after the command exits."`
	Full       bool   `opt:"-m" desc:"Pipe the full message."`
	Decrypt    bool   `opt:"-d" desc:"Decrypt the full message before piping."`
	Part       bool   `opt:"-p" desc:"Only pipe the selected message part."`
	Command    string `opt:"..."`
}

diff --git a/commands/msg/read.go b/commands/msg/read.go
index e548c12b0653..3c048e65234c 100644
--- a/commands/msg/read.go
+++ b/commands/msg/read.go
@@ -12,10 +12,10 @@ import (
)

type FlagMsg struct {
	Toggle    bool         `opt:"-t"`
	Answered  bool         `opt:"-a" aliases:"flag,unflag"`
	Forwarded bool         `opt:"-f" aliases:"flag,unflag"`
	Flag      models.Flags `opt:"-x" aliases:"flag,unflag" action:"ParseFlag" complete:"CompleteFlag"`
	Toggle    bool         `opt:"-t" desc:"Toggle between set and unset."`
	Answered  bool         `opt:"-a" aliases:"flag,unflag" desc:"Set/unset the answered flag."`
	Forwarded bool         `opt:"-f" aliases:"flag,unflag" desc:"Set/unset the forwarded flag."`
	Flag      models.Flags `opt:"-x" aliases:"flag,unflag" action:"ParseFlag" complete:"CompleteFlag" desc:"Flag name."`
	FlagName  string
}

diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index 5611f2891e3a..53a0de347342 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -20,9 +20,9 @@ import (
)

type Recall struct {
	Force  bool `opt:"-f"`
	Edit   bool `opt:"-e"`
	NoEdit bool `opt:"-E"`
	Force  bool `opt:"-f" desc:"Force recall if not in postpone directory."`
	Edit   bool `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit bool `opt:"-E" desc:"Force [compose].edit-headers = false."`
}

func init() {
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 5f1c8e32026b..a443afa7b887 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -23,13 +23,13 @@ import (
)

type reply struct {
	All      bool   `opt:"-a"`
	Close    bool   `opt:"-c"`
	Quote    bool   `opt:"-q"`
	Template string `opt:"-T" complete:"CompleteTemplate"`
	Edit     bool   `opt:"-e"`
	NoEdit   bool   `opt:"-E"`
	Account  string `opt:"-A" complete:"CompleteAccount"`
	All      bool   `opt:"-a" desc:"Reply to all recipients."`
	Close    bool   `opt:"-c" desc:"Close the view tab when replying."`
	Quote    bool   `opt:"-q" desc:"Alias of -T quoted-reply."`
	Template string `opt:"-T" complete:"CompleteTemplate" desc:"Template name."`
	Edit     bool   `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit   bool   `opt:"-E" desc:"Force [compose].edit-headers = false."`
	Account  string `opt:"-A" complete:"CompleteAccount" desc:"Reply with the specified account."`
}

func init() {
diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go
index 46b86f306f41..57299aa8c972 100644
--- a/commands/msg/unsubscribe.go
+++ b/commands/msg/unsubscribe.go
@@ -19,8 +19,8 @@ import (
// Unsubscribe helps people unsubscribe from mailing lists by way of the
// List-Unsubscribe header.
type Unsubscribe struct {
	Edit   bool `opt:"-e"`
	NoEdit bool `opt:"-E"`
	Edit   bool `opt:"-e" desc:"Force [compose].edit-headers = true."`
	NoEdit bool `opt:"-E" desc:"Force [compose].edit-headers = false."`
}

func init() {
diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index 7b23423d8582..4293b7e4892c 100644
--- a/commands/msgview/open.go
+++ b/commands/msgview/open.go
@@ -14,7 +14,7 @@ import (
)

type Open struct {
	Delete bool   `opt:"-d"`
	Delete bool   `opt:"-d" desc:"Delete temp file after the opener exits."`
	Cmd    string `opt:"..." required:"false"`
}

diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 0310ed0ec93e..2dd09cf10df7 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.go
@@ -18,10 +18,10 @@ import (
)

type Save struct {
	Force          bool   `opt:"-f"`
	CreateDirs     bool   `opt:"-p"`
	Attachments    bool   `opt:"-a"`
	AllAttachments bool   `opt:"-A"`
	Force          bool   `opt:"-f" desc:"Overwrite destination path."`
	CreateDirs     bool   `opt:"-p" desc:"Create missing directories."`
	Attachments    bool   `opt:"-a" desc:"Save all attachments parts."`
	AllAttachments bool   `opt:"-A" desc:"Save all named parts."`
	Path           string `opt:"path" required:"false" complete:"CompletePath"`
}

diff --git a/commands/new-account.go b/commands/new-account.go
index 9792d7e1409e..e0fe3b269474 100644
--- a/commands/new-account.go
+++ b/commands/new-account.go
@@ -5,7 +5,7 @@ import (
)

type NewAccount struct {
	Temp bool `opt:"-t"`
	Temp bool `opt:"-t" desc:"Create a temporary account."`
}

func init() {
diff --git a/commands/patch/apply.go b/commands/patch/apply.go
index 12f5b3f8ac95..1128765b253c 100644
--- a/commands/patch/apply.go
+++ b/commands/patch/apply.go
@@ -15,8 +15,8 @@ import (
)

type Apply struct {
	Cmd      string `opt:"-c"`
	Worktree string `opt:"-w"`
	Cmd      string `opt:"-c" desc:"Apply patches with provided command."`
	Worktree string `opt:"-w" desc:"Create linked worktree on this <commit-ish>."`
	Tag      string `opt:"tag" required:"true" complete:"CompleteTag"`
}

diff --git a/commands/patch/drop.go b/commands/patch/drop.go
index dbb1fb079feb..89d574f16c0d 100644
--- a/commands/patch/drop.go
+++ b/commands/patch/drop.go
@@ -11,7 +11,7 @@ import (
)

type Drop struct {
	Tag string `opt:"tag" complete:"CompleteTag"`
	Tag string `opt:"tag" complete:"CompleteTag" desc:"Repository patch tag."`
}

func init() {
diff --git a/commands/patch/find.go b/commands/patch/find.go
index a74f926edb6f..ab5252c41bb1 100644
--- a/commands/patch/find.go
+++ b/commands/patch/find.go
@@ -15,8 +15,8 @@ import (
)

type Find struct {
	Filter bool     `opt:"-f"`
	Commit []string `opt:"..." required:"true" complete:"Complete"`
	Filter bool     `opt:"-f" desc:"Filter message list instead of search."`
	Commit []string `opt:"..." required:"true" complete:"Complete" desc:"Search for <commit-ish>."`
}

func init() {
diff --git a/commands/patch/init.go b/commands/patch/init.go
index b7316ee44a72..7640f268f5e0 100644
--- a/commands/patch/init.go
+++ b/commands/patch/init.go
@@ -10,7 +10,7 @@ import (
)

type Init struct {
	Force bool   `opt:"-f"`
	Force bool   `opt:"-f" desc:"Overwrite any existing project."`
	Name  string `opt:"name" required:"false"`
}

diff --git a/commands/patch/list.go b/commands/patch/list.go
index a9c68e4d0a7b..f4a1e5e9e43e 100644
--- a/commands/patch/list.go
+++ b/commands/patch/list.go
@@ -18,7 +18,7 @@ import (
)

type List struct {
	All bool `opt:"-a"`
	All bool `opt:"-a" desc:"List all projects."`
}

func init() {
diff --git a/commands/patch/patch.go b/commands/patch/patch.go
index 0e19d888fa0d..8e2803985df6 100644
--- a/commands/patch/patch.go
+++ b/commands/patch/patch.go
@@ -23,7 +23,7 @@ func register(cmd commands.Command) {
}

type Patch struct {
	SubCmd commands.Command `opt:"command" action:"ParseSub" complete:"CompleteSubNames"`
	SubCmd commands.Command `opt:"command" action:"ParseSub" complete:"CompleteSubNames" desc:"Sub command."`
	Args   string           `opt:"..." required:"false" complete:"CompleteSubArgs"`
}

diff --git a/commands/patch/switch.go b/commands/patch/switch.go
index 9eb26ef29c95..3eea1268deba 100644
--- a/commands/patch/switch.go
+++ b/commands/patch/switch.go
@@ -11,7 +11,7 @@ import (
)

type Switch struct {
	Project string `opt:"project" complete:"Complete"`
	Project string `opt:"project" complete:"Complete" desc:"Project name."`
}

func init() {
diff --git a/commands/patch/unlink.go b/commands/patch/unlink.go
index da21c15ee1f6..5c47d89f0fbe 100644
--- a/commands/patch/unlink.go
+++ b/commands/patch/unlink.go
@@ -11,7 +11,7 @@ import (
)

type Unlink struct {
	Tag string `opt:"tag" required:"false" complete:"Complete"`
	Tag string `opt:"tag" required:"false" complete:"Complete" desc:"Project tag name."`
}

func init() {
diff --git a/commands/quit.go b/commands/quit.go
index 8e828823ca54..9c4fec5c3ad9 100644
--- a/commands/quit.go
+++ b/commands/quit.go
@@ -7,7 +7,7 @@ import (
)

type Quit struct {
	Force bool `opt:"-f"`
	Force bool `opt:"-f" desc:"Force quit even if a task is pending."`
}

func init() {
diff --git a/commands/reload.go b/commands/reload.go
index e09290c1e1b0..0d4c489a5934 100644
--- a/commands/reload.go
+++ b/commands/reload.go
@@ -10,9 +10,9 @@ import (
)

type Reload struct {
	Binds bool   `opt:"-B"`
	Conf  bool   `opt:"-C"`
	Style string `opt:"-s" complete:"CompleteStyle"`
	Binds bool   `opt:"-B" desc:"Reload binds.conf."`
	Conf  bool   `opt:"-C" desc:"Reload aerc.conf."`
	Style string `opt:"-s" complete:"CompleteStyle" desc:"Reload the specified styleset."`
}

func init() {
-- 
2.46.2

[aerc/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D4KHT900FM5J.2FCPIXH3QFKVG@fra01>
In-Reply-To
<20241001130716.345110-6-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
aerc/patches: SUCCESS in 2m15s

[completion: display descriptions next to choices][0] from [Robin Jarry][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/55266
[1]: robin@jarry.cc

✓ #1341401 SUCCESS aerc/patches/openbsd.yml     https://builds.sr.ht/~rjarry/job/1341401
✓ #1341400 SUCCESS aerc/patches/alpine-edge.yml https://builds.sr.ht/~rjarry/job/1341400
Details
Message ID
<D4KLMYZQQVR3.BCKFRB0CT6LR@jasoncarloscox.com>
In-Reply-To
<20241001130716.345110-4-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Very nice -- it seems to be working well for me.

Tested-by: Jason Cox <me@jasoncarloscox.com>

Re: [PATCH aerc 3/3] completion: add command option descriptions

Details
Message ID
<D4N1T85382Z4.KJESSH5DX7RB@bojangabric.com>
In-Reply-To
<20241001130716.345110-6-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Looks really good! Makes it much easier to learn aerc commands.

Tested-by: Bojan Gabric <bojan@bojangabric.com>
Reply to thread Export thread (mbox)