mu4e: support batched query results

Support the new batched query results from the mu server; these are much faster
to render (2-3x it seems).

Rearrange the code a bit to avoid byte-compiler warnings.

Obsolete mu4e-header-func, to be replaced by mu4e-headers-append-func.
This commit is contained in:
Dirk-Jan C. Binnema
2021-10-21 19:21:09 +03:00
parent f17995b113
commit 8ad5fd49c9
4 changed files with 155 additions and 156 deletions

View File

@ -318,93 +318,10 @@ This is mostly useful for profiling.")
(goto-char (point-min)) (goto-char (point-min))
(insert (propertize msg 'face 'mu4e-system-face 'intangible t))))))) (insert (propertize msg 'face 'mu4e-system-face 'intangible t)))))))
;;; Handler functions
;; next are a bunch of handler functions; those will be called from mu4e~proc in
;; response to output from the server process
(defun mu4e~headers-view-handler (msg)
"Handler function for displaying a message."
(mu4e-view msg))
(defun mu4e~headers-view-this-message-p (docid)
"Is DOCID currently being viewed?"
(when (buffer-live-p (mu4e-get-view-buffer))
(with-current-buffer (mu4e-get-view-buffer)
(eq docid (plist-get mu4e~view-message :docid)))))
(defun mu4e~headers-update-handler (msg is-move maybe-view)
"Update handler, will be called when a message has been updated
in the database. This function will update the current list of
headers."
(when (buffer-live-p (mu4e-get-headers-buffer))
(with-current-buffer (mu4e-get-headers-buffer)
(let* ((docid (mu4e-message-field msg :docid))
(initial-message-at-point (mu4e~headers-docid-at-point))
(initial-column (current-column))
(point (mu4e~headers-docid-pos docid))
(markinfo (gethash docid mu4e~mark-map)))
(when point ;; is the message present in this list?
;; if it's marked, unmark it now
(when (mu4e-mark-docid-marked-p docid)
(mu4e-mark-set 'unmark))
;; re-use the thread info from the old one; this is needed because
;; *update* messages don't have thread info by themselves (unlike
;; search results)
;; since we still have the search results, re-use
;; those
(plist-put msg :meta
(mu4e~headers-field-for-docid docid :meta))
;; first, remove the old one (otherwise, we'd have two headers with
;; the same docid...
(mu4e~headers-remove-header docid t)
;; if we're actually viewing this message (in mu4e-view mode), we
;; update it; that way, the flags can be updated, as well as the path
;; (which is useful for viewing the raw message)
(when (and maybe-view (mu4e~headers-view-this-message-p docid))
(mu4e-view msg))
;; now, if this update was about *moving* a message, we don't show it
;; anymore (of course, we cannot be sure if the message really no
;; longer matches the query, but this seem a good heuristic. if it
;; was only a flag-change, show the message with its updated flags.
(unless is-move
(mu4e~headers-header-handler msg point))
;; restore the mark, if any. See #2076.
(when (and markinfo (mu4e~headers-goto-docid docid))
(mu4e-mark-at-point (car markinfo) (cdr markinfo)))
(if (and initial-message-at-point
(mu4e~headers-goto-docid initial-message-at-point))
(progn
(move-to-column initial-column)
(mu4e~headers-highlight initial-message-at-point))
;; attempt to highlight the corresponding line and make it visible
(mu4e~headers-highlight docid))
(run-hooks 'mu4e-message-changed-hook))))))
(defun mu4e~headers-remove-handler (docid &optional skip-hook)
"Remove handler, will be called when a message with DOCID has
been removed from the database. This function will hide the removed
message from the current list of headers. If the message is not
present, don't do anything.
If SKIP-HOOK is absent or nil, `mu4e-message-changed-hook' will be invoked."
(when (buffer-live-p (mu4e-get-headers-buffer))
(mu4e~headers-remove-header docid t))
;; if we were viewing this message, close it now.
(when (and (mu4e~headers-view-this-message-p docid)
(buffer-live-p (mu4e-get-view-buffer)))
(unless (eq mu4e-split-view 'single-window)
(mapc #'delete-window (get-buffer-window-list
(mu4e-get-view-buffer) nil t)))
(kill-buffer (mu4e-get-view-buffer)))
(unless skip-hook
(run-hooks 'mu4e-message-changed-hook)))
;;; Misc ;;; Misc
@ -464,6 +381,7 @@ nil. Returns the docid, or nil if there is none."
(get-text-property (line-beginning-position) 'docid))) (get-text-property (line-beginning-position) 'docid)))
(defun mu4e~headers-goto-docid (docid &optional to-mark) (defun mu4e~headers-goto-docid (docid &optional to-mark)
"Go to the beginning of the line with the header with docid "Go to the beginning of the line with the header with docid
DOCID, or nil if it cannot be found. If the optional TO-MARK is DOCID, or nil if it cannot be found. If the optional TO-MARK is
@ -669,15 +587,12 @@ found."
(:size (mu4e-display-size val)) (:size (mu4e-display-size val))
(t (mu4e~headers-custom-field-value msg field))))) (t (mu4e~headers-custom-field-value msg field)))))
(defun mu4e~headers-truncate-field-fast (val width) (defun mu4e~headers-truncate-field-fast (val width)
"Truncate VAL to WIDTH. Fast and somewhat inaccurate." "Truncate VAL to WIDTH. Fast and somewhat inaccurate."
(if width (if width
(truncate-string-to-width val width 0 ?\s truncate-string-ellipsis) (truncate-string-to-width val width 0 ?\s truncate-string-ellipsis)
val)) val))
(defun mu4e~headers-truncate-field-precise (field val width) (defun mu4e~headers-truncate-field-precise (field val width)
"Return VAL truncated to one less than WIDTH, with a trailing "Return VAL truncated to one less than WIDTH, with a trailing
space propertized with a 'display text property which expands to space propertized with a 'display text property which expands to
@ -742,16 +657,131 @@ displaying in the header view."
(mapconcat (lambda (f-w) (mu4e~headers-field-handler f-w msg)) (mapconcat (lambda (f-w) (mu4e~headers-field-handler f-w msg))
mu4e-headers-fields " ")))) mu4e-headers-fields " "))))
(defsubst mu4e~headers-insert-header (msg pos)
"Insert a header for MSG at point POS."
(when-let ((line (mu4e~message-header-line msg))
(docid (plist-get msg :docid)))
(goto-char pos)
(insert
(propertize
(concat
(mu4e~headers-docid-cookie docid)
mu4e~mark-fringe line "\n")
'docid docid 'msg msg))))
(defun mu4e~headers-remove-header (docid &optional ignore-missing)
"Remove header with DOCID at point.
When IGNORE-MISSING is non-nill, don't raise an error when the
docid is not found."
(with-current-buffer (mu4e-get-headers-buffer)
(if (mu4e~headers-goto-docid docid)
(let ((inhibit-read-only t))
(delete-region (line-beginning-position) (line-beginning-position 2)))
(unless ignore-missing
(mu4e-error "Cannot find message with docid %S" docid)))))
;;; Handler functions
;; next are a bunch of handler functions; those will be called from mu4e~proc in
;; response to output from the server process
(defun mu4e~headers-view-handler (msg)
"Handler function for displaying a message."
(mu4e-view msg))
(defun mu4e~headers-view-this-message-p (docid)
"Is DOCID currently being viewed?"
(when (buffer-live-p (mu4e-get-view-buffer))
(with-current-buffer (mu4e-get-view-buffer)
(eq docid (plist-get mu4e~view-message :docid)))))
;; note: this function is very performance-sensitive ;; note: this function is very performance-sensitive
(defun mu4e~headers-header-handler (msg &optional point) (defun mu4e~headers-append-handler (msglst)
"Create a one line description of MSG in this buffer, at POINT, "Append one-line descriptions of messages in MSGLIST.
if provided, or at the end of the buffer otherwise." Do this at the end of the headers-buffer."
(when (buffer-live-p (mu4e-get-headers-buffer)) (when (buffer-live-p (mu4e-get-headers-buffer))
(with-current-buffer (mu4e-get-headers-buffer) (with-current-buffer (mu4e-get-headers-buffer)
(let ((line (mu4e~message-header-line msg))) (save-excursion
(when line (let ((inhibit-read-only t))
(mu4e~headers-add-header line (mu4e-message-field msg :docid) (seq-do
point msg)))))) (lambda (msg)
(mu4e~headers-insert-header msg (point-max)))
msglst))))))
(defun mu4e~headers-update-handler (msg is-move maybe-view)
"Update handler, will be called when a message has been updated
in the database. This function will update the current list of
headers."
(when (buffer-live-p (mu4e-get-headers-buffer))
(with-current-buffer (mu4e-get-headers-buffer)
(let* ((docid (mu4e-message-field msg :docid))
(initial-message-at-point (mu4e~headers-docid-at-point))
(initial-column (current-column))
(inhibit-read-only t)
(point (mu4e~headers-docid-pos docid))
(markinfo (gethash docid mu4e~mark-map)))
(when point ;; is the message present in this list?
;; if it's marked, unmark it now
(when (mu4e-mark-docid-marked-p docid)
(mu4e-mark-set 'unmark))
;; re-use the thread info from the old one; this is needed because
;; *update* messages don't have thread info by themselves (unlike
;; search results)
;; since we still have the search results, re-use
;; those
(plist-put msg :meta
(mu4e~headers-field-for-docid docid :meta))
;; first, remove the old one (otherwise, we'd have two headers with
;; the same docid...
(mu4e~headers-remove-header docid t)
;; if we're actually viewing this message (in mu4e-view mode), we
;; update it; that way, the flags can be updated, as well as the path
;; (which is useful for viewing the raw message)
(when (and maybe-view (mu4e~headers-view-this-message-p docid))
(mu4e-view msg))
;; now, if this update was about *moving* a message, we don't show it
;; anymore (of course, we cannot be sure if the message really no
;; longer matches the query, but this seem a good heuristic. if it
;; was only a flag-change, show the message with its updated flags.
(unless is-move
(save-excursion
(mu4e~headers-insert-header msg point)))
;; restore the mark, if any. See #2076.
(when (and markinfo (mu4e~headers-goto-docid docid))
(mu4e-mark-at-point (car markinfo) (cdr markinfo)))
(if (and initial-message-at-point
(mu4e~headers-goto-docid initial-message-at-point))
(progn
(move-to-column initial-column)
(mu4e~headers-highlight initial-message-at-point))
;; attempt to highlight the corresponding line and make it visible
(mu4e~headers-highlight docid))
(run-hooks 'mu4e-message-changed-hook))))))
(defun mu4e~headers-remove-handler (docid)
"Remove handler, will be called when a message with DOCID has
been removed from the database. This function will hide the removed
message from the current list of headers. If the message is not
present, don't do anything."
(when (buffer-live-p (mu4e-get-headers-buffer))
(mu4e~headers-remove-header docid t))
;; if we were viewing this message, close it now.
(when (and (mu4e~headers-view-this-message-p docid)
(buffer-live-p (mu4e-get-view-buffer)))
(unless (eq mu4e-split-view 'single-window)
(mapc #'delete-window (get-buffer-window-list
(mu4e-get-view-buffer) nil t)))
(kill-buffer (mu4e-get-view-buffer))))
;;; Performing queries (internal) ;;; Performing queries (internal)
@ -1087,7 +1117,7 @@ after the end of the search results."
(defun mu4e~headers-maybe-auto-update () (defun mu4e~headers-maybe-auto-update ()
"Update the current headers buffer after indexing has brought "Update the current headers buffer after indexing has brought
some changes, `mu4e-headers-auto-update' is non-nil and there some changes, `mu4e-headers-auto-update' is non-nil and there
isno user-interaction ongoing." is no user-interaction ongoing."
(when (and mu4e-headers-auto-update ;; must be set (when (and mu4e-headers-auto-update ;; must be set
mu4e-index-update-status mu4e-index-update-status
(> 0 (plist-get mu4e-index-update-status :updated)) (> 0 (plist-get mu4e-index-update-status :updated))
@ -1183,33 +1213,6 @@ message plist, or nil if not found."
'msg msg))) 'msg msg)))
(goto-char oldpoint)))) (goto-char oldpoint))))
(defun mu4e~headers-add-header (str docid point &optional msg)
"Add header STR with DOCID to the buffer at POINT if non-nil, or
at (point-max) otherwise. If MSG is not nil, add it as the
text-property `msg'."
(when (buffer-live-p (mu4e-get-headers-buffer))
(with-current-buffer (mu4e-get-headers-buffer)
(let ((inhibit-read-only t))
(save-excursion
(goto-char (if point point (point-max)))
(insert
(propertize
(concat
(mu4e~headers-docid-cookie docid)
mu4e~mark-fringe
str "\n")
'docid docid 'msg msg)))))))
(defun mu4e~headers-remove-header (docid &optional ignore-missing)
"Remove header with DOCID at point.
When IGNORE-MISSING is non-nill, don't raise an error when the
docid is not found."
(with-current-buffer (mu4e-get-headers-buffer)
(if (mu4e~headers-goto-docid docid)
(let ((inhibit-read-only t))
(delete-region (line-beginning-position) (line-beginning-position 2)))
(unless ignore-missing
(mu4e-error "Cannot find message with docid %S" docid)))))
;;; Queries & searching ;;; Queries & searching
(defvar mu4e~headers-mode-line-label "") (defvar mu4e~headers-mode-line-label "")

View File

@ -39,7 +39,6 @@
"Search-related settings." "Search-related settings."
:group 'mu4e) :group 'mu4e)
(define-obsolete-variable-alias 'mu4e-headers-results-limit (define-obsolete-variable-alias 'mu4e-headers-results-limit
'mu4e-search-results-limit "1.7.0") 'mu4e-search-results-limit "1.7.0")
(defcustom mu4e-search-results-limit 500 (defcustom mu4e-search-results-limit 500
@ -130,8 +129,12 @@ but also manually invoked searches."
"Maximum size for the query stacks.") "Maximum size for the query stacks.")
(defvar mu4e--search-last-query nil (defvar mu4e--search-last-query nil
"The present (most recent) query.") "The present (most recent) query.")
;;; Interactive functions ;;; Interactive functions
(declare-function mu4e--search-execute "mu4e-headers")
(defun mu4e-search (&optional expr prompt edit ignore-history msgid show) (defun mu4e-search (&optional expr prompt edit ignore-history msgid show)
"Search for query EXPR. "Search for query EXPR.

View File

@ -98,10 +98,12 @@ passed the docid and the draft-path of the sent message.")
The function is passed a message sexp as argument. See The function is passed a message sexp as argument. See
`mu4e--server-filter' for the format.") `mu4e--server-filter' for the format.")
(defvar mu4e-header-func nil (make-obsolete-variable 'mu4e-header-func "mu4e-headers-append-func" "1.7.4")
"Function called for each message-header received.
The function is passed a msg plist as argument. See (defvar mu4e-headers-append-func nil
`mu4e--server-filter' for the format.") "Function called with a list of headers to append.
The function is passed a list of message plists as argument. See
See `mu4e--server-filter' for the details.")
(defvar mu4e-found-func nil (defvar mu4e-found-func nil
"Function called for when we received a :found sexp. "Function called for when we received a :found sexp.
@ -191,17 +193,6 @@ removed."
(setq mu4e--server-buf (substring mu4e--server-buf sexp-len)) (setq mu4e--server-buf (substring mu4e--server-buf sexp-len))
(car objcons))))))) (car objcons)))))))
(defsubst mu4e--message (msgdata)
"Convert MSGDATA into a msg plist.
This receives a 'message-data' blob of the form
(:meta (...) :message (...))
and turns it into (:message :meta (...) ... ).
The former version is what the server is optimized for,
but the latter is what the header wants."
(plist-put (plist-get msgdata :message) :meta (plist-get msgdata :meta)))
(defun mu4e--server-filter (_proc str) (defun mu4e--server-filter (_proc str)
"Filter string STR from PROC. "Filter string STR from PROC.
This processes the 'mu server' output. It accumulates the This processes the 'mu server' output. It accumulates the
@ -216,9 +207,8 @@ The server output is as follows:
=> passed to `mu4e-error-func'. => passed to `mu4e-error-func'.
2a. a header exp looks something like: 2a. a header exp looks something like:
(:header (:headers
:meta (....) ( ;; message 1
:message (
:docid 1585 :docid 1585
:from ((\"Donald Duck\" . \"donald@example.com\")) :from ((\"Donald Duck\" . \"donald@example.com\"))
:to ((\"Mickey Mouse\" . \"mickey@example.com\")) :to ((\"Mickey Mouse\" . \"mickey@example.com\"))
@ -231,9 +221,13 @@ The server output is as follows:
:maildir: \"/archive\" :maildir: \"/archive\"
:path \"/home/mickey/Maildir/inbox/cur/1312_3.32282.pluto,4cd5bd4e9:2,\" :path \"/home/mickey/Maildir/inbox/cur/1312_3.32282.pluto,4cd5bd4e9:2,\"
:priority high :priority high
:flags (new unread))) :flags (new unread)
:meta <meta-data>
)
( .... more messages )
)
;; eox ;; eox
=> this will be passed to `mu4e-header-func'. => this will be passed to `mu4e-headers-append-func'.
2b. After the list of headers has been returned (see 2a.), 2b. After the list of headers has been returned (see 2a.),
we'll receive a sexp that looks like we'll receive a sexp that looks like
@ -243,7 +237,7 @@ The server output is as follows:
3. a view looks like: 3. a view looks like:
(:view <msg-sexp>) (:view <msg-sexp>)
=> the <msg-sexp> (see 2.) will be passed to `mu4e-view-func'. => the <msg-sexp> (see 2.) will be passed to `mu4e-view-func'.
like :header, but the :message also contains :body-txt and/or :body-html the <msg-sexp> also contains :body-txt and/or :body-html
4. a database update looks like: 4. a database update looks like:
(:update <msg-sexp> :move <nil-or-t>) (:update <msg-sexp> :move <nil-or-t>)
@ -268,8 +262,8 @@ The server output is as follows:
(mu4e-log 'from-server "%S" sexp) (mu4e-log 'from-server "%S" sexp)
(cond (cond
;; a header plist can be recognized by the existence of a :date field ;; a header plist can be recognized by the existence of a :date field
((plist-get sexp :header) ((plist-get sexp :headers)
(funcall mu4e-header-func (mu4e--message (plist-get sexp :header)))) (funcall mu4e-headers-append-func (plist-get sexp :headers)))
;; the found sexp, we receive after getting all the headers ;; the found sexp, we receive after getting all the headers
((plist-get sexp :found) ((plist-get sexp :found)
@ -277,7 +271,7 @@ The server output is as follows:
;; viewing a specific message ;; viewing a specific message
((plist-get sexp :view) ((plist-get sexp :view)
(funcall mu4e-view-func (mu4e--message (plist-get sexp :view)))) (funcall mu4e-view-func (plist-get sexp :view)))
;; receive an erase message ;; receive an erase message
((plist-get sexp :erase) ((plist-get sexp :erase)
@ -304,7 +298,7 @@ The server output is as follows:
;; something got moved/flags changed ;; something got moved/flags changed
((plist-get sexp :update) ((plist-get sexp :update)
(funcall mu4e-update-func (funcall mu4e-update-func
(mu4e--message (plist-get sexp :update)) (plist-get sexp :update)
(plist-get sexp :move) (plist-get sexp :move)
(plist-get sexp :maybe-view))) (plist-get sexp :maybe-view)))
@ -316,7 +310,7 @@ The server output is as follows:
((plist-get sexp :compose) ((plist-get sexp :compose)
(funcall mu4e-compose-func (funcall mu4e-compose-func
(plist-get sexp :compose) (plist-get sexp :compose)
(mu4e--message (plist-get sexp :original)) (plist-get sexp :original)
(plist-get sexp :include))) (plist-get sexp :include)))
;; get some info ;; get some info

View File

@ -64,7 +64,6 @@
;; desktop-save-mode; so let's turn that off. ;; desktop-save-mode; so let's turn that off.
(with-eval-after-load 'desktop (with-eval-after-load 'desktop
(eval '(add-to-list 'desktop-modes-not-to-save 'mu4e-compose-mode))) (eval '(add-to-list 'desktop-modes-not-to-save 'mu4e-compose-mode)))
;;;###autoload ;;;###autoload
(defun mu4e (&optional background) (defun mu4e (&optional background)
@ -232,13 +231,13 @@ successful, call FUNC (if non-nil) afterwards."
"Initialize the server message handlers. "Initialize the server message handlers.
Only set set them if they were nil before, so overriding has a Only set set them if they were nil before, so overriding has a
chance." chance."
(mu4e-setq-if-nil mu4e-error-func #'mu4e--error-handler) (mu4e-setq-if-nil mu4e-error-func #'mu4e--error-handler)
(mu4e-setq-if-nil mu4e-update-func #'mu4e~headers-update-handler) (mu4e-setq-if-nil mu4e-update-func #'mu4e~headers-update-handler)
(mu4e-setq-if-nil mu4e-remove-func #'mu4e~headers-remove-handler) (mu4e-setq-if-nil mu4e-remove-func #'mu4e~headers-remove-handler)
(mu4e-setq-if-nil mu4e-view-func #'mu4e~headers-view-handler) (mu4e-setq-if-nil mu4e-view-func #'mu4e~headers-view-handler)
(mu4e-setq-if-nil mu4e-header-func #'mu4e~headers-header-handler) (mu4e-setq-if-nil mu4e-headers-append-func #'mu4e~headers-append-handler)
(mu4e-setq-if-nil mu4e-found-func #'mu4e~headers-found-handler) (mu4e-setq-if-nil mu4e-found-func #'mu4e~headers-found-handler)
(mu4e-setq-if-nil mu4e-erase-func #'mu4e~headers-clear) (mu4e-setq-if-nil mu4e-erase-func #'mu4e~headers-clear)
(mu4e-setq-if-nil mu4e-sent-func #'mu4e--default-handler) (mu4e-setq-if-nil mu4e-sent-func #'mu4e--default-handler)
(mu4e-setq-if-nil mu4e-compose-func #'mu4e~compose-handler) (mu4e-setq-if-nil mu4e-compose-func #'mu4e~compose-handler)