branch: externals/minimail
commit d20314c2ad77da2730cab9d93050b9e74c4fb5c1
Author: Augusto Stoffel <[email protected]>
Commit: Augusto Stoffel <[email protected]>
Support for server-side thread information
---
README.org | 6 ++++--
minimail.el | 48 ++++++++++++++++++++++++++++--------------------
2 files changed, 32 insertions(+), 22 deletions(-)
diff --git a/README.org b/README.org
index b90c8f74f4..aa1b35f103 100644
--- a/README.org
+++ b/README.org
@@ -12,8 +12,10 @@ to messages. Below is a listing of implemented and planned
features.
- [X] Full text
- [ ] Structured (by sender, subject, etc.)
- Sorting by thread
- - [X] Simple algorithm based on subject lines
- - [ ] Fancy algorithm based on reference message IDs.
+ - [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] 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 ed16b2cf8b..5dc1ebbef6 100644
--- a/minimail.el
+++ b/minimail.el
@@ -699,6 +699,7 @@ it is nil."
(dquote () (char ?\"))
(crlf () "\r\n")
(anil () "NIL" `(-- nil))
+ (tagged () (bol) (char ?T) (+ [0-9]) sp) ;we always format our tags as
T<number>
(untagged () (bol) "* ")
(number () (substring (+ [0-9])) `(s -- (string-to-number s)))
(achar () (and (not [cntrl "(){] %*\"\\"]) (any))) ;characters allowed
in an atom
@@ -853,24 +854,25 @@ it is nil."
sp qstring
`(s -- '(media-type . "MULTIPART") `(media-subtype .
,s)))
")")
- ;; (body "BODY " (or body-single body-multi)
- ;; `(s -- `(body . ,s)))
- (body "BODY " ;; (funcall (lambda () (forward-sexp) t))
- balanced
- )
+ ;; (body "BODY " (or body-single body-multi) `(s -- `(body . ,s)))
+ (body "BODY " balanced)
(content "BODY[] " literal `(start end -- `(content ,start . ,end)))
(flags "FLAGS (" (list (* (opt sp) flag)) ")"
`(v -- `(flags . ,v)))
(x-gm-labels "X-GM-LABELS (" (list (* (opt sp) astring7)) ")"
`(v -- `(x-gm-labels . ,v)))
+ (thread-id (or (and "THREADID " (or anil (and "(" atom ")")))
+ (and "X-GM-THRID " number))
+ `(v -- `(thread-id . ,v)))
+ (email-id (or (and "EMAILID (" atom ")") (and "X-GM-MSGID " number))
+ `(v -- `(email-id . ,v)))
(internal-date "INTERNALDATE " imapdate
`(v -- `(internal-date . ,v)))
(size "RFC822.SIZE " number `(n -- `(rfc822-size . ,n)))
(uid "UID " number `(n -- `(uid . ,n)))
(item untagged number `(n -- `(id . ,n))
" FETCH ("
- (* (opt sp) (or uid flags size envelope body content
- internal-date x-gm-labels))
+ (* (opt sp) (or uid flags size envelope content thread-id
x-gm-labels))
")" crlf))
(car-safe
(peg-run (peg (list (* (list item))))))))
@@ -1032,10 +1034,13 @@ If SEQUENTIAL is non-nil, SEQ is regarded as a set of
sequential IDs
rather than UIDs."
(athunk-let*
((caps <- (-aget-capability account))
- (cmd (format "%sFETCH %s (UID FLAGS%s%s)"
+ (cmd (format "%sFETCH %s (UID FLAGS%s%s%s)"
(if sequential "" "UID ")
(-format-sequence-set set)
(if (memq 'x-gm-ext-1 caps) " X-GM-LABELS" "")
+ (cond ((memq 'objectid caps) " THREADID")
+ ((memq 'x-gm-ext-1 caps) " X-GM-THRID")
+ (t ""))
(if brief "" " RFC822.SIZE ENVELOPE")))
(buffer <- (-amake-request account mailbox cmd))
(messages (with-current-buffer buffer (-parse-fetch))))
@@ -1548,7 +1553,7 @@ 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-by-subject messages))
+ (setq -thread-tree (-thread-tree-shallow messages))
(when-let* ((how (alist-get 'sort-by-thread -local-state)))
(-sort-messages-by-thread (eq how 'descend)))
(save-excursion
@@ -1731,23 +1736,27 @@ If KILL is non-nil, kill the message buffer instead of
burying it."
"A prefix added to message subjects when sorting by thread."
(make-string (* 2 (or (-thread-level uid) 0)) ?\s))
-(defun -thread-by-subject (messages)
- "Compute a message thread tree from MESSAGES based on subject strings.
-This is the ORDEREDSUBJECT algorithm described in RFC 5256. The return
-value is as described in loc. cit. §4, with message UIDs as tree leaves."
+(defun -thread-tree-shallow (messages)
+ "Compute a shallow message thread tree from MESSAGES.
+Use server-side thread identifiers if available; otherwise, infer the
+thread structure from the message sujects, as in the ORDEREDSUBJECT
+algorithm described in RFC 5256. The return value is as described in
+loc. cit. §4, with message UIDs as tree leaves."
(let* ((hash (make-hash-table :test #'equal))
(threads (progn
(dolist (msg messages)
(let-alist msg
- (push msg (gethash (-base-subject (or
.envelope.subject ""))
+ (push msg (gethash (or .thread-id
+ (-base-subject
+ (or .envelope.subject "")))
hash))))
- (mapcar (lambda (thread) (sort thread :key
#'-message-timestamp))
- (hash-table-values hash))))
- (sorted (sort threads :key (lambda (v) (-message-timestamp (car
v))))))
+ (mapcar (lambda (thread)
+ (sort thread :key #'-message-timestamp :in-place
t))
+ (hash-table-values hash)))))
(mapcar (lambda (thread)
(cons (let-alist (car thread) .uid)
(mapcar (lambda (v) (let-alist v (list .uid))) (cdr
thread))))
- sorted)))
+ threads)))
(defun -sort-messages-by-thread (&optional descend)
"Sort messages with grouping by threads.
@@ -1757,8 +1766,7 @@ preserve the existing order, in the sense that thread A
sorts before
thread B if some message from A comes before all messages of B. This
makes sense when the current sort order is in the “most relevant at top”
style. If DESCEND is non-nil, use the opposite convention."
- (let* ((table (or (vtable-current-table)
- (user-error "No table under point")))
+ (let* ((table (-ensure-vtable))
(mhash (make-hash-table)) ;maps message id -> root id and position
within thread
(rhash (make-hash-table)) ;maps root id -> position across threads
(lessp (lambda (o1 o2)