aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2022-10-20 16:43:41 +0200
committerRobin Jarry <robin@jarry.cc>2022-10-27 22:44:39 +0200
commitc5face0b6f922781f1d2ae754c00fdee6c58af20 (patch)
tree13fd5d1026319f36921be6776b13ae87c68f16ec
parentc83ffabf3853e5f06294bba78dc23bc7ff84b0af (diff)
downloadaerc-c5face0b6f922781f1d2ae754c00fdee6c58af20.tar.gz
store: reverse message list order with iterators
Reverse the order of the messages in the message list. The complexity of reversing the order is abstracted away by the iterators. To reverse the message list, add the following to your aerc.conf: [ui] reverse-msglist-order=true Thanks to |cos| for sharing his initial implementation of reversing the order in the message list [0]. [0]: https://git.netizen.se/aerc/commit/?h=topic/asc_sort_imap Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--CHANGELOG.md2
-rw-r--r--config/aerc.conf9
-rw-r--r--config/config.go2
-rw-r--r--doc/aerc-config.5.scd8
-rw-r--r--lib/msgstore.go71
-rw-r--r--lib/threadbuilder.go26
-rw-r--r--widgets/account.go1
-rw-r--r--widgets/msglist.go56
8 files changed, 123 insertions, 52 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79197172..615c9c78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- View common email envelope headers with `:envelope`.
- Notmuch accounts now support maildir operations: `:copy`, `:move`, `:mkdir`,
`:rmdir`, `:archive` and the `copy-to` option.
+- Display messages from bottom to top with `reverse-msglist-order=true` in
+ `aerc.conf`.
### Fixed
diff --git a/config/aerc.conf b/config/aerc.conf
index 384a0db3..1650cf1d 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -184,6 +184,15 @@ completion-popovers=true
#icon-signed-encrypted=✔
#icon-unknown=✘
#icon-invalid=⚠
+#
+
+# Reverses the order of the message list. By default, the message list is
+# ordered with the newest (highest UID) message on top. Reversing the order
+# will put the oldest (lowest UID) message on top. This can be useful in cases
+# where the backend does not support sorting.
+#
+# Default: false
+#reverse-msglist-order = false
#[ui:account=foo]
#
diff --git a/config/config.go b/config/config.go
index 8b8d9d21..8eb40070 100644
--- a/config/config.go
+++ b/config/config.go
@@ -78,6 +78,8 @@ type UIConfig struct {
// customize border appearance
BorderCharVertical rune `ini:"-"`
BorderCharHorizontal rune `ini:"-"`
+
+ ReverseOrder bool `ini:"reverse-msglist-order"`
}
type ContextType int
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 310721ad..76adaa02 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -327,6 +327,14 @@ These options are configured in the *[ui]* section of aerc.conf.
instances of items /containing/ the string, starting at any position and
need not be consecutive characters in the command or option.
+*reverse-msglist-order*
+ Reverses the order of the message list. By default, the message list is
+ ordered with the newest (highest UID) message on top. Reversing the
+ order will put the oldest (lowest UID) message on top. This can be
+ useful in cases where the backend does not support sorting.
+
+ Default: false
+
*threading-enabled*
Enable a threaded view of messages. If this is not supported by the
backend (IMAP server or notmuch), threads will be built by the client.
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 23fbb8f8..7b2fbbf1 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -5,6 +5,7 @@ import (
"sync"
"time"
+ "git.sr.ht/~rjarry/aerc/lib/iterator"
"git.sr.ht/~rjarry/aerc/lib/marker"
"git.sr.ht/~rjarry/aerc/lib/sort"
"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -63,6 +64,8 @@ type MessageStore struct {
// threads mutex protects the store.threads and store.threadCallback
threadsMutex sync.Mutex
+
+ iterFactory iterator.Factory
}
const MagicUid = 0xFFFFFFFF
@@ -71,6 +74,7 @@ func NewMessageStore(worker *types.Worker,
dirInfo *models.DirectoryInfo,
defaultSortCriteria []*types.SortCriterion,
thread bool, clientThreads bool, clientThreadsDelay time.Duration,
+ reverseOrder bool,
triggerNewEmail func(*models.MessageInfo),
triggerDirectoryChange func(),
) *MessageStore {
@@ -104,6 +108,8 @@ func NewMessageStore(worker *types.Worker,
triggerDirectoryChange: triggerDirectoryChange,
threadBuilderDelay: clientThreadsDelay,
+
+ iterFactory: iterator.NewFactory(reverseOrder),
}
}
@@ -222,25 +228,23 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
store.runThreadBuilderNow()
}
case *types.DirectoryThreaded:
- var uids []uint32
newMap := make(map[uint32]*models.MessageInfo)
- for i := len(msg.Threads) - 1; i >= 0; i-- {
- _ = msg.Threads[i].Walk(func(t *types.Thread, level int, currentErr error) error {
- uid := t.Uid
- uids = append([]uint32{uid}, uids...)
- if msg, ok := store.Messages[uid]; ok {
- newMap[uid] = msg
- } else {
- newMap[uid] = nil
- directoryChange = true
- }
- return nil
- })
+ builder := NewThreadBuilder(store.iterFactory)
+ builder.RebuildUids(msg.Threads)
+ store.uids = builder.Uids()
+ store.threads = msg.Threads
+
+ for _, uid := range store.uids {
+ if msg, ok := store.Messages[uid]; ok {
+ newMap[uid] = msg
+ } else {
+ newMap[uid] = nil
+ directoryChange = true
+ }
}
+
store.Messages = newMap
- store.uids = uids
- store.threads = msg.Threads
update = true
case *types.MessageInfo:
if existing, ok := store.Messages[msg.Info.Uid]; ok && existing != nil {
@@ -379,6 +383,12 @@ func (store *MessageStore) Threads() []*types.Thread {
return store.threads
}
+func (store *MessageStore) ThreadsIterator() iterator.Iterator {
+ store.threadsMutex.Lock()
+ defer store.threadsMutex.Unlock()
+ return store.iterFactory.NewIterator(store.threads)
+}
+
func (store *MessageStore) ThreadedView() bool {
return store.threadedView
}
@@ -388,6 +398,12 @@ func (store *MessageStore) BuildThreads() bool {
}
func (store *MessageStore) runThreadBuilder() {
+ if store.builder == nil {
+ store.builder = NewThreadBuilder(store.iterFactory)
+ for _, msg := range store.Messages {
+ store.builder.Update(msg)
+ }
+ }
if store.threadBuilderDebounce != nil {
if store.threadBuilderDebounce.Stop() {
logging.Infof("thread builder debounced")
@@ -402,7 +418,7 @@ func (store *MessageStore) runThreadBuilder() {
// runThreadBuilderNow runs the threadbuilder without any debounce logic
func (store *MessageStore) runThreadBuilderNow() {
if store.builder == nil {
- store.builder = NewThreadBuilder()
+ store.builder = NewThreadBuilder(store.iterFactory)
for _, msg := range store.Messages {
store.builder.Update(msg)
}
@@ -544,14 +560,18 @@ func (store *MessageStore) Uids() []uint32 {
return store.uids
}
+func (store *MessageStore) UidsIterator() iterator.Iterator {
+ return store.iterFactory.NewIterator(store.Uids())
+}
+
func (store *MessageStore) Selected() *models.MessageInfo {
return store.Messages[store.selectedUid]
}
func (store *MessageStore) SelectedUid() uint32 {
if store.selectedUid == MagicUid && len(store.Uids()) > 0 {
- uids := store.Uids()
- store.selectedUid = uids[len(uids)-1]
+ iter := store.UidsIterator()
+ store.selectedUid = store.Uids()[iter.StartIndex()]
}
return store.selectedUid
}
@@ -573,20 +593,27 @@ func (store *MessageStore) NextPrev(delta int) {
if len(uids) == 0 {
return
}
+ iter := store.iterFactory.NewIterator(uids)
uid := store.SelectedUid()
newIdx := store.FindIndexByUid(uid)
if newIdx < 0 {
- store.Select(uids[len(uids)-1])
+ store.Select(uids[iter.StartIndex()])
return
}
- newIdx -= delta
+ low, high := iter.EndIndex(), iter.StartIndex()
+ sign := -1
+ if high < low {
+ low, high = high, low
+ sign = 1
+ }
+ newIdx += sign * delta
if newIdx >= len(uids) {
- newIdx = len(uids) - 1
+ newIdx = high
} else if newIdx < 0 {
- newIdx = 0
+ newIdx = low
}
store.Select(uids[newIdx])
diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go
index 6cd98e87..75a80797 100644
--- a/lib/threadbuilder.go
+++ b/lib/threadbuilder.go
@@ -4,6 +4,7 @@ import (
"sync"
"time"
+ "git.sr.ht/~rjarry/aerc/lib/iterator"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/worker/types"
@@ -16,13 +17,15 @@ type ThreadBuilder struct {
messageidToUid map[string]uint32
seen map[uint32]bool
threadedUids []uint32
+ iterFactory iterator.Factory
}
-func NewThreadBuilder() *ThreadBuilder {
+func NewThreadBuilder(i iterator.Factory) *ThreadBuilder {
tb := &ThreadBuilder{
threadBlocks: make(map[uint32]jwz.Threadable),
messageidToUid: make(map[string]uint32),
seen: make(map[uint32]bool),
+ iterFactory: i,
}
return tb
}
@@ -154,17 +157,20 @@ func (builder *ThreadBuilder) sortThreads(threads []*types.Thread, orderedUids [
// RebuildUids rebuilds the uids from the given slice of threads
func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread) {
uids := make([]uint32, 0, len(threads))
- for i := len(threads) - 1; i >= 0; i-- {
- _ = threads[i].Walk(func(t *types.Thread, level int, currentErr error) error {
- uids = append(uids, t.Uid)
- return nil
- })
+ iterT := builder.iterFactory.NewIterator(threads)
+ for iterT.Next() {
+ _ = iterT.Value().(*types.Thread).Walk(
+ func(t *types.Thread, level int, currentErr error) error {
+ uids = append(uids, t.Uid)
+ return nil
+ })
}
- // copy in reverse as msgList displays backwards
- for i, j := 0, len(uids)-1; i < j; i, j = i+1, j-1 {
- uids[i], uids[j] = uids[j], uids[i]
+ result := make([]uint32, 0, len(uids))
+ iterU := builder.iterFactory.NewIterator(uids)
+ for iterU.Next() {
+ result = append(result, iterU.Value().(uint32))
}
- builder.threadedUids = uids
+ builder.threadedUids = result
}
// threadable implements the jwz.threadable interface which is required for the
diff --git a/widgets/account.go b/widgets/account.go
index c82646a9..1ad149f0 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -285,6 +285,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
acct.dirlist.UiConfig(name).ThreadingEnabled,
acct.dirlist.UiConfig(name).ForceClientThreads,
acct.dirlist.UiConfig(name).ClientThreadsDelay,
+ acct.dirlist.UiConfig(name).ReverseOrder,
func(msg *models.MessageInfo) {
acct.conf.Triggers.ExecNewEmail(acct.acct,
acct.conf, msg)
diff --git a/widgets/msglist.go b/widgets/msglist.go
index 7857a3d7..f53c0b5f 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -66,11 +66,13 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
ml.UpdateScroller(ml.height, len(store.Uids()))
if store := ml.Store(); store != nil && len(store.Uids()) > 0 {
- idx := store.FindIndexByUid(store.SelectedUid())
- if idx < 0 {
- idx = len(store.Uids()) - 1
+ iter := store.UidsIterator()
+ for i := 0; iter.Next(); i++ {
+ if store.SelectedUid() == iter.Value().(uint32) {
+ ml.EnsureScroll(i)
+ break
+ }
}
- ml.EnsureScroll(len(store.Uids()) - idx - 1)
}
textWidth := ctx.Width()
@@ -87,23 +89,24 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
)
if store.ThreadedView() {
- threads := store.Threads()
- counter := len(store.Uids())
+ iter := store.ThreadsIterator()
+ var i int = 0
- for i := len(threads) - 1; i >= 0; i-- {
+ for iter.Next() {
+ thread := iter.Value().(*types.Thread)
var lastSubject string
- err := threads[i].Walk(func(t *types.Thread, _ int, currentErr error) error {
+ err := thread.Walk(func(t *types.Thread, _ int, currentErr error) error {
if currentErr != nil {
return currentErr
}
if t.Hidden || t.Deleted {
return nil
}
- counter--
- if counter > len(store.Uids())-1-ml.Scroll() {
- // skip messages which are higher than the viewport
+ if i < ml.Scroll() {
+ i++
return nil
}
+ i++
msg := store.Messages[t.Uid]
var prefix string
var subject string
@@ -139,9 +142,13 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
}
} else {
- uids := store.Uids()
- for i := len(uids) - 1 - ml.Scroll(); i >= 0; i-- {
- uid := uids[i]
+ iter := store.UidsIterator()
+ for i := 0; iter.Next(); i++ {
+ if i < ml.Scroll() {
+ continue
+ }
+ uid := iter.Value().(uint32)
+
msg := store.Messages[uid]
fmtCtx := format.Ctx{
FromAddress: acct.acct.From,
@@ -395,13 +402,22 @@ func (ml *MessageList) Select(index int) {
if len(uids) == 0 {
return
}
- uidIdx := len(uids) - index - 1
- if uidIdx >= len(store.Uids()) {
- uidIdx = 0
- } else if uidIdx < 0 {
- uidIdx = len(store.Uids()) - 1
+
+ iter := store.UidsIterator()
+
+ var uid uint32
+ if index < 0 {
+ uid = uids[iter.EndIndex()]
+ } else {
+ uid = uids[iter.StartIndex()]
+ for i := 0; iter.Next(); i++ {
+ if i >= index {
+ uid = iter.Value().(uint32)
+ break
+ }
+ }
}
- store.Select(store.Uids()[uidIdx])
+ store.Select(uid)
ml.Invalidate()
}