Move exline handling up to aerc, add :term
This commit is contained in:
parent
9e28a02f6a
commit
589db742cb
5 changed files with 186 additions and 115 deletions
33
commands/term.go
Normal file
33
commands/term.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
|
||||
"git.sr.ht/~sircmpwn/aerc2/lib/ui"
|
||||
"git.sr.ht/~sircmpwn/aerc2/widgets"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("term", Term)
|
||||
}
|
||||
|
||||
func Term(aerc *widgets.Aerc, args []string) error {
|
||||
if len(args) > 2 {
|
||||
return errors.New("Usage: term [<command>]")
|
||||
}
|
||||
term, err := widgets.NewTerminal(exec.Command(args[1], args[2:]...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
}).Columns([]ui.GridSpec{
|
||||
{ui.SIZE_EXACT, aerc.Config().Ui.SidebarWidth},
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
})
|
||||
grid.AddChild(term).At(0, 1)
|
||||
aerc.NewTab(grid, "Terminal")
|
||||
// TODO: update tab name when child process changes it
|
||||
return nil
|
||||
}
|
|
@ -30,13 +30,15 @@ func NewTabs() *Tabs {
|
|||
return tabs
|
||||
}
|
||||
|
||||
func (tabs *Tabs) Add(content Drawable, name string) {
|
||||
tabs.Tabs = append(tabs.Tabs, &Tab{
|
||||
func (tabs *Tabs) Add(content Drawable, name string) *Tab {
|
||||
tab := &Tab{
|
||||
Content: content,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
tabs.Tabs = append(tabs.Tabs, tab)
|
||||
tabs.TabStrip.Invalidate()
|
||||
content.OnInvalidate(tabs.invalidateChild)
|
||||
return tab
|
||||
}
|
||||
|
||||
func (tabs *Tabs) invalidateChild(d Drawable) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package widgets
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
||||
|
@ -19,63 +18,51 @@ type AccountView struct {
|
|||
conf *config.AercConfig
|
||||
dirlist *DirectoryList
|
||||
grid *ui.Grid
|
||||
host TabHost
|
||||
logger *log.Logger
|
||||
interactive []ui.Interactive
|
||||
onInvalidate func(d ui.Drawable)
|
||||
runCmd func(cmd string) error
|
||||
msglist *MessageList
|
||||
msgStores map[string]*lib.MessageStore
|
||||
pendingKeys []config.KeyStroke
|
||||
statusline *StatusLine
|
||||
statusbar *ui.Stack
|
||||
worker *types.Worker
|
||||
}
|
||||
|
||||
func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
||||
logger *log.Logger, runCmd func(cmd string) error) *AccountView {
|
||||
|
||||
statusbar := ui.NewStack()
|
||||
statusline := NewStatusLine()
|
||||
statusbar.Push(statusline)
|
||||
logger *log.Logger, host TabHost) *AccountView {
|
||||
|
||||
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
{ui.SIZE_EXACT, 1},
|
||||
}).Columns([]ui.GridSpec{
|
||||
{ui.SIZE_EXACT, conf.Ui.SidebarWidth},
|
||||
{ui.SIZE_WEIGHT, 1},
|
||||
})
|
||||
grid.AddChild(statusbar).At(1, 1)
|
||||
|
||||
worker, err := worker.NewWorker(acct.Source, logger)
|
||||
if err != nil {
|
||||
statusline.Set(fmt.Sprintf("%s", err))
|
||||
host.SetStatus(fmt.Sprintf("%s: %s", acct.Name, err))
|
||||
return &AccountView{
|
||||
acct: acct,
|
||||
grid: grid,
|
||||
logger: logger,
|
||||
statusline: statusline,
|
||||
acct: acct,
|
||||
grid: grid,
|
||||
host: host,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
dirlist := NewDirectoryList(acct, logger, worker)
|
||||
grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT)).Span(2, 1)
|
||||
grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT))
|
||||
|
||||
msglist := NewMessageList(logger)
|
||||
grid.AddChild(msglist).At(0, 1)
|
||||
|
||||
view := &AccountView{
|
||||
acct: acct,
|
||||
conf: conf,
|
||||
dirlist: dirlist,
|
||||
grid: grid,
|
||||
logger: logger,
|
||||
msglist: msglist,
|
||||
msgStores: make(map[string]*lib.MessageStore),
|
||||
runCmd: runCmd,
|
||||
statusbar: statusbar,
|
||||
statusline: statusline,
|
||||
worker: worker,
|
||||
acct: acct,
|
||||
conf: conf,
|
||||
dirlist: dirlist,
|
||||
grid: grid,
|
||||
host: host,
|
||||
logger: logger,
|
||||
msglist: msglist,
|
||||
msgStores: make(map[string]*lib.MessageStore),
|
||||
worker: worker,
|
||||
}
|
||||
|
||||
go worker.Backend.Run()
|
||||
|
@ -89,7 +76,7 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
|||
|
||||
worker.PostAction(&types.Configure{Config: acct}, nil)
|
||||
worker.PostAction(&types.Connect{}, view.connected)
|
||||
statusline.Set("Connecting...")
|
||||
host.SetStatus("Connecting...")
|
||||
|
||||
return view
|
||||
}
|
||||
|
@ -116,75 +103,14 @@ func (acct *AccountView) Draw(ctx *ui.Context) {
|
|||
acct.grid.Draw(ctx)
|
||||
}
|
||||
|
||||
func (acct *AccountView) popInteractive() {
|
||||
acct.interactive = acct.interactive[:len(acct.interactive)-1]
|
||||
if len(acct.interactive) != 0 {
|
||||
acct.interactive[len(acct.interactive)-1].Focus(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (acct *AccountView) pushInteractive(item ui.Interactive) {
|
||||
if len(acct.interactive) != 0 {
|
||||
acct.interactive[len(acct.interactive)-1].Focus(false)
|
||||
}
|
||||
acct.interactive = append(acct.interactive, item)
|
||||
item.Focus(true)
|
||||
}
|
||||
|
||||
func (acct *AccountView) beginExCommand() {
|
||||
exline := NewExLine(func(command string) {
|
||||
err := acct.runCmd(command)
|
||||
if err != nil {
|
||||
acct.statusline.Push(" "+err.Error(), 10*time.Second).
|
||||
Color(tcell.ColorRed, tcell.ColorWhite)
|
||||
}
|
||||
acct.statusbar.Pop()
|
||||
acct.popInteractive()
|
||||
}, func() {
|
||||
acct.statusbar.Pop()
|
||||
acct.popInteractive()
|
||||
})
|
||||
acct.pushInteractive(exline)
|
||||
acct.statusbar.Push(exline)
|
||||
}
|
||||
|
||||
func (acct *AccountView) Event(event tcell.Event) bool {
|
||||
if len(acct.interactive) != 0 {
|
||||
return acct.interactive[len(acct.interactive)-1].Event(event)
|
||||
}
|
||||
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
acct.pendingKeys = append(acct.pendingKeys, config.KeyStroke{
|
||||
Key: event.Key(),
|
||||
Rune: event.Rune(),
|
||||
})
|
||||
result, output := acct.conf.Lbinds.GetBinding(acct.pendingKeys)
|
||||
switch result {
|
||||
case config.BINDING_FOUND:
|
||||
acct.pendingKeys = []config.KeyStroke{}
|
||||
for _, stroke := range output {
|
||||
simulated := tcell.NewEventKey(
|
||||
stroke.Key, stroke.Rune, tcell.ModNone)
|
||||
acct.Event(simulated)
|
||||
}
|
||||
case config.BINDING_INCOMPLETE:
|
||||
return false
|
||||
case config.BINDING_NOT_FOUND:
|
||||
acct.pendingKeys = []config.KeyStroke{}
|
||||
if event.Rune() == ':' {
|
||||
acct.beginExCommand()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
func (acct *AccountView) Focus(focus bool) {
|
||||
// TODO: Unfocus children I guess
|
||||
}
|
||||
|
||||
func (acct *AccountView) connected(msg types.WorkerMessage) {
|
||||
switch msg := msg.(type) {
|
||||
case *types.Done:
|
||||
acct.statusline.Set("Listing mailboxes...")
|
||||
acct.host.SetStatus("Listing mailboxes...")
|
||||
acct.logger.Println("Listing mailboxes...")
|
||||
acct.dirlist.UpdateList(func(dirs []string) {
|
||||
var dir string
|
||||
|
@ -199,7 +125,7 @@ func (acct *AccountView) connected(msg types.WorkerMessage) {
|
|||
}
|
||||
acct.dirlist.Select(dir)
|
||||
acct.logger.Println("Connected.")
|
||||
acct.statusline.Set("Connected.")
|
||||
acct.host.SetStatus("Connected.")
|
||||
})
|
||||
case *types.CertificateApprovalRequest:
|
||||
// TODO: Ask the user
|
||||
|
@ -252,7 +178,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
store.Update(msg)
|
||||
case *types.Error:
|
||||
acct.logger.Printf("%v", msg.Error)
|
||||
acct.statusline.Set(fmt.Sprintf("%v", msg.Error)).
|
||||
acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)).
|
||||
Color(tcell.ColorRed, tcell.ColorDefault)
|
||||
}
|
||||
}
|
||||
|
|
129
widgets/aerc.go
129
widgets/aerc.go
|
@ -2,6 +2,7 @@ package widgets
|
|||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
||||
|
@ -11,10 +12,16 @@ import (
|
|||
)
|
||||
|
||||
type Aerc struct {
|
||||
accounts map[string]*AccountView
|
||||
cmd func(cmd string) error
|
||||
grid *libui.Grid
|
||||
tabs *libui.Tabs
|
||||
accounts map[string]*AccountView
|
||||
cmd func(cmd string) error
|
||||
conf *config.AercConfig
|
||||
focused libui.Interactive
|
||||
grid *libui.Grid
|
||||
logger *log.Logger
|
||||
statusbar *libui.Stack
|
||||
statusline *StatusLine
|
||||
pendingKeys []config.KeyStroke
|
||||
tabs *libui.Tabs
|
||||
}
|
||||
|
||||
func NewAerc(conf *config.AercConfig, logger *log.Logger,
|
||||
|
@ -22,29 +29,42 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
|
|||
|
||||
tabs := libui.NewTabs()
|
||||
|
||||
mainGrid := libui.NewGrid().Rows([]libui.GridSpec{
|
||||
statusbar := ui.NewStack()
|
||||
statusline := NewStatusLine()
|
||||
statusbar.Push(statusline)
|
||||
|
||||
grid := libui.NewGrid().Rows([]libui.GridSpec{
|
||||
{libui.SIZE_EXACT, 1},
|
||||
{libui.SIZE_WEIGHT, 1},
|
||||
{libui.SIZE_EXACT, 1},
|
||||
}).Columns([]libui.GridSpec{
|
||||
{libui.SIZE_EXACT, conf.Ui.SidebarWidth},
|
||||
{libui.SIZE_WEIGHT, 1},
|
||||
})
|
||||
grid.AddChild(statusbar).At(2, 1)
|
||||
// Minor hack
|
||||
grid.AddChild(libui.NewBordered(
|
||||
libui.NewFill(' '), libui.BORDER_RIGHT)).At(2, 0)
|
||||
|
||||
mainGrid.AddChild(libui.NewText("aerc").
|
||||
grid.AddChild(libui.NewText("aerc").
|
||||
Strategy(libui.TEXT_CENTER).
|
||||
Color(tcell.ColorBlack, tcell.ColorWhite))
|
||||
mainGrid.AddChild(tabs.TabStrip).At(0, 1)
|
||||
mainGrid.AddChild(tabs.TabContent).At(1, 0).Span(1, 2)
|
||||
grid.AddChild(tabs.TabStrip).At(0, 1)
|
||||
grid.AddChild(tabs.TabContent).At(1, 0).Span(1, 2)
|
||||
|
||||
aerc := &Aerc{
|
||||
accounts: make(map[string]*AccountView),
|
||||
cmd: cmd,
|
||||
grid: mainGrid,
|
||||
tabs: tabs,
|
||||
accounts: make(map[string]*AccountView),
|
||||
conf: conf,
|
||||
cmd: cmd,
|
||||
grid: grid,
|
||||
logger: logger,
|
||||
statusbar: statusbar,
|
||||
statusline: statusline,
|
||||
tabs: tabs,
|
||||
}
|
||||
|
||||
for _, acct := range conf.Accounts {
|
||||
view := NewAccountView(conf, &acct, logger, cmd)
|
||||
view := NewAccountView(conf, &acct, logger, aerc)
|
||||
aerc.accounts[acct.Name] = view
|
||||
tabs.Add(view, acct.Name)
|
||||
}
|
||||
|
@ -75,8 +95,41 @@ func (aerc *Aerc) Draw(ctx *libui.Context) {
|
|||
}
|
||||
|
||||
func (aerc *Aerc) Event(event tcell.Event) bool {
|
||||
acct, _ := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(*AccountView)
|
||||
return acct.Event(event)
|
||||
if aerc.focused != nil {
|
||||
aerc.logger.Println("sending event to focused child")
|
||||
return aerc.focused.Event(event)
|
||||
}
|
||||
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
aerc.pendingKeys = append(aerc.pendingKeys, config.KeyStroke{
|
||||
Key: event.Key(),
|
||||
Rune: event.Rune(),
|
||||
})
|
||||
result, output := aerc.conf.Lbinds.GetBinding(aerc.pendingKeys)
|
||||
switch result {
|
||||
case config.BINDING_FOUND:
|
||||
aerc.pendingKeys = []config.KeyStroke{}
|
||||
for _, stroke := range output {
|
||||
simulated := tcell.NewEventKey(
|
||||
stroke.Key, stroke.Rune, tcell.ModNone)
|
||||
aerc.Event(simulated)
|
||||
}
|
||||
case config.BINDING_INCOMPLETE:
|
||||
return false
|
||||
case config.BINDING_NOT_FOUND:
|
||||
aerc.pendingKeys = []config.KeyStroke{}
|
||||
if event.Rune() == ':' {
|
||||
aerc.BeginExCommand()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (aerc *Aerc) Config() *config.AercConfig {
|
||||
return aerc.conf
|
||||
}
|
||||
|
||||
func (aerc *Aerc) SelectedAccount() *AccountView {
|
||||
|
@ -86,3 +139,49 @@ func (aerc *Aerc) SelectedAccount() *AccountView {
|
|||
}
|
||||
return acct
|
||||
}
|
||||
|
||||
func (aerc *Aerc) NewTab(drawable ui.Drawable, name string) *ui.Tab {
|
||||
tab := aerc.tabs.Add(drawable, name)
|
||||
aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
|
||||
return tab
|
||||
}
|
||||
|
||||
// TODO: Use per-account status lines, but a global ex line
|
||||
func (aerc *Aerc) SetStatus(status string) *StatusMessage {
|
||||
return aerc.statusline.Set(status)
|
||||
}
|
||||
|
||||
func (aerc *Aerc) PushStatus(text string, expiry time.Duration) *StatusMessage {
|
||||
return aerc.statusline.Push(text, expiry)
|
||||
}
|
||||
|
||||
func (aerc *Aerc) focus(item libui.Interactive) {
|
||||
if aerc.focused == item {
|
||||
return
|
||||
}
|
||||
if aerc.focused != nil {
|
||||
aerc.focused.Focus(false)
|
||||
}
|
||||
aerc.focused = item
|
||||
if item != nil {
|
||||
item.Focus(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (aerc *Aerc) BeginExCommand() {
|
||||
previous := aerc.focused
|
||||
exline := NewExLine(func(cmd string) {
|
||||
err := aerc.cmd(cmd)
|
||||
if err != nil {
|
||||
aerc.PushStatus(" "+err.Error(), 10*time.Second).
|
||||
Color(tcell.ColorRed, tcell.ColorWhite)
|
||||
}
|
||||
aerc.statusbar.Pop()
|
||||
aerc.focus(previous)
|
||||
}, func() {
|
||||
aerc.statusbar.Pop()
|
||||
aerc.focus(previous)
|
||||
})
|
||||
aerc.statusbar.Push(exline)
|
||||
aerc.focus(exline)
|
||||
}
|
||||
|
|
11
widgets/tabhost.go
Normal file
11
widgets/tabhost.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package widgets
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type TabHost interface {
|
||||
BeginExCommand()
|
||||
SetStatus(status string) *StatusMessage
|
||||
PushStatus(text string, expiry time.Duration) *StatusMessage
|
||||
}
|
Loading…
Reference in a new issue