2019-01-13 20:25:46 +01:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
2022-06-25 20:47:24 +02:00
|
|
|
"context"
|
2019-09-11 18:37:21 +02:00
|
|
|
"fmt"
|
2020-06-09 20:49:23 +02:00
|
|
|
"math"
|
2022-02-21 00:18:42 +01:00
|
|
|
"os"
|
2019-08-16 09:11:37 +02:00
|
|
|
"regexp"
|
2019-01-13 20:25:46 +01:00
|
|
|
"sort"
|
2022-04-24 16:50:08 +02:00
|
|
|
"strings"
|
2022-01-23 10:31:41 +01:00
|
|
|
"time"
|
2019-01-13 20:25:46 +01:00
|
|
|
|
2020-11-30 23:07:03 +01:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2019-09-11 18:37:21 +02:00
|
|
|
"github.com/mattn/go-runewidth"
|
2019-01-13 20:25:46 +01:00
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/config"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
2022-04-28 22:09:53 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/format"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
2022-03-22 09:52:27 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
2019-01-13 20:25:46 +01:00
|
|
|
)
|
|
|
|
|
2022-02-21 00:18:42 +01:00
|
|
|
type DirectoryLister interface {
|
|
|
|
ui.Drawable
|
|
|
|
|
|
|
|
Selected() string
|
|
|
|
Select(string)
|
|
|
|
|
|
|
|
UpdateList(func([]string))
|
|
|
|
List() []string
|
2022-06-07 17:59:05 +02:00
|
|
|
ClearList()
|
2022-02-21 00:18:42 +01:00
|
|
|
|
|
|
|
NextPrev(int)
|
|
|
|
|
|
|
|
CollapseFolder()
|
|
|
|
ExpandFolder()
|
|
|
|
|
|
|
|
SelectedMsgStore() (*lib.MessageStore, bool)
|
|
|
|
MsgStore(string) (*lib.MessageStore, bool)
|
|
|
|
SetMsgStore(string, *lib.MessageStore)
|
2022-05-30 14:34:18 +02:00
|
|
|
|
|
|
|
FilterDirs([]string, []string, bool) []string
|
2022-07-03 17:11:13 +02:00
|
|
|
|
|
|
|
UiConfig() *config.UIConfig
|
2022-02-21 00:18:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-13 20:25:46 +01:00
|
|
|
type DirectoryList struct {
|
2019-04-27 18:47:59 +02:00
|
|
|
ui.Invalidatable
|
2022-02-25 17:53:33 +01:00
|
|
|
Scrollable
|
2022-06-25 20:47:24 +02:00
|
|
|
aercConf *config.AercConfig
|
|
|
|
acctConf *config.AccountConfig
|
|
|
|
store *lib.DirStore
|
|
|
|
dirs []string
|
|
|
|
selecting string
|
|
|
|
selected string
|
|
|
|
spinner *Spinner
|
|
|
|
worker *types.Worker
|
|
|
|
skipSelect context.Context
|
|
|
|
skipSelectCancel context.CancelFunc
|
2022-07-03 17:11:13 +02:00
|
|
|
uiConf map[string]*config.UIConfig
|
2019-01-13 20:25:46 +01:00
|
|
|
}
|
|
|
|
|
2020-01-26 12:43:46 +01:00
|
|
|
func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
|
2022-07-19 22:31:51 +02:00
|
|
|
worker *types.Worker,
|
2022-06-25 20:47:24 +02:00
|
|
|
) DirectoryLister {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2019-01-13 20:32:22 +01:00
|
|
|
|
2022-07-03 17:11:13 +02:00
|
|
|
uiConfMap := make(map[string]*config.UIConfig)
|
|
|
|
|
2019-01-14 02:02:21 +01:00
|
|
|
dirlist := &DirectoryList{
|
2022-06-25 20:47:24 +02:00
|
|
|
aercConf: conf,
|
|
|
|
acctConf: acctConf,
|
|
|
|
store: lib.NewDirStore(),
|
|
|
|
worker: worker,
|
|
|
|
skipSelect: ctx,
|
|
|
|
skipSelectCancel: cancel,
|
2022-07-03 17:11:13 +02:00
|
|
|
uiConf: uiConfMap,
|
2019-01-14 02:02:21 +01:00
|
|
|
}
|
2020-01-26 12:43:46 +01:00
|
|
|
uiConf := dirlist.UiConfig()
|
2022-07-03 17:11:12 +02:00
|
|
|
dirlist.spinner = NewSpinner(uiConf)
|
2019-01-14 02:02:21 +01:00
|
|
|
dirlist.spinner.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
dirlist.Invalidate()
|
|
|
|
})
|
|
|
|
dirlist.spinner.Start()
|
2022-02-21 00:18:42 +01:00
|
|
|
|
|
|
|
if uiConf.DirListTree {
|
|
|
|
return NewDirectoryTree(dirlist, string(os.PathSeparator))
|
|
|
|
}
|
|
|
|
|
2019-01-14 02:02:21 +01:00
|
|
|
return dirlist
|
2019-01-13 20:25:46 +01:00
|
|
|
}
|
|
|
|
|
2022-07-03 17:11:12 +02:00
|
|
|
func (dirlist *DirectoryList) UiConfig() *config.UIConfig {
|
2022-07-03 17:11:13 +02:00
|
|
|
if ui, ok := dirlist.uiConf[dirlist.Selected()]; ok {
|
|
|
|
return ui
|
|
|
|
}
|
|
|
|
ui := dirlist.aercConf.GetUiConfig(map[config.ContextType]string{
|
2020-01-26 12:43:46 +01:00
|
|
|
config.UI_CONTEXT_ACCOUNT: dirlist.acctConf.Name,
|
|
|
|
config.UI_CONTEXT_FOLDER: dirlist.Selected(),
|
|
|
|
})
|
2022-07-03 17:11:13 +02:00
|
|
|
dirlist.uiConf[dirlist.Selected()] = ui
|
|
|
|
return ui
|
2020-01-26 12:43:46 +01:00
|
|
|
}
|
|
|
|
|
2019-07-03 18:54:10 +02:00
|
|
|
func (dirlist *DirectoryList) List() []string {
|
|
|
|
return dirlist.store.List()
|
|
|
|
}
|
|
|
|
|
2022-06-07 17:59:05 +02:00
|
|
|
func (dirlist *DirectoryList) ClearList() {
|
|
|
|
dirlist.dirs = []string{}
|
|
|
|
}
|
|
|
|
|
2019-01-13 21:32:52 +01:00
|
|
|
func (dirlist *DirectoryList) UpdateList(done func(dirs []string)) {
|
2019-07-04 18:31:27 +02:00
|
|
|
// TODO: move this logic into dirstore
|
2019-01-13 21:27:56 +01:00
|
|
|
var dirs []string
|
2019-01-13 20:25:46 +01:00
|
|
|
dirlist.worker.PostAction(
|
|
|
|
&types.ListDirectories{}, func(msg types.WorkerMessage) {
|
|
|
|
switch msg := msg.(type) {
|
|
|
|
case *types.Directory:
|
2019-07-08 04:43:56 +02:00
|
|
|
dirs = append(dirs, msg.Dir.Name)
|
2019-01-13 20:25:46 +01:00
|
|
|
case *types.Done:
|
2019-06-27 19:33:12 +02:00
|
|
|
dirlist.store.Update(dirs)
|
2019-07-04 11:17:21 +02:00
|
|
|
dirlist.filterDirsByFoldersConfig()
|
2019-12-03 20:20:21 +01:00
|
|
|
dirlist.sortDirsByFoldersSortConfig()
|
|
|
|
dirlist.store.Update(dirlist.dirs)
|
2019-01-14 02:02:21 +01:00
|
|
|
dirlist.spinner.Stop()
|
2019-01-13 21:27:56 +01:00
|
|
|
dirlist.Invalidate()
|
2019-01-13 21:32:52 +01:00
|
|
|
if done != nil {
|
|
|
|
done(dirs)
|
|
|
|
}
|
2019-01-13 20:25:46 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-21 00:18:42 +01:00
|
|
|
func (dirlist *DirectoryList) CollapseFolder() {
|
|
|
|
// no effect for the DirectoryList
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dirlist *DirectoryList) ExpandFolder() {
|
|
|
|
// no effect for the DirectoryList
|
|
|
|
}
|
|
|
|
|
2019-01-13 21:32:52 +01:00
|
|
|
func (dirlist *DirectoryList) Select(name string) {
|
2019-03-15 03:34:34 +01:00
|
|
|
dirlist.selecting = name
|
2022-01-23 10:31:41 +01:00
|
|
|
|
2022-06-25 20:47:24 +02:00
|
|
|
dirlist.skipSelectCancel()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
dirlist.skipSelect = ctx
|
|
|
|
dirlist.skipSelectCancel = cancel
|
2022-01-23 10:31:41 +01:00
|
|
|
|
2022-06-25 20:47:24 +02:00
|
|
|
go func(ctx context.Context) {
|
2022-03-22 09:52:27 +01:00
|
|
|
defer logging.PanicHandler()
|
|
|
|
|
2022-01-23 10:31:41 +01:00
|
|
|
select {
|
2022-02-24 13:21:45 +01:00
|
|
|
case <-time.After(dirlist.UiConfig().DirListDelay):
|
2022-04-13 22:04:59 +02:00
|
|
|
newStore := true
|
|
|
|
for _, s := range dirlist.store.List() {
|
|
|
|
if s == dirlist.selecting {
|
|
|
|
newStore = false
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 10:31:41 +01:00
|
|
|
dirlist.worker.PostAction(&types.OpenDirectory{Directory: name},
|
|
|
|
func(msg types.WorkerMessage) {
|
|
|
|
switch msg.(type) {
|
|
|
|
case *types.Error:
|
|
|
|
dirlist.selecting = ""
|
|
|
|
dirlist.selected = ""
|
|
|
|
case *types.Done:
|
|
|
|
dirlist.selected = dirlist.selecting
|
|
|
|
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()
|
2022-04-13 22:04:59 +02:00
|
|
|
if newStore {
|
2022-07-05 21:48:40 +02:00
|
|
|
store, ok := dirlist.MsgStore(name)
|
|
|
|
if ok {
|
|
|
|
// Fetch directory contents via store.Sort
|
|
|
|
store.Sort(nil, nil)
|
|
|
|
}
|
2022-04-13 22:04:59 +02:00
|
|
|
}
|
2019-06-12 08:31:51 +02:00
|
|
|
}
|
2022-01-23 10:31:41 +01:00
|
|
|
dirlist.Invalidate()
|
|
|
|
})
|
2019-03-15 03:34:34 +01:00
|
|
|
dirlist.Invalidate()
|
2022-06-25 20:47:24 +02:00
|
|
|
case <-ctx.Done():
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Debugf("dirlist: skip %s", name)
|
2022-01-23 10:31:41 +01:00
|
|
|
return
|
|
|
|
}
|
2022-06-25 20:47:24 +02:00
|
|
|
}(ctx)
|
2019-01-13 21:32:52 +01:00
|
|
|
}
|
|
|
|
|
2019-03-15 03:34:34 +01:00
|
|
|
func (dirlist *DirectoryList) Selected() string {
|
|
|
|
return dirlist.selected
|
|
|
|
}
|
|
|
|
|
2019-01-13 20:25:46 +01:00
|
|
|
func (dirlist *DirectoryList) Invalidate() {
|
2019-04-27 18:47:59 +02:00
|
|
|
dirlist.DoInvalidate(dirlist)
|
2019-01-13 20:25:46 +01:00
|
|
|
}
|
|
|
|
|
2019-09-11 18:37:21 +02:00
|
|
|
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), "…")
|
|
|
|
}
|
2020-01-26 12:43:46 +01:00
|
|
|
for _, char := range dirlist.UiConfig().DirListFormat {
|
2019-09-11 18:37:21 +02:00
|
|
|
switch char {
|
|
|
|
case '%':
|
|
|
|
if percent {
|
|
|
|
formatted += string(char)
|
|
|
|
percent = false
|
|
|
|
} else {
|
|
|
|
percent = true
|
|
|
|
}
|
|
|
|
case '>':
|
|
|
|
if percent {
|
|
|
|
rightJustify = true
|
|
|
|
}
|
2022-04-28 22:09:53 +02:00
|
|
|
case 'N':
|
|
|
|
name = format.CompactPath(name, os.PathSeparator)
|
|
|
|
fallthrough
|
2019-09-11 18:37:21 +02:00
|
|
|
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 {
|
2020-02-15 14:14:43 +01:00
|
|
|
msgStore, ok := dirlist.MsgStore(name)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
2022-06-02 02:24:53 +02:00
|
|
|
if !msgStore.DirInfo.AccurateCounts {
|
2022-06-07 22:22:26 +02:00
|
|
|
msgStore.DirInfo.Recent, msgStore.DirInfo.Unseen = countRUE(msgStore)
|
2019-09-11 18:37:21 +02:00
|
|
|
}
|
2022-06-07 22:22:26 +02:00
|
|
|
di := msgStore.DirInfo
|
2019-09-11 18:37:21 +02:00
|
|
|
rueString := ""
|
2022-07-31 14:32:48 +02:00
|
|
|
switch {
|
|
|
|
case di.Recent > 0:
|
2022-06-07 22:22:26 +02:00
|
|
|
rueString = fmt.Sprintf("%d/%d/%d", di.Recent, di.Unseen, di.Exists)
|
2022-07-31 14:32:48 +02:00
|
|
|
case di.Unseen > 0:
|
2022-06-07 22:22:26 +02:00
|
|
|
rueString = fmt.Sprintf("%d/%d", di.Unseen, di.Exists)
|
2022-07-31 14:32:48 +02:00
|
|
|
case di.Exists > 0:
|
2022-06-07 22:22:26 +02:00
|
|
|
rueString = fmt.Sprintf("%d", di.Exists)
|
2019-09-11 18:37:21 +02:00
|
|
|
}
|
|
|
|
return rueString
|
|
|
|
}
|
|
|
|
|
2019-01-13 20:25:46 +01:00
|
|
|
func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
|
2020-07-27 10:03:55 +02:00
|
|
|
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
|
|
|
|
dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT))
|
2019-01-14 02:02:21 +01:00
|
|
|
|
|
|
|
if dirlist.spinner.IsRunning() {
|
|
|
|
dirlist.spinner.Draw(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-04 11:17:21 +02:00
|
|
|
if len(dirlist.dirs) == 0 {
|
2020-07-27 10:03:55 +02:00
|
|
|
style := dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT)
|
2020-01-26 12:43:46 +01:00
|
|
|
ctx.Printf(0, 0, style, dirlist.UiConfig().EmptyDirlist)
|
2019-06-12 08:31:52 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-25 17:53:33 +01:00
|
|
|
dirlist.UpdateScroller(ctx.Height(), len(dirlist.dirs))
|
|
|
|
dirlist.EnsureScroll(findString(dirlist.dirs, dirlist.selecting))
|
2020-06-09 20:49:23 +02:00
|
|
|
|
|
|
|
textWidth := ctx.Width()
|
2022-02-25 17:53:33 +01:00
|
|
|
if dirlist.NeedScrollbar() {
|
2020-06-09 20:49:23 +02:00
|
|
|
textWidth -= 1
|
|
|
|
}
|
|
|
|
if textWidth < 0 {
|
|
|
|
textWidth = 0
|
|
|
|
}
|
|
|
|
|
2020-05-30 22:27:27 +02:00
|
|
|
for i, name := range dirlist.dirs {
|
2022-02-25 17:53:33 +01:00
|
|
|
if i < dirlist.Scroll() {
|
2020-05-30 22:27:27 +02:00
|
|
|
continue
|
|
|
|
}
|
2022-02-25 17:53:33 +01:00
|
|
|
row := i - dirlist.Scroll()
|
2019-01-13 21:27:56 +01:00
|
|
|
if row >= ctx.Height() {
|
|
|
|
break
|
|
|
|
}
|
2020-05-30 22:27:27 +02:00
|
|
|
|
2022-04-24 16:50:08 +02:00
|
|
|
dirStyle := []config.StyleObject{}
|
|
|
|
s := dirlist.getRUEString(name)
|
|
|
|
switch strings.Count(s, "/") {
|
|
|
|
case 1:
|
|
|
|
dirStyle = append(dirStyle, config.STYLE_DIRLIST_UNREAD)
|
|
|
|
case 2:
|
|
|
|
dirStyle = append(dirStyle, config.STYLE_DIRLIST_RECENT)
|
|
|
|
}
|
|
|
|
style := dirlist.UiConfig().GetComposedStyle(
|
|
|
|
config.STYLE_DIRLIST_DEFAULT, dirStyle)
|
2022-01-23 10:31:40 +01:00
|
|
|
if name == dirlist.selecting {
|
2022-04-24 16:50:08 +02:00
|
|
|
style = dirlist.UiConfig().GetComposedStyleSelected(
|
|
|
|
config.STYLE_DIRLIST_DEFAULT, dirStyle)
|
2019-01-13 21:32:52 +01:00
|
|
|
}
|
2020-06-09 20:49:23 +02:00
|
|
|
ctx.Fill(0, row, textWidth, 1, ' ', style)
|
2019-09-11 18:37:21 +02:00
|
|
|
|
2020-06-09 20:49:23 +02:00
|
|
|
dirString := dirlist.getDirString(name, textWidth, func() string {
|
2022-04-24 16:50:08 +02:00
|
|
|
return s
|
2019-09-11 18:37:21 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
ctx.Printf(0, row, style, dirString)
|
2020-05-30 22:27:27 +02:00
|
|
|
}
|
2020-06-09 20:49:23 +02:00
|
|
|
|
2022-02-25 17:53:33 +01:00
|
|
|
if dirlist.NeedScrollbar() {
|
2020-06-09 20:49:23 +02:00
|
|
|
scrollBarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
|
2022-02-25 17:53:33 +01:00
|
|
|
dirlist.drawScrollbar(scrollBarCtx)
|
2020-06-09 20:49:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 17:53:33 +01:00
|
|
|
func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
|
2020-06-09 20:49:23 +02:00
|
|
|
gutterStyle := tcell.StyleDefault
|
|
|
|
pillStyle := tcell.StyleDefault.Reverse(true)
|
|
|
|
|
|
|
|
// gutter
|
|
|
|
ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
|
|
|
|
|
|
|
|
// pill
|
2022-02-25 17:53:33 +01:00
|
|
|
pillSize := int(math.Ceil(float64(ctx.Height()) * dirlist.PercentVisible()))
|
|
|
|
pillOffset := int(math.Floor(float64(ctx.Height()) * dirlist.PercentScrolled()))
|
2020-06-09 20:49:23 +02:00
|
|
|
ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
|
2020-05-30 22:27:27 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 00:32:36 +02:00
|
|
|
func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
|
2022-07-31 14:32:48 +02:00
|
|
|
if event, ok := event.(*tcell.EventMouse); ok {
|
2019-09-06 00:32:36 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-08-04 16:05:06 +02:00
|
|
|
func (dirlist *DirectoryList) NextPrev(delta int) {
|
2022-01-23 10:31:40 +01:00
|
|
|
curIdx := findString(dirlist.dirs, dirlist.selecting)
|
2019-07-04 11:17:23 +02:00
|
|
|
if curIdx == len(dirlist.dirs) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newIdx := curIdx + delta
|
|
|
|
ndirs := len(dirlist.dirs)
|
Ensure we aren't selecting negative directories
When the list of directories is empty trying to navigate in the
directory list did previously lead to a crash. With this change we
instead return early before trying to change the directory.
Example backtrace:
> panic: runtime error: index out of range [-1]
>
> goroutine 1 [running]:
> git.sr.ht/~sircmpwn/aerc/widgets.(*DirectoryList).NextPrev(0xc000160680, 0xffffffffffffffff)
> source/aerc/widgets/dirlist.go:285 +0xd4
> git.sr.ht/~sircmpwn/aerc/commands/account.NextPrevFolder.Execute(0xc000191040, 0xc00025c210, 0x1, 0x1, 0x0, 0xc00016f420)
> source/aerc/commands/account/next-folder.go:44 +0xe0
> git.sr.ht/~sircmpwn/aerc/commands.(*Commands).ExecuteCommand(0xc0000101a8, 0xc000191040, 0xc00025c210, 0x1, 0x1, 0xc000020070, 0xb46d01)
> source/aerc/commands/commands.go:66 +0xa7
> main.execCommand(0xc000191040, 0xc0001ca190, 0xc00025c210, 0x1, 0x1, 0xc00025c210, 0xc0003fb080)
> source/aerc/aerc.go:60 +0xc7
> main.main.func3(0xc00025c210, 0x1, 0x1, 0x1, 0x1)
> source/aerc/aerc.go:162 +0x57
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).BeginExCommand.func1(0xc000201db0, 0xb)
> source/aerc/widgets/aerc.go:382 +0x83
> git.sr.ht/~sircmpwn/aerc/widgets.(*ExLine).Event(0xc0003be100, 0xb475a0, 0xc00023cba0, 0xc00023cba0)
> source/aerc/widgets/exline.go:79 +0x131
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event(0xc000191040, 0xb475a0, 0xc00023cba0, 0x99ee01)
> source/aerc/widgets/aerc.go:202 +0x4c1
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).simulate(0xc000191040, 0xc000036f00, 0xd, 0x10)
> source/aerc/widgets/aerc.go:195 +0x8d
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event(0xc000191040, 0xb475a0, 0xc00023c9c0, 0x9c5a60)
> source/aerc/widgets/aerc.go:218 +0x3e8
> git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick(0xc0001ca190, 0xa99d00)
> source/aerc/lib/ui/ui.go:92 +0x190
> main.main()
> source/aerc/aerc.go:192 +0x5f2
2020-02-06 12:47:38 +01:00
|
|
|
|
|
|
|
if ndirs == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-04 11:17:23 +02:00
|
|
|
if newIdx < 0 {
|
|
|
|
newIdx = ndirs - 1
|
|
|
|
} else if newIdx >= ndirs {
|
|
|
|
newIdx = 0
|
2019-03-11 02:15:24 +01:00
|
|
|
}
|
Ensure we aren't selecting negative directories
When the list of directories is empty trying to navigate in the
directory list did previously lead to a crash. With this change we
instead return early before trying to change the directory.
Example backtrace:
> panic: runtime error: index out of range [-1]
>
> goroutine 1 [running]:
> git.sr.ht/~sircmpwn/aerc/widgets.(*DirectoryList).NextPrev(0xc000160680, 0xffffffffffffffff)
> source/aerc/widgets/dirlist.go:285 +0xd4
> git.sr.ht/~sircmpwn/aerc/commands/account.NextPrevFolder.Execute(0xc000191040, 0xc00025c210, 0x1, 0x1, 0x0, 0xc00016f420)
> source/aerc/commands/account/next-folder.go:44 +0xe0
> git.sr.ht/~sircmpwn/aerc/commands.(*Commands).ExecuteCommand(0xc0000101a8, 0xc000191040, 0xc00025c210, 0x1, 0x1, 0xc000020070, 0xb46d01)
> source/aerc/commands/commands.go:66 +0xa7
> main.execCommand(0xc000191040, 0xc0001ca190, 0xc00025c210, 0x1, 0x1, 0xc00025c210, 0xc0003fb080)
> source/aerc/aerc.go:60 +0xc7
> main.main.func3(0xc00025c210, 0x1, 0x1, 0x1, 0x1)
> source/aerc/aerc.go:162 +0x57
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).BeginExCommand.func1(0xc000201db0, 0xb)
> source/aerc/widgets/aerc.go:382 +0x83
> git.sr.ht/~sircmpwn/aerc/widgets.(*ExLine).Event(0xc0003be100, 0xb475a0, 0xc00023cba0, 0xc00023cba0)
> source/aerc/widgets/exline.go:79 +0x131
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event(0xc000191040, 0xb475a0, 0xc00023cba0, 0x99ee01)
> source/aerc/widgets/aerc.go:202 +0x4c1
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).simulate(0xc000191040, 0xc000036f00, 0xd, 0x10)
> source/aerc/widgets/aerc.go:195 +0x8d
> git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event(0xc000191040, 0xb475a0, 0xc00023c9c0, 0x9c5a60)
> source/aerc/widgets/aerc.go:218 +0x3e8
> git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick(0xc0001ca190, 0xa99d00)
> source/aerc/lib/ui/ui.go:92 +0x190
> main.main()
> source/aerc/aerc.go:192 +0x5f2
2020-02-06 12:47:38 +01:00
|
|
|
|
2019-07-04 11:17:23 +02:00
|
|
|
dirlist.Select(dirlist.dirs[newIdx])
|
2019-03-11 02:15:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dirlist *DirectoryList) Next() {
|
2019-08-04 16:05:06 +02:00
|
|
|
dirlist.NextPrev(1)
|
2019-03-11 02:15:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dirlist *DirectoryList) Prev() {
|
2019-08-04 16:05:06 +02:00
|
|
|
dirlist.NextPrev(-1)
|
2019-03-11 02:15:24 +01:00
|
|
|
}
|
2019-06-12 08:31:51 +02:00
|
|
|
|
2019-08-16 09:11:37 +02:00
|
|
|
func folderMatches(folder string, pattern string) bool {
|
2019-08-20 06:04:21 +02:00
|
|
|
if len(pattern) == 0 {
|
2019-08-16 09:11:37 +02:00
|
|
|
return false
|
|
|
|
}
|
2019-08-20 06:04:21 +02:00
|
|
|
if pattern[0] == '~' {
|
2019-08-20 07:10:48 +02:00
|
|
|
r, err := regexp.Compile(pattern[1:])
|
2019-08-20 06:04:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return r.Match([]byte(folder))
|
|
|
|
}
|
|
|
|
return pattern == folder
|
2019-08-16 09:11:37 +02:00
|
|
|
}
|
|
|
|
|
2019-12-03 20:20:21 +01:00
|
|
|
// 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() {
|
2021-11-13 09:10:09 +01:00
|
|
|
if !dirlist.acctConf.EnableFoldersSort {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-03 20:20:21 +01:00
|
|
|
sort.Slice(dirlist.dirs, func(i, j int) bool {
|
2020-01-26 12:43:46 +01:00
|
|
|
foldersSort := dirlist.acctConf.FoldersSort
|
|
|
|
iInFoldersSort := findString(foldersSort, dirlist.dirs[i])
|
|
|
|
jInFoldersSort := findString(foldersSort, dirlist.dirs[j])
|
2019-12-03 20:20:21 +01:00
|
|
|
if iInFoldersSort >= 0 && jInFoldersSort >= 0 {
|
|
|
|
return iInFoldersSort < jInFoldersSort
|
|
|
|
}
|
|
|
|
if iInFoldersSort >= 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if jInFoldersSort >= 0 {
|
|
|
|
return false
|
|
|
|
}
|
2020-01-26 12:03:21 +01:00
|
|
|
return dirlist.dirs[i] < dirlist.dirs[j]
|
2019-12-03 20:20:21 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-04 11:17:21 +02:00
|
|
|
// filterDirsByFoldersConfig sets dirlist.dirs to the filtered subset of the
|
2020-07-01 09:52:14 +02:00
|
|
|
// dirstore, based on AccountConfig.Folders (inclusion) and
|
|
|
|
// AccountConfig.FoldersExclude (exclusion), in that order.
|
2019-06-12 08:31:51 +02:00
|
|
|
func (dirlist *DirectoryList) filterDirsByFoldersConfig() {
|
2020-07-01 09:52:14 +02:00
|
|
|
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
|
2022-05-30 14:34:18 +02:00
|
|
|
dirlist.dirs = dirlist.FilterDirs(dirlist.dirs, configFolders, false)
|
2020-07-01 09:52:14 +02:00
|
|
|
|
|
|
|
configFoldersExclude := dirlist.acctConf.FoldersExclude
|
2022-05-30 14:34:18 +02:00
|
|
|
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
|
2019-06-12 08:31:51 +02:00
|
|
|
}
|
2019-07-21 23:39:36 +02:00
|
|
|
|
|
|
|
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) SetMsgStore(name string, msgStore *lib.MessageStore) {
|
|
|
|
dirlist.store.SetMessageStore(name, msgStore)
|
2019-09-11 18:37:21 +02:00
|
|
|
msgStore.OnUpdateDirs(func() {
|
|
|
|
dirlist.Invalidate()
|
|
|
|
})
|
2019-07-21 23:39:36 +02:00
|
|
|
}
|
2019-12-03 20:20:21 +01:00
|
|
|
|
|
|
|
func findString(slice []string, str string) int {
|
|
|
|
for i, s := range slice {
|
|
|
|
if str == s {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
2020-02-15 14:14:42 +01:00
|
|
|
|
2020-02-24 20:12:16 +01:00
|
|
|
func countRUE(msgStore *lib.MessageStore) (recent, unread int) {
|
2020-02-15 14:14:43 +01:00
|
|
|
for _, msg := range msgStore.Messages {
|
|
|
|
if msg == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
seen := false
|
|
|
|
for _, flag := range msg.Flags {
|
|
|
|
if flag == models.SeenFlag {
|
|
|
|
seen = true
|
|
|
|
}
|
2022-05-31 20:22:28 +02:00
|
|
|
if flag == models.RecentFlag {
|
2020-02-15 14:14:43 +01:00
|
|
|
recent++
|
|
|
|
}
|
|
|
|
}
|
2022-05-31 20:22:28 +02:00
|
|
|
if !seen {
|
|
|
|
unread++
|
|
|
|
}
|
2020-02-15 14:14:43 +01:00
|
|
|
}
|
2020-02-24 20:12:16 +01:00
|
|
|
return recent, unread
|
2020-02-15 14:14:43 +01:00
|
|
|
}
|