diff options
Diffstat (limited to 'widgets/dirlist.go')
-rw-r--r-- | widgets/dirlist.go | 532 |
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 -} |