aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/term.go33
-rw-r--r--lib/ui/tab.go8
-rw-r--r--widgets/account.go120
-rw-r--r--widgets/aerc.go129
-rw-r--r--widgets/tabhost.go11
5 files changed, 186 insertions, 115 deletions
diff --git a/commands/term.go b/commands/term.go
new file mode 100644
index 0000000..0a2aa3b
--- /dev/null
+++ b/commands/term.go
@@ -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
+}
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index ecd48eb..e41e906 100644
--- a/lib/ui/tab.go
+++ b/lib/ui/tab.go
@@ -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) {
diff --git a/widgets/account.go b/widgets/account.go
index b6ba595..8a3b989 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -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)
}
}
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 3537897..5841876 100644
--- a/widgets/aerc.go
+++ b/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)
+}
diff --git a/widgets/tabhost.go b/widgets/tabhost.go
new file mode 100644
index 0000000..7c502cb
--- /dev/null
+++ b/widgets/tabhost.go
@@ -0,0 +1,11 @@
+package widgets
+
+import (
+ "time"
+)
+
+type TabHost interface {
+ BeginExCommand()
+ SetStatus(status string) *StatusMessage
+ PushStatus(text string, expiry time.Duration) *StatusMessage
+}