diff options
| author | Drew DeVault <sir@cmpwn.com> | 2019-03-17 16:19:15 -0400 | 
|---|---|---|
| committer | Drew DeVault <sir@cmpwn.com> | 2019-03-17 16:19:15 -0400 | 
| commit | 589db742cb2af4b29607ceba62ceca38ec982f62 (patch) | |
| tree | 0975f74796cb809cd1bd8d04ee0c7a052a45f3f5 | |
| parent | 9e28a02f6a4345ec7b5fdee68864610186f34e91 (diff) | |
Move exline handling up to aerc, add :term
| -rw-r--r-- | commands/term.go | 33 | ||||
| -rw-r--r-- | lib/ui/tab.go | 8 | ||||
| -rw-r--r-- | widgets/account.go | 120 | ||||
| -rw-r--r-- | widgets/aerc.go | 129 | ||||
| -rw-r--r-- | widgets/tabhost.go | 11 | 
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 +} | 
