diff options
author | Robin Jarry <robin@jarry.cc> | 2023-02-05 23:23:02 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-02-20 14:48:42 +0100 |
commit | 6af06c9dfec03e923589d34187ba8358e3423d5c (patch) | |
tree | 3722f17464ca651ebd12d7d6d55a0e97ae72c8ec /lib/state | |
parent | 34db5942bd7b642107002b75de9d5d5c7fe90e4c (diff) | |
download | aerc-6af06c9dfec03e923589d34187ba8358e3423d5c.tar.gz |
statusline: move files to lib/state
These modules will not handle statusline rendering after next commit.
Move them in lib/state to make next commit easier to review.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'lib/state')
-rw-r--r-- | lib/state/renderer.go | 205 | ||||
-rw-r--r-- | lib/state/state.go | 135 | ||||
-rw-r--r-- | lib/state/texter.go | 73 |
3 files changed, 413 insertions, 0 deletions
diff --git a/lib/state/renderer.go b/lib/state/renderer.go new file mode 100644 index 00000000..13e593fe --- /dev/null +++ b/lib/state/renderer.go @@ -0,0 +1,205 @@ +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 new file mode 100644 index 00000000..b5f925ac --- /dev/null +++ b/lib/state/state.go @@ -0,0 +1,135 @@ +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 + Connected bool + Passthrough bool +} + +type folderState struct { + Name string + Search string + Filter string + FilterActivity string + Sorting bool + 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} + } + return s.fldr[folder] +} + +func (s *State) SetWidth(w int) bool { + changeState := false + if s.width != w { + s.width = w + changeState = true + } + return changeState +} + +func (s *State) Connected() bool { + return s.acct.Connected +} + +type SetStateFunc func(s *State, folder string) + +func SetConnected(state bool) SetStateFunc { + return func(s *State, folder string) { + s.acct.ConnActivity = "" + s.acct.Connected = state + } +} + +func ConnectionActivity(desc string) SetStateFunc { + return func(s *State, folder string) { + s.acct.ConnActivity = desc + } +} + +func SearchFilterClear() SetStateFunc { + return func(s *State, folder string) { + s.folderState(folder).Search = "" + s.folderState(folder).FilterActivity = "" + s.folderState(folder).Filter = "" + } +} + +func FilterActivity(str string) SetStateFunc { + return func(s *State, folder string) { + s.folderState(folder).FilterActivity = str + } +} + +func FilterResult(str string) SetStateFunc { + return func(s *State, folder string) { + s.folderState(folder).FilterActivity = "" + s.folderState(folder).Filter = concatFilters(s.folderState(folder).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, folder string) { + s.folderState(folder).Search = desc + } +} + +func Sorting(on bool) SetStateFunc { + return func(s *State, folder string) { + s.folderState(folder).Sorting = on + } +} + +func Threading(on bool) SetStateFunc { + return func(s *State, folder string) { + s.folderState(folder).Threading = on + } +} + +func Passthrough(on bool) SetStateFunc { + return func(s *State, folder string) { + s.acct.Passthrough = on + } +} diff --git a/lib/state/texter.go b/lib/state/texter.go new file mode 100644 index 00000000..21cf3627 --- /dev/null +++ b/lib/state/texter.go @@ -0,0 +1,73 @@ +package state + +import "strings" + +type Texter interface { + Connected() string + Disconnected() string + Passthrough() string + Sorting() string + Threading() string + FormatFilter(string) string + FormatSearch(string) string +} + +type text struct{} + +func (t text) Connected() string { + return "Connected" +} + +func (t text) Disconnected() string { + return "Disconnected" +} + +func (t text) Passthrough() string { + return "passthrough" +} + +func (t text) Sorting() string { + return "sorting" +} + +func (t text) Threading() string { + return "threading" +} + +func (t text) FormatFilter(s string) string { + return s +} + +func (t text) FormatSearch(s string) string { + return s +} + +type icon struct{} + +func (i icon) Connected() string { + return "โ" +} + +func (i icon) Disconnected() string { + return "โ" +} + +func (i icon) Passthrough() string { + return "โ" +} + +func (i icon) Sorting() string { + return "โ" +} + +func (i icon) Threading() string { + return "๐งต" +} + +func (i icon) FormatFilter(s string) string { + return strings.ReplaceAll(s, "filter", "๐ฆ") +} + +func (i icon) FormatSearch(s string) string { + return strings.ReplaceAll(s, "search", "๐") +} |