diff options
-rw-r--r-- | app/account.go | 1 | ||||
-rw-r--r-- | config/aerc.conf | 7 | ||||
-rw-r--r-- | config/ui.go | 1 | ||||
-rw-r--r-- | doc/aerc-config.5.scd | 6 | ||||
-rw-r--r-- | lib/msgstore.go | 12 | ||||
-rw-r--r-- | lib/threadbuilder.go | 26 | ||||
-rw-r--r-- | worker/maildir/worker.go | 2 |
7 files changed, 43 insertions, 12 deletions
diff --git a/app/account.go b/app/account.go index 7ce10f0f..9de1eaf9 100644 --- a/app/account.go +++ b/app/account.go @@ -254,6 +254,7 @@ func (acct *AccountView) newStore(name string) *lib.MessageStore { uiConf.ForceClientThreads, uiConf.ClientThreadsDelay, uiConf.SelectLast, + uiConf.ThreadingBySubject, uiConf.ReverseOrder, uiConf.ReverseThreadOrder, uiConf.SortThreadSiblings, diff --git a/config/aerc.conf b/config/aerc.conf index a7d763d7..4a83625a 100644 --- a/config/aerc.conf +++ b/config/aerc.conf @@ -392,7 +392,6 @@ # Default: 0 #msglist-scroll-offset = 0 -#[ui:account=foo] # # 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. @@ -405,6 +404,12 @@ # Default: false #force-client-threads=false +# If no References nor In-Reply-To headers can be matched to build client side +# threads, fallback to similar subjects. +# +# Default: false +#threading-by-subject=false + # Show thread context enables messages which do not match the current query (or # belong to the current mailbox) to be shown for context. These messages can be # styled separately using "msglist_thread_context" in a styleset. This feature diff --git a/config/ui.go b/config/ui.go index 7c7f1869..ee8f7054 100644 --- a/config/ui.go +++ b/config/ui.go @@ -40,6 +40,7 @@ type UIConfig struct { MouseEnabled bool `ini:"mouse-enabled"` ThreadingEnabled bool `ini:"threading-enabled"` ForceClientThreads bool `ini:"force-client-threads"` + ThreadingBySubject bool `ini:"threading-by-subject"` ClientThreadsDelay time.Duration `ini:"client-threads-delay" default:"50ms"` ThreadContext bool `ini:"show-thread-context"` FuzzyComplete bool `ini:"fuzzy-complete"` diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 7655737e..d54609ca 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -495,6 +495,12 @@ These options are configured in the *[ui]* section of _aerc.conf_. Default: _false_ +*threading-by-subject* = _true_|_false_ + If no References nor In-Reply-To headers can be matched to build client + side threads, fallback to similar subjects. + + Default: _false_ + *client-threads-delay* = _<duration>_ Delay of inactivity after which the client threads are rebuilt. Setting this to _0s_ may introduce a noticeable lag when scrolling through the diff --git a/lib/msgstore.go b/lib/msgstore.go index b4790735..45b9cfd7 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -51,6 +51,7 @@ type MessageStore struct { selectLast bool reverseThreadOrder bool threadContext bool + threadBySubject bool sortThreadSiblings bool buildThreads bool builder *ThreadBuilder @@ -90,7 +91,7 @@ const MagicUid = 0xFFFFFFFF func NewMessageStore(worker *types.Worker, defaultSortCriteria []*types.SortCriterion, thread bool, clientThreads bool, clientThreadsDelay time.Duration, - selectLast bool, + selectLast bool, threadBySubject bool, reverseOrder bool, reverseThreadOrder bool, sortThreadSiblings bool, triggerNewEmail func(*models.MessageInfo), triggerDirectoryChange func(), triggerMailDeleted func(), @@ -118,6 +119,7 @@ func NewMessageStore(worker *types.Worker, threadedView: thread, buildThreads: clientThreads, threadContext: threadContext, + threadBySubject: threadBySubject, selectLast: selectLast, reverseThreadOrder: reverseThreadOrder, sortThreadSiblings: sortThreadSiblings, @@ -280,7 +282,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { } case *types.DirectoryThreaded: if store.builder == nil { - store.builder = NewThreadBuilder(store.iterFactory) + store.builder = NewThreadBuilder(store.iterFactory, store.threadBySubject) } store.builder.RebuildUids(msg.Threads, store.reverseThreadOrder) store.uids = store.builder.Uids() @@ -424,7 +426,7 @@ func (store *MessageStore) update(threads bool) { store.runThreadBuilder() default: if store.builder == nil { - store.builder = NewThreadBuilder(store.iterFactory) + store.builder = NewThreadBuilder(store.iterFactory, store.threadBySubject) } store.threadsMutex.Lock() store.builder.RebuildUids(store.threads, store.reverseThreadOrder) @@ -478,7 +480,7 @@ func (store *MessageStore) BuildThreads() bool { func (store *MessageStore) runThreadBuilder() { if store.builder == nil { - store.builder = NewThreadBuilder(store.iterFactory) + store.builder = NewThreadBuilder(store.iterFactory, store.threadBySubject) for _, msg := range store.Messages { store.builder.Update(msg) } @@ -495,7 +497,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.iterFactory) + store.builder = NewThreadBuilder(store.iterFactory, store.threadBySubject) for _, msg := range store.Messages { store.builder.Update(msg) } diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go index b7dae431..abfbadb7 100644 --- a/lib/threadbuilder.go +++ b/lib/threadbuilder.go @@ -9,6 +9,7 @@ import ( "git.sr.ht/~rjarry/aerc/lib/log" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" + sortthread "github.com/emersion/go-imap-sortthread" "github.com/gatherstars-com/jwz" ) @@ -18,13 +19,15 @@ type ThreadBuilder struct { threadedUids []uint32 threadMap map[uint32]*types.Thread iterFactory iterator.Factory + bySubject bool } -func NewThreadBuilder(i iterator.Factory) *ThreadBuilder { +func NewThreadBuilder(i iterator.Factory, bySubject bool) *ThreadBuilder { tb := &ThreadBuilder{ threadBlocks: make(map[uint32]jwz.Threadable), iterFactory: i, threadMap: make(map[uint32]*types.Thread), + bySubject: bySubject, } return tb } @@ -57,7 +60,8 @@ func (builder *ThreadBuilder) Update(msg *models.MessageInfo) { defer builder.Unlock() if msg != nil { - if threadable := newThreadable(msg); threadable != nil { + threadable := newThreadable(msg, builder.bySubject) + if threadable != nil { builder.threadBlocks[msg.Uid] = threadable } } @@ -244,9 +248,10 @@ type threadable struct { Parent jwz.Threadable Child jwz.Threadable Dummy bool + bySubject bool } -func newThreadable(msg *models.MessageInfo) *threadable { +func newThreadable(msg *models.MessageInfo, bySubject bool) *threadable { msgid, err := msg.MsgId() if err != nil { return nil @@ -258,6 +263,7 @@ func newThreadable(msg *models.MessageInfo) *threadable { Parent: nil, Child: nil, Dummy: false, + bySubject: bySubject, } } @@ -312,15 +318,25 @@ func (t *threadable) UID() uint32 { } func (t *threadable) Subject() string { - // deactivate threading by subject for now - return "" + if !t.bySubject || t.MsgInfo == nil || t.MsgInfo.Envelope == nil { + return "" + } + return t.MsgInfo.Envelope.Subject } func (t *threadable) SimplifiedSubject() string { + if t.bySubject { + subject, _ := sortthread.GetBaseSubject(t.Subject()) + return subject + } return "" } func (t *threadable) SubjectIsReply() bool { + if t.bySubject { + _, replyOrForward := sortthread.GetBaseSubject(t.Subject()) + return replyOrForward + } return false } diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go index f5d3a8e1..c733ddc6 100644 --- a/worker/maildir/worker.go +++ b/worker/maildir/worker.go @@ -575,7 +575,7 @@ func (w *Worker) handleFetchDirectoryThreaded( func (w *Worker) threads(ctx context.Context, uids []uint32, criteria []*types.SortCriterion, ) ([]*types.Thread, error) { - builder := aercLib.NewThreadBuilder(iterator.NewFactory(false)) + builder := aercLib.NewThreadBuilder(iterator.NewFactory(false), false) msgInfos := make([]*models.MessageInfo, 0, len(uids)) mu := sync.Mutex{} wg := sync.WaitGroup{} |