aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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()
}