diff options
author | Robin Jarry <robin@jarry.cc> | 2022-12-27 22:59:10 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-02-20 14:48:42 +0100 |
commit | d2e74cdb91e140b50d14c3a8b315cde272f32587 (patch) | |
tree | 4f4a20c02f60b67d7a8eb967bb45d789a057453b /lib | |
parent | 6af06c9dfec03e923589d34187ba8358e3423d5c (diff) | |
download | aerc-d2e74cdb91e140b50d14c3a8b315cde272f32587.tar.gz |
statusline: add column based render format
In the spirit of commit 535300cfdbfc ("config: add columns based index
format"), reuse the column definitions and table widget.
Add automatic translation of render-format to column definitions. Allow
empty columns to be compatible with the %m (mute) flag.
Rename the State object to AccountState to be more precise. Reuse that
object in state.TempateData to expose account state info. Move actual
status line rendering in StatusLine.Draw().
Add new template fields for status specific data:
{{.ConnectionInfo}}
Connection state.
{{.ContentInfo}}
General status information (e.g. filter, search)
{{.StatusInfo}}
Combination of {{.ConnectionInfo}} and {{.StatusInfo}}
{{.TrayInfo}}
General on/off information (e.g. passthrough, threading,
sorting)
{{.PendingKeys}}
Currently pressed key sequence that does not match any key
binding and/or is incomplete.
Display a warning on startup if render-format has been converted to
status-columns.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/state/renderer.go | 205 | ||||
-rw-r--r-- | lib/state/state.go | 86 | ||||
-rw-r--r-- | lib/state/templates.go | 78 | ||||
-rw-r--r-- | lib/state/texter.go | 21 |
4 files changed, 121 insertions, 269 deletions
diff --git a/lib/state/renderer.go b/lib/state/renderer.go deleted file mode 100644 index 13e593fe..00000000 --- a/lib/state/renderer.go +++ /dev/null @@ -1,205 +0,0 @@ -package state - -import ( - "errors" - "fmt" - "os" - "strings" - "unicode" - - "git.sr.ht/~rjarry/aerc/config" - "github.com/mattn/go-runewidth" -) - -type renderParams struct { - width int - sep string - acct *accountState - fldr *folderState -} - -type renderFunc func(r renderParams) string - -func newRenderer() renderFunc { - var texter Texter - switch strings.ToLower(config.Statusline.DisplayMode) { - case "icon": - texter = &icon{} - default: - texter = &text{} - } - - return renderer(texter) -} - -func renderer(texter Texter) renderFunc { - var leftFmt, rightFmt string - if idx := strings.Index(config.Statusline.RenderFormat, "%>"); idx < 0 { - leftFmt = config.Statusline.RenderFormat - } else { - leftFmt = config.Statusline.RenderFormat[:idx] - rightFmt = strings.Replace(config.Statusline.RenderFormat[idx:], "%>", "", 1) - } - - return func(r renderParams) string { - lfmtStr, largs, err := parseStatuslineFormat(leftFmt, texter, r) - if err != nil { - return err.Error() - } - rfmtStr, rargs, err := parseStatuslineFormat(rightFmt, texter, r) - if err != nil { - return err.Error() - } - leftText, rightText := fmt.Sprintf(lfmtStr, largs...), fmt.Sprintf(rfmtStr, rargs...) - return runewidth.FillRight(leftText, r.width-len(rightText)-1) + rightText - } -} - -func connectionInfo(acct *accountState, texter Texter) (conn string) { - if acct.ConnActivity != "" { - conn += acct.ConnActivity - } else { - if acct.Connected { - conn += texter.Connected() - } else { - conn += texter.Disconnected() - } - } - return -} - -func contentInfo(acct *accountState, fldr *folderState, texter Texter) []string { - var status []string - if fldr.FilterActivity != "" { - status = append(status, fldr.FilterActivity) - } else if fldr.Filter != "" { - status = append(status, texter.FormatFilter(fldr.Filter)) - } - if fldr.Search != "" { - status = append(status, texter.FormatSearch(fldr.Search)) - } - return status -} - -func trayInfo(acct *accountState, fldr *folderState, texter Texter) []string { - var tray []string - if fldr.Sorting { - tray = append(tray, texter.Sorting()) - } - if fldr.Threading { - tray = append(tray, texter.Threading()) - } - if acct.Passthrough { - tray = append(tray, texter.Passthrough()) - } - return tray -} - -func parseStatuslineFormat(format string, texter Texter, r renderParams) (string, []interface{}, error) { - retval := make([]byte, 0, len(format)) - var args []interface{} - mute := false - - var c rune - for i, ni := 0, 0; i < len(format); { - ni = strings.IndexByte(format[i:], '%') - if ni < 0 { - ni = len(format) - retval = append(retval, []byte(format[i:ni])...) - break - } - ni += i + 1 - // Check for fmt flags - if ni == len(format) { - goto handle_end_error - } - c = rune(format[ni]) - if c == '+' || c == '-' || c == '#' || c == ' ' || c == '0' { - ni++ - } - - // Check for precision and width - if ni == len(format) { - goto handle_end_error - } - c = rune(format[ni]) - for unicode.IsDigit(c) { - ni++ - c = rune(format[ni]) - } - if c == '.' { - ni++ - c = rune(format[ni]) - for unicode.IsDigit(c) { - ni++ - c = rune(format[ni]) - } - } - - retval = append(retval, []byte(format[i:ni])...) - // Get final format verb - if ni == len(format) { - goto handle_end_error - } - c = rune(format[ni]) - switch c { - case '%': - retval = append(retval, '%') - case 'a': - retval = append(retval, 's') - args = append(args, r.acct.Name) - case 'c': - retval = append(retval, 's') - args = append(args, connectionInfo(r.acct, texter)) - case 'd': - retval = append(retval, 's') - args = append(args, r.fldr.Name) - case 'm': - mute = true - case 'S': - var status []string - if conn := connectionInfo(r.acct, texter); conn != "" { - status = append(status, conn) - } - - if r.acct.Connected { - status = append(status, contentInfo(r.acct, r.fldr, texter)...) - } - retval = append(retval, 's') - args = append(args, strings.Join(status, r.sep)) - case 'T': - var tray []string - if r.acct.Connected { - tray = trayInfo(r.acct, r.fldr, texter) - } - retval = append(retval, 's') - args = append(args, strings.Join(tray, r.sep)) - case 'p': - path, err := os.Getwd() - if err == nil { - home, _ := os.UserHomeDir() - if strings.HasPrefix(path, home) { - path = strings.Replace(path, home, "~", 1) - } - retval = append(retval, 's') - args = append(args, path) - } - default: - // Just ignore it and print as is - // so %k in index format becomes %%k to Printf - retval = append(retval, '%') - retval = append(retval, byte(c)) - } - i = ni + 1 - } - - if mute { - return "", nil, nil - } - - return string(retval), args, nil - -handle_end_error: - return "", nil, - errors.New("reached end of string while parsing statusline format") -} diff --git a/lib/state/state.go b/lib/state/state.go index b5f925ac..49431029 100644 --- a/lib/state/state.go +++ b/lib/state/state.go @@ -2,27 +2,16 @@ package state import ( "fmt" - - "git.sr.ht/~rjarry/aerc/config" ) -type State struct { - renderer renderFunc - acct *accountState - fldr map[string]*folderState - width int -} - -type accountState struct { - Name string - Multiple bool - ConnActivity string +type AccountState struct { Connected bool - Passthrough bool + connActivity string + passthrough bool + folders map[string]*folderState } type folderState struct { - Name string Search string Filter string FilterActivity string @@ -30,60 +19,33 @@ type folderState struct { Threading bool } -func NewState(name string, multipleAccts bool) *State { - return &State{ - renderer: newRenderer(), - acct: &accountState{Name: name, Multiple: multipleAccts}, - fldr: make(map[string]*folderState), - } -} - -func (s *State) StatusLine(folder string) string { - return s.renderer(renderParams{ - width: s.width, - sep: config.Statusline.Separator, - acct: s.acct, - fldr: s.folderState(folder), - }) -} - -func (s *State) folderState(folder string) *folderState { - if _, ok := s.fldr[folder]; !ok { - s.fldr[folder] = &folderState{Name: folder} +func (s *AccountState) folderState(folder string) *folderState { + if s.folders == nil { + s.folders = make(map[string]*folderState) } - return s.fldr[folder] -} - -func (s *State) SetWidth(w int) bool { - changeState := false - if s.width != w { - s.width = w - changeState = true + if _, ok := s.folders[folder]; !ok { + s.folders[folder] = &folderState{} } - return changeState -} - -func (s *State) Connected() bool { - return s.acct.Connected + return s.folders[folder] } -type SetStateFunc func(s *State, folder string) +type SetStateFunc func(s *AccountState, folder string) func SetConnected(state bool) SetStateFunc { - return func(s *State, folder string) { - s.acct.ConnActivity = "" - s.acct.Connected = state + return func(s *AccountState, folder string) { + s.connActivity = "" + s.Connected = state } } func ConnectionActivity(desc string) SetStateFunc { - return func(s *State, folder string) { - s.acct.ConnActivity = desc + return func(s *AccountState, folder string) { + s.connActivity = desc } } func SearchFilterClear() SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).Search = "" s.folderState(folder).FilterActivity = "" s.folderState(folder).Filter = "" @@ -91,13 +53,13 @@ func SearchFilterClear() SetStateFunc { } func FilterActivity(str string) SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).FilterActivity = str } } func FilterResult(str string) SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).FilterActivity = "" s.folderState(folder).Filter = concatFilters(s.folderState(folder).Filter, str) } @@ -111,25 +73,25 @@ func concatFilters(existing, next string) string { } func Search(desc string) SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).Search = desc } } func Sorting(on bool) SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).Sorting = on } } func Threading(on bool) SetStateFunc { - return func(s *State, folder string) { + return func(s *AccountState, folder string) { s.folderState(folder).Threading = on } } func Passthrough(on bool) SetStateFunc { - return func(s *State, folder string) { - s.acct.Passthrough = on + return func(s *AccountState, folder string) { + s.passthrough = on } } diff --git a/lib/state/templates.go b/lib/state/templates.go index 48106295..f37c4865 100644 --- a/lib/state/templates.go +++ b/lib/state/templates.go @@ -30,6 +30,9 @@ type TemplateData struct { folder string // selected folder name folders []string getRUEcount func(string) (int, int, int) + + state *AccountState + pendingKeys []config.KeyStroke } // only used for compose/reply/forward @@ -65,6 +68,14 @@ func (d *TemplateData) SetRUE(folders []string, cb func(string) (int, int, int)) d.getRUEcount = cb } +func (d *TemplateData) SetState(state *AccountState) { + d.state = state +} + +func (d *TemplateData) SetPendingKeys(keys []config.KeyStroke) { + d.pendingKeys = keys +} + func (d *TemplateData) Account() string { if d.account != nil { return d.account.Name @@ -357,3 +368,70 @@ func (d *TemplateData) Exists(folders ...string) int { _, _, e := d.rue(folders...) return e } + +func (d *TemplateData) Connected() bool { + if d.state != nil { + return d.state.Connected + } + return false +} + +func (d *TemplateData) ConnectionInfo() string { + switch { + case d.state == nil: + return "" + case d.state.connActivity != "": + return d.state.connActivity + case d.state.Connected: + return texter().Connected() + default: + return texter().Disconnected() + } +} + +func (d *TemplateData) ContentInfo() string { + if d.state == nil { + return "" + } + var content []string + fldr := d.state.folderState(d.folder) + if fldr.FilterActivity != "" { + content = append(content, fldr.FilterActivity) + } else if fldr.Filter != "" { + content = append(content, texter().FormatFilter(fldr.Filter)) + } + if fldr.Search != "" { + content = append(content, texter().FormatSearch(fldr.Search)) + } + return strings.Join(content, config.Statusline.Separator) +} + +func (d *TemplateData) StatusInfo() string { + stat := d.ConnectionInfo() + if content := d.ContentInfo(); content != "" { + stat += config.Statusline.Separator + content + } + return stat +} + +func (d *TemplateData) TrayInfo() string { + if d.state == nil { + return "" + } + var tray []string + fldr := d.state.folderState(d.folder) + if fldr.Sorting { + tray = append(tray, texter().Sorting()) + } + if fldr.Threading { + tray = append(tray, texter().Threading()) + } + if d.state.passthrough { + tray = append(tray, texter().Passthrough()) + } + return strings.Join(tray, config.Statusline.Separator) +} + +func (d *TemplateData) PendingKeys() string { + return config.FormatKeyStrokes(d.pendingKeys) +} diff --git a/lib/state/texter.go b/lib/state/texter.go index 21cf3627..9212108d 100644 --- a/lib/state/texter.go +++ b/lib/state/texter.go @@ -1,8 +1,12 @@ package state -import "strings" +import ( + "strings" -type Texter interface { + "git.sr.ht/~rjarry/aerc/config" +) + +type texterInterface interface { Connected() string Disconnected() string Passthrough() string @@ -14,6 +18,8 @@ type Texter interface { type text struct{} +var txt text + func (t text) Connected() string { return "Connected" } @@ -44,6 +50,8 @@ func (t text) FormatSearch(s string) string { type icon struct{} +var icn icon + func (i icon) Connected() string { return "✓" } @@ -71,3 +79,12 @@ func (i icon) FormatFilter(s string) string { func (i icon) FormatSearch(s string) string { return strings.ReplaceAll(s, "search", "🔎") } + +func texter() texterInterface { + switch strings.ToLower(config.Statusline.DisplayMode) { + case "icon": + return &icn + default: + return &txt + } +} |