From 8ad5fd49c91f6a8eef19b7d3cbd976fcf5614d2e Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Thu, 21 Oct 2021 19:21:09 +0300 Subject: [PATCH] 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. --- mu4e/mu4e-headers.el | 245 ++++++++++++++++++++++--------------------- mu4e/mu4e-search.el | 5 +- mu4e/mu4e-server.el | 46 ++++---- mu4e/mu4e.el | 15 ++- 4 files changed, 155 insertions(+), 156 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index c49ee206..bf29bb33 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -318,93 +318,10 @@ This is mostly useful for profiling.") (goto-char (point-min)) (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 @@ -464,6 +381,7 @@ nil. Returns the docid, or nil if there is none." (get-text-property (line-beginning-position) 'docid))) + (defun mu4e~headers-goto-docid (docid &optional to-mark) "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 @@ -669,15 +587,12 @@ found." (:size (mu4e-display-size val)) (t (mu4e~headers-custom-field-value msg field))))) - (defun mu4e~headers-truncate-field-fast (val width) "Truncate VAL to WIDTH. Fast and somewhat inaccurate." (if width (truncate-string-to-width val width 0 ?\s truncate-string-ellipsis) val)) - - (defun mu4e~headers-truncate-field-precise (field val width) "Return VAL truncated to one less than WIDTH, with a trailing 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)) 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 -(defun mu4e~headers-header-handler (msg &optional point) - "Create a one line description of MSG in this buffer, at POINT, -if provided, or at the end of the buffer otherwise." +(defun mu4e~headers-append-handler (msglst) + "Append one-line descriptions of messages in MSGLIST. +Do this at the end of the headers-buffer." (when (buffer-live-p (mu4e-get-headers-buffer)) (with-current-buffer (mu4e-get-headers-buffer) - (let ((line (mu4e~message-header-line msg))) - (when line - (mu4e~headers-add-header line (mu4e-message-field msg :docid) - point msg)))))) + (save-excursion + (let ((inhibit-read-only t)) + (seq-do + (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) @@ -1087,7 +1117,7 @@ after the end of the search results." (defun mu4e~headers-maybe-auto-update () "Update the current headers buffer after indexing has brought 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 mu4e-index-update-status (> 0 (plist-get mu4e-index-update-status :updated)) @@ -1183,33 +1213,6 @@ message plist, or nil if not found." 'msg msg))) (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 (defvar mu4e~headers-mode-line-label "") diff --git a/mu4e/mu4e-search.el b/mu4e/mu4e-search.el index e0f9aea4..4b60bab7 100644 --- a/mu4e/mu4e-search.el +++ b/mu4e/mu4e-search.el @@ -39,7 +39,6 @@ "Search-related settings." :group 'mu4e) - (define-obsolete-variable-alias 'mu4e-headers-results-limit 'mu4e-search-results-limit "1.7.0") (defcustom mu4e-search-results-limit 500 @@ -130,8 +129,12 @@ but also manually invoked searches." "Maximum size for the query stacks.") (defvar mu4e--search-last-query nil "The present (most recent) query.") + + ;;; Interactive functions +(declare-function mu4e--search-execute "mu4e-headers") + (defun mu4e-search (&optional expr prompt edit ignore-history msgid show) "Search for query EXPR. diff --git a/mu4e/mu4e-server.el b/mu4e/mu4e-server.el index 1917b0f9..a0e2b3cc 100644 --- a/mu4e/mu4e-server.el +++ b/mu4e/mu4e-server.el @@ -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 `mu4e--server-filter' for the format.") -(defvar mu4e-header-func nil - "Function called for each message-header received. -The function is passed a msg plist as argument. See -`mu4e--server-filter' for the format.") +(make-obsolete-variable 'mu4e-header-func "mu4e-headers-append-func" "1.7.4") + +(defvar mu4e-headers-append-func nil + "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 "Function called for when we received a :found sexp. @@ -191,17 +193,6 @@ removed." (setq mu4e--server-buf (substring mu4e--server-buf sexp-len)) (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) "Filter string STR from PROC. This processes the 'mu server' output. It accumulates the @@ -216,9 +207,8 @@ The server output is as follows: => passed to `mu4e-error-func'. 2a. a header exp looks something like: - (:header - :meta (....) - :message ( + (:headers + ( ;; message 1 :docid 1585 :from ((\"Donald Duck\" . \"donald@example.com\")) :to ((\"Mickey Mouse\" . \"mickey@example.com\")) @@ -231,9 +221,13 @@ The server output is as follows: :maildir: \"/archive\" :path \"/home/mickey/Maildir/inbox/cur/1312_3.32282.pluto,4cd5bd4e9:2,\" :priority high - :flags (new unread))) + :flags (new unread) + :meta + ) + ( .... more messages ) +) ;; 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.), we'll receive a sexp that looks like @@ -243,7 +237,7 @@ The server output is as follows: 3. a view looks like: (:view ) => the (see 2.) will be passed to `mu4e-view-func'. - like :header, but the :message also contains :body-txt and/or :body-html + the also contains :body-txt and/or :body-html 4. a database update looks like: (:update :move ) @@ -268,8 +262,8 @@ The server output is as follows: (mu4e-log 'from-server "%S" sexp) (cond ;; a header plist can be recognized by the existence of a :date field - ((plist-get sexp :header) - (funcall mu4e-header-func (mu4e--message (plist-get sexp :header)))) + ((plist-get sexp :headers) + (funcall mu4e-headers-append-func (plist-get sexp :headers))) ;; the found sexp, we receive after getting all the headers ((plist-get sexp :found) @@ -277,7 +271,7 @@ The server output is as follows: ;; viewing a specific message ((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 ((plist-get sexp :erase) @@ -304,7 +298,7 @@ The server output is as follows: ;; something got moved/flags changed ((plist-get sexp :update) (funcall mu4e-update-func - (mu4e--message (plist-get sexp :update)) + (plist-get sexp :update) (plist-get sexp :move) (plist-get sexp :maybe-view))) @@ -316,7 +310,7 @@ The server output is as follows: ((plist-get sexp :compose) (funcall mu4e-compose-func (plist-get sexp :compose) - (mu4e--message (plist-get sexp :original)) + (plist-get sexp :original) (plist-get sexp :include))) ;; get some info diff --git a/mu4e/mu4e.el b/mu4e/mu4e.el index 16d72af6..f87c9208 100644 --- a/mu4e/mu4e.el +++ b/mu4e/mu4e.el @@ -64,7 +64,6 @@ ;; desktop-save-mode; so let's turn that off. (with-eval-after-load 'desktop (eval '(add-to-list 'desktop-modes-not-to-save 'mu4e-compose-mode))) - ;;;###autoload (defun mu4e (&optional background) @@ -232,13 +231,13 @@ successful, call FUNC (if non-nil) afterwards." "Initialize the server message handlers. Only set set them if they were nil before, so overriding has a chance." - (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-remove-func #'mu4e~headers-remove-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-found-func #'mu4e~headers-found-handler) - (mu4e-setq-if-nil mu4e-erase-func #'mu4e~headers-clear) + (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-remove-func #'mu4e~headers-remove-handler) + (mu4e-setq-if-nil mu4e-view-func #'mu4e~headers-view-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-erase-func #'mu4e~headers-clear) (mu4e-setq-if-nil mu4e-sent-func #'mu4e--default-handler) (mu4e-setq-if-nil mu4e-compose-func #'mu4e~compose-handler)