Implement basic tab completion support

Tab completion currently only works on commands. Contextual completion
will be added in the future.
This commit is contained in:
Gregory Mullen 2019-06-27 10:33:11 -07:00 committed by Drew DeVault
parent 177651bdda
commit 2a0961701c
47 changed files with 598 additions and 154 deletions

60
aerc.go
View file

@ -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 ( var (
Prefix string Prefix string
ShareDir string ShareDir string
@ -96,27 +131,12 @@ func main() {
aerc *widgets.Aerc aerc *widgets.Aerc
ui *libui.UI ui *libui.UI
) )
aerc = widgets.NewAerc(conf, logger, func(cmd string) error { aerc = widgets.NewAerc(conf, logger, func(cmd string) error {
cmds := getCommands(aerc.SelectedTab()) return execCommand(aerc, ui, cmd)
for i, set := range cmds { }, func(cmd string) []string {
err := set.ExecuteCommand(aerc, cmd) return getCompletions(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
})
ui, err = libui.Initialize(conf, aerc) ui, err = libui.Initialize(conf, aerc)
if err != nil { if err != nil {

View file

@ -8,9 +8,9 @@ var (
AccountCommands *commands.Commands AccountCommands *commands.Commands
) )
func register(name string, cmd commands.AercCommand) { func register(cmd commands.Command) {
if AccountCommands == nil { if AccountCommands == nil {
AccountCommands = commands.NewCommands() AccountCommands = commands.NewCommands()
} }
AccountCommands.Register(name, cmd) AccountCommands.Register(cmd)
} }

View file

@ -10,12 +10,22 @@ var (
history map[string]string history map[string]string
) )
type ChangeFolder struct{}
func init() { func init() {
history = make(map[string]string) 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 { if len(args) != 2 {
return errors.New("Usage: cf <folder>") return errors.New("Usage: cf <folder>")
} }

View file

@ -6,12 +6,22 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Compose struct{}
func init() { 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 // 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 { if len(args) != 1 {
return errors.New("Usage: compose") return errors.New("Usage: compose")
} }

View file

@ -10,11 +10,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type MakeDir struct{}
func init() { 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 { if len(args) != 2 {
return errors.New("Usage: :mkdir <name>") return errors.New("Usage: :mkdir <name>")
} }

View file

@ -8,16 +8,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevFolder struct{}
func init() { func init() {
register("next-folder", NextPrevFolder) register(NextPrevFolder{})
register("prev-folder", NextPrevFolder)
} }
func nextPrevFolderUsage(cmd string) error { func (_ NextPrevFolder) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) 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 { if len(args) > 2 {
return nextPrevFolderUsage(args[0]) return nextPrevFolderUsage(args[0])
} }
@ -44,3 +49,7 @@ func NextPrevFolder(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevFolderUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd))
}

View file

@ -7,16 +7,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevResult struct{}
func init() { func init() {
register("next-result", NextPrevResult) register(NextPrevResult{})
register("prev-result", NextPrevResult)
} }
func nextPrevResultUsage(cmd string) error { func (_ NextPrevResult) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s [<n>[%%]]", cmd)) 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 { if len(args) > 1 {
return nextPrevResultUsage(args[0]) return nextPrevResultUsage(args[0])
} }
@ -39,3 +44,7 @@ func NextPrevResult(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevResultUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s [<n>[%%]]", cmd))
}

View file

@ -9,18 +9,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevMsg struct{}
func init() { func init() {
register("next", NextPrevMessage) register(NextPrevMsg{})
register("next-message", NextPrevMessage)
register("prev", NextPrevMessage)
register("prev-message", NextPrevMessage)
} }
func nextPrevMessageUsage(cmd string) error { func (_ NextPrevMsg) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s [<n>[%%]]", cmd)) 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 { if len(args) > 2 {
return nextPrevMessageUsage(args[0]) return nextPrevMessageUsage(args[0])
} }
@ -63,3 +66,7 @@ func NextPrevMessage(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevMessageUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s [<n>[%%]]", cmd))
}

View file

@ -8,11 +8,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Pipe struct{}
func init() { 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 { if len(args) < 2 {
return errors.New("Usage: :pipe <cmd> [args...]") return errors.New("Usage: :pipe <cmd> [args...]")
} }

View file

@ -9,12 +9,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type SearchFilter struct{}
func init() { func init() {
register("search", SearchFilter) register(SearchFilter{})
//register("filter", SearchFilter) // TODO
} }
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 ( var (
criteria *imap.SearchCriteria = imap.NewSearchCriteria() criteria *imap.SearchCriteria = imap.NewSearchCriteria()
) )

View file

@ -7,12 +7,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type SelectMessage struct{}
func init() { func init() {
register("select", SelectMessage) register(SelectMessage{})
register("select-message", 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 { if len(args) != 2 {
return errors.New("Usage: :select-message <n>") return errors.New("Usage: :select-message <n>")
} }

View file

@ -6,12 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type ViewMessage struct{}
func init() { func init() {
register("view", ViewMessage) register(ViewMessage{})
register("view-message", 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 { if len(args) != 1 {
return errors.New("Usage: view-message") return errors.New("Usage: view-message")
} }

View file

@ -12,11 +12,21 @@ var (
previousDir string previousDir string
) )
type ChangeDirectory struct{}
func init() { 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 { if len(args) < 1 || len(args) > 2 {
return errors.New("Usage: cd [directory]") return errors.New("Usage: cd [directory]")
} }

View file

@ -2,27 +2,47 @@ package commands
import ( import (
"errors" "errors"
"strings"
"github.com/google/shlex" "github.com/google/shlex"
"git.sr.ht/~sircmpwn/aerc/widgets" "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 { func NewCommands() *Commands {
cmds := Commands(make(map[string]AercCommand)) cmds := Commands(make(map[string]Command))
return &cmds return &cmds
} }
func (cmds *Commands) dict() map[string]AercCommand { func (cmds *Commands) dict() map[string]Command {
return map[string]AercCommand(*cmds) return map[string]Command(*cmds)
} }
func (cmds *Commands) Register(name string, cmd AercCommand) { func (cmds *Commands) Names() []string {
cmds.dict()[name] = cmd 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 type NoSuchCommand string
@ -43,8 +63,48 @@ func (cmds *Commands) ExecuteCommand(aerc *widgets.Aerc, cmd string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("Expected a command.") return errors.New("Expected a command.")
} }
if fn, ok := cmds.dict()[args[0]]; ok { if cmd, ok := cmds.dict()[args[0]]; ok {
return fn(aerc, args) return cmd.Execute(aerc, args)
} }
return NoSuchCommand(args[0]) 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
}

View file

@ -6,11 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Abort struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: abort") return errors.New("Usage: abort")
} }

View file

@ -8,9 +8,9 @@ var (
ComposeCommands *commands.Commands ComposeCommands *commands.Commands
) )
func register(name string, cmd commands.AercCommand) { func register(cmd commands.Command) {
if ComposeCommands == nil { if ComposeCommands == nil {
ComposeCommands = commands.NewCommands() ComposeCommands = commands.NewCommands()
} }
ComposeCommands.Register(name, cmd) ComposeCommands.Register(cmd)
} }

View file

@ -6,11 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Edit struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: edit") return errors.New("Usage: edit")
} }

View file

@ -7,16 +7,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevField struct{}
func init() { func init() {
register("next-field", NextPrevField) register(NextPrevField{})
register("prev-field", NextPrevField)
} }
func nextPrevFieldUsage(cmd string) error { func (_ NextPrevField) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s", cmd)) 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 { if len(args) > 2 {
return nextPrevFieldUsage(args[0]) return nextPrevFieldUsage(args[0])
} }
@ -28,3 +33,7 @@ func NextPrevField(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevFieldUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s", cmd))
}

View file

@ -20,13 +20,23 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type Send struct{}
func init() { 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 { if len(args) > 1 {
return errors.New("Usage: send-message") return errors.New("Usage: send")
} }
composer, _ := aerc.SelectedTab().(*widgets.Composer) composer, _ := aerc.SelectedTab().(*widgets.Composer)
config := composer.Config() config := composer.Config()

View file

@ -4,9 +4,9 @@ var (
GlobalCommands *Commands GlobalCommands *Commands
) )
func register(name string, cmd AercCommand) { func register(cmd Command) {
if GlobalCommands == nil { if GlobalCommands == nil {
GlobalCommands = NewCommands() GlobalCommands = NewCommands()
} }
GlobalCommands.Register(name, cmd) GlobalCommands.Register(cmd)
} }

View file

@ -6,16 +6,26 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Help struct{}
func init() { 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" page := "aerc"
if len(args) == 2 { if len(args) == 2 {
page = "aerc-" + args[1] page = "aerc-" + args[1]
} else if len(args) > 2 { } else if len(args) > 2 {
return errors.New("Usage: help [topic]") return errors.New("Usage: help [topic]")
} }
return Term(aerc, []string{"term", "man", page}) return TermCore(aerc, []string{"term", "man", page})
} }

View file

@ -18,11 +18,21 @@ const (
ARCHIVE_MONTH = "month" ARCHIVE_MONTH = "month"
) )
type Archive struct{}
func init() { 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 { if len(args) != 2 {
return errors.New("Usage: archive <flat|year|month>") return errors.New("Usage: archive <flat|year|month>")
} }

View file

@ -11,12 +11,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type Copy struct{}
func init() { func init() {
register("cp", Copy) register(Copy{})
register("copy", 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") opts, optind, err := getopt.Getopts(args, "p")
if err != nil { if err != nil {
return err return err

View file

@ -10,12 +10,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type Delete struct{}
func init() { func init() {
register("delete", DeleteMessage) register(Delete{})
register("delete-message", DeleteMessage)
} }
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 { if len(args) != 1 {
return errors.New("Usage: :delete") return errors.New("Usage: :delete")
} }

View file

@ -11,12 +11,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type Move struct{}
func init() { func init() {
register("mv", Move) register(Move{})
register("move", 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") opts, optind, err := getopt.Getopts(args, "p")
if err != nil { if err != nil {
return err return err

View file

@ -8,9 +8,9 @@ var (
MessageCommands *commands.Commands MessageCommands *commands.Commands
) )
func register(name string, cmd commands.AercCommand) { func register(cmd commands.Command) {
if MessageCommands == nil { if MessageCommands == nil {
MessageCommands = commands.NewCommands() MessageCommands = commands.NewCommands()
} }
MessageCommands.Register(name, cmd) MessageCommands.Register(cmd)
} }

View file

@ -10,12 +10,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
type Read struct{}
func init() { func init() {
register("read", Read) register(Read{})
register("unread", 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 { if len(args) != 1 {
return errors.New("Usage: " + args[0]) return errors.New("Usage: " + args[0])
} }

View file

@ -18,12 +18,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type reply struct{}
func init() { func init() {
register("reply", Reply) register(reply{})
register("forward", 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") opts, optind, err := getopt.Getopts(args, "aq")
if err != nil { if err != nil {
return err return err

View file

@ -6,11 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Close struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: close") return errors.New("Usage: close")
} }

View file

@ -8,9 +8,9 @@ var (
MessageViewCommands *commands.Commands MessageViewCommands *commands.Commands
) )
func register(name string, cmd commands.AercCommand) { func register(cmd commands.Command) {
if MessageViewCommands == nil { if MessageViewCommands == nil {
MessageViewCommands = commands.NewCommands() MessageViewCommands = commands.NewCommands()
} }
MessageViewCommands.Register(name, cmd) MessageViewCommands.Register(cmd)
} }

View file

@ -8,16 +8,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevPart struct{}
func init() { func init() {
register("next-part", NextPrevPart) register(NextPrevPart{})
register("prev-part", NextPrevPart)
} }
func nextPrevPartUsage(cmd string) error { func (_ NextPrevPart) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) 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 { if len(args) > 2 {
return nextPrevPartUsage(args[0]) return nextPrevPartUsage(args[0])
} }
@ -41,3 +46,7 @@ func NextPrevPart(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevPartUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd))
}

View file

@ -6,14 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevMsg struct{}
func init() { func init() {
register("next", NextPrevMessage) register(NextPrevMsg{})
register("next-message", NextPrevMessage)
register("prev", NextPrevMessage)
register("prev-message", NextPrevMessage)
} }
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) mv, _ := aerc.SelectedTab().(*widgets.MessageViewer)
acct := mv.SelectedAccount() acct := mv.SelectedAccount()
store := mv.Store() store := mv.Store()

View file

@ -14,11 +14,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Open struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: open") return errors.New("Usage: open")
} }

View file

@ -12,11 +12,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Pipe struct{}
func init() { 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 { if len(args) < 2 {
return errors.New("Usage: :pipe <cmd> [args...]") return errors.New("Usage: :pipe <cmd> [args...]")
} }

View file

@ -16,19 +16,31 @@ import (
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
) )
type Save struct{}
func init() { 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") opts, optind, err := getopt.Getopts(args, "p")
if err != nil { if err != nil {
return err return err
} }
var ( var (
mkdirs bool mkdirs bool
path string path string
) )
for _, opt := range opts { for _, opt := range opts {
switch opt.Option { switch opt.Option {
case 'p': case 'p':

View file

@ -7,15 +7,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type ToggleHeaders struct{}
func init() { func init() {
register("toggle-headers", ToggleHeaders) register(ToggleHeaders{})
} }
func toggleHeadersUsage(cmd string) error { func (_ ToggleHeaders) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s", cmd)) 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 { if len(args) > 1 {
return toggleHeadersUsage(args[0]) return toggleHeadersUsage(args[0])
} }
@ -23,3 +29,7 @@ func ToggleHeaders(aerc *widgets.Aerc, args []string) error {
mv.ToggleHeaders() mv.ToggleHeaders()
return nil return nil
} }
func toggleHeadersUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s", cmd))
}

View file

@ -7,11 +7,21 @@ import (
"git.sr.ht/~sircmpwn/getopt" "git.sr.ht/~sircmpwn/getopt"
) )
type NewAccount struct{}
func init() { 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") opts, _, err := getopt.Getopts(args, "t")
if err != nil { if err != nil {
return errors.New("Usage: new-account [-t]") return errors.New("Usage: new-account [-t]")

View file

@ -8,16 +8,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type NextPrevTab struct{}
func init() { func init() {
register("next-tab", NextPrevTab) register(NextPrevTab{})
register("prev-tab", NextPrevTab)
} }
func nextPrevTabUsage(cmd string) error { func (_ NextPrevTab) Aliases() []string {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd)) 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 { if len(args) > 2 {
return nextPrevTabUsage(args[0]) return nextPrevTabUsage(args[0])
} }
@ -40,3 +45,7 @@ func NextPrevTab(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func nextPrevTabUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s [n]", cmd))
}

View file

@ -8,11 +8,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type PrintWorkDir struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: pwd") return errors.New("Usage: pwd")
} }

View file

@ -6,8 +6,18 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Quit struct{}
func init() { 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 type ErrorExit int
@ -16,7 +26,7 @@ func (err ErrorExit) Error() string {
return "exit" return "exit"
} }
func CommandQuit(aerc *widgets.Aerc, args []string) error { func (_ Quit) Execute(aerc *widgets.Aerc, args []string) error {
if len(args) != 1 { if len(args) != 1 {
return errors.New("Usage: quit") return errors.New("Usage: quit")
} }

View file

@ -10,11 +10,22 @@ import (
"github.com/riywo/loginshell" "github.com/riywo/loginshell"
) )
type Term struct{}
func init() { 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 { if len(args) == 1 {
shell, err := loginshell.Shell() shell, err := loginshell.Shell()
if err != nil { if err != nil {
@ -43,3 +54,7 @@ func Term(aerc *widgets.Aerc, args []string) error {
} }
return nil return nil
} }
func (_ Term) Execute(aerc *widgets.Aerc, args []string) error {
return TermCore(aerc, args)
}

View file

@ -6,11 +6,21 @@ import (
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
) )
type Close struct{}
func init() { 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 { if len(args) != 1 {
return errors.New("Usage: close") return errors.New("Usage: close")
} }

View file

@ -8,9 +8,9 @@ var (
TerminalCommands *commands.Commands TerminalCommands *commands.Commands
) )
func register(name string, cmd commands.AercCommand) { func register(cmd commands.Command) {
if TerminalCommands == nil { if TerminalCommands == nil {
TerminalCommands = commands.NewCommands() TerminalCommands = commands.NewCommands()
} }
TerminalCommands.Register(name, cmd) TerminalCommands.Register(cmd)
} }

View file

@ -46,6 +46,14 @@ func (ti *TextInput) String() string {
return string(ti.text) 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) { func (ti *TextInput) Set(value string) {
ti.text = []rune(value) ti.text = []rune(value)
ti.index = len(ti.text) ti.index = len(ti.text)

View file

@ -14,6 +14,7 @@ import (
type Aerc struct { type Aerc struct {
accounts map[string]*AccountView accounts map[string]*AccountView
cmd func(cmd string) error cmd func(cmd string) error
complete func(cmd string) []string
conf *config.AercConfig conf *config.AercConfig
focused libui.Interactive focused libui.Interactive
grid *libui.Grid grid *libui.Grid
@ -26,7 +27,7 @@ type Aerc struct {
} }
func NewAerc(conf *config.AercConfig, logger *log.Logger, 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() tabs := libui.NewTabs()
@ -49,6 +50,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
accounts: make(map[string]*AccountView), accounts: make(map[string]*AccountView),
conf: conf, conf: conf,
cmd: cmd, cmd: cmd,
complete: complete,
grid: grid, grid: grid,
logger: logger, logger: logger,
statusbar: statusbar, statusbar: statusbar,
@ -289,6 +291,8 @@ func (aerc *Aerc) BeginExCommand() {
}, func() { }, func() {
aerc.statusbar.Pop() aerc.statusbar.Pop()
aerc.focus(previous) aerc.focus(previous)
}, func(cmd string) []string {
return aerc.complete(cmd)
}) })
aerc.statusbar.Push(exline) aerc.statusbar.Push(exline)
aerc.focus(exline) aerc.focus(exline)

View file

@ -40,6 +40,10 @@ func NewDirectoryList(acctConf *config.AccountConfig, uiConf *config.UIConfig,
return dirlist return dirlist
} }
func (dirlist *DirectoryList) List() []string {
return dirlist.dirs
}
func (dirlist *DirectoryList) UpdateList(done func(dirs []string)) { func (dirlist *DirectoryList) UpdateList(done func(dirs []string)) {
var dirs []string var dirs []string
dirlist.worker.PostAction( dirlist.worker.PostAction(

View file

@ -8,17 +8,21 @@ import (
type ExLine struct { type ExLine struct {
ui.Invalidatable ui.Invalidatable
cancel func() cancel func()
commit func(cmd string) commit func(cmd string)
input *ui.TextInput 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(":") input := ui.NewTextInput("").Prompt(":")
exline := &ExLine{ exline := &ExLine{
cancel: cancel, cancel: cancel,
commit: commit, commit: commit,
input: input, tabcomplete: tabcomplete,
input: input,
} }
input.OnInvalidate(func(d ui.Drawable) { input.OnInvalidate(func(d ui.Drawable) {
exline.Invalidate() exline.Invalidate()
@ -48,6 +52,12 @@ func (ex *ExLine) Event(event tcell.Event) bool {
case tcell.KeyEsc, tcell.KeyCtrlC: case tcell.KeyEsc, tcell.KeyCtrlC:
ex.input.Focus(false) ex.input.Focus(false)
ex.cancel() 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: default:
return ex.input.Event(event) return ex.input.Event(event)
} }