about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2019-03-17 16:19:15 -0400
committerDrew DeVault <sir@cmpwn.com>2019-03-17 16:19:15 -0400
commit589db742cb2af4b29607ceba62ceca38ec982f62 (patch)
tree0975f74796cb809cd1bd8d04ee0c7a052a45f3f5
parent9e28a02f6a4345ec7b5fdee68864610186f34e91 (diff)
downloadaerc-589db742cb2af4b29607ceba62ceca38ec982f62.tar.gz
Move exline handling up to aerc, add :term
-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
+}