diff --git a/config/aerc.conf.in b/config/aerc.conf.in index 4d0f9fd..c50b7b9 100644 --- a/config/aerc.conf.in +++ b/config/aerc.conf.in @@ -43,6 +43,11 @@ mouse-enabled=false # Default: yes new-message-bell=true +# Describes the format string to use for the directory list +# +# Default: %n %>r +dirlist-format=%n %>r + [viewer] # # Specifies the pager to use when displaying emails. Note that some filters diff --git a/config/config.go b/config/config.go index 06caec1..738fd1d 100644 --- a/config/config.go +++ b/config/config.go @@ -35,6 +35,7 @@ type UIConfig struct { NewMessageBell bool `ini:"new-message-bell"` Spinner string `ini:"spinner"` SpinnerDelimiter string `ini:"spinner-delimiter"` + DirListFormat string `ini:"dirlist-format"` } const ( @@ -349,9 +350,9 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { EmptyDirlist: "(no folders)", MouseEnabled: false, NewMessageBell: true, - Spinner: - "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ", - SpinnerDelimiter: ",", + Spinner: "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ", + SpinnerDelimiter: ",", + DirListFormat: "%n %>r", }, Viewer: ViewerConfig{ diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 9257bde..d422e5d 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -125,6 +125,22 @@ These options are configured in the *[ui]* section of aerc.conf. Default: "," +*dirlist-format* + Describes the format string to use for the directory list + + Default: %n %>r + +[- *Format specifier* +:[ *Description* +| %% +: literal % +| %n +: directory name +| %r +: recent/unseen/total message count +| %>X +: make format specifier 'X' be right justified + ## VIEWER These options are configured in the *[viewer]* section of aerc.conf. diff --git a/lib/msgstore.go b/lib/msgstore.go index bbdfa57..2733288 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -27,6 +27,7 @@ type MessageStore struct { // Map of uids we've asked the worker to fetch onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers + onUpdateDirs func() pendingBodies map[uint32]interface{} pendingHeaders map[uint32]interface{} worker *types.Worker @@ -234,10 +235,17 @@ func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) { store.onUpdate = fn } +func (store *MessageStore) OnUpdateDirs(fn func()) { + store.onUpdateDirs = fn +} + func (store *MessageStore) update() { if store.onUpdate != nil { store.onUpdate(store) } + if store.onUpdateDirs != nil { + store.onUpdateDirs() + } } func (store *MessageStore) Delete(uids []uint32, diff --git a/widgets/dirlist.go b/widgets/dirlist.go index ec73082..ef2dd1e 100644 --- a/widgets/dirlist.go +++ b/widgets/dirlist.go @@ -1,15 +1,18 @@ package widgets import ( + "fmt" "log" "regexp" "sort" "github.com/gdamore/tcell" + "github.com/mattn/go-runewidth" "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib" "git.sr.ht/~sircmpwn/aerc/lib/ui" + "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/types" ) @@ -105,6 +108,92 @@ func (dirlist *DirectoryList) Invalidate() { dirlist.DoInvalidate(dirlist) } +func (dirlist *DirectoryList) getDirString(name string, width int, recentUnseen func() string) string { + percent := false + rightJustify := false + formatted := "" + doRightJustify := func(s string) { + formatted = runewidth.FillRight(formatted, width-len(s)) + formatted = runewidth.Truncate(formatted, width-len(s), "…") + } + for _, char := range dirlist.uiConf.DirListFormat { + switch char { + case '%': + if percent { + formatted += string(char) + percent = false + } else { + percent = true + } + case '>': + if percent { + rightJustify = true + } + case 'n': + if percent { + if rightJustify { + doRightJustify(name) + rightJustify = false + } + formatted += name + percent = false + } + case 'r': + if percent { + rString := recentUnseen() + if rightJustify { + doRightJustify(rString) + rightJustify = false + } + formatted += rString + percent = false + } + default: + formatted += string(char) + } + } + return formatted +} + +func (dirlist *DirectoryList) getRUEString(name string) string { + totalUnseen := 0 + totalRecent := 0 + totalExists := 0 + if msgStore, ok := dirlist.MsgStore(name); ok { + for _, msg := range msgStore.Messages { + if msg == nil { + continue + } + seen := false + recent := false + for _, flag := range msg.Flags { + if flag == models.SeenFlag { + seen = true + } else if flag == models.RecentFlag { + recent = true + } + } + if !seen { + if recent { + totalRecent++ + } else { + totalUnseen++ + } + } + } + totalExists = msgStore.DirInfo.Exists + } + rueString := "" + if totalRecent > 0 { + rueString = fmt.Sprintf("%d/%d/%d", totalRecent, totalUnseen, totalExists) + } else if totalUnseen > 0 { + rueString = fmt.Sprintf("%d/%d", totalUnseen, totalExists) + } else if totalExists > 0 { + rueString = fmt.Sprintf("%d", totalExists) + } + return rueString +} + func (dirlist *DirectoryList) Draw(ctx *ui.Context) { ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) @@ -132,7 +221,12 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) { style = style.Foreground(tcell.ColorGray) } ctx.Fill(0, row, ctx.Width(), 1, ' ', style) - ctx.Printf(0, row, style, "%s", name) + + dirString := dirlist.getDirString(name, ctx.Width(), func() string { + return dirlist.getRUEString(name) + }) + + ctx.Printf(0, row, style, dirString) row++ } } @@ -233,4 +327,7 @@ func (dirlist *DirectoryList) MsgStore(name string) (*lib.MessageStore, bool) { func (dirlist *DirectoryList) SetMsgStore(name string, msgStore *lib.MessageStore) { dirlist.store.SetMessageStore(name, msgStore) + msgStore.OnUpdateDirs(func() { + dirlist.Invalidate() + }) }