From 10041eb2e1b8ae3f3db223fe4223a1e3beb04131 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Mon, 9 Jan 2023 23:58:10 +0200 Subject: [PATCH] mu4e: implement mu4e-query-items Implement function / datas structure to conveniently aggregate all query-related data, for reuse in various places in mu4e... ... to start with the main menu, which gets much simpler. And the modeline. --- mu4e/meson.build | 1 + mu4e/mu4e-bookmarks.el | 147 +++++--------------------- mu4e/mu4e-folders.el | 12 --- mu4e/mu4e-headers.el | 7 +- mu4e/mu4e-main.el | 138 ++++++++++-------------- mu4e/mu4e-modeline.el | 12 ++- mu4e/mu4e-query-items.el | 221 +++++++++++++++++++++++++++++++++++++++ mu4e/mu4e-search.el | 10 +- mu4e/mu4e-server.el | 19 ++-- mu4e/mu4e.el | 59 ++++------- 10 files changed, 345 insertions(+), 281 deletions(-) create mode 100644 mu4e/mu4e-query-items.el diff --git a/mu4e/meson.build b/mu4e/meson.build index cb52c7b6..2cae9546 100644 --- a/mu4e/meson.build +++ b/mu4e/meson.build @@ -48,6 +48,7 @@ mu4e_srcs=[ 'mu4e-modeline.el', 'mu4e-obsolete.el', 'mu4e-org.el', + 'mu4e-query-items.el', 'mu4e-search.el', 'mu4e-server.el', 'mu4e-speedbar.el', diff --git a/mu4e/mu4e-bookmarks.el b/mu4e/mu4e-bookmarks.el index c22937c1..e7ab60bd 100644 --- a/mu4e/mu4e-bookmarks.el +++ b/mu4e/mu4e-bookmarks.el @@ -24,8 +24,9 @@ ;;; Code: (require 'mu4e-helpers) -(require 'mu4e-server) (require 'mu4e-modeline) +(require 'mu4e-folders) +(require 'mu4e-query-items) ;;; Configuration @@ -60,9 +61,11 @@ Note that the :query parameter can be a function/lambda. Optionally, you can add the following: - `:favorite' - if t, monitor the results of this query, and make -it eligible for showing its status in the emacs modeline. At mose +it eligible for showing its status in the modeline. At mose one bookmark should have this set to t (otherwise the _first_ -bookmark is the implicit favorite) +bookmark is the implicit favorite). The query for the `:favorite' +item must be unique among `mu4e-bookmarks' and +`mu4e-maildir-shortcuts'. - `:hide' - if t, the bookmark is hidden from the main-view and speedbar. - `:hide-unread' - do not show the counts of @@ -80,37 +83,23 @@ query." (defun mu4e-ask-bookmark (prompt) - "Ask the user for a bookmark (using PROMPT) as defined in -`mu4e-bookmarks', then return the corresponding query." + "Ask user for bookmark using PROMPT. +Return the corresponding query. The bookmark are as defined in +`mu4e-bookmarks'." (unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined")) (let* ((prompt (mu4e-format "%s" prompt)) (bmarks (mapconcat (lambda (bm) (concat - "[" (propertize (make-string 1 (plist-get bm :key)) - 'face 'mu4e-highlight-face) + "[" + (propertize (make-string 1 (plist-get bm :key)) + 'face 'mu4e-highlight-face) "]" (plist-get bm :name))) (mu4e-bookmarks) ", ")) (kar (read-char (concat prompt bmarks)))) (mu4e-get-bookmark-query kar))) - -(defun mu4e--bookmark-query (bm) - "Get query string for some bookmark." - (when bm - (let* ((query (or (plist-get bm :query) - (mu4e-warn "No query in %S" bm))) - ;; queries being functions is deprecated. - (query (if (functionp query) (funcall query) query))) - ;; earlier, we allowed for the queries being fucntions - (unless (stringp query) - (mu4e-warn "Could not get query string from %s" bm)) - ;; apparently, non-UTF8 queries exist, i.e., - ;; with maild dir names. - (decode-coding-string query 'utf-8 t)))) - - (defun mu4e-get-bookmark-query (kar) "Get the corresponding bookmarked query for shortcut KAR. Raise an error if none is found." @@ -146,88 +135,14 @@ Convert from the old format if needed." item)) mu4e-bookmarks)) -(defun mu4e-favorite-bookmark () - "Find the favorite bookmark. -The favorit bookmark is the first one that has a non-nil -':favorite' property, or the first if there is none." - (let ((bookmarks (mu4e-bookmarks))) - (or (seq-find (lambda (bm) (plist-get bm :favorite)) - (mu4e-bookmarks)) - (car-safe bookmarks)))) - -;;; Last & baseline query results for bookmarks. - -(defvar mu4e--baseline nil - "Some previous version of the query-results. -This is used as the baseline to track updates by comparing it to -the latest query-results.") -(defvar mu4e--baseline-tstamp nil - "Timestamp for when the query-results baseline was updated.") - -(defun mu4e--reset-baseline () - (setq mu4e--baseline (mu4e-server-query-results) - mu4e--baseline-tstamp (current-time)) - (mu4e-last-query-results 'refresh)) ; for side-effects - - -(defvar mu4e--last-query-results-cached nil) -(defun mu4e-last-query-results(&optional refresh) - "Get the results (counts) of the latest queries. - -Either read form the cache or update them when oudated or FORCE -is non-nil. - -The queries are the bookmark / maildir queries that are used to -populate the read/unread counts in the main view and modeline. -They are refreshed when calling `(mu4e)', i.e., when going to the -main view. - -When available, the baseline results are added as well. - -The results are a list of elements of the form - (:query \"query string\" - :count - :unread - [:favorite t] - :baseline ( ;; baseline results - :count - :unread )) The -baseline part is optional (see `mu4e-reset-query-results') for -more details). - -Uses a cached string unless it is nil or REFRESH is non-nil." - (or (and (not refresh) mu4e--last-query-results-cached) - (setq mu4e--last-query-results-cached - (let* ((favorite (mu4e-favorite-bookmark)) - (favorite-query - (and favorite (mu4e--bookmark-query favorite)))) - ;; walk over the remembered queries - ;; and augment them with the baseline data and ':favorite' flag, if - ;; any. - (seq-map - (lambda (qres) - ;; note: queries can be _functions_ too; use their - ;; string value. - (let* ((query (mu4e--bookmark-query qres)) - (bres (seq-find ;; find the corresponding baseline entry - (lambda (bq) - (string= query (mu4e--bookmark-query bq))) - mu4e--baseline))) - (when (string= query (or favorite-query "")) - (plist-put qres :favorite t)) - (when bres - (plist-put qres :baseline - `(:count ,(plist-get bres :count) - :unread ,(plist-get bres :unread)))) - qres)) - (mu4e-server-query-results)))))) - -(defun mu4e-last-query-result (query) - "Get the last result for some QUERY or nil if not found. -See `mu4e-last-query-results' for the format." +(defun mu4e-bookmark-favorite () + "Find the favorite bookmark." +;; note, use query-items, which will have picked a favorite +;; even if user did not provide one explictly (seq-find - (lambda (elm) (string= query (mu4e--bookmark-query elm))) - (mu4e-last-query-results))) + (lambda (item) + (plist-get item :favorite)) + (mu4e-query-items 'bookmarks))) ;; for Zero-Inbox afficionados (defvar mu4e-modeline-all-clear '("C:" . "🌀") @@ -244,7 +159,7 @@ I.e., very new messages.") (defun mu4e-jump-to-favorite () "Jump to to the favorite bookmark, if any." (interactive) - (when-let ((fav (mu4e--bookmark-query (mu4e-favorite-bookmark)))) + (when-let ((fav (mu4e--bookmark-query (mu4e-bookmark-favorite)))) (mu4e-search-bookmark fav))) (defun mu4e--bookmarks-modeline-item () @@ -254,34 +169,24 @@ This uses the one special ':favorite' bookmark, and if there is one, creates a propertized string for display in the modeline." (when-let ((fav ;; any results for the favorite bookmark item? (seq-find (lambda (bm) (plist-get bm :favorite)) - (mu4e-last-query-results)))) - (let* ((unread (plist-get fav :unread)) - (count (plist-get fav :count)) - (baseline (plist-get fav :baseline)) - (baseline-unread - (or (when baseline (plist-get baseline :unread)) unread)) - (delta (- unread baseline-unread))) + (mu4e-query-items 'bookmarks)))) + (cl-destructuring-bind (&key unread count delta-unread + &allow-other-keys) fav (propertize - (format "%s%s%s/%s " + (format "%s%s " (funcall (if mu4e-use-fancy-chars 'cdr 'car) (cond - ((> delta 0) mu4e-modeline-new-items) + ((> delta-unread 0) mu4e-modeline-new-items) ((> unread 0) mu4e-modeline-unread-items) ((> count 0) mu4e-modeline-all-read) (t mu4e-modeline-all-clear))) - (propertize (number-to-string unread) 'face 'mu4e-header-key-face) - (if (<= delta 0) "" - (propertize (format "(%+d)" delta) - 'face 'mu4e-unread-face)) - (number-to-string count)) + (mu4e--query-item-display-counts fav)) 'help-echo (format "mu4e query: '%s'" (mu4e--bookmark-query fav)) 'mouse-face 'mode-line-highlight 'keymap '(mode-line keymap (mouse-1 . mu4e-jump-to-favorite) (mouse-2 . mu4e-jump-to-favorite) (mouse-3 . mu4e-jump-to-favorite)))))) - - (provide 'mu4e-bookmarks) ;;; mu4e-bookmarks.el ends here diff --git a/mu4e/mu4e-folders.el b/mu4e/mu4e-folders.el index 2d6e7eac..121b33bf 100644 --- a/mu4e/mu4e-folders.el +++ b/mu4e/mu4e-folders.el @@ -159,18 +159,6 @@ Converts from the old format if needed." item)) mu4e-maildir-shortcuts)) -(defun mu4e--maildirs-with-query () - "Like `mu4e-maildir-shortcuts', but with :query populated. -This is compatibile with `mu4e-bookmarks'." - (seq-map - (lambda (item) - (let* ((maildir (plist-get item :maildir)) - (name (or (plist-get item :name) maildir)) - (item (plist-put item :name name)) - (item (plist-put item :query (format "maildir:\"%s\"" maildir)))) - item)) ;; we don't need ":maildir", but it's harmless. - (mu4e-maildir-shortcuts))) - ;; the standard folders can be functions too (defun mu4e--get-folder (foldervar msg) "Within the mu-context of MSG, get message folder FOLDERVAR. diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index b6fa5b02..071af58b 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -1526,13 +1526,12 @@ region if there is a region, then move to the next message." (when mu4e-headers-advance-after-mark (mu4e-headers-next))) (defun mu4e~headers-quit-buffer () - "Quit the mu4e-headers buffer. -This is a rather complex function, to ensure we don't disturb -other windows." + "Quit the mu4e-headers buffer." (interactive) (mu4e-mark-handle-when-leaving) (quit-window t) - (mu4e--main-view 'refresh)) + (mu4e--query-items-reset-baseline) + (mu4e--main-view)) ;;; Loading messages diff --git a/mu4e/mu4e-main.el b/mu4e/mu4e-main.el index fa141719..7f120085 100644 --- a/mu4e/mu4e-main.el +++ b/mu4e/mu4e-main.el @@ -27,6 +27,7 @@ (require 'smtpmail) (require 'mu4e-helpers) (require 'mu4e-context) +(require 'mu4e-compose) (require 'mu4e-bookmarks) (require 'mu4e-folders) (require 'mu4e-update) @@ -34,6 +35,7 @@ (require 'mu4e-search) (require 'mu4e-vars) ;; mu-wide variables (require 'mu4e-window) +(require 'mu4e-query-items) (declare-function mu4e-compose-new "mu4e-compose") (declare-function mu4e-quit "mu4e") @@ -87,10 +89,9 @@ the personal addresses." (mu4e-info (concat mu4e-doc-dir "/NEWS.org"))) (defun mu4e--main-reset-baseline() - "Main-view version of `mu4e-reset-query-results'. -This version handles updating the current screen as well." + "Reset the query baseline." (interactive) - (mu4e--reset-baseline) + (mu4e--query-items-reset-baseline) (revert-buffer)) (defvar mu4e-main-mode-map @@ -111,7 +112,8 @@ This version handles updating the current screen as well." (define-key map "S" #'mu4e-kill-update-mail) (define-key map (kbd "C-S-u") #'mu4e-update-mail-and-index) (define-key map ";" - (lambda()(interactive)(mu4e-context-switch)(revert-buffer))) + (lambda()(interactive) + (mu4e-context-switch)(revert-buffer))) (define-key map "$" #'mu4e-show-log) (define-key map "A" #'mu4e-about) @@ -137,6 +139,7 @@ This version handles updating the current screen as well." ["Quit" mu4e-quit :help "Quit mu4e"] ))) +(declare-function mu4e--server-bookmarks-queries "mu4e") (define-derived-mode mu4e-main-mode special-mode "mu4e:main" "Major mode for the mu4e main screen. @@ -146,10 +149,14 @@ This version handles updating the current screen as well." (mu4e-context-minor-mode) (mu4e-search-minor-mode) (mu4e-update-minor-mode) - (mu4e-modeline-mode) (setq-local revert-buffer-function (lambda (_ignore-auto _noconfirm) - (mu4e--main-view 'refresh)))) + ;; the query results will trigger a redraw + (mu4e--query-items-refresh))) + (add-hook 'mu4e-query-items-updated-hook + (lambda () + (when (get-buffer mu4e-main-buffer-name) + (mu4e--main-redraw-buffer))) nil t)) (defun mu4e--main-action-str (str &optional func-or-shortcut) "Highlight the first occurrence of [.] in STR. @@ -183,64 +190,28 @@ clicked. If HIGHLIGHT is non-nil, hightlight the name." (- (length newstr) 1)) 'mouse-face 'highlight newstr) newstr)) -(defun mu4e--longest-of-maildirs-and-bookmarks () - "Return the length of longest name of bookmarks and maildirs." - (cl-loop for b in (append (mu4e-bookmarks) - (mu4e--maildirs-with-query)) - maximize (string-width (plist-get b :name)))) - -(defun mu4e--main-item (fullkey name qcounts max-length) - "Display a main-view bookmarks/maildir item. -- FULLKEY is a 2-character string describing the item's shortcut -- NAME is the name of the of the item -- QCOUNTS is a structure with unread information - for this item (or nil) -- MAX-LENGTH is the maximum length for an item name - (used for alignment)." - (concat - (mu4e--main-action-str - (format "\t* [%s] %s" fullkey - (propertize name 'face - (if (and qcounts (plist-get qcounts :favorite)) - 'mu4e-header-key-face nil))) - fullkey) - ;; append all/unread numbers, if available. - (if qcounts - (let* ((unread (plist-get qcounts :unread)) - (count (plist-get qcounts :count)) - (baseline (plist-get qcounts :baseline)) - (baseline-unread - (or (when baseline (plist-get baseline :unread)) unread)) - (delta (- unread baseline-unread))) - (format "%s (%s%s/%s)" - (make-string (- max-length (string-width name)) ? ) - (propertize - (number-to-string unread) 'face 'mu4e-header-key-face - 'help-echo "Number of unread messages") - (if (> delta 0) - (propertize (format "(%+d)" delta) 'face - 'mu4e-unread-face) "") - (propertize (number-to-string count) - 'help-echo "Total number of messages"))) - "") "\n")) - -(defun mu4e--main-items (shortcut items max-length) +(defun mu4e--main-items (data shortcut max-length) "Display the entries for the bookmark/maildir menu - SHORTCUT is a single character which is the first character of the keyboard shortcut -- ITEMS is a list of items, for format see `(mu4e-bookmarks)' -- MAX-LENGTH is the maximum length for an item name - (used for alignment)." - (cl-loop for item in items - for fullkey = (format "%c%c" shortcut (plist-get item :key)) - for name = (plist-get item :name) - for query = (funcall (or mu4e-query-rewrite-function #'identity) - (mu4e--bookmark-query item)) - for qcounts = (mu4e-last-query-result query) - for unread = (and qcounts (plist-get (car qcounts) :unread)) - when (not (plist-get item :hide)) - when (not (and mu4e-main-hide-fully-read (eq unread 0))) - concat (mu4e--main-item fullkey name qcounts max-length))) +- ITEMS is a list of items, for format see `(mu4e-query-data)' +- MAX-LENGTH is the maximum length for an item name" + (mapconcat + (lambda (item) + (cl-destructuring-bind + (&key hide name key favorite &allow-other-keys) item + ;; hide items explicitly hidden, without key or wrong category. + (if hide + "" + (concat + (mu4e--main-action-str + (format "\t* [%s] %s " (format "%c%c" shortcut key) + (propertize + name 'face + (if favorite 'mu4e-header-key-face nil)))) + (format "%s%s\n" + (make-string (- max-length (string-width name)) ?\s) + (mu4e--query-item-display-counts item)))))) data "")) (defun mu4e--key-val (key val &optional unit) "Show a KEY / VAL pair, with optional UNIT." @@ -255,7 +226,7 @@ character of the keyboard shortcut (defun mu4e--baseline-time-string () "Calculate the baseline time string." - (let* ((baseline-t mu4e--baseline-tstamp) + (let* ((baseline-t mu4e--query-items-baseline-tstamp) (updated-t (plist-get mu4e-index-update-status :tstamp)) (delta-t (and baseline-t updated-t (float-time (time-subtract updated-t baseline-t))))) @@ -268,10 +239,12 @@ character of the keyboard shortcut (defun mu4e--main-redraw-buffer () "Redraw the main buffer." (with-current-buffer mu4e-main-buffer-name - (let ((inhibit-read-only t) - (pos (point)) - (addrs (mu4e-personal-addresses)) - (max-length (mu4e--longest-of-maildirs-and-bookmarks))) + (let* ((inhibit-read-only t) + (pos (point)) + (addrs (mu4e-personal-addresses)) + (max-length (seq-reduce (lambda (a b) + (max a (length (plist-get b :name)))) + (mu4e-query-items) 0))) (erase-buffer) (insert "* " @@ -281,17 +254,17 @@ character of the keyboard shortcut "\n\n" (propertize " Basics\n\n" 'face 'mu4e-title-face) (mu4e--main-action-str - "\t* [j]ump to some maildir\n" #'mu4e~headers-jump-to-maildir) + "\t* [j]ump to some maildir\n" #'mu4e-search-maildir) (mu4e--main-action-str "\t* enter a [s]earch query\n" #'mu4e-search) (mu4e--main-action-str "\t* [C]ompose a new message\n" #'mu4e-compose-new) "\n" (propertize " Bookmarks\n\n" 'face 'mu4e-title-face) - (mu4e--main-items ?b (mu4e-bookmarks) max-length) + (mu4e--main-items (mu4e-query-items 'bookmarks) ?b max-length) "\n" (propertize " Maildirs\n\n" 'face 'mu4e-title-face) - (mu4e--main-items ?j (mu4e--maildirs-with-query) max-length) + (mu4e--main-items (mu4e-query-items 'maildirs) ?j max-length) "\n" (propertize " Misc\n\n" 'face 'mu4e-title-face) @@ -301,7 +274,8 @@ character of the keyboard shortcut (mu4e--main-action-str "\t* [U]pdate email & database\n" #'mu4e-update-mail-and-index) - (mu4e--main-action-str "\t* [R]eset baseline\n" #'mu4e--reset-baseline) + (mu4e--main-action-str "\t* [R]eset baseline\n" + #'mu4e--main-reset-baseline) ;; show the queue functions if `smtpmail-queue-dir' is defined (if (file-directory-p smtpmail-queue-dir) @@ -318,7 +292,7 @@ character of the keyboard shortcut (mu4e--key-val "last updated" (current-time-string (plist-get mu4e-index-update-status :tstamp))) - (if mu4e--baseline-tstamp + (if mu4e--query-items-baseline-tstamp (mu4e--key-val "baseline" (mu4e--baseline-time-string)) "") (mu4e--key-val "database-path" (mu4e-database-path)) @@ -369,27 +343,23 @@ character of the keyboard shortcut (declare-function mu4e--start "mu4e") -(defun mu4e--main-view (&optional refresh no-reset) - "Create or refresh the mu4e main-view, and switch to it. -When REFRESH is non nil refresh infos from server. +(defun mu4e--main-view () + "(Re)create the mu4e main-view, and switch to it. If `mu4e-split-view' equals \='single-window, show a mu4e menu instead." - (unless no-reset - (mu4e--reset-baseline)) (if (eq mu4e-split-view 'single-window) (mu4e--main-menu) (let ((buf (get-buffer-create mu4e-main-buffer-name)) (inhibit-read-only t)) - ;; `mu4e--main-view' is called from `mu4e--start', so don't call it - ;; a second time here i.e. do not refresh unless specified - ;; explicitly with REFRESH arg. + ;; `mu4e--main-view' is called from `mu4e--start', so don't call it a + ;; second time here i.e. do not refresh unless specified explicitly with + ;; REFRESH arg. (with-current-buffer buf - (if refresh - (mu4e--start 'mu4e--main-redraw-buffer) - (mu4e--main-redraw-buffer))) - (mu4e-display-buffer buf t) - (goto-char (point-min))))) + (mu4e--main-redraw-buffer)) + (mu4e-display-buffer buf t))) + + (goto-char (point-min))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactive functions diff --git a/mu4e/mu4e-modeline.el b/mu4e/mu4e-modeline.el index 22d81c72..4b799b0e 100644 --- a/mu4e/mu4e-modeline.el +++ b/mu4e/mu4e-modeline.el @@ -63,11 +63,6 @@ the buffer-local one." mu4e--modeline-global-items) " ")))) -(defun mu4e--modeline-update () - "Recalculate and force-update the modeline." - (setq mu4e--modeline-string-cached nil) - (force-mode-line-update)) - (define-minor-mode mu4e-modeline-mode "Minor mode for showing mu4e information on the modeline." ;; This is a bit special 'global' mode, since it consists of both @@ -87,6 +82,13 @@ the buffer-local one." global-mode-string))) (force-mode-line-update))) +(defun mu4e--modeline-update () + "Recalculate and force-update the modeline." + (when mu4e-modeline-mode + (setq mu4e--modeline-string-cached nil) + (force-mode-line-update))) + + (provide 'mu4e-modeline) ;;; mu4e-modeline.el ends here diff --git a/mu4e/mu4e-query-items.el b/mu4e/mu4e-query-items.el new file mode 100644 index 00000000..004da729 --- /dev/null +++ b/mu4e/mu4e-query-items.el @@ -0,0 +1,221 @@ +;;; mu4e-query-items.el -- part of mu4e -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Dirk-Jan C. Binnema + +;; Author: Dirk-Jan C. Binnema +;; Maintainer: Dirk-Jan C. Binnema + +;; This file is not part of GNU Emacs. + +;; mu4e is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; mu4e is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with mu4e. If not, see . + +;;; Commentary: +;; +;; Managing the last query results / baseline, which we use to get the +;; unread-counts, i.e., query items. `mu4e-query-items` delivers these items, +;; aggregated from various sources. + + +;;; Code: + +;;; Last & baseline query results for bookmarks. +(require 'cl-lib) +(require 'mu4e-helpers) +(require 'mu4e-server) + +(defvar mu4e--query-items-baseline nil + "Some previous version of the query-items. +This is used as the baseline to track updates by comparing it to +the latest query-items.") +(defvar mu4e--query-items-baseline-tstamp nil + "Timestamp for when the query-items baseline was updated.") + +(defun mu4e--bookmark-query (bm) + "Get query string for some bookmark BM." + (when bm + (let* ((query (or (plist-get bm :query) + (mu4e-warn "No query in %S" bm))) + ;; queries being functions is deprecated. + (query (if (functionp query) (funcall query) query))) + ;; earlier, we allowed for the queries being fucntions + (unless (stringp query) + (mu4e-warn "Could not get query string from %s" bm)) + ;; apparently, non-UTF8 queries exist, i.e., + ;; with maild dir names. + (decode-coding-string query 'utf-8 t)))) + +(defun mu4e--query-items-pick-favorite (items) + "Pick the :favorite querty item. +If ITEMS does not yet have a favorite item, pick the first." + (unless (seq-find + (lambda (item) (plist-get item :favorite)) items) + (plist-put (car items) :favorite t)) + items) + +(defvar mu4e--bookmark-items-cached nil "Cached bookmarks query items.") +(defvar mu4e--maildir-items-cached nil "Cached maildirs query items.") + +(declare-function mu4e-bookmarks "mu4e-bookmarks") +(declare-function mu4e-maildir-shortcuts "mu4e-folders") + +(defun mu4e--query-items-reset () + "Reset the query items." + (setq mu4e--bookmark-items-cached nil + mu4e--maildir-items-cached nil) + (run-hooks 'mu4e-query-items-updated-hook)) + +(defun mu4e--query-items-reset-baseline () + "Reset the baseline query-items." + (setq mu4e--query-items-baseline (mu4e-server-query-items) + mu4e--query-items-baseline-tstamp (current-time)) + (mu4e--query-items-reset)) + +(defun mu4e--query-item-display-counts (item) + "Get the count display string for some query-data ITEM." + ;; purely for display, but we need it in the main menu, modeline + ;; so let's keep it consistent. + (cl-destructuring-bind (&key unread hide-unread delta-unread count + &allow-other-keys) item + (if hide-unread + "" + (concat + (propertize (number-to-string unread) + 'face 'mu4e-header-key-face + 'help-echo "Number of unread") + (if (<= delta-unread 0) "" + (propertize (format "(%+d)" delta-unread) 'face + 'mu4e-unread-face)) + "/" + (propertize (number-to-string count) + 'help-echo "Total number"))))) + + +(defun mu4e--query-items-refresh() + "Get the latest query data from the mu4e server." + (mu4e--server-queries + (mapcar #'mu4e--bookmark-query + (seq-filter (lambda (item) + (and (not (or (plist-get item :hide) + (plist-get item :hide-unread))))) + (mu4e-query-items))))) + +(defun mu4e--query-items-queries-handler (_sexp) + "Handler for queries responses from the mu4e-server. +I.e. what we get in response to mu4e--query-items-refresh." + ;; if we don't have a baseline yet, set it. (note that + ;; mu4e--query-items-reset-baseline also calls mu4e--query-items-reset. + (if (not mu4e--query-items-baseline) + (progn + (mu4e--query-items-reset-baseline)) + (mu4e--query-items-reset)) + ;; for side-effects; recalculate. + (mu4e-query-items)) + +;; this makes for O(n*m)... but with typically small(ish) n,m. Perhaps use a +;; hash for last-query-items and baseline-results? +(defun mu4e--query-find-item (query data) + "Find the item in DATA for the given QUERY." + (seq-find (lambda (item) + (equal query (mu4e--bookmark-query item))) + data)) + +(defun mu4e--make-query-items (data type) + "Map the items in DATA to plists with aggregated query information. + +DATA is either the bookmarks or maildirs (user-defined). + +LAST-RESULTS-DATA contains unread/counts we received from the +server, while BASELINE-DATA contains the same but taken at some +earier time. + +The TYPE denotes the category for the query item, a symbol +bookmark or maildir." + (seq-map + (lambda (item) + (let* ((maildir (plist-get item :maildir)) + ;; for maildirs, construct the query + (query (if (equal type 'maildirs) + (format "maildir:\"%s\"" maildir) + (plist-get item :query))) + (name (plist-get item :name)) + ;; maildir items may have an implicit name + ;; which is the maildir value. + (name (or name (and (equal type 'maildirs) maildir))) + + (last-results (mu4e-server-query-items)) + (baseline mu4e--query-items-baseline) + + (baseline-item (mu4e--query-find-item query baseline)) + (last-results-item (mu4e--query-find-item query last-results)) + (count (or (plist-get last-results-item :count) 0)) + (unread (or (plist-get last-results-item :unread) 0)) + (baseline-count (or (plist-get baseline-item :count) count)) + (baseline-unread (or (plist-get baseline-item :unread) unread)) + (delta-unread (- unread baseline-unread)) + (value + (list + :name name + :query query + :key (plist-get item :key) + :count count + :unread unread + :delta-count (- count baseline-count) + :delta-unread delta-unread))) + + ;; nil props bring me discomfort + (when (plist-get item :favorite) + (plist-put value :favorite t)) + (when (plist-get item :hide) + (plist-put value :hide t)) + (when (plist-get item :hide-unread) + (plist-put value :hide-unread t)) + value)) + data)) + +;; Note: uipdating is lazy, only happens with the first caller to +;; mu4e-query items. +(defvar mu4e-query-items-updated-hook nil + "Hook run when the query items have been updated.") + +(defun mu4e-query-items (&optional type) + "Grab query items of TYPE. + +TYPE is symbol; either bookmarks or maildirs, or nil for both. + +This combines: + - the latest queries data (i.e., `(mu4e-server-query-items)') + - baseline queries data (i.e. `mu4e-baseline') + with the combined queries for `(mu4e-bookmarks)' and + `(mu4e-maildir-shortcuts)' in bookmarks-compatible plists. + +This packages the aggregated information in a format that is convenient +for use in various places." + (cond + ((equal type 'bookmarks) + (or mu4e--bookmark-items-cached + (setq mu4e--bookmark-items-cached + (mu4e--query-items-pick-favorite + (mu4e--make-query-items (mu4e-bookmarks) 'bookmarks))))) + ((equal type 'maildirs) + (or mu4e--maildir-items-cached + (setq mu4e--maildir-items-cached + (mu4e--make-query-items (mu4e-maildir-shortcuts) 'maildirs)))) + ((not type) + (append (mu4e-query-items 'bookmarks) + (mu4e-query-items 'maildirs))) + (t + (mu4e-error "No such type %s" type)))) + +(provide 'mu4e-query-items) +;;; mu4e-query-data.el ends here diff --git a/mu4e/mu4e-search.el b/mu4e/mu4e-search.el index 9ccba762..cd9e4ec4 100644 --- a/mu4e/mu4e-search.el +++ b/mu4e/mu4e-search.el @@ -33,6 +33,7 @@ (require 'mu4e-contacts) (require 'mu4e-lists) (require 'mu4e-mark) +(require 'mu4e-query-items) ;;; Configuration @@ -219,11 +220,12 @@ the search." (interactive) (let* ((expr (or expr - (mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))) - (fav (mu4e--bookmark-query (mu4e-favorite-bookmark)))) - ;; reset baseline when searching for bookmark query + (mu4e-ask-bookmark + (if edit "Select bookmark: " "Bookmark: ")))) + (fav (mu4e--bookmark-query (mu4e-bookmark-favorite)))) + ;; reset baseline when searching for the favorite bookmark query (when (and fav (string= fav expr)) - (mu4e--reset-baseline)) + (mu4e--query-items-reset-baseline)) (run-hook-with-args 'mu4e-search-bookmark-hook expr) (mu4e-search expr (when edit "Edit bookmark: ") edit))) diff --git a/mu4e/mu4e-server.el b/mu4e/mu4e-server.el index 96f0cf14..c64297ab 100644 --- a/mu4e/mu4e-server.el +++ b/mu4e/mu4e-server.el @@ -166,18 +166,15 @@ sexp received from the server process.") (plist-get mu4e--server-props :version)) (mu4e-error "Version unknown; did you start mu4e?"))) -;; -;; server-query-results are the results from the counting-queries -;; we do for bookmarks etc. to populate the main view with unread -;; counts. - ;;; remember queries result. -(defvar mu4e--server-query-results nil - "Metadata we receive from the mu4e server.") +(defvar mu4e--server-query-items nil + "Query items results we receive from the mu4e server. +Those are the results from the counting-queries +for bookmarks and maildirs.") -(defun mu4e-server-query-results () - "Get the latest server queries list." - mu4e--server-query-results) +(defun mu4e-server-query-items () + "Get the latest server query items." + mu4e--server-query-items) ;;; Handling raw server data @@ -333,7 +330,7 @@ The server output is as follows: ;; receive queries info ((plist-get sexp :queries) - (setq mu4e--server-query-results (plist-get sexp :queries)) + (setq mu4e--server-query-items (plist-get sexp :queries)) (funcall mu4e-queries-func sexp)) ;; received a contacts message diff --git a/mu4e/mu4e.el b/mu4e/mu4e.el index 8cb272f2..c79abbf4 100644 --- a/mu4e/mu4e.el +++ b/mu4e/mu4e.el @@ -82,12 +82,8 @@ is non-nil." (interactive "P") ;; start mu4e, then show the main view (mu4e--init-handlers) - ;; i.e., only auto update baseline when user explicitly requested - (when (or (not mu4e--baseline-tstamp) - (and (not background) (called-interactively-p 'interactive))) - (mu4e--reset-baseline)) - (mu4e--modeline-update) - (mu4e--start (unless background 'mu4e--main-view))) + (mu4e--start (unless background #'mu4e--main-view)) + (mu4e--query-items-refresh)) (defun mu4e-quit() "Quit the mu4e session." @@ -138,34 +134,12 @@ Invoke FUNC if non-nil." (lambda () (mu4e-update-mail-and-index mu4e-index-update-in-background))))))) -(defun mu4e--server-bookmarks-queries() - (mu4e--server-queries - (mapcar ;; send it a list of queries we'd like to see read/unread info for - (lambda (bm) - (funcall (or mu4e-query-rewrite-function #'identity) - (mu4e--bookmark-query bm))) - ;; exclude bookmarks with certain flags - (seq-filter (lambda (bm) - (and (not (or (plist-get bm :hide) - (plist-get bm :hide-unread))))) - (append (mu4e-bookmarks) - (mu4e--maildirs-with-query)))))) - -(defun mu4e--queries-handler (_sexp) - ;; we've received new server-queries; so update the main view - ;; (if any) and the modeline. - - ;; 1. update the query results (i.e. process the new server queries) - (mu4e-last-query-results 'refresh) - (if mu4e--baseline - (mu4e-last-query-results 'refresh) - (mu4e--reset-baseline)) - ;; note: mu4e-reset-baseline implies mu4e--last-query-results - - ;; 2. update the main view, if any - (when (buffer-live-p mu4e-main-buffer-name) - (with-current-buffer mu4e-main-buffer-name - (revert-buffer)))) + ;; ;; 2. update the main view, if any + ;; (when-let ((mainbuf (get-buffer mu4e-main-buffer-name))) + ;; (when (buffer-live-p mainbuf) + ;; (mu4e--main-redraw-buffer))) + ;; ;; 3. modeline. + ;; (mu4e--modeline-update) (defun mu4e--start (&optional func) "Start mu4e. @@ -177,10 +151,14 @@ Otherwise, check requirements, then start mu4e. When successful, invoke FUNC (if non-nil) afterwards." (unless (mu4e-context-current) (mu4e--context-autoswitch nil mu4e-context-policy)) - (setq mu4e-pong-func (lambda (info) (mu4e--pong-handler info func))) + (setq mu4e-pong-func + (lambda (info) (mu4e--pong-handler info func))) (mu4e--modeline-register #'mu4e--bookmarks-modeline-item 'global) + (when mu4e-modeline-support + (mu4e-modeline-mode) + (add-hook 'mu4e-query-items-updated-hook + #'mu4e--modeline-update)) (mu4e-modeline-mode (if mu4e-modeline-support 1 -1)) - (mu4e--server-bookmarks-queries) (mu4e--server-ping) ;; maybe request the list of contacts, automatically refreshed after ;; reindexing @@ -192,7 +170,8 @@ Otherwise, check requirements, then start mu4e. When successful, invoke (cancel-timer mu4e--update-timer) (setq mu4e--update-timer nil)) (mu4e-clear-caches) - + (remove-hook 'mu4e-query-items-updated-hook + #'mu4e--modeline-update) (mu4e-modeline-mode -1) (mu4e--server-kill) ;; kill all mu4e buffers @@ -251,7 +230,7 @@ Otherwise, check requirements, then start mu4e. When successful, invoke (if mu4e-index-lazy-check "Lazy indexing" "Indexing") checked updated cleaned-up) ;; index done; grab updated queries - (mu4e--server-bookmarks-queries) + (mu4e--query-items-refresh) (run-hooks 'mu4e-index-updated-hook) ;; backward compatibility... (unless (zerop (+ updated cleaned-up)) @@ -262,7 +241,7 @@ Otherwise, check requirements, then start mu4e. When successful, invoke (when (and (buffer-live-p mainbuf) (get-buffer-window mainbuf)) (save-window-excursion (select-window (get-buffer-window mainbuf)) - (mu4e--main-view 'refresh 'no-reset)))))) + (mu4e--main-view)))))) ((plist-get info :message) (mu4e-index-message "%s" (plist-get info :message)))))) @@ -283,7 +262,7 @@ chance." (mu4e-setq-if-nil mu4e-contacts-func #'mu4e--update-contacts) (mu4e-setq-if-nil mu4e-info-func #'mu4e--info-handler) (mu4e-setq-if-nil mu4e-pong-func #'mu4e--default-handler) - (mu4e-setq-if-nil mu4e-queries-func #'mu4e--queries-handler)) + (mu4e-setq-if-nil mu4e-queries-func #'mu4e--query-items-queries-handler)) (defun mu4e-clear-caches () "Clear any cached resources."