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 <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
main
Koni Marti 1 month ago committed by Robin Jarry
parent 88afe7bb4a
commit 006e10357b
  1. 2
      CHANGELOG.md
  2. 8
      config/aerc.conf
  3. 3
      config/config.go
  4. 8
      doc/aerc-config.5.scd
  5. 26
      lib/msgstore.go
  6. 16
      lib/threadbuilder.go
  7. 1
      widgets/account.go

@ -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

@ -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

@ -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

@ -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.

@ -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)

@ -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)

@ -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)

Loading…
Cancel
Save