about summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authory0ast <joost@joo.st>2021-11-12 18:12:02 +0100
committerRobin Jarry <robin@jarry.cc>2021-11-13 15:05:59 +0100
commitdc2a2c2dfd6dc327fe40fbf2da922ef6c3d520be (patch)
tree4987160692aca01e27b068cb256d66d373556a52 /lib
parentc303b953360994966ff657c4e17670853198ecf7 (diff)
downloadaerc-dc2a2c2dfd6dc327fe40fbf2da922ef6c3d520be.tar.gz
messages: allow displaying email threads
Display threads in the message list. For now, only supported by the
notmuch backend and on IMAP when the server supports the THREAD
extension.

Setting threading-enable=true is global and will cause the message list
to be empty with maildir:// accounts.

Co-authored-by: Kevin Kuehler <keur@xcf.berkeley.edu>
Co-authored-by: Reto Brunner <reto@labrat.space>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'lib')
-rw-r--r--lib/format/format.go12
-rw-r--r--lib/msgstore.go55
2 files changed, 61 insertions, 6 deletions
diff --git a/lib/format/format.go b/lib/format/format.go
index 8681de8..59b5c47 100644
--- a/lib/format/format.go
+++ b/lib/format/format.go
@@ -45,6 +45,10 @@ type Ctx struct {
 	MsgNum      int
 	MsgInfo     *models.MessageInfo
 	MsgIsMarked bool
+
+	// UI controls for threading
+	ThreadPrefix      string
+	ThreadSameSubject bool
 }
 
 func ParseMessageFormat(format string, timeFmt string, thisDayTimeFmt string,
@@ -213,7 +217,13 @@ func ParseMessageFormat(format string, timeFmt string, thisDayTimeFmt string,
 			args = append(args, addrs)
 		case 's':
 			retval = append(retval, 's')
-			args = append(args, envelope.Subject)
+			// if we are threaded strip the repeated subjects unless it's the
+			// first on the screen
+			subject := envelope.Subject
+			if ctx.ThreadSameSubject {
+				subject = ""
+			}
+			args = append(args, ctx.ThreadPrefix+subject)
 		case 't':
 			if len(envelope.To) == 0 {
 				return "", nil,
diff --git a/lib/msgstore.go b/lib/msgstore.go
index b1faa73..40720b4 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -17,7 +17,8 @@ type MessageStore struct {
 	Sorting  bool
 
 	// Ordered list of known UIDs
-	uids []uint32
+	uids    []uint32
+	Threads []*types.Thread
 
 	selected        int
 	bodyCallbacks   map[uint32][]func(*types.FullMessage)
@@ -35,6 +36,8 @@ type MessageStore struct {
 
 	defaultSortCriteria []*types.SortCriterion
 
+	thread bool
+
 	// Map of uids we've asked the worker to fetch
 	onUpdate       func(store *MessageStore) // TODO: multiple onUpdate handlers
 	onUpdateDirs   func()
@@ -52,6 +55,7 @@ type MessageStore struct {
 func NewMessageStore(worker *types.Worker,
 	dirInfo *models.DirectoryInfo,
 	defaultSortCriteria []*types.SortCriterion,
+	thread bool,
 	triggerNewEmail func(*models.MessageInfo),
 	triggerDirectoryChange func()) *MessageStore {
 
@@ -67,6 +71,8 @@ func NewMessageStore(worker *types.Worker,
 		bodyCallbacks:   make(map[uint32][]func(*types.FullMessage)),
 		headerCallbacks: make(map[uint32][]func(*types.MessageInfo)),
 
+		thread: thread,
+
 		defaultSortCriteria: defaultSortCriteria,
 
 		pendingBodies:  make(map[uint32]interface{}),
@@ -189,6 +195,27 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
 		store.Messages = newMap
 		store.uids = msg.Uids
 		update = true
+	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
+			})
+		}
+		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 {
 			merge(existing, msg.Info)
@@ -257,6 +284,15 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
 		}
 		store.results = newResults
 
+		for _, thread := range store.Threads {
+			thread.Walk(func(t *types.Thread, _ int, _ error) error {
+				if _, deleted := toDelete[t.Uid]; deleted {
+					t.Deleted = true
+				}
+				return nil
+			})
+		}
+
 		update = true
 	}
 
@@ -592,14 +628,23 @@ func (store *MessageStore) ModifyLabels(uids []uint32, add, remove []string,
 
 func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func()) {
 	store.Sorting = true
-	store.worker.PostAction(&types.FetchDirectoryContents{
-		SortCriteria: criteria,
-	}, func(_ types.WorkerMessage) {
+
+	handle_return := func(msg types.WorkerMessage) {
 		store.Sorting = false
 		if cb != nil {
 			cb()
 		}
-	})
+	}
+
+	if store.thread {
+		store.worker.PostAction(&types.FetchDirectoryThreaded{
+			SortCriteria: criteria,
+		}, handle_return)
+	} else {
+		store.worker.PostAction(&types.FetchDirectoryContents{
+			SortCriteria: criteria,
+		}, handle_return)
+	}
 }
 
 // returns the index of needle in haystack or -1 if not found