branch: externals/minimail
commit de29310507d84a2473652bba5002c5659cfc0d89
Author: Augusto Stoffel <[email protected]>
Commit: Augusto Stoffel <[email protected]>

    Add optional hierarchical threading mode
---
 README.org  |  3 ++-
 minimail.el | 50 +++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/README.org b/README.org
index aa1b35f103..f20afb43bd 100644
--- a/README.org
+++ b/README.org
@@ -15,7 +15,8 @@ to messages.  Below is a listing of implemented and planned 
features.
   - [X] “Shallow” threading (just one nesting level, sorted by date)
     using server-side thread information if available or subject lines
     as a fallback.
-  - [ ] Hierarchical threads based on reference message IDs.
+  - [X] Optionally, hierarchical threads based on reference message
+    IDs.
 - [X] Move messages (also archive, move to trash, flag as junk)
 - [ ] Mark and operate on sets of messages (move, etc.)
 - [X] "Load more messages" button
diff --git a/minimail.el b/minimail.el
index 5dc1ebbef6..6af57c0021 100644
--- a/minimail.el
+++ b/minimail.el
@@ -250,7 +250,8 @@ Give up after MAX-TRIES, if that is non-negative."
   :group 'mail)
 
 (defcustom minimail-accounts
-  '((yhetil :incoming-url "imaps://:@yhetil.org/yhetil.emacs"))
+  '((yhetil :incoming-url "imaps://:@yhetil.org/yhetil.emacs"
+            :thread-style hierarchical))
   "Account configuration for the Minimail client.
 This is an alist where keys are names used to refer to each account and
 values are a plist with the following information:
@@ -331,6 +332,12 @@ sorting by thread."
   "Maximum number of messages to fetch at a time when displaying a mailbox."
   :type 'natnum)
 
+(defcustom minimail-thread-style 'shallow
+  "How to display message threads."
+  :type '(choice (const :tag "Shallow" shallow)
+                 (const :tag "Hierarchical" hierarchical)
+                 (const :tag "Don't compute threads" nil)))
+
 (defface minimail-unseen '((t :inherit bold))
   "Face for unseen messages.")
 
@@ -462,7 +469,8 @@ Return the first matching value."
                          '((:fetch-limit . minimail-fetch-limit)
                            (:full-name . user-full-name)
                            (:mail-address . user-mail-address)
-                           (:signature . message-signature))))))))
+                           (:signature . message-signature)
+                           (:thread-style . minimail-thread-style))))))))
 
 (defun -settings-alist-get (keyword account mailbox)
   "Retrieve the most specific configuration value for KEYWORD.
@@ -1553,7 +1561,14 @@ Cf. RFC 5256, §2.1."
     (when-let* ((msg (seq-find (lambda (msg) (let-alist msg (eq .uid uid)))
                                messages)))
       (vtable-goto-object msg)))
-  (setq -thread-tree (-thread-tree-shallow messages))
+  (setq -thread-tree (funcall (pcase-exhaustive
+                                  (-settings-scalar-get :thread-style
+                                                        -current-account
+                                                        -current-mailbox)
+                                ('shallow #'-thread-tree-shallow)
+                                ('hierarchical #'-thread-tree-hierarchical)
+                                ('nil #'ignore))
+                              messages))
   (when-let* ((how (alist-get 'sort-by-thread -local-state)))
     (-sort-messages-by-thread (eq how 'descend)))
   (save-excursion
@@ -1758,6 +1773,35 @@ loc. cit. §4, with message UIDs as tree leaves."
                     (mapcar (lambda (v) (let-alist v (list .uid))) (cdr 
thread))))
             threads)))
 
+(defun -thread-tree-hierarchical (messages)
+  "Compute a hierarchical message thread tree from MESSAGES.
+This relies solely on Message-ID and In-Reply-To headers from the IMAP
+envelope and doesn't use server-side threading information.  The return
+value is as described in RFC 5256, §4, with message UIDs as tree leaves."
+  (let* ((msgid (make-hash-table :test #'equal)) ;map Message-ID -> UID
+         (children (make-hash-table))            ;map UID -> list of children 
messages
+         (roots nil))                            ;list of root messages
+    (dolist (msg messages)
+      (let-alist msg
+        (when .envelope.message-id
+          (puthash .envelope.message-id .uid msgid))))
+    (dolist (msg messages)
+      (if-let* ((inreply (let-alist msg
+                           (and .envelope.in-reply-to
+                                (string-match "<.*?>" .envelope.in-reply-to)
+                                (match-string-no-properties 0 
.envelope.in-reply-to))))
+                (parent (gethash inreply msgid)))
+          (push msg (gethash parent children))
+        (push msg roots)))
+    (cl-labels
+        ((recur (msg)
+           (let-alist msg
+             (pcase (gethash .uid children)
+               ('nil (list .uid))
+               (`(,one) (cons .uid (recur one)))
+               (many (cons .uid (mapcar #'recur (sort many :key 
#'-message-timestamp))))))))
+      (mapcar #'recur roots))))
+
 (defun -sort-messages-by-thread (&optional descend)
   "Sort messages with grouping by threads.
 

Reply via email to