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 (
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 {

View file

@ -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)
}

View file

@ -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 <folder>")
}

View file

@ -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")
}

View file

@ -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 <name>")
}

View file

@ -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))
}

View file

@ -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 [<n>[%%]]", 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 [<n>[%%]]", cmd))
}

View file

@ -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 [<n>[%%]]", 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 [<n>[%%]]", cmd))
}

View file

@ -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 <cmd> [args...]")
}

View file

@ -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()
)

View file

@ -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 <n>")
}

View file

@ -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")
}

View file

@ -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]")
}

View file

@ -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
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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))
}

View file

@ -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()

View file

@ -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)
}

View file

@ -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})
}

View file

@ -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 <flat|year|month>")
}

View file

@ -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

View file

@ -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")
}

View file

@ -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

View file

@ -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)
}

View file

@ -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])
}

View file

@ -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

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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))
}

View file

@ -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()

View file

@ -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")
}

View file

@ -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 <cmd> [args...]")
}

View file

@ -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':

View file

@ -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))
}

View file

@ -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]")

View file

@ -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))
}

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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)
}