diff options
81 files changed, 197 insertions, 968 deletions
diff --git a/commands/account/cf.go b/commands/account/cf.go index 59203a89..d73d4978 100644 --- a/commands/account/cf.go +++ b/commands/account/cf.go @@ -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 { diff --git a/commands/account/check-mail.go b/commands/account/check-mail.go index a8d80a66..d31b2648 100644 --- a/commands/account/check-mail.go +++ b/commands/account/check-mail.go @@ -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 { diff --git a/commands/account/clear.go b/commands/account/clear.go index fd209d07..dec6bcd2 100644 --- a/commands/account/clear.go +++ b/commands/account/clear.go @@ -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 { diff --git a/commands/account/compose.go b/commands/account/compose.go index 81eb3de0..fe86a179 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -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 { diff --git a/commands/account/connection.go b/commands/account/connection.go index b9cd887b..d633c1ce 100644 --- a/commands/account/connection.go +++ b/commands/account/connection.go @@ -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 { diff --git a/commands/account/expand-folder.go b/commands/account/expand-folder.go index f26da70a..c264872a 100644 --- a/commands/account/expand-folder.go +++ b/commands/account/expand-folder.go @@ -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 { diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go index 00e03ca6..14d9290e 100644 --- a/commands/account/export-mbox.go +++ b/commands/account/export-mbox.go @@ -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 { diff --git a/commands/account/import-mbox.go b/commands/account/import-mbox.go index b3ad2a08..774dfa8f 100644 --- a/commands/account/import-mbox.go +++ b/commands/account/import-mbox.go @@ -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 { diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go index 9beeb01a..3e546ace 100644 --- a/commands/account/mkdir.go +++ b/commands/account/mkdir.go @@ -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 { diff --git a/commands/account/next-folder.go b/commands/account/next-folder.go index 1b5651c8..f44abdc1 100644 --- a/commands/account/next-folder.go +++ b/commands/account/next-folder.go @@ -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 { diff --git a/commands/account/next-result.go b/commands/account/next-result.go index e841899f..d624e559 100644 --- a/commands/account/next-result.go +++ b/commands/account/next-result.go @@ -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 { diff --git a/commands/account/next.go b/commands/account/next.go index 14679b1b..142f6151 100644 --- a/commands/account/next.go +++ b/commands/account/next.go @@ -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 { diff --git a/commands/account/recover.go b/commands/account/recover.go index dae3d807..cba6e0cb 100644 --- a/commands/account/recover.go +++ b/commands/account/recover.go @@ -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 { diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go index 36bfc699..398f375f 100644 --- a/commands/account/rmdir.go +++ b/commands/account/rmdir.go @@ -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 { diff --git a/commands/account/search.go b/commands/account/search.go index bb5617c0..ca1b9684 100644 --- a/commands/account/search.go +++ b/commands/account/search.go @@ -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 { diff --git a/commands/account/select.go b/commands/account/select.go index efd8f53b..884b4bce 100644 --- a/commands/account/select.go +++ b/commands/account/select.go @@ -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 { diff --git a/commands/account/sort.go b/commands/account/sort.go index 2adfbf19..ccccab25 100644 --- a/commands/account/sort.go +++ b/commands/account/sort.go @@ -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 { diff --git a/commands/account/split.go b/commands/account/split.go index 4774297f..8690d99a 100644 --- a/commands/account/split.go +++ b/commands/account/split.go @@ -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 { diff --git a/commands/account/view.go b/commands/account/view.go index 701a7738..0cdc175d 100644 --- a/commands/account/view.go +++ b/commands/account/view.go @@ -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 { diff --git a/commands/cd.go b/commands/cd.go index 6620e732..d2dc5310 100644 --- a/commands/cd.go +++ b/commands/cd.go @@ -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 { diff --git a/commands/choose.go b/commands/choose.go index 4e2007fd..19958dce 100644 --- a/commands/choose.go +++ b/commands/choose.go @@ -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]) diff --git a/commands/commands.go b/commands/commands.go index d76194af..9c193018 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -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 + } + } + } + names := make([]string, len(templates)) + for n := range templates { + names = append(names, n) } - return FilterList(valid, args[0], "", app.SelectedAccountUiConfig().FuzzyComplete) + 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 = "" - } - trimmed := strings.TrimLeft(last, "+-") - - var prev string - if len(others) > 0 { - prev = others + " " + 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, "+-") } - 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 -} diff --git a/commands/compose/abort.go b/commands/compose/abort.go index 4bacb9c3..cde43f46 100644 --- a/commands/compose/abort.go +++ b/commands/compose/abort.go @@ -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) diff --git a/commands/compose/attach-key.go b/commands/compose/attach-key.go index e81a6a5a..6ba5eae0 100644 --- a/commands/compose/attach-key.go +++ b/commands/compose/attach-key.go @@ -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()) diff --git a/commands/compose/attach.go b/commands/compose/attach.go index 3acf28c2..49d63bb8 100644 --- a/commands/compose/attach.go +++ b/commands/compose/attach.go @@ -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 { diff --git a/commands/compose/cc-bcc.go b/commands/compose/cc-bcc.go index aeb6af97..dd5e9614 100644 --- a/commands/compose/cc-bcc.go +++ b/commands/compose/cc-bcc.go @@ -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) diff --git a/commands/compose/detach.go b/commands/compose/detach.go index a2996516..91cf2a58 100644 --- a/commands/compose/detach.go +++ b/commands/compose/detach.go @@ -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 { diff --git a/commands/compose/edit.go b/commands/compose/edit.go index 80f4e6f4..1929f45a 100644 --- a/commands/compose/edit.go +++ b/commands/compose/edit.go @@ -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 { diff --git a/commands/compose/encrypt.go b/commands/compose/encrypt.go index 3121dff0..ee902f6e 100644 --- a/commands/compose/encrypt.go +++ b/commands/compose/encrypt.go @@ -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()) diff --git a/commands/compose/header.go b/commands/compose/header.go index aaa14e43..afc27e92 100644 --- a/commands/compose/header.go +++ b/commands/compose/header.go @@ -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 { diff --git a/commands/compose/multipart.go b/commands/compose/multipart.go index 96941062..5b701342 100644 --- a/commands/compose/multipart.go +++ b/commands/compose/multipart.go @@ -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 { diff --git a/commands/compose/next-field.go b/commands/compose/next-field.go index 88fbb03d..396c1ce1 100644 --- a/commands/compose/next-field.go +++ b/commands/compose/next-field.go @@ -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" { diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go index 767e759c..3ce9cc84 100644 --- a/commands/compose/postpone.go +++ b/commands/compose/postpone.go @@ -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 { diff --git a/commands/compose/send.go b/commands/compose/send.go index e3672471..6f905628 100644 --- a/commands/compose/send.go +++ b/commands/compose/send.go @@ -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 { diff --git a/commands/compose/sign.go b/commands/compose/sign.go index e30fca05..faeceed4 100644 --- a/commands/compose/sign.go +++ b/commands/compose/sign.go @@ -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) diff --git a/commands/compose/switch.go b/commands/compose/switch.go index 0c027a41..637099b5 100644 --- a/commands/compose/switch.go +++ b/commands/compose/switch.go @@ -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 { diff --git a/commands/ct.go b/commands/ct.go index 8a6bb063..2d057b4f 100644 --- a/commands/ct.go +++ b/commands/ct.go @@ -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 { diff --git a/commands/eml.go b/commands/eml.go index adacd05b..fe735243 100644 --- a/commands/eml.go +++ b/commands/eml.go @@ -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 { diff --git a/commands/exec.go b/commands/exec.go index 4c2f3d1b..2a3ed5f5 100644 --- a/commands/exec.go +++ b/commands/exec.go @@ -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() diff --git a/commands/help.go b/commands/help.go index b2bcdf7c..07332303 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"` + 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 { diff --git a/commands/move-tab.go b/commands/move-tab.go index 23580f2d..7ebe269c 100644 --- a/commands/move-tab.go +++ b/commands/move-tab.go @@ -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 diff --git a/commands/msg/archive.go b/commands/msg/archive.go index f4d6e3be..34cba8b8 100644 --- a/commands/msg/archive.go +++ b/commands/msg/archive.go @@ -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 { diff --git a/commands/msg/copy.go b/commands/msg/copy.go index 4109ef99..77c9ade1 100644 --- a/commands/msg/copy.go +++ b/commands/msg/copy.go @@ -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 { diff --git a/commands/msg/delete.go b/commands/msg/delete.go index 49463abc..1a1a130c 100644 --- a/commands/msg/delete.go +++ b/commands/msg/delete.go @@ -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() diff --git a/commands/msg/envelope.go b/commands/msg/envelope.go index 6da82a1e..e104648d 100644 --- a/commands/msg/envelope.go +++ b/commands/msg/envelope.go @@ -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 { diff --git a/commands/msg/fold.go b/commands/msg/fold.go index 0621c8c3..06e933d7 100644 --- a/commands/msg/fold.go +++ b/commands/msg/fold.go @@ -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() diff --git a/commands/msg/forward.go b/commands/msg/forward.go index e6e386a9..4147e8c7 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -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 { diff --git a/commands/msg/invite.go b/commands/msg/invite.go index 5b8558b0..12bea93e 100644 --- a/commands/msg/invite.go +++ b/commands/msg/invite.go @@ -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 { diff --git a/commands/msg/mark.go b/commands/msg/mark.go index c2c21cf2..9547548f 100644 --- a/commands/msg/mark.go +++ b/commands/msg/mark.go @@ -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 { diff --git a/commands/msg/modify-labels.go b/commands/msg/modify-labels.go index 6fdbeac4..8cc72f2a 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>"` + 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 { diff --git a/commands/msg/move.go b/commands/msg/move.go index 1dd68d35..06c7fc15 100644 --- a/commands/msg/move.go +++ b/commands/msg/move.go @@ -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 { diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go index b984752b..b32d7159 100644 --- a/commands/msg/pipe.go +++ b/commands/msg/pipe.go @@ -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") diff --git a/commands/msg/read.go b/commands/msg/read.go index e55ed00e..72159a53 100644 --- a/commands/msg/read.go +++ b/commands/msg/read.go @@ -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 diff --git a/commands/msg/recall.go b/commands/msg/recall.go index 3b78a763..cea02ddb 100644 --- a/commands/msg/recall.go +++ b/commands/msg/recall.go @@ -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 diff --git a/commands/msg/reply.go b/commands/msg/reply.go index 2ab9e9f8..ebf20317 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -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 { diff --git a/commands/msg/toggle-thread-context.go b/commands/msg/toggle-thread-context.go index 0ef6778f..4b87eaa8 100644 --- a/commands/msg/toggle-thread-context.go +++ b/commands/msg/toggle-thread-context.go @@ -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() diff --git a/commands/msg/toggle-threads.go b/commands/msg/toggle-threads.go index 88cc763f..d2933bba 100644 --- a/commands/msg/toggle-threads.go +++ b/commands/msg/toggle-threads.go @@ -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() diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go index 6fef16f5..c6a0b23f 100644 --- a/commands/msg/unsubscribe.go +++ b/commands/msg/unsubscribe.go @@ -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 diff --git a/commands/msgview/close.go b/commands/msgview/close.go index 32702da9..95960ef3 100644 --- a/commands/msgview/close.go +++ b/commands/msgview/close.go @@ -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) diff --git a/commands/msgview/next-part.go b/commands/msgview/next-part.go index 9b1b1fcc..77eb008d 100644 --- a/commands/msgview/next-part.go +++ b/commands/msgview/next-part.go @@ -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++ { diff --git a/commands/msgview/next.go b/commands/msgview/next.go index d8f046f8..c953cd5d 100644 --- a/commands/msgview/next.go +++ b/commands/msgview/next.go @@ -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) diff --git a/commands/msgview/open-link.go b/commands/msgview/open-link.go index 55aee08c..eceb4232 100644 --- a/commands/msgview/open-link.go +++ b/commands/msgview/open-link.go @@ -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 diff --git a/commands/msgview/open.go b/commands/msgview/open.go index 0d6db5b0..a8443404 100644 --- a/commands/msgview/open.go +++ b/commands/msgview/open.go @@ -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 { diff --git a/commands/msgview/save.go b/commands/msgview/save.go index ea7599d3..9f2b71fc 100644 --- a/commands/msgview/save.go +++ b/commands/msgview/save.go @@ -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 { diff --git a/commands/msgview/toggle-headers.go b/commands/msgview/toggle-headers.go index c27307d3..37f5b22f 100644 --- a/commands/msgview/toggle-headers.go +++ b/commands/msgview/toggle-headers.go @@ -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() diff --git a/commands/msgview/toggle-key-passthrough.go b/commands/msgview/toggle-key-passthrough.go index 32735870..b4c5f2c7 100644 --- a/commands/msgview/toggle-key-passthrough.go +++ b/commands/msgview/toggle-key-passthrough.go @@ -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() diff --git a/commands/new-account.go b/commands/new-account.go index b30e0e34..1a14a821 100644 --- a/commands/new-account.go +++ b/commands/new-account.go @@ -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) diff --git a/commands/next-tab.go b/commands/next-tab.go index d8374191..f1f34d22 100644 --- a/commands/next-tab.go +++ b/commands/next-tab.go @@ -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" { diff --git a/commands/parser.go b/commands/parser.go deleted file mode 100644 index e8146506..00000000 --- a/commands/parser.go +++ /dev/null @@ -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 -} diff --git a/commands/parser_test.go b/commands/parser_test.go deleted file mode 100644 index d6ccd385..00000000 --- a/commands/parser_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/commands/pin-tab.go b/commands/pin-tab.go index 276442ce..d5013bf6 100644 --- a/commands/pin-tab.go +++ b/commands/pin-tab.go @@ -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": diff --git a/commands/prompt.go b/commands/prompt.go index dd259c30..d791f7a9 100644 --- a/commands/prompt.go +++ b/commands/prompt.go @@ -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 { diff --git a/commands/pwd.go b/commands/pwd.go index 426be78b..2b1961a1 100644 --- a/commands/pwd.go +++ b/commands/pwd.go @@ -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 { diff --git a/commands/quit.go b/commands/quit.go index 3e0f2968..e96c9253 100644 --- a/commands/quit.go +++ b/commands/quit.go @@ -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 { diff --git a/commands/send-keys.go b/commands/send-keys.go index 9541b88d..bb872a33 100644 --- a/commands/send-keys.go +++ b/commands/send-keys.go @@ -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 { diff --git a/commands/suspend.go b/commands/suspend.go index 80d59028..a8996441 100644 --- a/commands/suspend.go +++ b/commands/suspend.go @@ -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 diff --git a/commands/term.go b/commands/term.go index 225fee57..3f8f9875 100644 --- a/commands/term.go +++ b/commands/term.go @@ -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() diff --git a/commands/terminal/close.go b/commands/terminal/close.go index 913a3387..610a8463 100644 --- a/commands/terminal/close.go +++ b/commands/terminal/close.go @@ -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() diff --git a/commands/util.go b/commands/util.go index e9a377a6..a9aa81b3 100644 --- a/commands/util.go +++ b/commands/util.go @@ -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)) } } } diff --git a/commands/z.go b/commands/z.go index 5aee2a2c..2ea65e0c 100644 --- a/commands/z.go +++ b/commands/z.go @@ -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 @@ -143,7 +143,7 @@ func getCompletions(cmdline string) ([]string, string) { for _, set := range cmds { for _, n := range set.Names() { if strings.HasPrefix(n, cmdline) { - completions = append(completions, n) + completions = append(completions, n+" ") } } } |