aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/dirlist.go
diff options
context:
space:
mode:
Diffstat (limited to 'widgets/dirlist.go')
-rw-r--r--widgets/dirlist.go532
1 files changed, 0 insertions, 532 deletions
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
deleted file mode 100644
index e9cec458..00000000
--- a/widgets/dirlist.go
+++ /dev/null
@@ -1,532 +0,0 @@
-package widgets
-
-import (
- "bytes"
- "context"
- "math"
- "regexp"
- "sort"
- "time"
-
- "github.com/gdamore/tcell/v2"
-
- "git.sr.ht/~rjarry/aerc/config"
- "git.sr.ht/~rjarry/aerc/lib"
- "git.sr.ht/~rjarry/aerc/lib/parse"
- "git.sr.ht/~rjarry/aerc/lib/state"
- "git.sr.ht/~rjarry/aerc/lib/templates"
- "git.sr.ht/~rjarry/aerc/lib/ui"
- "git.sr.ht/~rjarry/aerc/log"
- "git.sr.ht/~rjarry/aerc/models"
- "git.sr.ht/~rjarry/aerc/worker/types"
-)
-
-type DirectoryLister interface {
- ui.Drawable
-
- Selected() string
- Select(string)
-
- Update(types.WorkerMessage)
- List() []string
- ClearList()
-
- OnVirtualNode(func())
-
- NextPrev(int)
-
- CollapseFolder()
- ExpandFolder()
-
- SelectedMsgStore() (*lib.MessageStore, bool)
- MsgStore(string) (*lib.MessageStore, bool)
- SelectedDirectory() *models.Directory
- Directory(string) *models.Directory
- SetMsgStore(*models.Directory, *lib.MessageStore)
-
- FilterDirs([]string, []string, bool) []string
- GetRUECount(string) (int, int, int)
-
- UiConfig(string) *config.UIConfig
-}
-
-type DirectoryList struct {
- Scrollable
- acctConf *config.AccountConfig
- store *lib.DirStore
- dirs []string
- selecting string
- selected string
- spinner *Spinner
- worker *types.Worker
- ctx context.Context
- cancel context.CancelFunc
-}
-
-func NewDirectoryList(acctConf *config.AccountConfig,
- worker *types.Worker,
-) DirectoryLister {
- ctx, cancel := context.WithCancel(context.Background())
-
- dirlist := &DirectoryList{
- acctConf: acctConf,
- store: lib.NewDirStore(),
- worker: worker,
- ctx: ctx,
- cancel: cancel,
- }
- uiConf := dirlist.UiConfig("")
- dirlist.spinner = NewSpinner(uiConf)
- dirlist.spinner.Start()
-
- if uiConf.DirListTree {
- return NewDirectoryTree(dirlist)
- }
-
- return dirlist
-}
-
-func (dirlist *DirectoryList) UiConfig(dir string) *config.UIConfig {
- if dir == "" {
- dir = dirlist.Selected()
- }
- return config.Ui.ForAccount(dirlist.acctConf.Name).ForFolder(dir)
-}
-
-func (dirlist *DirectoryList) List() []string {
- return dirlist.dirs
-}
-
-func (dirlist *DirectoryList) ClearList() {
- dirlist.dirs = []string{}
-}
-
-func (dirlist *DirectoryList) OnVirtualNode(_ func()) {
-}
-
-func (dirlist *DirectoryList) Update(msg types.WorkerMessage) {
- switch msg := msg.(type) {
- case *types.Done:
- switch msg := msg.InResponseTo().(type) {
- case *types.OpenDirectory:
- dirlist.selected = msg.Directory
- dirlist.filterDirsByFoldersConfig()
- hasSelected := false
- for _, d := range dirlist.dirs {
- if d == dirlist.selected {
- hasSelected = true
- break
- }
- }
- if !hasSelected && dirlist.selected != "" {
- dirlist.dirs = append(dirlist.dirs, dirlist.selected)
- }
- if dirlist.acctConf.EnableFoldersSort {
- sort.Strings(dirlist.dirs)
- }
- dirlist.sortDirsByFoldersSortConfig()
- store, ok := dirlist.SelectedMsgStore()
- if !ok {
- return
- }
- store.SetContext(msg.Context)
- case *types.ListDirectories:
- dirlist.filterDirsByFoldersConfig()
- dirlist.sortDirsByFoldersSortConfig()
- dirlist.spinner.Stop()
- dirlist.Invalidate()
- case *types.RemoveDirectory:
- dirlist.store.Remove(msg.Directory)
- dirlist.filterDirsByFoldersConfig()
- dirlist.sortDirsByFoldersSortConfig()
- case *types.CreateDirectory:
- dirlist.filterDirsByFoldersConfig()
- dirlist.sortDirsByFoldersSortConfig()
- dirlist.Invalidate()
- }
- case *types.DirectoryInfo:
- dir := dirlist.Directory(msg.Info.Name)
- if dir == nil {
- return
- }
- dir.Exists = msg.Info.Exists
- dir.Recent = msg.Info.Recent
- dir.Unseen = msg.Info.Unseen
- if msg.Refetch {
- store, ok := dirlist.SelectedMsgStore()
- if ok {
- store.Sort(store.GetCurrentSortCriteria(), nil)
- }
- }
- default:
- return
- }
-}
-
-func (dirlist *DirectoryList) CollapseFolder() {
- // no effect for the DirectoryList
-}
-
-func (dirlist *DirectoryList) ExpandFolder() {
- // no effect for the DirectoryList
-}
-
-func (dirlist *DirectoryList) Select(name string) {
- dirlist.selecting = name
-
- dirlist.cancel()
- dirlist.ctx, dirlist.cancel = context.WithCancel(context.Background())
- delay := dirlist.UiConfig(name).DirListDelay
-
- go func(ctx context.Context) {
- defer log.PanicHandler()
-
- select {
- case <-time.After(delay):
- dirlist.worker.PostAction(&types.OpenDirectory{
- Context: ctx,
- Directory: name,
- },
- func(msg types.WorkerMessage) {
- switch msg := msg.(type) {
- case *types.Error:
- dirlist.selecting = ""
- log.Errorf("(%s) couldn't open directory %s: %v",
- dirlist.acctConf.Name,
- name,
- msg.Error)
- case *types.Cancelled:
- log.Debugf("OpenDirectory cancelled")
- }
- })
- case <-ctx.Done():
- log.Tracef("dirlist: skip %s", name)
- return
- }
- }(dirlist.ctx)
-}
-
-func (dirlist *DirectoryList) Selected() string {
- return dirlist.selected
-}
-
-func (dirlist *DirectoryList) Invalidate() {
- ui.Invalidate()
-}
-
-// Returns the Recent, Unread, and Exist counts for the named directory
-func (dirlist *DirectoryList) GetRUECount(name string) (int, int, int) {
- dir := dirlist.Directory(name)
- if dir == nil {
- return 0, 0, 0
- }
- return dir.Recent, dir.Unseen, dir.Exists
-}
-
-func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
- uiConfig := dirlist.UiConfig("")
- ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
- uiConfig.GetStyle(config.STYLE_DIRLIST_DEFAULT))
-
- if dirlist.spinner.IsRunning() {
- dirlist.spinner.Draw(ctx)
- return
- }
-
- if len(dirlist.dirs) == 0 {
- style := uiConfig.GetStyle(config.STYLE_DIRLIST_DEFAULT)
- ctx.Printf(0, 0, style, uiConfig.EmptyDirlist)
- return
- }
-
- dirlist.UpdateScroller(ctx.Height(), len(dirlist.dirs))
- dirlist.EnsureScroll(findString(dirlist.dirs, dirlist.selecting))
-
- textWidth := ctx.Width()
- if dirlist.NeedScrollbar() {
- textWidth -= 1
- }
- if textWidth < 0 {
- return
- }
-
- listCtx := ctx.Subcontext(0, 0, textWidth, ctx.Height())
-
- data := state.NewDataSetter()
- data.SetAccount(dirlist.acctConf)
-
- for i, name := range dirlist.dirs {
- if i < dirlist.Scroll() {
- continue
- }
- row := i - dirlist.Scroll()
- if row >= ctx.Height() {
- break
- }
-
- data.SetFolder(dirlist.Directory(name))
- data.SetRUE([]string{name}, dirlist.GetRUECount)
- left, right, style := dirlist.renderDir(
- name, uiConfig, data.Data(),
- name == dirlist.selecting, listCtx.Width(),
- )
- listCtx.Printf(0, row, style, "%s %s", left, right)
- }
-
- if dirlist.NeedScrollbar() {
- scrollBarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
- dirlist.drawScrollbar(scrollBarCtx)
- }
-}
-
-func (dirlist *DirectoryList) renderDir(
- path string, conf *config.UIConfig, data models.TemplateData,
- selected bool, width int,
-) (string, string, tcell.Style) {
- var left, right string
- var buf bytes.Buffer
-
- var styles []config.StyleObject
- var style tcell.Style
-
- r, u, _ := dirlist.GetRUECount(path)
- if u > 0 {
- styles = append(styles, config.STYLE_DIRLIST_UNREAD)
- }
- if r > 0 {
- styles = append(styles, config.STYLE_DIRLIST_RECENT)
- }
- conf = conf.ForFolder(path)
- if selected {
- style = conf.GetComposedStyleSelected(
- config.STYLE_DIRLIST_DEFAULT, styles)
- } else {
- style = conf.GetComposedStyle(
- config.STYLE_DIRLIST_DEFAULT, styles)
- }
-
- err := templates.Render(conf.DirListLeft, &buf, data)
- if err != nil {
- log.Errorf("dirlist-left: %s", err)
- left = err.Error()
- style = conf.GetStyle(config.STYLE_ERROR)
- } else {
- left = buf.String()
- }
- buf.Reset()
- err = templates.Render(conf.DirListRight, &buf, data)
- if err != nil {
- log.Errorf("dirlist-right: %s", err)
- right = err.Error()
- style = conf.GetStyle(config.STYLE_ERROR)
- } else {
- right = buf.String()
- }
- buf.Reset()
-
- lbuf := parse.ParseANSI(left)
- lbuf.ApplyAttrs(style)
- lwidth := lbuf.Len()
- rbuf := parse.ParseANSI(right)
- rbuf.ApplyAttrs(style)
- rwidth := rbuf.Len()
-
- if lwidth+rwidth+1 > width {
- if rwidth > 3*width/4 {
- rwidth = 3 * width / 4
- }
- lwidth = width - rwidth - 1
- right = rbuf.TruncateHead(rwidth, '…')
- left = lbuf.Truncate(lwidth-1, '…')
- } else {
- for i := 0; i < (width - lwidth - rwidth - 1); i += 1 {
- lbuf.Write(' ', tcell.StyleDefault)
- }
- left = lbuf.String()
- right = rbuf.String()
- }
-
- return left, right, style
-}
-
-func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
- gutterStyle := tcell.StyleDefault
- pillStyle := tcell.StyleDefault.Reverse(true)
-
- // gutter
- ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
-
- // pill
- pillSize := int(math.Ceil(float64(ctx.Height()) * dirlist.PercentVisible()))
- pillOffset := int(math.Floor(float64(ctx.Height()) * dirlist.PercentScrolled()))
- ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
-}
-
-func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
- if event, ok := event.(*tcell.EventMouse); ok {
- switch event.Buttons() {
- case tcell.Button1:
- clickedDir, ok := dirlist.Clicked(localX, localY)
- if ok {
- dirlist.Select(clickedDir)
- }
- case tcell.WheelDown:
- dirlist.Next()
- case tcell.WheelUp:
- dirlist.Prev()
- }
- }
-}
-
-func (dirlist *DirectoryList) Clicked(x int, y int) (string, bool) {
- if dirlist.dirs == nil || len(dirlist.dirs) == 0 {
- return "", false
- }
- for i, name := range dirlist.dirs {
- if i == y {
- return name, true
- }
- }
- return "", false
-}
-
-func (dirlist *DirectoryList) NextPrev(delta int) {
- curIdx := findString(dirlist.dirs, dirlist.selecting)
- if curIdx == len(dirlist.dirs) {
- return
- }
- newIdx := curIdx + delta
- ndirs := len(dirlist.dirs)
-
- if ndirs == 0 {
- return
- }
-
- if newIdx < 0 {
- newIdx = ndirs - 1
- } else if newIdx >= ndirs {
- newIdx = 0
- }
-
- dirlist.Select(dirlist.dirs[newIdx])
-}
-
-func (dirlist *DirectoryList) Next() {
- dirlist.NextPrev(1)
-}
-
-func (dirlist *DirectoryList) Prev() {
- dirlist.NextPrev(-1)
-}
-
-func folderMatches(folder string, pattern string) bool {
- if len(pattern) == 0 {
- return false
- }
- if pattern[0] == '~' {
- r, err := regexp.Compile(pattern[1:])
- if err != nil {
- return false
- }
- return r.Match([]byte(folder))
- }
- return pattern == folder
-}
-
-// sortDirsByFoldersSortConfig sets dirlist.dirs to be sorted based on the
-// AccountConfig.FoldersSort option. Folders not included in the option
-// will be appended at the end in alphabetical order
-func (dirlist *DirectoryList) sortDirsByFoldersSortConfig() {
- if !dirlist.acctConf.EnableFoldersSort {
- return
- }
-
- sort.Slice(dirlist.dirs, func(i, j int) bool {
- foldersSort := dirlist.acctConf.FoldersSort
- iInFoldersSort := findString(foldersSort, dirlist.dirs[i])
- jInFoldersSort := findString(foldersSort, dirlist.dirs[j])
- if iInFoldersSort >= 0 && jInFoldersSort >= 0 {
- return iInFoldersSort < jInFoldersSort
- }
- if iInFoldersSort >= 0 {
- return true
- }
- if jInFoldersSort >= 0 {
- return false
- }
- return dirlist.dirs[i] < dirlist.dirs[j]
- })
-}
-
-// filterDirsByFoldersConfig sets dirlist.dirs to the filtered subset of the
-// dirstore, based on AccountConfig.Folders (inclusion) and
-// AccountConfig.FoldersExclude (exclusion), in that order.
-func (dirlist *DirectoryList) filterDirsByFoldersConfig() {
- dirlist.dirs = dirlist.store.List()
-
- // 'folders' (if available) is used to make the initial list and
- // 'folders-exclude' removes from that list.
- configFolders := dirlist.acctConf.Folders
- dirlist.dirs = dirlist.FilterDirs(dirlist.dirs, configFolders, false)
-
- configFoldersExclude := dirlist.acctConf.FoldersExclude
- dirlist.dirs = dirlist.FilterDirs(dirlist.dirs, configFoldersExclude, true)
-}
-
-// FilterDirs filters directories by the supplied filter. If exclude is false,
-// the filter will only include directories from orig which exist in filters.
-// If exclude is true, the directories in filters are removed from orig
-func (dirlist *DirectoryList) FilterDirs(orig, filters []string, exclude bool) []string {
- if len(filters) == 0 {
- return orig
- }
- var dest []string
- for _, folder := range orig {
- // When excluding, include things by default, and vice-versa
- include := exclude
- for _, f := range filters {
- if folderMatches(folder, f) {
- // If matched an exclusion, don't include
- // If matched an inclusion, do include
- include = !exclude
- break
- }
- }
- if include {
- dest = append(dest, folder)
- }
- }
- return dest
-}
-
-func (dirlist *DirectoryList) SelectedMsgStore() (*lib.MessageStore, bool) {
- return dirlist.store.MessageStore(dirlist.selected)
-}
-
-func (dirlist *DirectoryList) MsgStore(name string) (*lib.MessageStore, bool) {
- return dirlist.store.MessageStore(name)
-}
-
-func (dirlist *DirectoryList) SelectedDirectory() *models.Directory {
- return dirlist.store.Directory(dirlist.selected)
-}
-
-func (dirlist *DirectoryList) Directory(name string) *models.Directory {
- return dirlist.store.Directory(name)
-}
-
-func (dirlist *DirectoryList) SetMsgStore(dir *models.Directory, msgStore *lib.MessageStore) {
- dirlist.store.SetMessageStore(dir, msgStore)
- msgStore.OnUpdateDirs(func() {
- dirlist.Invalidate()
- })
-}
-
-func findString(slice []string, str string) int {
- for i, s := range slice {
- if str == s {
- return i
- }
- }
- return -1
-}