diff --git a/aerc.go b/aerc.go index 20e2bb1..a248e18 100644 --- a/aerc.go +++ b/aerc.go @@ -51,6 +51,41 @@ func getCommands(selected libui.Drawable) []*commands.Commands { } } +func execCommand(aerc *widgets.Aerc, ui *libui.UI, cmd string) error { + cmds := getCommands((*aerc).SelectedTab()) + for i, set := range cmds { + err := set.ExecuteCommand(aerc, cmd) + if _, ok := err.(commands.NoSuchCommand); ok { + if i == len(cmds)-1 { + return err + } + continue + } else if _, ok := err.(commands.ErrorExit); ok { + ui.Exit() + return nil + } else if err != nil { + return err + } else { + break + } + } + return nil +} + +func getCompletions(aerc *widgets.Aerc, cmd string) []string { + cmds := getCommands((*aerc).SelectedTab()) + completions := make([]string, 0) + for _, set := range cmds { + opts := set.GetCompletions(aerc, cmd) + if len(opts) > 0 { + for _, opt := range opts { + completions = append(completions, opt) + } + } + } + return completions +} + var ( Prefix string ShareDir string @@ -96,27 +131,12 @@ func main() { aerc *widgets.Aerc ui *libui.UI ) + aerc = widgets.NewAerc(conf, logger, func(cmd string) error { - cmds := getCommands(aerc.SelectedTab()) - for i, set := range cmds { - err := set.ExecuteCommand(aerc, cmd) - if _, ok := err.(commands.NoSuchCommand); ok { - if i == len(cmds)-1 { - return err - } else { - continue - } - } else if _, ok := err.(commands.ErrorExit); ok { - ui.Exit() - return nil - } else if err != nil { - return err - } else { - break - } - } - return nil - }) + return execCommand(aerc, ui, cmd) + }, func(cmd string) []string { + return getCompletions(aerc, cmd) + }) ui, err = libui.Initialize(conf, aerc) if err != nil { diff --git a/commands/account/account.go b/commands/account/account.go index c590c8a..9c90087 100644 --- a/commands/account/account.go +++ b/commands/account/account.go @@ -8,9 +8,9 @@ var ( AccountCommands *commands.Commands ) -func register(name string, cmd commands.AercCommand) { +func register(cmd commands.Command) { if AccountCommands == nil { AccountCommands = commands.NewCommands() } - AccountCommands.Register(name, cmd) + AccountCommands.Register(cmd) } diff --git a/commands/account/cf.go b/commands/account/cf.go index 2816473..197e956 100644 --- a/commands/account/cf.go +++ b/commands/account/cf.go @@ -10,12 +10,22 @@ var ( history map[string]string ) +type ChangeFolder struct{} + func init() { history = make(map[string]string) - register("cf", ChangeFolder) + register(ChangeFolder{}) } -func ChangeFolder(aerc *widgets.Aerc, args []string) error { +func (_ ChangeFolder) Aliases() []string { + return []string{"cf"} +} + +func (_ ChangeFolder) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ ChangeFolder) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 2 { return errors.New("Usage: cf ") } diff --git a/commands/account/compose.go b/commands/account/compose.go index aeb415e..cafba78 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -6,12 +6,22 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Compose struct{} + func init() { - register("compose", Compose) + register(Compose{}) +} + +func (_ Compose) Aliases() []string { + return []string{"compose"} +} + +func (_ Compose) Complete(aerc *widgets.Aerc, args []string) []string { + return nil } // TODO: Accept arguments for default headers, message body -func Compose(aerc *widgets.Aerc, args []string) error { +func (_ Compose) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: compose") } diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go index d245821..be9b14a 100644 --- a/commands/account/mkdir.go +++ b/commands/account/mkdir.go @@ -10,11 +10,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type MakeDir struct{} + func init() { - register("mkdir", Mkdir) + register(MakeDir{}) } -func Mkdir(aerc *widgets.Aerc, args []string) error { +func (_ MakeDir) Aliases() []string { + return []string{"mkdir"} +} + +func (_ MakeDir) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ MakeDir) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 2 { return errors.New("Usage: :mkdir ") } diff --git a/commands/account/next-folder.go b/commands/account/next-folder.go index 6ad3d54..414e606 100644 --- a/commands/account/next-folder.go +++ b/commands/account/next-folder.go @@ -8,16 +8,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevFolder struct{} + func init() { - register("next-folder", NextPrevFolder) - register("prev-folder", NextPrevFolder) + register(NextPrevFolder{}) } -func nextPrevFolderUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +func (_ NextPrevFolder) Aliases() []string { + return []string{"next-folder", "prev-folder"} } -func NextPrevFolder(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevFolder) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevFolder) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 2 { return nextPrevFolderUsage(args[0]) } @@ -44,3 +49,7 @@ func NextPrevFolder(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevFolderUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +} diff --git a/commands/account/next-result.go b/commands/account/next-result.go index d89de56..24d53be 100644 --- a/commands/account/next-result.go +++ b/commands/account/next-result.go @@ -7,16 +7,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevResult struct{} + func init() { - register("next-result", NextPrevResult) - register("prev-result", NextPrevResult) + register(NextPrevResult{}) } -func nextPrevResultUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s [[%%]]", cmd)) +func (_ NextPrevResult) Aliases() []string { + return []string{"next-result", "prev-result"} } -func NextPrevResult(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevResult) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevResult) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 1 { return nextPrevResultUsage(args[0]) } @@ -39,3 +44,7 @@ func NextPrevResult(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevResultUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s [[%%]]", cmd)) +} diff --git a/commands/account/next.go b/commands/account/next.go index 3b9260c..f306b48 100644 --- a/commands/account/next.go +++ b/commands/account/next.go @@ -9,18 +9,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevMsg struct{} + func init() { - register("next", NextPrevMessage) - register("next-message", NextPrevMessage) - register("prev", NextPrevMessage) - register("prev-message", NextPrevMessage) + register(NextPrevMsg{}) } -func nextPrevMessageUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s [[%%]]", cmd)) +func (_ NextPrevMsg) Aliases() []string { + return []string{"next", "next-message", "prev", "prev-message"} } -func NextPrevMessage(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevMsg) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 2 { return nextPrevMessageUsage(args[0]) } @@ -63,3 +66,7 @@ func NextPrevMessage(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevMessageUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s [[%%]]", cmd)) +} diff --git a/commands/account/pipe.go b/commands/account/pipe.go index d3cc80a..a68ef64 100644 --- a/commands/account/pipe.go +++ b/commands/account/pipe.go @@ -8,11 +8,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Pipe struct{} + func init() { - register("pipe", Pipe) + register(Pipe{}) } -func Pipe(aerc *widgets.Aerc, args []string) error { +func (_ Pipe) Aliases() []string { + return []string{"pipe"} +} + +func (_ Pipe) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Pipe) Execute(aerc *widgets.Aerc, args []string) error { if len(args) < 2 { return errors.New("Usage: :pipe [args...]") } diff --git a/commands/account/search.go b/commands/account/search.go index 513ad43..a8640dc 100644 --- a/commands/account/search.go +++ b/commands/account/search.go @@ -9,12 +9,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type SearchFilter struct{} + func init() { - register("search", SearchFilter) - //register("filter", SearchFilter) // TODO + register(SearchFilter{}) } -func SearchFilter(aerc *widgets.Aerc, args []string) error { +func (_ SearchFilter) Aliases() []string { + return []string{"search"} +} + +func (_ SearchFilter) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ SearchFilter) Execute(aerc *widgets.Aerc, args []string) error { var ( criteria *imap.SearchCriteria = imap.NewSearchCriteria() ) diff --git a/commands/account/select.go b/commands/account/select.go index 707f6c9..70e08ac 100644 --- a/commands/account/select.go +++ b/commands/account/select.go @@ -7,12 +7,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type SelectMessage struct{} + func init() { - register("select", SelectMessage) - register("select-message", SelectMessage) + register(SelectMessage{}) } -func SelectMessage(aerc *widgets.Aerc, args []string) error { +func (_ SelectMessage) Aliases() []string { + return []string{"select", "select-message"} +} + +func (_ SelectMessage) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ SelectMessage) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 2 { return errors.New("Usage: :select-message ") } diff --git a/commands/account/view.go b/commands/account/view.go index f7f3ec6..cec65aa 100644 --- a/commands/account/view.go +++ b/commands/account/view.go @@ -6,12 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type ViewMessage struct{} + func init() { - register("view", ViewMessage) - register("view-message", ViewMessage) + register(ViewMessage{}) } -func ViewMessage(aerc *widgets.Aerc, args []string) error { +func (_ ViewMessage) Aliases() []string { + return []string{"view-message", "view"} +} + +func (_ ViewMessage) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ ViewMessage) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: view-message") } diff --git a/commands/cd.go b/commands/cd.go index fb495d7..8c9cb21 100644 --- a/commands/cd.go +++ b/commands/cd.go @@ -12,11 +12,21 @@ var ( previousDir string ) +type ChangeDirectory struct{} + func init() { - register("cd", ChangeDirectory) + register(ChangeDirectory{}) } -func ChangeDirectory(aerc *widgets.Aerc, args []string) error { +func (_ ChangeDirectory) Aliases() []string { + return []string{"cd"} +} + +func (_ ChangeDirectory) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ ChangeDirectory) Execute(aerc *widgets.Aerc, args []string) error { if len(args) < 1 || len(args) > 2 { return errors.New("Usage: cd [directory]") } diff --git a/commands/commands.go b/commands/commands.go index 04462d2..8d50b41 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -2,27 +2,47 @@ package commands import ( "errors" + "strings" "github.com/google/shlex" "git.sr.ht/~sircmpwn/aerc/widgets" ) -type AercCommand func(aerc *widgets.Aerc, args []string) error +type Command interface { + Aliases() []string + Execute(*widgets.Aerc, []string) error + Complete(*widgets.Aerc, []string) []string +} -type Commands map[string]AercCommand +type Commands map[string]Command func NewCommands() *Commands { - cmds := Commands(make(map[string]AercCommand)) + cmds := Commands(make(map[string]Command)) return &cmds } -func (cmds *Commands) dict() map[string]AercCommand { - return map[string]AercCommand(*cmds) +func (cmds *Commands) dict() map[string]Command { + return map[string]Command(*cmds) } -func (cmds *Commands) Register(name string, cmd AercCommand) { - cmds.dict()[name] = cmd +func (cmds *Commands) Names() []string { + names := make([]string, 0) + + for k := range cmds.dict() { + names = append(names, k) + } + return names +} + +func (cmds *Commands) Register(cmd Command) { + // TODO enforce unique aliases, until then, duplicate each + if len(cmd.Aliases()) < 1 { + return + } + for _, alias := range cmd.Aliases() { + cmds.dict()[alias] = cmd + } } type NoSuchCommand string @@ -43,8 +63,48 @@ func (cmds *Commands) ExecuteCommand(aerc *widgets.Aerc, cmd string) error { if len(args) == 0 { return errors.New("Expected a command.") } - if fn, ok := cmds.dict()[args[0]]; ok { - return fn(aerc, args) + if cmd, ok := cmds.dict()[args[0]]; ok { + return cmd.Execute(aerc, args) } return NoSuchCommand(args[0]) } + +func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string { + args, err := shlex.Split(cmd) + if err != nil { + return nil + } + + if len(args) == 0 { + return nil + } + + if len(args) > 1 { + if cmd, ok := cmds.dict()[args[0]]; ok { + completions := cmd.Complete(aerc, args[1:]) + if completions != nil && len(completions) == 0 { + return nil + } + + options := make([]string, 0) + for _, option := range completions { + options = append(options, args[0]+" "+option) + } + return options + } + return nil + } + + names := cmds.Names() + options := make([]string, 0) + for _, name := range names { + if strings.HasPrefix(name, args[0]) { + options = append(options, name) + } + } + + if len(options) > 0 { + return options + } + return nil +} diff --git a/commands/compose/abort.go b/commands/compose/abort.go index c60793c..4c121d7 100644 --- a/commands/compose/abort.go +++ b/commands/compose/abort.go @@ -6,11 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Abort struct{} + func init() { - register("abort", CommandAbort) + register(Abort{}) } -func CommandAbort(aerc *widgets.Aerc, args []string) error { +func (_ Abort) Aliases() []string { + return []string{"abort"} +} + +func (_ Abort) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Abort) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: abort") } diff --git a/commands/compose/compose.go b/commands/compose/compose.go index 35a2ed7..d61696e 100644 --- a/commands/compose/compose.go +++ b/commands/compose/compose.go @@ -8,9 +8,9 @@ var ( ComposeCommands *commands.Commands ) -func register(name string, cmd commands.AercCommand) { +func register(cmd commands.Command) { if ComposeCommands == nil { ComposeCommands = commands.NewCommands() } - ComposeCommands.Register(name, cmd) + ComposeCommands.Register(cmd) } diff --git a/commands/compose/edit.go b/commands/compose/edit.go index 18ba481..e888350 100644 --- a/commands/compose/edit.go +++ b/commands/compose/edit.go @@ -6,11 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Edit struct{} + func init() { - register("edit", CommandEdit) + register(Edit{}) } -func CommandEdit(aerc *widgets.Aerc, args []string) error { +func (_ Edit) Aliases() []string { + return []string{"edit"} +} + +func (_ Edit) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Edit) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: edit") } diff --git a/commands/compose/next-field.go b/commands/compose/next-field.go index a10aa32..3496dfd 100644 --- a/commands/compose/next-field.go +++ b/commands/compose/next-field.go @@ -7,16 +7,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevField struct{} + func init() { - register("next-field", NextPrevField) - register("prev-field", NextPrevField) + register(NextPrevField{}) } -func nextPrevFieldUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s", cmd)) +func (_ NextPrevField) Aliases() []string { + return []string{"next-field", "prev-field"} } -func NextPrevField(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevField) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevField) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 2 { return nextPrevFieldUsage(args[0]) } @@ -28,3 +33,7 @@ func NextPrevField(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevFieldUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s", cmd)) +} diff --git a/commands/compose/send.go b/commands/compose/send.go index c4bff11..26df82a 100644 --- a/commands/compose/send.go +++ b/commands/compose/send.go @@ -20,13 +20,23 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type Send struct{} + func init() { - register("send", SendMessage) + register(Send{}) } -func SendMessage(aerc *widgets.Aerc, args []string) error { +func (_ Send) Aliases() []string { + return []string{"send"} +} + +func (_ Send) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Send) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 1 { - return errors.New("Usage: send-message") + return errors.New("Usage: send") } composer, _ := aerc.SelectedTab().(*widgets.Composer) config := composer.Config() diff --git a/commands/global.go b/commands/global.go index c24869a..459192f 100644 --- a/commands/global.go +++ b/commands/global.go @@ -4,9 +4,9 @@ var ( GlobalCommands *Commands ) -func register(name string, cmd AercCommand) { +func register(cmd Command) { if GlobalCommands == nil { GlobalCommands = NewCommands() } - GlobalCommands.Register(name, cmd) + GlobalCommands.Register(cmd) } diff --git a/commands/help.go b/commands/help.go index e269fcf..c4ed4ff 100644 --- a/commands/help.go +++ b/commands/help.go @@ -6,16 +6,26 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Help struct{} + func init() { - register("help", Help) + register(Help{}) } -func Help(aerc *widgets.Aerc, args []string) error { +func (_ Help) Aliases() []string { + return []string{"help"} +} + +func (_ Help) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Help) Execute(aerc *widgets.Aerc, args []string) error { page := "aerc" if len(args) == 2 { page = "aerc-" + args[1] } else if len(args) > 2 { return errors.New("Usage: help [topic]") } - return Term(aerc, []string{"term", "man", page}) + return TermCore(aerc, []string{"term", "man", page}) } diff --git a/commands/msg/archive.go b/commands/msg/archive.go index 4fe7330..40fb48b 100644 --- a/commands/msg/archive.go +++ b/commands/msg/archive.go @@ -18,11 +18,21 @@ const ( ARCHIVE_MONTH = "month" ) +type Archive struct{} + func init() { - register("archive", Archive) + register(Archive{}) } -func Archive(aerc *widgets.Aerc, args []string) error { +func (_ Archive) Aliases() []string { + return []string{"archive"} +} + +func (_ Archive) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Archive) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 2 { return errors.New("Usage: archive ") } diff --git a/commands/msg/copy.go b/commands/msg/copy.go index 0735e98..4d65d24 100644 --- a/commands/msg/copy.go +++ b/commands/msg/copy.go @@ -11,12 +11,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type Copy struct{} + func init() { - register("cp", Copy) - register("copy", Copy) + register(Copy{}) } -func Copy(aerc *widgets.Aerc, args []string) error { +func (_ Copy) Aliases() []string { + return []string{"copy"} +} + +func (_ Copy) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Copy) Execute(aerc *widgets.Aerc, args []string) error { opts, optind, err := getopt.Getopts(args, "p") if err != nil { return err diff --git a/commands/msg/delete.go b/commands/msg/delete.go index ee3dd29..5a72fc9 100644 --- a/commands/msg/delete.go +++ b/commands/msg/delete.go @@ -10,12 +10,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type Delete struct{} + func init() { - register("delete", DeleteMessage) - register("delete-message", DeleteMessage) + register(Delete{}) } -func DeleteMessage(aerc *widgets.Aerc, args []string) error { +func (_ Delete) Aliases() []string { + return []string{"delete", "delete-message"} +} + +func (_ Delete) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Delete) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: :delete") } diff --git a/commands/msg/move.go b/commands/msg/move.go index 2367076..45199b6 100644 --- a/commands/msg/move.go +++ b/commands/msg/move.go @@ -11,12 +11,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type Move struct{} + func init() { - register("mv", Move) - register("move", Move) + register(Move{}) } -func Move(aerc *widgets.Aerc, args []string) error { +func (_ Move) Aliases() []string { + return []string{"mv", "move"} +} + +func (_ Move) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Move) Execute(aerc *widgets.Aerc, args []string) error { opts, optind, err := getopt.Getopts(args, "p") if err != nil { return err diff --git a/commands/msg/msg.go b/commands/msg/msg.go index 73755aa..ecf2102 100644 --- a/commands/msg/msg.go +++ b/commands/msg/msg.go @@ -8,9 +8,9 @@ var ( MessageCommands *commands.Commands ) -func register(name string, cmd commands.AercCommand) { +func register(cmd commands.Command) { if MessageCommands == nil { MessageCommands = commands.NewCommands() } - MessageCommands.Register(name, cmd) + MessageCommands.Register(cmd) } diff --git a/commands/msg/read.go b/commands/msg/read.go index 9844797..db463f1 100644 --- a/commands/msg/read.go +++ b/commands/msg/read.go @@ -10,12 +10,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) +type Read struct{} + func init() { - register("read", Read) - register("unread", Read) + register(Read{}) } -func Read(aerc *widgets.Aerc, args []string) error { +func (_ Read) Aliases() []string { + return []string{"read", "unread"} +} + +func (_ Read) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Read) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: " + args[0]) } diff --git a/commands/msg/reply.go b/commands/msg/reply.go index 51f6584..7a64d21 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -18,12 +18,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type reply struct{} + func init() { - register("reply", Reply) - register("forward", Reply) + register(reply{}) } -func Reply(aerc *widgets.Aerc, args []string) error { +func (_ reply) Aliases() []string { + return []string{"reply", "forward"} +} + +func (_ reply) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ reply) Execute(aerc *widgets.Aerc, args []string) error { opts, optind, err := getopt.Getopts(args, "aq") if err != nil { return err diff --git a/commands/msgview/close.go b/commands/msgview/close.go index 4ce15c4..6a7eb0a 100644 --- a/commands/msgview/close.go +++ b/commands/msgview/close.go @@ -6,11 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Close struct{} + func init() { - register("close", CommandClose) + register(Close{}) } -func CommandClose(aerc *widgets.Aerc, args []string) error { +func (_ Close) Aliases() []string { + return []string{"close"} +} + +func (_ Close) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Close) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: close") } diff --git a/commands/msgview/msgview.go b/commands/msgview/msgview.go index f0e42ad..d60549e 100644 --- a/commands/msgview/msgview.go +++ b/commands/msgview/msgview.go @@ -8,9 +8,9 @@ var ( MessageViewCommands *commands.Commands ) -func register(name string, cmd commands.AercCommand) { +func register(cmd commands.Command) { if MessageViewCommands == nil { MessageViewCommands = commands.NewCommands() } - MessageViewCommands.Register(name, cmd) + MessageViewCommands.Register(cmd) } diff --git a/commands/msgview/next-part.go b/commands/msgview/next-part.go index fcf8f19..8f25e02 100644 --- a/commands/msgview/next-part.go +++ b/commands/msgview/next-part.go @@ -8,16 +8,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevPart struct{} + func init() { - register("next-part", NextPrevPart) - register("prev-part", NextPrevPart) + register(NextPrevPart{}) } -func nextPrevPartUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +func (_ NextPrevPart) Aliases() []string { + return []string{"next-part", "prev-part"} } -func NextPrevPart(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevPart) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevPart) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 2 { return nextPrevPartUsage(args[0]) } @@ -41,3 +46,7 @@ func NextPrevPart(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevPartUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +} diff --git a/commands/msgview/next.go b/commands/msgview/next.go index 0c86839..82fb12f 100644 --- a/commands/msgview/next.go +++ b/commands/msgview/next.go @@ -6,14 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevMsg struct{} + func init() { - register("next", NextPrevMessage) - register("next-message", NextPrevMessage) - register("prev", NextPrevMessage) - register("prev-message", NextPrevMessage) + register(NextPrevMsg{}) } -func NextPrevMessage(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevMsg) Aliases() []string { + return []string{"next", "next-message", "prev", "prev-message"} +} + +func (_ NextPrevMsg) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error { mv, _ := aerc.SelectedTab().(*widgets.MessageViewer) acct := mv.SelectedAccount() store := mv.Store() diff --git a/commands/msgview/open.go b/commands/msgview/open.go index 1a33cec..d1b3238 100644 --- a/commands/msgview/open.go +++ b/commands/msgview/open.go @@ -14,11 +14,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Open struct{} + func init() { - register("open", Open) + register(Open{}) } -func Open(aerc *widgets.Aerc, args []string) error { +func (_ Open) Aliases() []string { + return []string{"open"} +} + +func (_ Open) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Open) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: open") } diff --git a/commands/msgview/pipe.go b/commands/msgview/pipe.go index a84cdf5..56c125b 100644 --- a/commands/msgview/pipe.go +++ b/commands/msgview/pipe.go @@ -12,11 +12,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Pipe struct{} + func init() { - register("pipe", Pipe) + register(Pipe{}) } -func Pipe(aerc *widgets.Aerc, args []string) error { +func (_ Pipe) Aliases() []string { + return []string{"pipe"} +} + +func (_ Pipe) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Pipe) Execute(aerc *widgets.Aerc, args []string) error { if len(args) < 2 { return errors.New("Usage: :pipe [args...]") } diff --git a/commands/msgview/save.go b/commands/msgview/save.go index 59d94b2..93fa83f 100644 --- a/commands/msgview/save.go +++ b/commands/msgview/save.go @@ -16,19 +16,31 @@ import ( "github.com/mitchellh/go-homedir" ) +type Save struct{} + func init() { - register("save", Save) + register(Save{}) } -func Save(aerc *widgets.Aerc, args []string) error { +func (_ Save) Aliases() []string { + return []string{"save"} +} + +func (_ Save) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Save) Execute(aerc *widgets.Aerc, args []string) error { opts, optind, err := getopt.Getopts(args, "p") if err != nil { return err } + var ( mkdirs bool path string ) + for _, opt := range opts { switch opt.Option { case 'p': diff --git a/commands/msgview/toggle-headers.go b/commands/msgview/toggle-headers.go index fc29042..d9d7eba 100644 --- a/commands/msgview/toggle-headers.go +++ b/commands/msgview/toggle-headers.go @@ -7,15 +7,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type ToggleHeaders struct{} + func init() { - register("toggle-headers", ToggleHeaders) + register(ToggleHeaders{}) } -func toggleHeadersUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s", cmd)) +func (_ ToggleHeaders) Aliases() []string { + return []string{"toggle-headers"} } -func ToggleHeaders(aerc *widgets.Aerc, args []string) error { +func (_ ToggleHeaders) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ ToggleHeaders) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 1 { return toggleHeadersUsage(args[0]) } @@ -23,3 +29,7 @@ func ToggleHeaders(aerc *widgets.Aerc, args []string) error { mv.ToggleHeaders() return nil } + +func toggleHeadersUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s", cmd)) +} diff --git a/commands/new-account.go b/commands/new-account.go index 9e70dec..8d2fef5 100644 --- a/commands/new-account.go +++ b/commands/new-account.go @@ -7,11 +7,21 @@ import ( "git.sr.ht/~sircmpwn/getopt" ) +type NewAccount struct{} + func init() { - register("new-account", CommandNewAccount) + register(NewAccount{}) } -func CommandNewAccount(aerc *widgets.Aerc, args []string) error { +func (_ NewAccount) Aliases() []string { + return []string{"new-account"} +} + +func (_ NewAccount) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NewAccount) Execute(aerc *widgets.Aerc, args []string) error { opts, _, err := getopt.Getopts(args, "t") if err != nil { return errors.New("Usage: new-account [-t]") diff --git a/commands/next-tab.go b/commands/next-tab.go index fb5b664..aa6e1dc 100644 --- a/commands/next-tab.go +++ b/commands/next-tab.go @@ -8,16 +8,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type NextPrevTab struct{} + func init() { - register("next-tab", NextPrevTab) - register("prev-tab", NextPrevTab) + register(NextPrevTab{}) } -func nextPrevTabUsage(cmd string) error { - return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +func (_ NextPrevTab) Aliases() []string { + return []string{"next-tab", "prev-tab"} } -func NextPrevTab(aerc *widgets.Aerc, args []string) error { +func (_ NextPrevTab) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ NextPrevTab) Execute(aerc *widgets.Aerc, args []string) error { if len(args) > 2 { return nextPrevTabUsage(args[0]) } @@ -40,3 +45,7 @@ func NextPrevTab(aerc *widgets.Aerc, args []string) error { } return nil } + +func nextPrevTabUsage(cmd string) error { + return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) +} diff --git a/commands/pwd.go b/commands/pwd.go index b4f3eb1..4903c29 100644 --- a/commands/pwd.go +++ b/commands/pwd.go @@ -8,11 +8,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type PrintWorkDir struct{} + func init() { - register("pwd", PrintWorkDirectory) + register(PrintWorkDir{}) } -func PrintWorkDirectory(aerc *widgets.Aerc, args []string) error { +func (_ PrintWorkDir) Aliases() []string { + return []string{"pwd"} +} + +func (_ PrintWorkDir) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ PrintWorkDir) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: pwd") } diff --git a/commands/quit.go b/commands/quit.go index c9d83b7..535ee61 100644 --- a/commands/quit.go +++ b/commands/quit.go @@ -6,8 +6,18 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Quit struct{} + func init() { - register("quit", CommandQuit) + register(Quit{}) +} + +func (_ Quit) Aliases() []string { + return []string{"quit", "exit"} +} + +func (_ Quit) Complete(aerc *widgets.Aerc, args []string) []string { + return nil } type ErrorExit int @@ -16,7 +26,7 @@ func (err ErrorExit) Error() string { return "exit" } -func CommandQuit(aerc *widgets.Aerc, args []string) error { +func (_ Quit) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: quit") } diff --git a/commands/term.go b/commands/term.go index 3f70d67..8575019 100644 --- a/commands/term.go +++ b/commands/term.go @@ -10,11 +10,22 @@ import ( "github.com/riywo/loginshell" ) +type Term struct{} + func init() { - register("term", Term) + register(Term{}) } -func Term(aerc *widgets.Aerc, args []string) error { +func (_ Term) Aliases() []string { + return []string{"terminal", "term"} +} + +func (_ Term) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +// The help command is an alias for `term man` thus Term requires a simple func +func TermCore(aerc *widgets.Aerc, args []string) error { if len(args) == 1 { shell, err := loginshell.Shell() if err != nil { @@ -43,3 +54,7 @@ func Term(aerc *widgets.Aerc, args []string) error { } return nil } + +func (_ Term) Execute(aerc *widgets.Aerc, args []string) error { + return TermCore(aerc, args) +} diff --git a/commands/terminal/close.go b/commands/terminal/close.go index 0ea7a5a..35c4799 100644 --- a/commands/terminal/close.go +++ b/commands/terminal/close.go @@ -6,11 +6,21 @@ import ( "git.sr.ht/~sircmpwn/aerc/widgets" ) +type Close struct{} + func init() { - register("close", CommandClose) + register(Close{}) } -func CommandClose(aerc *widgets.Aerc, args []string) error { +func (_ Close) Aliases() []string { + return []string{"close"} +} + +func (_ Close) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Close) Execute(aerc *widgets.Aerc, args []string) error { if len(args) != 1 { return errors.New("Usage: close") } diff --git a/commands/terminal/terminal.go b/commands/terminal/terminal.go index fb1583f..710d796 100644 --- a/commands/terminal/terminal.go +++ b/commands/terminal/terminal.go @@ -8,9 +8,9 @@ var ( TerminalCommands *commands.Commands ) -func register(name string, cmd commands.AercCommand) { +func register(cmd commands.Command) { if TerminalCommands == nil { TerminalCommands = commands.NewCommands() } - TerminalCommands.Register(name, cmd) + TerminalCommands.Register(cmd) } diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go index 892646d..a3127e5 100644 --- a/lib/ui/textinput.go +++ b/lib/ui/textinput.go @@ -46,6 +46,14 @@ func (ti *TextInput) String() string { return string(ti.text) } +func (ti *TextInput) StringLeft() string { + return string(ti.text[:ti.index]) +} + +func (ti *TextInput) StringRight() string { + return string(ti.text[ti.index:]) +} + func (ti *TextInput) Set(value string) { ti.text = []rune(value) ti.index = len(ti.text) diff --git a/widgets/aerc.go b/widgets/aerc.go index 8aa1e2c..ade56d1 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -14,6 +14,7 @@ import ( type Aerc struct { accounts map[string]*AccountView cmd func(cmd string) error + complete func(cmd string) []string conf *config.AercConfig focused libui.Interactive grid *libui.Grid @@ -26,7 +27,7 @@ type Aerc struct { } func NewAerc(conf *config.AercConfig, logger *log.Logger, - cmd func(cmd string) error) *Aerc { + cmd func(cmd string) error, complete func(cmd string) []string) *Aerc { tabs := libui.NewTabs() @@ -49,6 +50,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, accounts: make(map[string]*AccountView), conf: conf, cmd: cmd, + complete: complete, grid: grid, logger: logger, statusbar: statusbar, @@ -289,6 +291,8 @@ func (aerc *Aerc) BeginExCommand() { }, func() { aerc.statusbar.Pop() aerc.focus(previous) + }, func(cmd string) []string { + return aerc.complete(cmd) }) aerc.statusbar.Push(exline) aerc.focus(exline) diff --git a/widgets/dirlist.go b/widgets/dirlist.go index 2b4773a..71cf79d 100644 --- a/widgets/dirlist.go +++ b/widgets/dirlist.go @@ -40,6 +40,10 @@ func NewDirectoryList(acctConf *config.AccountConfig, uiConf *config.UIConfig, return dirlist } +func (dirlist *DirectoryList) List() []string { + return dirlist.dirs +} + func (dirlist *DirectoryList) UpdateList(done func(dirs []string)) { var dirs []string dirlist.worker.PostAction( diff --git a/widgets/exline.go b/widgets/exline.go index ff18d13..e984ee1 100644 --- a/widgets/exline.go +++ b/widgets/exline.go @@ -8,17 +8,21 @@ import ( type ExLine struct { ui.Invalidatable - cancel func() - commit func(cmd string) - input *ui.TextInput + cancel func() + commit func(cmd string) + tabcomplete func(cmd string) []string + input *ui.TextInput } -func NewExLine(commit func(cmd string), cancel func()) *ExLine { +func NewExLine(commit func(cmd string), cancel func(), + tabcomplete func(cmd string) []string) *ExLine { + input := ui.NewTextInput("").Prompt(":") exline := &ExLine{ - cancel: cancel, - commit: commit, - input: input, + cancel: cancel, + commit: commit, + tabcomplete: tabcomplete, + input: input, } input.OnInvalidate(func(d ui.Drawable) { exline.Invalidate() @@ -48,6 +52,12 @@ func (ex *ExLine) Event(event tcell.Event) bool { case tcell.KeyEsc, tcell.KeyCtrlC: ex.input.Focus(false) ex.cancel() + case tcell.KeyTab: + complete := ex.tabcomplete(ex.input.StringLeft()) + if len(complete) == 1 { + ex.input.Set(complete[0] + " " + ex.input.StringRight()) + } + ex.Invalidate() default: return ex.input.Event(event) }