aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/account.go1
-rw-r--r--config/aerc.conf7
-rw-r--r--config/ui.go1
-rw-r--r--doc/aerc-config.5.scd6
-rw-r--r--lib/msgstore.go12
-rw-r--r--lib/threadbuilder.go26
-rw-r--r--worker/maildir/worker.go2
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{}