From f0f14d7505c8a56493c8d0aad0c01a47bd537677 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 7 Jan 2023 15:08:14 +0200 Subject: [PATCH] mu4e-bookmarks: show favorite bookmark info --- mu4e/mu4e-bookmarks.el | 226 +++++++++++++++++++++++++++++++++++------ 1 file changed, 196 insertions(+), 30 deletions(-) diff --git a/mu4e/mu4e-bookmarks.el b/mu4e/mu4e-bookmarks.el index 89c6ffa8..1189172e 100644 --- a/mu4e/mu4e-bookmarks.el +++ b/mu4e/mu4e-bookmarks.el @@ -1,6 +1,6 @@ ;;; mu4e-bookmarks.el -- part of mu4e -*- lexical-binding: t -*- -;; Copyright (C) 2011-2022 Dirk-Jan C. Binnema +;; Copyright (C) 2011-2023 Dirk-Jan C. Binnema ;; Author: Dirk-Jan C. Binnema ;; Maintainer: Dirk-Jan C. Binnema @@ -24,6 +24,8 @@ ;;; Code: (require 'mu4e-helpers) +(require 'mu4e-server) +(require 'mu4e-modeline) ;;; Configuration @@ -50,19 +52,24 @@ Each of the list elements is a plist with at least: `:name' - the name of the query -`:query' - the query expression or function -`:key' - the shortcut key. +`:query' - the query expression string (not a function) +`:key' - the shortcut key (single character) Note that the :query parameter can be a function/lambda. -Optionally, you can add the following: `:hide' - if t, the -bookmark is hidden from the main-view and speedbar. -`:hide-unread' - do not show the counts of unread/total number of -matches for the query in the main-view. This can be useful if a -bookmark uses a very slow query. +Optionally, you can add the following: -`:hide-unread' is implied from `:hide'. Furthermore, it is -implied when `:query' is a function. +- `:favorite' - if t, monitor the results of this query, and make +it eligible for showing its status in the emacs modeline. At mose +one bookmark should have this set to t (otherwise the _first_ +bookmark is the implicit favorite) +- `:hide' - if t, the bookmark is hidden from the main-view and +speedbar. +- `:hide-unread' - do not show the counts of +unread/total number of matches for the query in the main-view. +This can be useful if a bookmark uses a very slow query. + +`:hide-unread' is implied from `:hide'. Note: for efficiency, queries used to determine the unread/all counts do not discard duplicate or unreadable messages. Thus, the @@ -72,7 +79,7 @@ query." :group 'mu4e-bookmarks) - (defun mu4e-ask-bookmark (prompt) +(defun mu4e-ask-bookmark (prompt) "Ask the user for a bookmark (using PROMPT) as defined in `mu4e-bookmarks', then return the corresponding query." (unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined")) @@ -88,23 +95,32 @@ query." (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." - (let* ((chosen-bm - (or (seq-find - (lambda (bm) - (= kar (plist-get bm :key))) - (mu4e-bookmarks)) - (mu4e-warn "Unknown shortcut '%c'" kar))) - (expr (plist-get chosen-bm :query)) - (expr (if (not (functionp expr)) expr - (funcall expr))) - (query (eval expr))) - (if (stringp query) - query - (mu4e-warn "Expression must evaluate to query string ('%S')" expr)))) - + (let ((chosen-bm + (or (seq-find + (lambda (bm) + (= kar (plist-get bm :key))) + (mu4e-bookmarks)) + (mu4e-warn "Unknown shortcut '%c'" kar)))) + (mu4e--bookmark-query chosen-bm))) (defun mu4e-bookmark-define (query name key) "Define a bookmark for QUERY with NAME and shortcut KEY. @@ -116,8 +132,8 @@ with KEY." (= (plist-get bm :key) key)) (mu4e-bookmarks))) (cl-pushnew `(:name ,name - :query ,query - :key ,key) + :query ,query + :key ,key) mu4e-bookmarks :test 'equal)) (defun mu4e-bookmarks () @@ -125,9 +141,159 @@ with KEY." Convert from the old format if needed." (seq-map (lambda (item) (if (and (listp item) (= (length item) 3)) - `(:name ,(nth 1 item) :query ,(nth 0 item) - :key ,(nth 2 item)) - item)) mu4e-bookmarks)) + `(:name ,(nth 1 item) :query ,(nth 0 item) + :key ,(nth 2 item)) + 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 'force)) ; for side-effects + + +(defvar mu4e--last-query-results-cached nil) +(defun mu4e-last-query-results(&optional force) + "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 its nil or FORCE is non-nil." + (if (and mu4e--last-query-results-cached (not force)) + mu4e--last-query-results-cached ;; use cache + ;; otherwise, recalculate. + (let* ((favorite (mu4e-favorite-bookmark)) + (favorite-query + (and favorite (mu4e--bookmark-query favorite)))) + (setq mu4e--last-query-results-cached ;; 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." + (seq-find + (lambda (elm) (string= query (mu4e--bookmark-query elm))) + (mu4e-last-query-results))) + +;; for Zero-Inbox afficionados +(defvar mu4e-modeline-all-clear '("C:" . "🌀") + "No more messages at all for this query.") +(defvar mu4e-modeline-all-read '("R:" . "✅") + "No unread messages left.") +(defvar mu4e-modeline-unread-items '("U:" . "📫") + "There are some unread items.") +(defvar mu4e-modeline-new-items '("N:" . "🔥") + "There are some new items after the baseline. +I.e., very new messages.") + +(declare-function mu4e-search-bookmark "mu4e-search") +(defun mu4e-jump-to-favorite () + "Jump to to the favorite bookmark, if any." + (interactive) + (when-let ((fav (mu4e--bookmark-query (mu4e-favorite-bookmark)))) + (mu4e-search-bookmark fav))) + +(defvar mu4e--bookmarks-modeline-cached nil) + +(defun mu4e--bookmarks-modeline-item () + "Modeline item showing message counts for the favorite bookmark. + +This uses the one special ':favorite' bookmark, and if there is +one, creates a propertized string for display in the modeline." + (or mu4e--bookmarks-modeline-cached + (setq mu4e--bookmarks-modeline-cached + (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))) + (propertize + (format "%s%s%s/%s " + (funcall (if mu4e-use-fancy-chars 'cdr 'car) + (cond + ((> delta 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)) + '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)))))))) + + +(defun mu4e--modeline-update() + "Update the modeline and redisplay if mu4e-modeline-mode is +active." + (when mu4e-modeline-mode + (setq mu4e--bookmarks-modeline-cached nil) ;; force recalculation + (force-mode-line-update))) + + (provide 'mu4e-bookmarks) ;;; mu4e-bookmarks.el ends here