618a500341d54ec5bec6d035a86b1307ff1dad0a — Jeffas 11 days ago 572d9ff
Add display of unread messages in dirlist

Add an onUpdateDirs handler. This is used to invalidate the dirlist and
redraw with the correct number of recent/unread/total messages is shown.

A config option and formatting options are provided.
5 files changed, 131 insertions(+), 4 deletions(-)

M config/aerc.conf.in
M config/config.go
M doc/aerc-config.5.scd
M lib/msgstore.go
M widgets/dirlist.go
M config/aerc.conf.in => config/aerc.conf.in +5 -0
@@ 43,6 43,11 @@ # 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

M config/config.go => config/config.go +4 -3
@@ 35,6 35,7 @@ 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 @@ EmptyDirlist:      "(no folders)",
  			MouseEnabled:      false,
  			NewMessageBell:    true,
- 			Spinner:
- 				 "[..]    , [..]   ,  [..]  ,   [..] ,    [..],   [..] ,  [..]  , [..]   ",
- 			SpinnerDelimiter: ",",
+ 			Spinner:           "[..]    , [..]   ,  [..]  ,   [..] ,    [..],   [..] ,  [..]  , [..]   ",
+ 			SpinnerDelimiter:  ",",
+ 			DirListFormat:     "%n %>r",
  		},
  
  		Viewer: ViewerConfig{

M doc/aerc-config.5.scd => doc/aerc-config.5.scd +16 -0
@@ 125,6 125,22 @@   	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.

M lib/msgstore.go => lib/msgstore.go +8 -0
@@ 27,6 27,7 @@   	// 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 @@ 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,

M widgets/dirlist.go => widgets/dirlist.go +98 -1
@@ 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 @@ 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 @@ 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) SetMsgStore(name string, msgStore *lib.MessageStore) {
  	dirlist.store.SetMessageStore(name, msgStore)
+ 	msgStore.OnUpdateDirs(func() {
+ 		dirlist.Invalidate()
+ 	})
  }