From 006e10357b0e89ba3a8216ff5a6435c5d8f77187 Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Thu, 20 Oct 2022 16:43:44 +0200 Subject: [PATCH] threads: reverse thread ordering Add reverse-thread-order option to the ui config to enable reverse display of the mesage threads. Default order is the the intial message is on the top with all the replies being displayed below. The reverse options will put the initial message at the bottom with the replies on top. Signed-off-by: Koni Marti Acked-by: Robin Jarry --- CHANGELOG.md | 2 ++ config/aerc.conf | 8 ++++++++ config/config.go | 3 ++- doc/aerc-config.5.scd | 8 ++++++++ lib/msgstore.go | 26 ++++++++++++++++++-------- lib/threadbuilder.go | 16 ++++++++++++---- widgets/account.go | 1 + 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 615c9c7..8dc2b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). `:rmdir`, `:archive` and the `copy-to` option. - Display messages from bottom to top with `reverse-msglist-order=true` in `aerc.conf`. +- Display threads from bottom to top with `reverse-thread-order=true` in + `aerc.conf`. ### Fixed diff --git a/config/aerc.conf b/config/aerc.conf index 1650cf1..e290525 100644 --- a/config/aerc.conf +++ b/config/aerc.conf @@ -194,6 +194,14 @@ completion-popovers=true # Default: false #reverse-msglist-order = false +# Reverse display of the mesage threads. Default order is the the intial +# message is on the top with all the replies being displayed below. The +# reverse option will put the initial message at the bottom with the +# replies on top. +# +# Default: false +#reverse-thread-order=false + #[ui:account=foo] # # Enable a threaded view of messages. If this is not supported by the backend diff --git a/config/config.go b/config/config.go index 8eb4007..a15e1d5 100644 --- a/config/config.go +++ b/config/config.go @@ -79,7 +79,8 @@ type UIConfig struct { BorderCharVertical rune `ini:"-"` BorderCharHorizontal rune `ini:"-"` - ReverseOrder bool `ini:"reverse-msglist-order"` + ReverseOrder bool `ini:"reverse-msglist-order"` + ReverseThreadOrder bool `ini:"reverse-thread-order"` } type ContextType int diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 76adaa0..94dfb59 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -335,6 +335,14 @@ These options are configured in the *[ui]* section of aerc.conf. Default: false +*reverse-thread-order* + Reverse display of the mesage threads. Default order is the the intial + message is on the top with all the replies being displayed below. The + reverse option will put the initial message at the bottom with the + replies on top. + + 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 d6ca26e..a22e196 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -39,9 +39,10 @@ type MessageStore struct { sortCriteria []*types.SortCriterion - threadedView bool - buildThreads bool - builder *ThreadBuilder + threadedView bool + reverseThreadOrder bool + buildThreads bool + builder *ThreadBuilder // Map of uids we've asked the worker to fetch onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers @@ -74,7 +75,7 @@ func NewMessageStore(worker *types.Worker, dirInfo *models.DirectoryInfo, defaultSortCriteria []*types.SortCriterion, thread bool, clientThreads bool, clientThreadsDelay time.Duration, - reverseOrder bool, + reverseOrder bool, reverseThreadOrder bool, triggerNewEmail func(*models.MessageInfo), triggerDirectoryChange func(), ) *MessageStore { @@ -91,8 +92,9 @@ func NewMessageStore(worker *types.Worker, bodyCallbacks: make(map[uint32][]func(*types.FullMessage)), - threadedView: thread, - buildThreads: clientThreads, + threadedView: thread, + buildThreads: clientThreads, + reverseThreadOrder: reverseThreadOrder, filter: []string{"filter"}, sortCriteria: defaultSortCriteria, @@ -231,7 +233,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { newMap := make(map[uint32]*models.MessageInfo) builder := NewThreadBuilder(store.iterFactory) - builder.RebuildUids(msg.Threads) + builder.RebuildUids(msg.Threads, store.reverseThreadOrder) store.uids = builder.Uids() store.threads = msg.Threads @@ -364,6 +366,14 @@ func (store *MessageStore) update(threads bool) { } } +func (store *MessageStore) SetReverseThreadOrder(reverse bool) { + store.reverseThreadOrder = reverse +} + +func (store *MessageStore) ReverseThreadOrder() bool { + return store.reverseThreadOrder +} + func (store *MessageStore) SetThreadedView(thread bool) { store.threadedView = thread if store.buildThreads { @@ -424,7 +434,7 @@ func (store *MessageStore) runThreadBuilderNow() { } } // build new threads - th := store.builder.Threads(store.uids) + th := store.builder.Threads(store.uids, store.reverseThreadOrder) // save local threads to the message store variable and // run callback if defined (callback should reposition cursor) diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go index 75a8079..b160925 100644 --- a/lib/threadbuilder.go +++ b/lib/threadbuilder.go @@ -55,7 +55,7 @@ func (builder *ThreadBuilder) Update(msg *models.MessageInfo) { } // Threads returns a slice of threads for the given list of uids -func (builder *ThreadBuilder) Threads(uids []uint32) []*types.Thread { +func (builder *ThreadBuilder) Threads(uids []uint32, inverse bool) []*types.Thread { builder.Lock() defer builder.Unlock() @@ -67,7 +67,7 @@ func (builder *ThreadBuilder) Threads(uids []uint32) []*types.Thread { builder.sortThreads(threads, uids) // rebuild uids from threads - builder.RebuildUids(threads) + builder.RebuildUids(threads, inverse) elapsed := time.Since(start) logging.Infof("%d threads created in %s", len(threads), elapsed) @@ -155,15 +155,23 @@ 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) { +func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread, inverse bool) { uids := make([]uint32, 0, len(threads)) iterT := builder.iterFactory.NewIterator(threads) for iterT.Next() { + var threaduids []uint32 _ = iterT.Value().(*types.Thread).Walk( func(t *types.Thread, level int, currentErr error) error { - uids = append(uids, t.Uid) + threaduids = append(threaduids, t.Uid) return nil }) + if inverse { + for j := len(threaduids) - 1; j >= 0; j-- { + uids = append(uids, threaduids[j]) + } + } else { + uids = append(uids, threaduids...) + } } result := make([]uint32, 0, len(uids)) iterU := builder.iterFactory.NewIterator(uids) diff --git a/widgets/account.go b/widgets/account.go index 1ad149f..9761c49 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -286,6 +286,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) { acct.dirlist.UiConfig(name).ForceClientThreads, acct.dirlist.UiConfig(name).ClientThreadsDelay, acct.dirlist.UiConfig(name).ReverseOrder, + acct.dirlist.UiConfig(name).ReverseThreadOrder, func(msg *models.MessageInfo) { acct.conf.Triggers.ExecNewEmail(acct.acct, acct.conf, msg)