statusline: implement per-account status
Implement a statusline state for each account. Keep the ex line and the push notifications global. Add account name prefix to push notifications. Prefix status line with account name when multiple accounts are available. Use account-specific status line for each tab where an account is defined. Handle threading, filter/search, viewer passthrough and connection status. Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
807870ea35
commit
2512c0403f
10 changed files with 196 additions and 45 deletions
|
@ -3,6 +3,7 @@ package account
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
)
|
||||
|
||||
|
@ -30,6 +31,7 @@ func (Clear) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
return errors.New("Cannot perform action. Messages still loading")
|
||||
}
|
||||
store.ApplyClear()
|
||||
aerc.ClearExtraStatus()
|
||||
acct.SetStatus(statusline.SearchFilterClear())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package account
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
"git.sr.ht/~rjarry/aerc/worker/types"
|
||||
)
|
||||
|
@ -26,12 +27,15 @@ func (Connection) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
if acct == nil {
|
||||
return errors.New("No account selected")
|
||||
}
|
||||
cb := func(msg types.WorkerMessage) {
|
||||
acct.SetStatus(statusline.ConnectionActivity(""))
|
||||
}
|
||||
if args[0] == "connect" {
|
||||
acct.Worker().PostAction(&types.Connect{}, nil)
|
||||
acct.SetStatus("Connecting...")
|
||||
acct.Worker().PostAction(&types.Connect{}, cb)
|
||||
acct.SetStatus(statusline.ConnectionActivity("Connecting..."))
|
||||
} else {
|
||||
acct.Worker().PostAction(&types.Disconnect{}, nil)
|
||||
acct.SetStatus("Disconnecting...")
|
||||
acct.Worker().PostAction(&types.Disconnect{}, cb)
|
||||
acct.SetStatus(statusline.ConnectionActivity("Disconnecting..."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ package account
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
)
|
||||
|
||||
|
@ -33,16 +34,16 @@ func (SearchFilter) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
|
||||
var cb func([]uint32)
|
||||
if args[0] == "filter" {
|
||||
aerc.SetExtraStatus("Filtering...")
|
||||
acct.SetStatus(statusline.FilterActivity("Filtering..."), statusline.Search(""))
|
||||
cb = func(uids []uint32) {
|
||||
aerc.SetExtraStatus(fmt.Sprintf("%s", args))
|
||||
acct.SetStatus(statusline.FilterResult(strings.Join(args, " ")))
|
||||
acct.Logger().Printf("Filter results: %v", uids)
|
||||
store.ApplyFilter(uids)
|
||||
}
|
||||
} else {
|
||||
aerc.SetExtraStatus("Searching...")
|
||||
acct.SetStatus(statusline.Search("Searching..."))
|
||||
cb = func(uids []uint32) {
|
||||
aerc.SetExtraStatus(fmt.Sprintf("%s", args))
|
||||
acct.SetStatus(statusline.Search(strings.Join(args, " ")))
|
||||
acct.Logger().Printf("Search results: %v", uids)
|
||||
store.ApplySearch(uids)
|
||||
// TODO: Remove when stores have multiple OnUpdate handlers
|
||||
|
|
|
@ -3,6 +3,7 @@ package msg
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
)
|
||||
|
||||
|
@ -34,6 +35,7 @@ func (ToggleThreads) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
return err
|
||||
}
|
||||
store.SetBuildThreads(!store.BuildThreads())
|
||||
acct.SetStatus(statusline.Threading(store.BuildThreads()))
|
||||
acct.Messages().Invalidate()
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package msgview
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/widgets"
|
||||
)
|
||||
|
||||
|
@ -26,10 +27,8 @@ func (ToggleKeyPassthrough) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
}
|
||||
mv, _ := aerc.SelectedTab().(*widgets.MessageViewer)
|
||||
keyPassthroughEnabled := mv.ToggleKeyPassthrough()
|
||||
if keyPassthroughEnabled {
|
||||
aerc.SetExtraStatus("[passthrough]")
|
||||
} else {
|
||||
aerc.ClearExtraStatus()
|
||||
if acct := mv.SelectedAccount(); acct != nil {
|
||||
acct.SetStatus(statusline.Passthrough(keyPassthroughEnabled))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ func (NextPrevTab) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
aerc.NextTab()
|
||||
}
|
||||
}
|
||||
aerc.UpdateStatus()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
133
lib/statusline/state.go
Normal file
133
lib/statusline/state.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package statusline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type State struct {
|
||||
Name string
|
||||
Multiple bool
|
||||
Separator string
|
||||
|
||||
Connection string
|
||||
ConnActivity string
|
||||
Connected bool
|
||||
|
||||
Search string
|
||||
Filter string
|
||||
FilterActivity string
|
||||
|
||||
Threading string
|
||||
Passthrough string
|
||||
}
|
||||
|
||||
func NewState(name string, multipleAccts bool, sep string) *State {
|
||||
return &State{Name: name, Multiple: multipleAccts, Separator: sep}
|
||||
}
|
||||
|
||||
func (s *State) String() string {
|
||||
var line []string
|
||||
if s.Connection != "" || s.ConnActivity != "" {
|
||||
conn := s.Connection
|
||||
if s.ConnActivity != "" {
|
||||
conn = s.ConnActivity
|
||||
}
|
||||
if s.Multiple {
|
||||
line = append(line, fmt.Sprintf("[%s] %s", s.Name, conn))
|
||||
} else {
|
||||
line = append(line, conn)
|
||||
}
|
||||
}
|
||||
if s.Connected {
|
||||
if s.FilterActivity != "" {
|
||||
line = append(line, s.FilterActivity)
|
||||
} else {
|
||||
if s.Filter != "" {
|
||||
line = append(line, s.Filter)
|
||||
}
|
||||
}
|
||||
if s.Search != "" {
|
||||
line = append(line, s.Search)
|
||||
}
|
||||
if s.Threading != "" {
|
||||
line = append(line, s.Threading)
|
||||
}
|
||||
if s.Passthrough != "" {
|
||||
line = append(line, s.Passthrough)
|
||||
}
|
||||
}
|
||||
return strings.Join(line, s.Separator)
|
||||
}
|
||||
|
||||
type SetStateFunc func(s *State)
|
||||
|
||||
func Connected(state bool) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.ConnActivity = ""
|
||||
s.Connected = state
|
||||
if state {
|
||||
s.Connection = "Connected"
|
||||
} else {
|
||||
s.Connection = "Disconnected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectionActivity(desc string) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.ConnActivity = desc
|
||||
}
|
||||
}
|
||||
|
||||
func SearchFilterClear() SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.Search = ""
|
||||
s.FilterActivity = ""
|
||||
s.Filter = ""
|
||||
}
|
||||
}
|
||||
|
||||
func FilterActivity(str string) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.FilterActivity = str
|
||||
}
|
||||
}
|
||||
|
||||
func FilterResult(str string) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.FilterActivity = ""
|
||||
s.Filter = concatFilters(s.Filter, str)
|
||||
}
|
||||
}
|
||||
|
||||
func concatFilters(existing, next string) string {
|
||||
if existing == "" {
|
||||
return next
|
||||
}
|
||||
return fmt.Sprintf("%s && %s", existing, next)
|
||||
}
|
||||
|
||||
func Search(desc string) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.Search = desc
|
||||
}
|
||||
}
|
||||
|
||||
func Threading(on bool) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.Threading = ""
|
||||
if on {
|
||||
s.Threading = "threading"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Passthrough(on bool) SetStateFunc {
|
||||
return func(s *State) {
|
||||
s.Passthrough = ""
|
||||
if on {
|
||||
s.Passthrough = "passthrough"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,12 +4,14 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
"git.sr.ht/~rjarry/aerc/config"
|
||||
"git.sr.ht/~rjarry/aerc/lib"
|
||||
"git.sr.ht/~rjarry/aerc/lib/sort"
|
||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||
"git.sr.ht/~rjarry/aerc/lib/ui"
|
||||
"git.sr.ht/~rjarry/aerc/models"
|
||||
"git.sr.ht/~rjarry/aerc/worker"
|
||||
|
@ -29,6 +31,7 @@ type AccountView struct {
|
|||
logger *log.Logger
|
||||
msglist *MessageList
|
||||
worker *types.Worker
|
||||
state *statusline.State
|
||||
}
|
||||
|
||||
func (acct *AccountView) UiConfig() config.UIConfig {
|
||||
|
@ -55,6 +58,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
|
|||
conf: conf,
|
||||
host: host,
|
||||
logger: logger,
|
||||
state: statusline.NewState(acct.Name, len(conf.Accounts) > 1, " | "),
|
||||
}
|
||||
|
||||
view.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
||||
|
@ -86,7 +90,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
|
|||
|
||||
worker.PostAction(&types.Configure{Config: acct}, nil)
|
||||
worker.PostAction(&types.Connect{}, nil)
|
||||
host.SetStatus("Connecting...")
|
||||
view.SetStatus(statusline.ConnectionActivity("Connecting..."))
|
||||
|
||||
return view, nil
|
||||
}
|
||||
|
@ -105,8 +109,22 @@ func (acct *AccountView) Tick() bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (acct *AccountView) SetStatus(msg string) {
|
||||
acct.host.SetStatus(msg)
|
||||
func (acct *AccountView) SetStatus(setters ...statusline.SetStateFunc) {
|
||||
for _, fn := range setters {
|
||||
fn(acct.state)
|
||||
}
|
||||
}
|
||||
|
||||
func (acct *AccountView) UpdateStatus() {
|
||||
acct.host.SetStatus(acct.state.String())
|
||||
}
|
||||
|
||||
func (acct *AccountView) PushStatus(status string, expiry time.Duration) {
|
||||
acct.aerc.PushStatus(fmt.Sprintf("%s: %v", acct.acct.Name, status), expiry)
|
||||
}
|
||||
|
||||
func (acct *AccountView) PushError(err error) {
|
||||
acct.aerc.PushError(fmt.Sprintf("%s: %v", acct.acct.Name, err))
|
||||
}
|
||||
|
||||
func (acct *AccountView) AccountConfig() *config.AccountConfig {
|
||||
|
@ -140,6 +158,7 @@ func (acct *AccountView) Invalidate() {
|
|||
}
|
||||
|
||||
func (acct *AccountView) Draw(ctx *ui.Context) {
|
||||
acct.UpdateStatus()
|
||||
acct.grid.Draw(ctx)
|
||||
}
|
||||
|
||||
|
@ -203,7 +222,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
case *types.Done:
|
||||
switch msg.InResponseTo().(type) {
|
||||
case *types.Connect, *types.Reconnect:
|
||||
acct.host.SetStatus("Listing mailboxes...")
|
||||
acct.SetStatus(statusline.ConnectionActivity("Listing mailboxes..."))
|
||||
acct.logger.Println("Listing mailboxes...")
|
||||
acct.dirlist.UpdateList(func(dirs []string) {
|
||||
var dir string
|
||||
|
@ -221,13 +240,13 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
}
|
||||
acct.msglist.SetInitDone()
|
||||
acct.logger.Println("Connected.")
|
||||
acct.host.SetStatus("Connected.")
|
||||
acct.SetStatus(statusline.Connected(true))
|
||||
})
|
||||
case *types.Disconnect:
|
||||
acct.dirlist.UpdateList(nil)
|
||||
acct.msglist.SetStore(nil)
|
||||
acct.logger.Println("Disconnected.")
|
||||
acct.host.SetStatus("Disconnected.")
|
||||
acct.SetStatus(statusline.Connected(false))
|
||||
case *types.OpenDirectory:
|
||||
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
||||
// If we've opened this dir before, we can re-render it from
|
||||
|
@ -289,14 +308,14 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|||
case *types.LabelList:
|
||||
acct.labels = msg.Labels
|
||||
case *types.ConnError:
|
||||
acct.logger.Printf("connection error: %v", msg.Error)
|
||||
acct.host.SetStatus("Disconnected.")
|
||||
acct.aerc.PushError(fmt.Sprintf("%v", msg.Error))
|
||||
acct.logger.Printf("connection error: [%s] %v", acct.acct.Name, msg.Error)
|
||||
acct.SetStatus(statusline.Connected(false))
|
||||
acct.PushError(msg.Error)
|
||||
acct.msglist.SetStore(nil)
|
||||
acct.worker.PostAction(&types.Reconnect{}, nil)
|
||||
case *types.Error:
|
||||
acct.logger.Printf("%v", msg.Error)
|
||||
acct.aerc.PushError(fmt.Sprintf("%v", msg.Error))
|
||||
acct.PushError(msg.Error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,7 +325,7 @@ func (acct *AccountView) getSortCriteria() []*types.SortCriterion {
|
|||
}
|
||||
criteria, err := sort.GetSortCriteria(acct.UiConfig().Sort)
|
||||
if err != nil {
|
||||
acct.aerc.PushError(" ui.sort: " + err.Error())
|
||||
acct.PushError(fmt.Errorf("ui sort: %v", err))
|
||||
return nil
|
||||
}
|
||||
return criteria
|
||||
|
|
|
@ -337,6 +337,7 @@ func (aerc *Aerc) NumTabs() int {
|
|||
func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
|
||||
tab := aerc.tabs.Add(clickable, name)
|
||||
aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
|
||||
aerc.UpdateStatus()
|
||||
return tab
|
||||
}
|
||||
|
||||
|
@ -400,17 +401,20 @@ func (aerc *Aerc) SelectPreviousTab() bool {
|
|||
return aerc.tabs.SelectPrevious()
|
||||
}
|
||||
|
||||
// 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) SetExtraStatus(status string) {
|
||||
aerc.statusline.SetExtra(status)
|
||||
func (aerc *Aerc) UpdateStatus() {
|
||||
if acct := aerc.SelectedAccount(); acct != nil {
|
||||
acct.UpdateStatus()
|
||||
} else {
|
||||
aerc.ClearStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func (aerc *Aerc) ClearExtraStatus() {
|
||||
aerc.statusline.ClearExtra()
|
||||
func (aerc *Aerc) ClearStatus() {
|
||||
aerc.statusline.Set("")
|
||||
}
|
||||
|
||||
func (aerc *Aerc) SetError(status string) *StatusMessage {
|
||||
|
|
|
@ -14,7 +14,6 @@ type StatusLine struct {
|
|||
ui.Invalidatable
|
||||
stack []*StatusMessage
|
||||
fallback StatusMessage
|
||||
extra string
|
||||
aerc *Aerc
|
||||
uiConfig config.UIConfig
|
||||
}
|
||||
|
@ -30,7 +29,6 @@ func NewStatusLine(uiConfig config.UIConfig) *StatusLine {
|
|||
style: uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT),
|
||||
message: "Idle",
|
||||
},
|
||||
extra: "",
|
||||
uiConfig: uiConfig,
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +49,7 @@ func (status *StatusLine) Draw(ctx *ui.Context) {
|
|||
pendingKeys += string(pendingKey.Rune)
|
||||
}
|
||||
}
|
||||
text := line.message
|
||||
if status.extra != "" {
|
||||
text += " " + status.extra
|
||||
}
|
||||
message := runewidth.FillRight(text, ctx.Width()-len(pendingKeys)-5)
|
||||
message := runewidth.FillRight(line.message, ctx.Width()-len(pendingKeys)-5)
|
||||
ctx.Printf(0, 0, line.style, "%s%s", message, pendingKeys)
|
||||
}
|
||||
|
||||
|
@ -109,14 +103,6 @@ func (status *StatusLine) PushSuccess(text string) *StatusMessage {
|
|||
return msg
|
||||
}
|
||||
|
||||
func (status *StatusLine) SetExtra(text string) {
|
||||
status.extra = text
|
||||
}
|
||||
|
||||
func (status *StatusLine) ClearExtra() {
|
||||
status.extra = ""
|
||||
}
|
||||
|
||||
func (status *StatusLine) Expire() {
|
||||
status.stack = nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue