mirror of
https://git.sr.ht/~rjarry/aerc
synced 2025-12-23 12:14:14 +01:00
commands: use completion from go-opt
Implement command completion with complete struct field tags from the get-opt library introduced earlier. Changelog-changed: Improved command completion. Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Koni Marti <koni.marti@gmail.com> Tested-by: Moritz Poldrack <moritz@poldrack.dev> Tested-by: Inwit <inwit@sindominio.net>
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
var history map[string]string
|
||||
|
||||
type ChangeFolder struct {
|
||||
Folder string `opt:"..." metavar:"<folder>"`
|
||||
Folder string `opt:"folder" complete:"CompleteFolder"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -23,8 +23,8 @@ func (ChangeFolder) Aliases() []string {
|
||||
return []string{"cf"}
|
||||
}
|
||||
|
||||
func (ChangeFolder) Complete(args []string) []string {
|
||||
return commands.GetFolders(args)
|
||||
func (*ChangeFolder) CompleteFolder(arg string) []string {
|
||||
return commands.GetFolders(arg)
|
||||
}
|
||||
|
||||
func (c ChangeFolder) Execute(args []string) error {
|
||||
|
||||
@@ -16,10 +16,6 @@ func (CheckMail) Aliases() []string {
|
||||
return []string{"check-mail"}
|
||||
}
|
||||
|
||||
func (CheckMail) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (CheckMail) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -19,10 +19,6 @@ func (Clear) Aliases() []string {
|
||||
return []string{"clear"}
|
||||
}
|
||||
|
||||
func (Clear) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Clear) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
"github.com/emersion/go-message/mail"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
"git.sr.ht/~rjarry/aerc/config"
|
||||
)
|
||||
|
||||
type Compose struct {
|
||||
Headers string `opt:"-H" action:"ParseHeader"`
|
||||
Template string `opt:"-T"`
|
||||
Template string `opt:"-T" complete:"CompleteTemplate"`
|
||||
Edit bool `opt:"-e"`
|
||||
NoEdit bool `opt:"-E"`
|
||||
Body string `opt:"..." required:"false"`
|
||||
@@ -37,12 +38,12 @@ func (c *Compose) ParseHeader(arg string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Compose) Aliases() []string {
|
||||
return []string{"compose"}
|
||||
func (*Compose) CompleteTemplate(arg string) []string {
|
||||
return commands.GetTemplates(arg)
|
||||
}
|
||||
|
||||
func (Compose) Complete(args []string) []string {
|
||||
return nil
|
||||
func (Compose) Aliases() []string {
|
||||
return []string{"compose"}
|
||||
}
|
||||
|
||||
func (c Compose) Execute(args []string) error {
|
||||
|
||||
@@ -18,10 +18,6 @@ func (Connection) Aliases() []string {
|
||||
return []string{"connect", "disconnect"}
|
||||
}
|
||||
|
||||
func (Connection) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Connection) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -16,10 +16,6 @@ func (ExpandCollapseFolder) Aliases() []string {
|
||||
return []string{"expand-folder", "collapse-folder"}
|
||||
}
|
||||
|
||||
func (ExpandCollapseFolder) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ExpandCollapseFolder) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type ExportMbox struct {
|
||||
Filename string `opt:"filename"`
|
||||
Filename string `opt:"filename" complete:"CompleteFilename"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -28,8 +28,8 @@ func (ExportMbox) Aliases() []string {
|
||||
return []string{"export-mbox"}
|
||||
}
|
||||
|
||||
func (ExportMbox) Complete(args []string) []string {
|
||||
return commands.CompletePath(filepath.Join(args...))
|
||||
func (*ExportMbox) CompleteFilename(arg string) []string {
|
||||
return commands.CompletePath(arg)
|
||||
}
|
||||
|
||||
func (e ExportMbox) Execute(args []string) error {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type ImportMbox struct {
|
||||
Filename string `opt:"filename"`
|
||||
Filename string `opt:"filename" complete:"CompleteFilename"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -30,8 +29,8 @@ func (ImportMbox) Aliases() []string {
|
||||
return []string{"import-mbox"}
|
||||
}
|
||||
|
||||
func (ImportMbox) Complete(args []string) []string {
|
||||
return commands.CompletePath(filepath.Join(args...))
|
||||
func (*ImportMbox) CompleteFilename(arg string) []string {
|
||||
return commands.CompletePath(arg)
|
||||
}
|
||||
|
||||
func (i ImportMbox) Execute(args []string) error {
|
||||
|
||||
@@ -2,15 +2,15 @@ package account
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
"git.sr.ht/~rjarry/aerc/worker/types"
|
||||
)
|
||||
|
||||
type MakeDir struct {
|
||||
Folder string `opt:"..." metavar:"<folder>"`
|
||||
Folder string `opt:"folder" complete:"CompleteFolder"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -21,26 +21,15 @@ func (MakeDir) Aliases() []string {
|
||||
return []string{"mkdir"}
|
||||
}
|
||||
|
||||
func (MakeDir) Complete(args []string) []string {
|
||||
if len(args) == 0 {
|
||||
func (*MakeDir) CompleteFolder(arg string) []string {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
return nil
|
||||
}
|
||||
name := strings.Join(args, " ")
|
||||
|
||||
list := app.SelectedAccount().Directories().List()
|
||||
inboxes := make([]string, len(list))
|
||||
copy(inboxes, list)
|
||||
|
||||
// remove inboxes that don't match and append the path separator to all
|
||||
// others
|
||||
for i := len(inboxes) - 1; i >= 0; i-- {
|
||||
if !strings.HasPrefix(inboxes[i], name) && name != "" {
|
||||
inboxes = append(inboxes[:i], inboxes[i+1:]...)
|
||||
continue
|
||||
}
|
||||
inboxes[i] += app.SelectedAccount().Worker().PathSeparator()
|
||||
}
|
||||
return inboxes
|
||||
return commands.FilterList(
|
||||
acct.Directories().List(), arg, "",
|
||||
app.SelectedAccount().Worker().PathSeparator(),
|
||||
app.SelectedAccountUiConfig().FuzzyComplete)
|
||||
}
|
||||
|
||||
func (m MakeDir) Execute(args []string) error {
|
||||
|
||||
@@ -18,10 +18,6 @@ func (NextPrevFolder) Aliases() []string {
|
||||
return []string{"next-folder", "prev-folder"}
|
||||
}
|
||||
|
||||
func (NextPrevFolder) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (np NextPrevFolder) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -17,10 +17,6 @@ func (NextPrevResult) Aliases() []string {
|
||||
return []string{"next-result", "prev-result"}
|
||||
}
|
||||
|
||||
func (NextPrevResult) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NextPrevResult) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -35,10 +35,6 @@ func (NextPrevMsg) Aliases() []string {
|
||||
return []string{"next", "next-message", "prev", "prev-message"}
|
||||
}
|
||||
|
||||
func (NextPrevMsg) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (np NextPrevMsg) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ type Recover struct {
|
||||
Force bool `opt:"-f"`
|
||||
Edit bool `opt:"-e"`
|
||||
NoEdit bool `opt:"-E"`
|
||||
File string `opt:"file"`
|
||||
File string `opt:"file" complete:"CompleteFile"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -31,7 +31,7 @@ func (Recover) Options() string {
|
||||
return "feE"
|
||||
}
|
||||
|
||||
func (r Recover) Complete(args []string) []string {
|
||||
func (*Recover) CompleteFile(arg string) []string {
|
||||
// file name of temp file is hard-coded in the NewComposer() function
|
||||
files, err := filepath.Glob(
|
||||
filepath.Join(os.TempDir(), "aerc-compose-*.eml"),
|
||||
@@ -39,8 +39,7 @@ func (r Recover) Complete(args []string) []string {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return commands.CompletionFromList(files,
|
||||
commands.Operands(args, r.Options()))
|
||||
return commands.CompletionFromList(files, arg)
|
||||
}
|
||||
|
||||
func (r Recover) Execute(args []string) error {
|
||||
|
||||
@@ -20,10 +20,6 @@ func (RemoveDir) Aliases() []string {
|
||||
return []string{"rmdir"}
|
||||
}
|
||||
|
||||
func (RemoveDir) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r RemoveDir) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -23,12 +23,12 @@ type SearchFilter struct {
|
||||
Body bool `opt:"-b"`
|
||||
All bool `opt:"-a"`
|
||||
Headers textproto.MIMEHeader `opt:"-H" action:"ParseHeader" metavar:"<header>:<value>"`
|
||||
WithFlags models.Flags `opt:"-x" action:"ParseFlag"`
|
||||
WithoutFlags models.Flags `opt:"-X" action:"ParseNotFlag"`
|
||||
To []string `opt:"-t" action:"ParseTo"`
|
||||
From []string `opt:"-f" action:"ParseFrom"`
|
||||
Cc []string `opt:"-c" action:"ParseCc"`
|
||||
StartDate time.Time `opt:"-d" action:"ParseDate"`
|
||||
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"`
|
||||
EndDate time.Time
|
||||
Terms string `opt:"..." required:"false"`
|
||||
}
|
||||
@@ -37,33 +37,20 @@ func init() {
|
||||
register(SearchFilter{})
|
||||
}
|
||||
|
||||
func (SearchFilter) Options() string {
|
||||
return "rubax:X:t:H:f:c:d:"
|
||||
}
|
||||
|
||||
func (SearchFilter) Aliases() []string {
|
||||
return []string{"search", "filter"}
|
||||
}
|
||||
|
||||
func (s SearchFilter) CompleteOption(
|
||||
r rune,
|
||||
search string,
|
||||
) []string {
|
||||
var valid []string
|
||||
switch r {
|
||||
case 'x', 'X':
|
||||
valid = commands.GetFlagList()
|
||||
case 't', 'f', 'c':
|
||||
valid = commands.GetAddress(search)
|
||||
case 'd':
|
||||
valid = commands.GetDateList()
|
||||
default:
|
||||
}
|
||||
return commands.CompletionFromList(valid, []string{search})
|
||||
func (*SearchFilter) CompleteFlag(arg string) []string {
|
||||
return commands.CompletionFromList(commands.GetFlagList(), arg)
|
||||
}
|
||||
|
||||
func (SearchFilter) Complete(args []string) []string {
|
||||
return nil
|
||||
func (*SearchFilter) CompleteAddress(arg string) []string {
|
||||
return commands.CompletionFromList(commands.GetAddress(arg), arg)
|
||||
}
|
||||
|
||||
func (*SearchFilter) CompleteDate(arg string) []string {
|
||||
return commands.CompletionFromList(commands.GetDateList(), arg)
|
||||
}
|
||||
|
||||
func (s *SearchFilter) ParseRead(arg string) error {
|
||||
|
||||
@@ -18,10 +18,6 @@ func (SelectMessage) Aliases() []string {
|
||||
return []string{"select", "select-message"}
|
||||
}
|
||||
|
||||
func (SelectMessage) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SelectMessage) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package account
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
@@ -13,6 +12,9 @@ import (
|
||||
|
||||
type Sort struct {
|
||||
Unused struct{} `opt:"-"`
|
||||
// these fields are only used for completion
|
||||
Reverse bool `opt:"-r"`
|
||||
Criteria []string `opt:"criteria" complete:"CompleteCriteria"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -23,46 +25,20 @@ func (Sort) Aliases() []string {
|
||||
return []string{"sort"}
|
||||
}
|
||||
|
||||
func (Sort) Complete(args []string) []string {
|
||||
supportedCriteria := []string{
|
||||
"arrival",
|
||||
"cc",
|
||||
"date",
|
||||
"from",
|
||||
"read",
|
||||
"size",
|
||||
"subject",
|
||||
"to",
|
||||
"flagged",
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return supportedCriteria
|
||||
}
|
||||
last := args[len(args)-1]
|
||||
var completions []string
|
||||
currentPrefix := strings.Join(args, " ") + " "
|
||||
// if there is a completed criteria or option then suggest all again
|
||||
for _, criteria := range append(supportedCriteria, "-r") {
|
||||
if criteria == last {
|
||||
for _, criteria := range supportedCriteria {
|
||||
completions = append(completions, currentPrefix+criteria)
|
||||
}
|
||||
return completions
|
||||
}
|
||||
}
|
||||
var supportedCriteria = []string{
|
||||
"arrival",
|
||||
"cc",
|
||||
"date",
|
||||
"from",
|
||||
"read",
|
||||
"size",
|
||||
"subject",
|
||||
"to",
|
||||
"flagged",
|
||||
}
|
||||
|
||||
currentPrefix = strings.Join(args[:len(args)-1], " ")
|
||||
if len(args) > 1 {
|
||||
currentPrefix += " "
|
||||
}
|
||||
// last was beginning an option
|
||||
if last == "-" {
|
||||
return []string{currentPrefix + "-r"}
|
||||
}
|
||||
// the last item is not complete
|
||||
completions = commands.FilterList(supportedCriteria, last, currentPrefix,
|
||||
app.SelectedAccountUiConfig().FuzzyComplete)
|
||||
return completions
|
||||
func (*Sort) CompleteCriteria(arg string) []string {
|
||||
return commands.CompletionFromList(supportedCriteria, arg)
|
||||
}
|
||||
|
||||
func (Sort) Execute(args []string) error {
|
||||
|
||||
@@ -33,10 +33,6 @@ func (Split) Aliases() []string {
|
||||
return []string{"split", "vsplit", "hsplit"}
|
||||
}
|
||||
|
||||
func (Split) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Split) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -19,10 +19,6 @@ func (ViewMessage) Aliases() []string {
|
||||
return []string{"view-message", "view"}
|
||||
}
|
||||
|
||||
func (ViewMessage) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v ViewMessage) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var previousDir string
|
||||
|
||||
type ChangeDirectory struct {
|
||||
Target string `opt:"directory" default:"~"`
|
||||
Target string `opt:"directory" default:"~" complete:"CompleteTarget"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -23,9 +23,8 @@ func (ChangeDirectory) Aliases() []string {
|
||||
return []string{"cd"}
|
||||
}
|
||||
|
||||
func (ChangeDirectory) Complete(args []string) []string {
|
||||
path := strings.Join(args, " ")
|
||||
completions := CompletePath(path)
|
||||
func (*ChangeDirectory) CompleteTarget(arg string) []string {
|
||||
completions := CompletePath(arg)
|
||||
|
||||
var dirs []string
|
||||
for _, c := range completions {
|
||||
|
||||
@@ -18,10 +18,6 @@ func (Choose) Aliases() []string {
|
||||
return []string{"choose"}
|
||||
}
|
||||
|
||||
func (Choose) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Choose) Execute(args []string) error {
|
||||
if len(args) < 5 || len(args)%4 != 1 {
|
||||
return chooseUsage(args[0])
|
||||
|
||||
@@ -3,12 +3,13 @@ package commands
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.sr.ht/~rjarry/go-opt"
|
||||
"github.com/google/shlex"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/config"
|
||||
@@ -21,17 +22,6 @@ import (
|
||||
type Command interface {
|
||||
Aliases() []string
|
||||
Execute([]string) error
|
||||
Complete([]string) []string
|
||||
}
|
||||
|
||||
type OptionsProvider interface {
|
||||
Command
|
||||
Options() string
|
||||
}
|
||||
|
||||
type OptionCompleter interface {
|
||||
OptionsProvider
|
||||
CompleteOption(rune, string) []string
|
||||
}
|
||||
|
||||
type Commands map[string]Command
|
||||
@@ -174,6 +164,7 @@ func GetTemplateCompletion(
|
||||
templates.Terms(),
|
||||
strings.TrimSpace(search),
|
||||
"",
|
||||
"",
|
||||
app.SelectedAccountUiConfig().FuzzyComplete,
|
||||
)
|
||||
return options, prefix + padding, true
|
||||
@@ -194,117 +185,62 @@ func GetTemplateCompletion(
|
||||
func GetCompletions(
|
||||
cmd Command, args *opt.Args,
|
||||
) (options []string, prefix string) {
|
||||
// complete options
|
||||
var spec string
|
||||
if provider, ok := cmd.(OptionsProvider); ok {
|
||||
spec = provider.Options()
|
||||
}
|
||||
|
||||
parser, err := newParser(args.String(), spec, strings.HasSuffix(args.String(), " "))
|
||||
if err != nil {
|
||||
log.Debugf("completion parser failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch parser.kind {
|
||||
case SHORT_OPTION:
|
||||
for _, r := range strings.ReplaceAll(spec, ":", "") {
|
||||
if strings.ContainsRune(parser.flag, r) {
|
||||
continue
|
||||
}
|
||||
option := string(r)
|
||||
if strings.Contains(spec, option+":") {
|
||||
option += " "
|
||||
}
|
||||
options = append(options, option)
|
||||
}
|
||||
prefix = args.String()
|
||||
case OPTION_ARGUMENT:
|
||||
cmpl, ok := cmd.(OptionCompleter)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
stem := args.String()
|
||||
if parser.arg != "" {
|
||||
stem = strings.TrimSuffix(stem, parser.arg)
|
||||
}
|
||||
pad := ""
|
||||
if !strings.HasSuffix(stem, " ") {
|
||||
pad += " "
|
||||
}
|
||||
s := parser.flag
|
||||
r := rune(s[len(s)-1])
|
||||
for _, option := range cmpl.CompleteOption(r, parser.arg) {
|
||||
options = append(options, pad+escape(option)+" ")
|
||||
}
|
||||
prefix = stem
|
||||
case OPERAND:
|
||||
clone := args.Clone()
|
||||
clone.Cut(clone.Count() - parser.optind)
|
||||
args.Shift(1)
|
||||
for _, option := range cmd.Complete(args.Args()) {
|
||||
if strings.Contains(option, " ") {
|
||||
option = escape(option)
|
||||
}
|
||||
options = append(options, " "+option)
|
||||
}
|
||||
prefix = clone.String()
|
||||
}
|
||||
|
||||
return
|
||||
// copy zeroed struct
|
||||
tmp := reflect.New(reflect.TypeOf(cmd)).Interface().(Command)
|
||||
spec := opt.NewCmdSpec(args.Arg(0), tmp)
|
||||
return spec.GetCompletions(args)
|
||||
}
|
||||
|
||||
func GetFolders(args []string) []string {
|
||||
func GetFolders(arg string) []string {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return acct.Directories().List()
|
||||
}
|
||||
return FilterList(acct.Directories().List(), args[0], "", acct.UiConfig().FuzzyComplete)
|
||||
return CompletionFromList(acct.Directories().List(), arg)
|
||||
}
|
||||
|
||||
// CompletionFromList provides a convenience wrapper for commands to use in the
|
||||
// Complete function. It simply matches the items provided in valid
|
||||
func CompletionFromList(valid []string, args []string) []string {
|
||||
if len(args) == 0 {
|
||||
return valid
|
||||
func GetTemplates(arg string) []string {
|
||||
templates := make(map[string]bool)
|
||||
for _, dir := range config.Templates.TemplateDirs {
|
||||
for _, f := range listDir(dir, false) {
|
||||
if !isDir(path.Join(dir, f)) {
|
||||
templates[f] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return FilterList(valid, args[0], "", app.SelectedAccountUiConfig().FuzzyComplete)
|
||||
names := make([]string, len(templates))
|
||||
for n := range templates {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return CompletionFromList(names, arg)
|
||||
}
|
||||
|
||||
func GetLabels(args []string) []string {
|
||||
// CompletionFromList provides a convenience wrapper for commands to use in a
|
||||
// complete callback. It simply matches the items provided in valid
|
||||
func CompletionFromList(valid []string, arg string) []string {
|
||||
return FilterList(valid, arg, "", "", app.SelectedAccountUiConfig().FuzzyComplete)
|
||||
}
|
||||
|
||||
func GetLabels(arg string) []string {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return acct.Labels()
|
||||
}
|
||||
|
||||
// + and - are used to denote tag addition / removal and need to be striped
|
||||
// only the last tag should be completed, so that multiple labels can be
|
||||
// selected
|
||||
last := args[len(args)-1]
|
||||
others := strings.Join(args[:len(args)-1], " ")
|
||||
var prefix string
|
||||
switch last[0] {
|
||||
case '+':
|
||||
prefix = "+"
|
||||
case '-':
|
||||
prefix = "-"
|
||||
default:
|
||||
prefix = ""
|
||||
if arg != "" {
|
||||
// + and - are used to denote tag addition / removal and need to
|
||||
// be striped only the last tag should be completed, so that
|
||||
// multiple labels can be selected
|
||||
switch arg[0] {
|
||||
case '+':
|
||||
prefix = "+"
|
||||
case '-':
|
||||
prefix = "-"
|
||||
}
|
||||
arg = strings.TrimLeft(arg, "+-")
|
||||
}
|
||||
trimmed := strings.TrimLeft(last, "+-")
|
||||
|
||||
var prev string
|
||||
if len(others) > 0 {
|
||||
prev = others + " "
|
||||
}
|
||||
out := FilterList(acct.Labels(), trimmed, prev+prefix, acct.UiConfig().FuzzyComplete)
|
||||
return out
|
||||
return FilterList(acct.Labels(), arg, prefix, " ", acct.UiConfig().FuzzyComplete)
|
||||
}
|
||||
|
||||
// hasCaseSmartPrefix checks whether s starts with prefix, using a case
|
||||
@@ -324,19 +260,3 @@ func hasUpper(s string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// splitCmd splits the command into arguments
|
||||
func splitCmd(cmd string) ([]string, error) {
|
||||
args, err := shlex.Split(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func escape(s string) string {
|
||||
if strings.Contains(s, " ") {
|
||||
return strings.ReplaceAll(s, " ", "\\ ")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ func (Abort) Aliases() []string {
|
||||
return []string{"abort"}
|
||||
}
|
||||
|
||||
func (Abort) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Abort) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
app.RemoveTab(composer, true)
|
||||
|
||||
@@ -14,10 +14,6 @@ func (AttachKey) Aliases() []string {
|
||||
return []string{"attach-key"}
|
||||
}
|
||||
|
||||
func (AttachKey) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (AttachKey) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
return composer.SetAttachKey(!composer.AttachKey())
|
||||
|
||||
@@ -23,7 +23,8 @@ import (
|
||||
type Attach struct {
|
||||
Menu bool `opt:"-m"`
|
||||
Name string `opt:"-r"`
|
||||
Path string `opt:"..." metavar:"<path>" required:"false"`
|
||||
Path string `opt:"path" required:"false" complete:"CompletePath"`
|
||||
Args string `opt:"..." required:"false"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -34,9 +35,8 @@ func (Attach) Aliases() []string {
|
||||
return []string{"attach"}
|
||||
}
|
||||
|
||||
func (Attach) Complete(args []string) []string {
|
||||
path := strings.Join(args, " ")
|
||||
return commands.CompletePath(path)
|
||||
func (*Attach) CompletePath(arg string) []string {
|
||||
return commands.CompletePath(arg)
|
||||
}
|
||||
|
||||
func (a Attach) Execute(args []string) error {
|
||||
@@ -52,6 +52,9 @@ func (a Attach) Execute(args []string) error {
|
||||
}
|
||||
return a.readCommand()
|
||||
default:
|
||||
if a.Args != "" {
|
||||
return errors.New("only a single path is supported")
|
||||
}
|
||||
return a.addPath(a.Path)
|
||||
}
|
||||
}
|
||||
@@ -186,7 +189,7 @@ func (a Attach) openMenu() error {
|
||||
}
|
||||
|
||||
func (a Attach) readCommand() error {
|
||||
cmd := exec.Command("sh", "-c", a.Path)
|
||||
cmd := exec.Command("sh", "-c", a.Path+" "+a.Args)
|
||||
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
@@ -16,10 +16,6 @@ func (CC) Aliases() []string {
|
||||
return []string{"cc", "bcc"}
|
||||
}
|
||||
|
||||
func (CC) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c CC) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
)
|
||||
|
||||
type Detach struct {
|
||||
Path string `opt:"path" required:"false"`
|
||||
Path string `opt:"path" required:"false" complete:"CompletePath"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -18,9 +19,9 @@ func (Detach) Aliases() []string {
|
||||
return []string{"detach"}
|
||||
}
|
||||
|
||||
func (Detach) Complete(args []string) []string {
|
||||
func (*Detach) CompletePath(arg string) []string {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
return composer.GetAttachments()
|
||||
return commands.CompletionFromList(composer.GetAttachments(), arg)
|
||||
}
|
||||
|
||||
func (d Detach) Execute(args []string) error {
|
||||
|
||||
@@ -20,10 +20,6 @@ func (Edit) Aliases() []string {
|
||||
return []string{"edit"}
|
||||
}
|
||||
|
||||
func (Edit) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Edit) Execute(args []string) error {
|
||||
composer, ok := app.SelectedTabContent().(*app.Composer)
|
||||
if !ok {
|
||||
|
||||
@@ -14,10 +14,6 @@ func (Encrypt) Aliases() []string {
|
||||
return []string{"encrypt"}
|
||||
}
|
||||
|
||||
func (Encrypt) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Encrypt) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
composer.SetEncrypt(!composer.Encrypt())
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type Header struct {
|
||||
Force bool `opt:"-f"`
|
||||
Remove bool `opt:"-d"`
|
||||
Name string `opt:"name"`
|
||||
Name string `opt:"name" complete:"CompleteHeaders"`
|
||||
Value string `opt:"..." required:"false"`
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func (Header) Options() string {
|
||||
return "fd"
|
||||
}
|
||||
|
||||
func (Header) Complete(args []string) []string {
|
||||
return commands.CompletionFromList(headers, args)
|
||||
func (*Header) CompleteHeaders(arg string) []string {
|
||||
return commands.CompletionFromList(headers, arg)
|
||||
}
|
||||
|
||||
func (h Header) Execute(args []string) error {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
type Multipart struct {
|
||||
Remove bool `opt:"-d"`
|
||||
Mime string `opt:"mime" metavar:"<mime/type>"`
|
||||
Mime string `opt:"mime" metavar:"<mime/type>" complete:"CompleteMime"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -22,13 +22,12 @@ func (Multipart) Aliases() []string {
|
||||
return []string{"multipart"}
|
||||
}
|
||||
|
||||
func (Multipart) Complete(args []string) []string {
|
||||
func (*Multipart) CompleteMime(arg string) []string {
|
||||
var completions []string
|
||||
completions = append(completions, "-d")
|
||||
for mime := range config.Converters {
|
||||
completions = append(completions, mime)
|
||||
}
|
||||
return commands.CompletionFromList(completions, args)
|
||||
return commands.CompletionFromList(completions, arg)
|
||||
}
|
||||
|
||||
func (m Multipart) Execute(args []string) error {
|
||||
|
||||
@@ -14,10 +14,6 @@ func (NextPrevField) Aliases() []string {
|
||||
return []string{"next-field", "prev-field"}
|
||||
}
|
||||
|
||||
func (NextPrevField) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NextPrevField) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
if args[0] == "prev-field" {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type Postpone struct {
|
||||
Folder string `opt:"-t"`
|
||||
Folder string `opt:"-t" complete:"CompleteFolder"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -25,20 +25,8 @@ func (Postpone) Aliases() []string {
|
||||
return []string{"postpone"}
|
||||
}
|
||||
|
||||
func (Postpone) Options() string {
|
||||
return "t:"
|
||||
}
|
||||
|
||||
func (Postpone) CompleteOption(r rune, arg string) []string {
|
||||
var valid []string
|
||||
if r == 't' {
|
||||
valid = commands.GetFolders([]string{arg})
|
||||
}
|
||||
return commands.CompletionFromList(valid, []string{arg})
|
||||
}
|
||||
|
||||
func (Postpone) Complete(args []string) []string {
|
||||
return nil
|
||||
func (*Postpone) CompleteFolder(arg string) []string {
|
||||
return commands.GetFolders(arg)
|
||||
}
|
||||
|
||||
func (p Postpone) Execute(args []string) error {
|
||||
|
||||
@@ -28,8 +28,8 @@ import (
|
||||
)
|
||||
|
||||
type Send struct {
|
||||
Archive string `opt:"-a" action:"ParseArchive" metavar:"flat|year|month"`
|
||||
CopyTo string `opt:"-t"`
|
||||
Archive string `opt:"-a" action:"ParseArchive" metavar:"flat|year|month" complete:"CompleteArchive"`
|
||||
CopyTo string `opt:"-t" complete:"CompleteFolders"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -40,19 +40,12 @@ func (Send) Aliases() []string {
|
||||
return []string{"send"}
|
||||
}
|
||||
|
||||
func (Send) Options() string {
|
||||
return "a:t:"
|
||||
func (*Send) CompleteArchive(arg string) []string {
|
||||
return commands.CompletionFromList(msg.ARCHIVE_TYPES, arg)
|
||||
}
|
||||
|
||||
func (s Send) CompleteOption(r rune, term string) []string {
|
||||
if r == 't' {
|
||||
return commands.GetFolders([]string{term})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Send) Complete(args []string) []string {
|
||||
return nil
|
||||
func (*Send) CompleteFolders(arg string) []string {
|
||||
return commands.GetFolders(arg)
|
||||
}
|
||||
|
||||
func (s *Send) ParseArchive(arg string) error {
|
||||
|
||||
@@ -16,10 +16,6 @@ func (Sign) Aliases() []string {
|
||||
return []string{"sign"}
|
||||
}
|
||||
|
||||
func (Sign) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Sign) Execute(args []string) error {
|
||||
composer, _ := app.SelectedTabContent().(*app.Composer)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
)
|
||||
|
||||
type AccountSwitcher interface {
|
||||
@@ -13,7 +14,7 @@ type AccountSwitcher interface {
|
||||
type SwitchAccount struct {
|
||||
Next bool `opt:"-n"`
|
||||
Prev bool `opt:"-p"`
|
||||
Account string `opt:"..." metavar:"<account>" required:"false"`
|
||||
Account string `opt:"account" required:"false" complete:"CompleteAccount"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -24,8 +25,8 @@ func (SwitchAccount) Aliases() []string {
|
||||
return []string{"switch-account"}
|
||||
}
|
||||
|
||||
func (SwitchAccount) Complete(args []string) []string {
|
||||
return app.AccountNames()
|
||||
func (*SwitchAccount) CompleteAccount(arg string) []string {
|
||||
return commands.CompletionFromList(app.AccountNames(), arg)
|
||||
}
|
||||
|
||||
func (s SwitchAccount) Execute(args []string) error {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type ChangeTab struct {
|
||||
Tab string `opt:"tab"`
|
||||
Tab string `opt:"tab" complete:"CompleteTab"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -20,12 +20,8 @@ func (ChangeTab) Aliases() []string {
|
||||
return []string{"ct", "change-tab"}
|
||||
}
|
||||
|
||||
func (ChangeTab) Complete(args []string) []string {
|
||||
if len(args) == 0 {
|
||||
return app.TabNames()
|
||||
}
|
||||
joinedArgs := strings.Join(args, " ")
|
||||
return FilterList(app.TabNames(), joinedArgs, "", app.SelectedAccountUiConfig().FuzzyComplete)
|
||||
func (*ChangeTab) CompleteTab(arg string) []string {
|
||||
return CompletionFromList(app.TabNames(), arg)
|
||||
}
|
||||
|
||||
func (c ChangeTab) Execute(args []string) error {
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/lib"
|
||||
)
|
||||
|
||||
type Eml struct {
|
||||
Path string `opt:"path" required:"false"`
|
||||
Path string `opt:"path" required:"false" complete:"CompletePath"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -23,8 +22,8 @@ func (Eml) Aliases() []string {
|
||||
return []string{"eml", "preview"}
|
||||
}
|
||||
|
||||
func (Eml) Complete(args []string) []string {
|
||||
return CompletePath(strings.Join(args, " "))
|
||||
func (*Eml) CompletePath(arg string) []string {
|
||||
return CompletePath(arg)
|
||||
}
|
||||
|
||||
func (e Eml) Execute(args []string) error {
|
||||
|
||||
@@ -22,10 +22,6 @@ func (ExecCmd) Aliases() []string {
|
||||
return []string{"exec"}
|
||||
}
|
||||
|
||||
func (ExecCmd) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ExecCmd) Execute(args []string) error {
|
||||
cmd := exec.Command(e.Args[0], e.Args[1:]...)
|
||||
env := os.Environ()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type Help struct {
|
||||
Topic string `opt:"topic" action:"ParseTopic" default:"aerc"`
|
||||
Topic string `opt:"topic" action:"ParseTopic" default:"aerc" complete:"CompleteTopic"`
|
||||
}
|
||||
|
||||
var pages = []string{
|
||||
@@ -35,8 +35,8 @@ func (Help) Aliases() []string {
|
||||
return []string{"help"}
|
||||
}
|
||||
|
||||
func (Help) Complete(args []string) []string {
|
||||
return CompletionFromList(pages, args)
|
||||
func (*Help) CompleteTopic(arg string) []string {
|
||||
return CompletionFromList(pages, arg)
|
||||
}
|
||||
|
||||
func (h *Help) ParseTopic(arg string) error {
|
||||
|
||||
@@ -32,10 +32,6 @@ func (MoveTab) Aliases() []string {
|
||||
return []string{"move-tab"}
|
||||
}
|
||||
|
||||
func (MoveTab) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MoveTab) Execute(args []string) error {
|
||||
app.MoveTab(m.Index, m.Relative)
|
||||
return nil
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
var ARCHIVE_TYPES = []string{ARCHIVE_FLAT, ARCHIVE_YEAR, ARCHIVE_MONTH}
|
||||
|
||||
type Archive struct {
|
||||
Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month"`
|
||||
Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType"`
|
||||
}
|
||||
|
||||
func (a *Archive) ParseArchiveType(arg string) error {
|
||||
@@ -42,9 +42,8 @@ func (Archive) Aliases() []string {
|
||||
return []string{"archive"}
|
||||
}
|
||||
|
||||
func (Archive) Complete(args []string) []string {
|
||||
valid := []string{"flat", "year", "month"}
|
||||
return commands.CompletionFromList(valid, args)
|
||||
func (*Archive) CompleteType(arg string) []string {
|
||||
return commands.CompletionFromList(ARCHIVE_TYPES, arg)
|
||||
}
|
||||
|
||||
func (a Archive) Execute(args []string) error {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
type Copy struct {
|
||||
CreateFolders bool `opt:"-p"`
|
||||
Folder string `opt:"..." metavar:"<folder>"`
|
||||
Folder string `opt:"folder" complete:"CompleteFolder"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -21,8 +21,8 @@ func (Copy) Aliases() []string {
|
||||
return []string{"cp", "copy"}
|
||||
}
|
||||
|
||||
func (Copy) Complete(args []string) []string {
|
||||
return commands.GetFolders(args)
|
||||
func (*Copy) CompleteFolder(arg string) []string {
|
||||
return commands.GetFolders(arg)
|
||||
}
|
||||
|
||||
func (c Copy) Execute(args []string) error {
|
||||
|
||||
@@ -21,10 +21,6 @@ func (Delete) Aliases() []string {
|
||||
return []string{"delete", "delete-message"}
|
||||
}
|
||||
|
||||
func (Delete) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Delete) Execute(args []string) error {
|
||||
h := newHelper()
|
||||
store, err := h.store()
|
||||
|
||||
@@ -25,10 +25,6 @@ func (Envelope) Aliases() []string {
|
||||
return []string{"envelope"}
|
||||
}
|
||||
|
||||
func (Envelope) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Envelope) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -17,10 +17,6 @@ func (Fold) Aliases() []string {
|
||||
return []string{"fold", "unfold"}
|
||||
}
|
||||
|
||||
func (Fold) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Fold) Execute(args []string) error {
|
||||
h := newHelper()
|
||||
store, err := h.store()
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
"git.sr.ht/~rjarry/aerc/config"
|
||||
"git.sr.ht/~rjarry/aerc/lib"
|
||||
"git.sr.ht/~rjarry/aerc/lib/format"
|
||||
@@ -27,7 +28,7 @@ type forward struct {
|
||||
AttachFull bool `opt:"-F"`
|
||||
Edit bool `opt:"-e"`
|
||||
NoEdit bool `opt:"-E"`
|
||||
Template string `opt:"-T"`
|
||||
Template string `opt:"-T" complete:"CompleteTemplate"`
|
||||
To []string `opt:"..." required:"false"`
|
||||
}
|
||||
|
||||
@@ -39,8 +40,8 @@ func (forward) Aliases() []string {
|
||||
return []string{"forward"}
|
||||
}
|
||||
|
||||
func (forward) Complete(args []string) []string {
|
||||
return nil
|
||||
func (*forward) CompleteTemplate(arg string) []string {
|
||||
return commands.GetTemplates(arg)
|
||||
}
|
||||
|
||||
func (f forward) Execute(args []string) error {
|
||||
|
||||
@@ -28,10 +28,6 @@ func (invite) Aliases() []string {
|
||||
return []string{"accept", "accept-tentative", "decline"}
|
||||
}
|
||||
|
||||
func (invite) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i invite) Execute(args []string) error {
|
||||
acct := app.SelectedAccount()
|
||||
if acct == nil {
|
||||
|
||||
@@ -20,10 +20,6 @@ func (Mark) Aliases() []string {
|
||||
return []string{"mark", "unmark", "remark"}
|
||||
}
|
||||
|
||||
func (Mark) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Mark) Execute(args []string) error {
|
||||
h := newHelper()
|
||||
OnSelectedMessage := func(fn func(uint32)) error {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type ModifyLabels struct {
|
||||
Labels []string `opt:"..." metavar:"[+-]<label>"`
|
||||
Labels []string `opt:"..." metavar:"[+-]<label>" complete:"CompleteLabels"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -20,8 +20,8 @@ func (ModifyLabels) Aliases() []string {
|
||||
return []string{"modify-labels", "tag"}
|
||||
}
|
||||
|
||||
func (ModifyLabels) Complete(args []string) []string {
|
||||
return commands.GetLabels(args)
|
||||
func (*ModifyLabels) CompleteLabels(arg string) []string {
|
||||
return commands.GetLabels(arg)
|
||||
}
|
||||
|
||||
func (m ModifyLabels) Execute(args []string) error {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type Move struct {
|
||||
CreateFolders bool `opt:"-p"`
|
||||
Folder string `opt:"..." metavar:"<folder>"`
|
||||
Folder string `opt:"folder" complete:"CompleteFolder"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -25,8 +25,8 @@ func (Move) Aliases() []string {
|
||||
return []string{"mv", "move"}
|
||||
}
|
||||
|
||||
func (Move) Complete(args []string) []string {
|
||||
return commands.GetFolders(args)
|
||||
func (*Move) CompleteFolder(arg string) []string {
|
||||
return commands.GetFolders(arg)
|
||||
}
|
||||
|
||||
func (m Move) Execute(args []string) error {
|
||||
|
||||
@@ -32,10 +32,6 @@ func (Pipe) Aliases() []string {
|
||||
return []string{"pipe"}
|
||||
}
|
||||
|
||||
func (Pipe) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Pipe) Execute(args []string) error {
|
||||
if p.Full && p.Part {
|
||||
return errors.New("-m and -p are mutually exclusive")
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
"git.sr.ht/~rjarry/aerc/models"
|
||||
"git.sr.ht/~rjarry/aerc/worker/types"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
type FlagMsg struct {
|
||||
Toggle bool `opt:"-t"`
|
||||
Answered bool `opt:"-a" aliases:"flag,unflag"`
|
||||
Flag models.Flags `opt:"-x" aliases:"flag,unflag" action:"ParseFlag"`
|
||||
Flag models.Flags `opt:"-x" aliases:"flag,unflag" action:"ParseFlag" complete:"CompleteFlag"`
|
||||
FlagName string
|
||||
}
|
||||
|
||||
@@ -25,10 +26,6 @@ func (FlagMsg) Aliases() []string {
|
||||
return []string{"flag", "unflag", "read", "unread"}
|
||||
}
|
||||
|
||||
func (FlagMsg) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagMsg) ParseFlag(arg string) error {
|
||||
switch strings.ToLower(arg) {
|
||||
case "seen":
|
||||
@@ -46,6 +43,12 @@ func (f *FlagMsg) ParseFlag(arg string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var validFlags = []string{"seen", "answered", "flagged"}
|
||||
|
||||
func (*FlagMsg) CompleteFlag(arg string) []string {
|
||||
return commands.CompletionFromList(validFlags, arg)
|
||||
}
|
||||
|
||||
// If this was called as 'flag' or 'unflag', without the toggle (-t)
|
||||
// option, then it will flag the corresponding messages with the given
|
||||
// flag. If the toggle option was given, it will individually toggle
|
||||
|
||||
@@ -31,10 +31,6 @@ func (Recall) Aliases() []string {
|
||||
return []string{"recall"}
|
||||
}
|
||||
|
||||
func (Recall) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Recall) Execute(args []string) error {
|
||||
editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
"git.sr.ht/~rjarry/aerc/commands"
|
||||
"git.sr.ht/~rjarry/aerc/commands/account"
|
||||
"git.sr.ht/~rjarry/aerc/config"
|
||||
"git.sr.ht/~rjarry/aerc/lib"
|
||||
@@ -26,7 +27,7 @@ type reply struct {
|
||||
All bool `opt:"-a"`
|
||||
Close bool `opt:"-c"`
|
||||
Quote bool `opt:"-q"`
|
||||
Template string `opt:"-T"`
|
||||
Template string `opt:"-T" complete:"CompleteTemplate"`
|
||||
Edit bool `opt:"-e"`
|
||||
NoEdit bool `opt:"-E"`
|
||||
}
|
||||
@@ -39,8 +40,8 @@ func (reply) Aliases() []string {
|
||||
return []string{"reply"}
|
||||
}
|
||||
|
||||
func (reply) Complete(args []string) []string {
|
||||
return nil
|
||||
func (*reply) CompleteTemplate(arg string) []string {
|
||||
return commands.GetTemplates(arg)
|
||||
}
|
||||
|
||||
func (r reply) Execute(args []string) error {
|
||||
|
||||
@@ -14,10 +14,6 @@ func (ToggleThreadContext) Aliases() []string {
|
||||
return []string{"toggle-thread-context"}
|
||||
}
|
||||
|
||||
func (ToggleThreadContext) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ToggleThreadContext) Execute(args []string) error {
|
||||
h := newHelper()
|
||||
store, err := h.store()
|
||||
|
||||
@@ -15,10 +15,6 @@ func (ToggleThreads) Aliases() []string {
|
||||
return []string{"toggle-threads"}
|
||||
}
|
||||
|
||||
func (ToggleThreads) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ToggleThreads) Execute(args []string) error {
|
||||
h := newHelper()
|
||||
acct, err := h.account()
|
||||
|
||||
@@ -31,11 +31,6 @@ func (Unsubscribe) Aliases() []string {
|
||||
return []string{"unsubscribe"}
|
||||
}
|
||||
|
||||
// Complete returns a list of completions
|
||||
func (Unsubscribe) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute runs the Unsubscribe command
|
||||
func (u Unsubscribe) Execute(args []string) error {
|
||||
editHeaders := (config.Compose.EditHeaders || u.Edit) && !u.NoEdit
|
||||
|
||||
@@ -14,10 +14,6 @@ func (Close) Aliases() []string {
|
||||
return []string{"close"}
|
||||
}
|
||||
|
||||
func (Close) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Close) Execute(args []string) error {
|
||||
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
|
||||
app.RemoveTab(mv, true)
|
||||
|
||||
@@ -16,10 +16,6 @@ func (NextPrevPart) Aliases() []string {
|
||||
return []string{"next-part", "prev-part"}
|
||||
}
|
||||
|
||||
func (NextPrevPart) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (np NextPrevPart) Execute(args []string) error {
|
||||
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
|
||||
for n := 0; n < np.Offset; n++ {
|
||||
|
||||
@@ -39,10 +39,6 @@ func (NextPrevMsg) Aliases() []string {
|
||||
return []string{"next", "next-message", "prev", "prev-message"}
|
||||
}
|
||||
|
||||
func (NextPrevMsg) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (np NextPrevMsg) Execute(args []string) error {
|
||||
cmd := account.NextPrevMsg{Amount: np.Amount, Percent: np.Percent}
|
||||
err := cmd.Execute(args)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type OpenLink struct {
|
||||
Url *url.URL `opt:"url" action:"ParseUrl"`
|
||||
Url *url.URL `opt:"url" action:"ParseUrl" complete:"CompleteUrl"`
|
||||
Cmd string `opt:"..." required:"false"`
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ func (OpenLink) Aliases() []string {
|
||||
return []string{"open-link"}
|
||||
}
|
||||
|
||||
func (OpenLink) Complete(args []string) []string {
|
||||
func (*OpenLink) CompleteUrl(arg string) []string {
|
||||
mv := app.SelectedTabContent().(*app.MessageViewer)
|
||||
if mv != nil {
|
||||
if p := mv.SelectedMessagePart(); p != nil {
|
||||
return commands.CompletionFromList(p.Links, args)
|
||||
return commands.CompletionFromList(p.Links, arg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -29,10 +29,6 @@ func (Open) Aliases() []string {
|
||||
return []string{"open"}
|
||||
}
|
||||
|
||||
func (Open) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o Open) Execute(args []string) error {
|
||||
mv := app.SelectedTabContent().(*app.MessageViewer)
|
||||
if mv == nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ type Save struct {
|
||||
CreateDirs bool `opt:"-p"`
|
||||
Attachments bool `opt:"-a"`
|
||||
AllAttachments bool `opt:"-A"`
|
||||
Path string `opt:"..." required:"false" metavar:"<path>"`
|
||||
Path string `opt:"path" required:"false" complete:"CompletePath"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -37,14 +37,12 @@ func (Save) Aliases() []string {
|
||||
return []string{"save"}
|
||||
}
|
||||
|
||||
func (s Save) Complete(args []string) []string {
|
||||
trimmed := commands.Operands(args, s.Options())
|
||||
path := strings.Join(trimmed, " ")
|
||||
func (*Save) CompletePath(arg string) []string {
|
||||
defaultPath := config.General.DefaultSavePath
|
||||
if defaultPath != "" && !isAbsPath(path) {
|
||||
path = filepath.Join(defaultPath, path)
|
||||
if defaultPath != "" && !isAbsPath(arg) {
|
||||
arg = filepath.Join(defaultPath, arg)
|
||||
}
|
||||
return commands.CompletePath(xdg.ExpandHome(path))
|
||||
return commands.CompletePath(xdg.ExpandHome(arg))
|
||||
}
|
||||
|
||||
func (s Save) Execute(args []string) error {
|
||||
|
||||
@@ -14,10 +14,6 @@ func (ToggleHeaders) Aliases() []string {
|
||||
return []string{"toggle-headers"}
|
||||
}
|
||||
|
||||
func (ToggleHeaders) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ToggleHeaders) Execute(args []string) error {
|
||||
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
|
||||
mv.ToggleHeaders()
|
||||
|
||||
@@ -15,10 +15,6 @@ func (ToggleKeyPassthrough) Aliases() []string {
|
||||
return []string{"toggle-key-passthrough"}
|
||||
}
|
||||
|
||||
func (ToggleKeyPassthrough) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ToggleKeyPassthrough) Execute(args []string) error {
|
||||
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
|
||||
keyPassthroughEnabled := mv.ToggleKeyPassthrough()
|
||||
|
||||
@@ -16,10 +16,6 @@ func (NewAccount) Aliases() []string {
|
||||
return []string{"new-account"}
|
||||
}
|
||||
|
||||
func (NewAccount) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NewAccount) Execute(args []string) error {
|
||||
wizard := app.NewAccountWizard()
|
||||
wizard.ConfigureTemporaryAccount(n.Temp)
|
||||
|
||||
@@ -16,10 +16,6 @@ func (NextPrevTab) Aliases() []string {
|
||||
return []string{"next-tab", "prev-tab"}
|
||||
}
|
||||
|
||||
func (NextPrevTab) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (np NextPrevTab) Execute(args []string) error {
|
||||
for n := 0; n < np.Offset; n++ {
|
||||
if args[0] == "prev-tab" {
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type completionType int
|
||||
|
||||
const (
|
||||
NONE completionType = iota
|
||||
COMMAND
|
||||
OPERAND
|
||||
SHORT_OPTION
|
||||
OPTION_ARGUMENT
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
tokens []string
|
||||
optind int
|
||||
spec string
|
||||
space bool
|
||||
kind completionType
|
||||
flag string
|
||||
arg string
|
||||
err error
|
||||
}
|
||||
|
||||
func newParser(cmd, spec string, spaceTerminated bool) (*parser, error) {
|
||||
args, err := splitCmd(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &parser{
|
||||
tokens: args,
|
||||
optind: 0,
|
||||
spec: spec,
|
||||
space: spaceTerminated,
|
||||
kind: NONE,
|
||||
flag: "",
|
||||
arg: "",
|
||||
err: nil,
|
||||
}
|
||||
|
||||
state := command
|
||||
for state != nil {
|
||||
state = state(p)
|
||||
}
|
||||
|
||||
return p, p.err
|
||||
}
|
||||
|
||||
func (p *parser) empty() bool {
|
||||
return len(p.tokens) == 0
|
||||
}
|
||||
|
||||
func (p *parser) peek() string {
|
||||
return p.tokens[0]
|
||||
}
|
||||
|
||||
func (p *parser) advance() string {
|
||||
if p.empty() {
|
||||
return ""
|
||||
}
|
||||
tok := p.tokens[0]
|
||||
p.tokens = p.tokens[1:]
|
||||
p.optind++
|
||||
return tok
|
||||
}
|
||||
|
||||
func (p *parser) set(t completionType) {
|
||||
p.kind = t
|
||||
}
|
||||
|
||||
func (p *parser) hasArgument() bool {
|
||||
n := len(p.flag)
|
||||
if n > 0 {
|
||||
s := string(p.flag[n-1]) + ":"
|
||||
return strings.Contains(p.spec, s)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type stateFn func(*parser) stateFn
|
||||
|
||||
func command(p *parser) stateFn {
|
||||
p.set(COMMAND)
|
||||
p.advance()
|
||||
return peek(p)
|
||||
}
|
||||
|
||||
func peek(p *parser) stateFn {
|
||||
if p.empty() {
|
||||
if p.space {
|
||||
return operand
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if p.spec == "" {
|
||||
return operand
|
||||
}
|
||||
s := p.peek()
|
||||
switch {
|
||||
case s == "--":
|
||||
p.advance()
|
||||
case strings.HasPrefix(s, "-"):
|
||||
return short_option
|
||||
}
|
||||
return operand
|
||||
}
|
||||
|
||||
func short_option(p *parser) stateFn {
|
||||
p.set(SHORT_OPTION)
|
||||
tok := p.advance()
|
||||
p.flag = tok[1:]
|
||||
if p.hasArgument() {
|
||||
return option_argument
|
||||
}
|
||||
return peek(p)
|
||||
}
|
||||
|
||||
func option_argument(p *parser) stateFn {
|
||||
p.set(OPTION_ARGUMENT)
|
||||
p.arg = p.advance()
|
||||
if p.empty() && len(p.arg) == 0 {
|
||||
return nil
|
||||
}
|
||||
return peek(p)
|
||||
}
|
||||
|
||||
func operand(p *parser) stateFn {
|
||||
p.set(OPERAND)
|
||||
return nil
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var parserTests = []struct {
|
||||
name string
|
||||
cmd string
|
||||
wantType completionType
|
||||
wantFlag string
|
||||
wantArg string
|
||||
wantOptind int
|
||||
}{
|
||||
{
|
||||
name: "empty command",
|
||||
cmd: "",
|
||||
wantType: COMMAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 0,
|
||||
},
|
||||
{
|
||||
name: "command only",
|
||||
cmd: "cmd",
|
||||
wantType: COMMAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 1,
|
||||
},
|
||||
{
|
||||
name: "with space",
|
||||
cmd: "cmd ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 1,
|
||||
},
|
||||
{
|
||||
name: "with two spaces",
|
||||
cmd: "cmd ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 1,
|
||||
},
|
||||
{
|
||||
name: "with single option flag",
|
||||
cmd: "cmd -",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with single option flag two spaces",
|
||||
cmd: "cmd -",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with single option flag completed",
|
||||
cmd: "cmd -a",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with single option flag completed and space",
|
||||
cmd: "cmd -a ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with single option flag completed and two spaces",
|
||||
cmd: "cmd -a ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with two single option flag completed",
|
||||
cmd: "cmd -b -a",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with two single option flag combined",
|
||||
cmd: "cmd -ab",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "ab",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with two single option flag and space",
|
||||
cmd: "cmd -ab ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "ab",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag",
|
||||
cmd: "cmd -f",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and space",
|
||||
cmd: "cmd -f ",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and two spaces",
|
||||
cmd: "cmd -f ",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and completed",
|
||||
cmd: "cmd -f a",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "a",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and completed quote",
|
||||
cmd: "cmd -f 'a b'",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "a b",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and operand",
|
||||
cmd: "cmd -f 'a b' hello",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "f",
|
||||
wantArg: "a b",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and two spaces between",
|
||||
cmd: "cmd -f a",
|
||||
wantType: OPTION_ARGUMENT,
|
||||
wantFlag: "f",
|
||||
wantArg: "a",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with mandatory option flag and more spaces",
|
||||
cmd: "cmd -f a ",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "f",
|
||||
wantArg: "a",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "with template data",
|
||||
cmd: "cmd -a {{if .Size}} hello {{else}} {{end}}",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with operand",
|
||||
cmd: "cmd -ab /tmp/aerc-",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "ab",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
{
|
||||
name: "with operand indicator",
|
||||
cmd: "cmd -ab -- /tmp/aerc-",
|
||||
wantType: OPERAND,
|
||||
wantFlag: "ab",
|
||||
wantArg: "",
|
||||
wantOptind: 3,
|
||||
},
|
||||
{
|
||||
name: "hyphen connected command",
|
||||
cmd: "cmd-dmc",
|
||||
wantType: COMMAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 1,
|
||||
},
|
||||
{
|
||||
name: "incomplete hyphen connected command",
|
||||
cmd: "cmd-",
|
||||
wantType: COMMAND,
|
||||
wantFlag: "",
|
||||
wantArg: "",
|
||||
wantOptind: 1,
|
||||
},
|
||||
{
|
||||
name: "hyphen connected command with option",
|
||||
cmd: "cmd-dmc -a",
|
||||
wantType: SHORT_OPTION,
|
||||
wantFlag: "a",
|
||||
wantArg: "",
|
||||
wantOptind: 2,
|
||||
},
|
||||
}
|
||||
|
||||
func TestCommands_Parser(t *testing.T) {
|
||||
for i, test := range parserTests {
|
||||
n := len(test.cmd)
|
||||
spaceTerminated := n > 0 && test.cmd[n-1] == ' '
|
||||
parser, err := newParser(test.cmd, "abf:", spaceTerminated)
|
||||
if err != nil {
|
||||
t.Errorf("parser error: %v", err)
|
||||
}
|
||||
|
||||
if test.wantType != parser.kind {
|
||||
t.Errorf("test %d '%s': completion type does not match: "+
|
||||
"want %d, but got %d", i, test.cmd, test.wantType,
|
||||
parser.kind)
|
||||
}
|
||||
|
||||
if test.wantFlag != parser.flag {
|
||||
t.Errorf("test %d '%s': flag does not match: "+
|
||||
"want %s, but got %s", i, test.cmd, test.wantFlag,
|
||||
parser.flag)
|
||||
}
|
||||
|
||||
if test.wantArg != parser.arg {
|
||||
t.Errorf("test %d '%s': arg does not match: "+
|
||||
"want %s, but got %s", i, test.cmd, test.wantArg,
|
||||
parser.arg)
|
||||
}
|
||||
|
||||
if test.wantOptind != parser.optind {
|
||||
t.Errorf("test %d '%s': optind does not match: "+
|
||||
"want %d, but got %d", i, test.cmd, test.wantOptind,
|
||||
parser.optind)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,6 @@ func (PinTab) Aliases() []string {
|
||||
return []string{"pin-tab", "unpin-tab"}
|
||||
}
|
||||
|
||||
func (PinTab) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PinTab) Execute(args []string) error {
|
||||
switch args[0] {
|
||||
case "pin-tab":
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~rjarry/go-opt"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/app"
|
||||
@@ -10,7 +8,7 @@ import (
|
||||
|
||||
type Prompt struct {
|
||||
Text string `opt:"text"`
|
||||
Cmd []string `opt:"..."`
|
||||
Cmd []string `opt:"..." complete:"CompleteCommand"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -21,56 +19,8 @@ func (Prompt) Aliases() []string {
|
||||
return []string{"prompt"}
|
||||
}
|
||||
|
||||
func (Prompt) Complete(args []string) []string {
|
||||
argc := len(args)
|
||||
if argc == 0 {
|
||||
return nil
|
||||
}
|
||||
hascommand := argc > 2
|
||||
if argc == 1 {
|
||||
args = append(args, "")
|
||||
}
|
||||
|
||||
cmd := GlobalCommands.ByName(args[1])
|
||||
var cs []string
|
||||
if cmd != nil {
|
||||
cs = cmd.Complete(args[2:])
|
||||
hascommand = true
|
||||
} else {
|
||||
if hascommand {
|
||||
return nil
|
||||
}
|
||||
cs = GlobalCommands.Names()
|
||||
}
|
||||
if cs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
// it seems '' quoting is enough
|
||||
// to keep quoted arguments in one piece
|
||||
b.WriteRune('\'')
|
||||
b.WriteString(args[0])
|
||||
b.WriteRune('\'')
|
||||
b.WriteRune(' ')
|
||||
if hascommand {
|
||||
b.WriteString(args[1])
|
||||
b.WriteRune(' ')
|
||||
}
|
||||
|
||||
src := b.String()
|
||||
b.Reset()
|
||||
|
||||
rs := make([]string, 0, len(cs))
|
||||
for _, c := range cs {
|
||||
b.WriteString(src)
|
||||
b.WriteString(c)
|
||||
|
||||
rs = append(rs, b.String())
|
||||
b.Reset()
|
||||
}
|
||||
|
||||
return rs
|
||||
func (*Prompt) CompleteCommand(arg string) []string {
|
||||
return CompletionFromList(GlobalCommands.Names(), arg)
|
||||
}
|
||||
|
||||
func (p Prompt) Execute(args []string) error {
|
||||
|
||||
@@ -17,10 +17,6 @@ func (PrintWorkDir) Aliases() []string {
|
||||
return []string{"pwd"}
|
||||
}
|
||||
|
||||
func (PrintWorkDir) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PrintWorkDir) Execute(args []string) error {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
|
||||
@@ -18,10 +18,6 @@ func (Quit) Aliases() []string {
|
||||
return []string{"quit", "exit"}
|
||||
}
|
||||
|
||||
func (Quit) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ErrorExit int
|
||||
|
||||
func (err ErrorExit) Error() string {
|
||||
|
||||
@@ -19,10 +19,6 @@ func (SendKeys) Aliases() []string {
|
||||
return []string{"send-keys"}
|
||||
}
|
||||
|
||||
func (SendKeys) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SendKeys) Execute(args []string) error {
|
||||
tab, ok := app.SelectedTabContent().(app.HasTerminal)
|
||||
if !ok {
|
||||
|
||||
@@ -12,10 +12,6 @@ func (Suspend) Aliases() []string {
|
||||
return []string{"suspend"}
|
||||
}
|
||||
|
||||
func (Suspend) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Suspend) Execute(args []string) error {
|
||||
ui.QueueSuspend()
|
||||
return nil
|
||||
|
||||
@@ -21,10 +21,6 @@ func (Term) Aliases() []string {
|
||||
return []string{"terminal", "term"}
|
||||
}
|
||||
|
||||
func (Term) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Term) Execute(args []string) error {
|
||||
if len(t.Cmd) == 0 {
|
||||
shell, err := loginshell.Shell()
|
||||
|
||||
@@ -14,10 +14,6 @@ func (Close) Aliases() []string {
|
||||
return []string{"close"}
|
||||
}
|
||||
|
||||
func (Close) Complete(args []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Close) Execute(args []string) error {
|
||||
term, _ := app.SelectedTabContent().(*app.Terminal)
|
||||
term.Close()
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"git.sr.ht/~rjarry/aerc/log"
|
||||
"git.sr.ht/~rjarry/aerc/models"
|
||||
"git.sr.ht/~rjarry/aerc/worker/types"
|
||||
"git.sr.ht/~rjarry/go-opt"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
@@ -93,7 +94,7 @@ func CompletePath(path string) []string {
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, ".") && !strings.Contains(path, "/.") {
|
||||
log.Debugf("removing hidden files from glob results")
|
||||
log.Tracef("removing hidden files from glob results")
|
||||
for i := len(matches) - 1; i >= 0; i-- {
|
||||
if strings.HasPrefix(filepath.Base(matches[i]), ".") {
|
||||
if i == len(matches)-1 {
|
||||
@@ -107,11 +108,13 @@ func CompletePath(path string) []string {
|
||||
|
||||
for i, m := range matches {
|
||||
if isDir(m) {
|
||||
matches[i] = m + "/"
|
||||
m += "/"
|
||||
}
|
||||
matches[i] = opt.QuoteArg((xdg.TildeHome(m)))
|
||||
}
|
||||
|
||||
sort.Strings(matches)
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
@@ -122,11 +125,11 @@ func CompletePath(path string) []string {
|
||||
if isDir(f) {
|
||||
f += "/"
|
||||
}
|
||||
|
||||
files[i] = f
|
||||
files[i] = opt.QuoteArg((xdg.TildeHome(f)))
|
||||
}
|
||||
|
||||
sort.Strings(files)
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
@@ -227,16 +230,16 @@ func MsgInfoFromUids(store *lib.MessageStore, uids []uint32, statusInfo func(str
|
||||
|
||||
// FilterList takes a list of valid completions and filters it, either
|
||||
// by case smart prefix, or by fuzzy matching, prepending "prefix" to each completion
|
||||
func FilterList(valid []string, search, prefix string, isFuzzy bool) []string {
|
||||
out := make([]string, 0)
|
||||
func FilterList(valid []string, search, prefix, suffix string, isFuzzy bool) []string {
|
||||
out := make([]string, 0, len(valid))
|
||||
if isFuzzy {
|
||||
for _, v := range fuzzy.RankFindFold(search, valid) {
|
||||
out = append(out, prefix+v.Target)
|
||||
out = append(out, opt.QuoteArg(prefix+v.Target+suffix))
|
||||
}
|
||||
} else {
|
||||
for _, v := range valid {
|
||||
if hasCaseSmartPrefix(v, search) {
|
||||
out = append(out, prefix+v)
|
||||
out = append(out, opt.QuoteArg(prefix+v+suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type Zoxide struct {
|
||||
Target string `opt:"..." default:"~" metavar:"<folder> | <query>..."`
|
||||
Target string `opt:"folder" default:"~" complete:"CompleteFolder"`
|
||||
Args []string `opt:"..." required:"false" metavar:"<query>..."`
|
||||
}
|
||||
|
||||
func ZoxideAdd(arg string) error {
|
||||
@@ -36,8 +37,8 @@ func (Zoxide) Aliases() []string {
|
||||
return []string{"z"}
|
||||
}
|
||||
|
||||
func (Zoxide) Complete(args []string) []string {
|
||||
return ChangeDirectory{}.Complete(args)
|
||||
func (*Zoxide) CompleteFolder(arg string) []string {
|
||||
return GetFolders(arg)
|
||||
}
|
||||
|
||||
// Execute calls zoxide add and query and delegates actually changing the
|
||||
|
||||
Reference in New Issue
Block a user