mu4e-bookmarks: show favorite bookmark info

This commit is contained in:
Dirk-Jan C. Binnema
2023-01-07 15:08:14 +02:00
parent 17b7790686
commit f0f14d7505

View File

@ -1,6 +1,6 @@
;;; mu4e-bookmarks.el -- part of mu4e -*- lexical-binding: t -*- ;;; 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 <djcb@djcbsoftware.nl> ;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -24,6 +24,8 @@
;;; Code: ;;; Code:
(require 'mu4e-helpers) (require 'mu4e-helpers)
(require 'mu4e-server)
(require 'mu4e-modeline)
;;; Configuration ;;; Configuration
@ -50,19 +52,24 @@
Each of the list elements is a plist with at least: Each of the list elements is a plist with at least:
`:name' - the name of the query `:name' - the name of the query
`:query' - the query expression or function `:query' - the query expression string (not a function)
`:key' - the shortcut key. `:key' - the shortcut key (single character)
Note that the :query parameter can be a function/lambda. Note that the :query parameter can be a function/lambda.
Optionally, you can add the following: `:hide' - if t, the Optionally, you can add the following:
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'. Furthermore, it is - `:favorite' - if t, monitor the results of this query, and make
implied when `:query' is a function. 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 Note: for efficiency, queries used to determine the unread/all
counts do not discard duplicate or unreadable messages. Thus, the counts do not discard duplicate or unreadable messages. Thus, the
@ -72,7 +79,7 @@ query."
:group 'mu4e-bookmarks) :group 'mu4e-bookmarks)
(defun mu4e-ask-bookmark (prompt) (defun mu4e-ask-bookmark (prompt)
"Ask the user for a bookmark (using PROMPT) as defined in "Ask the user for a bookmark (using PROMPT) as defined in
`mu4e-bookmarks', then return the corresponding query." `mu4e-bookmarks', then return the corresponding query."
(unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined")) (unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined"))
@ -88,23 +95,32 @@ query."
(kar (read-char (concat prompt bmarks)))) (kar (read-char (concat prompt bmarks))))
(mu4e-get-bookmark-query kar))) (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) (defun mu4e-get-bookmark-query (kar)
"Get the corresponding bookmarked query for shortcut KAR. "Get the corresponding bookmarked query for shortcut KAR.
Raise an error if none is found." Raise an error if none is found."
(let* ((chosen-bm (let ((chosen-bm
(or (seq-find (or (seq-find
(lambda (bm) (lambda (bm)
(= kar (plist-get bm :key))) (= kar (plist-get bm :key)))
(mu4e-bookmarks)) (mu4e-bookmarks))
(mu4e-warn "Unknown shortcut '%c'" kar))) (mu4e-warn "Unknown shortcut '%c'" kar))))
(expr (plist-get chosen-bm :query)) (mu4e--bookmark-query chosen-bm)))
(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))))
(defun mu4e-bookmark-define (query name key) (defun mu4e-bookmark-define (query name key)
"Define a bookmark for QUERY with NAME and shortcut KEY. "Define a bookmark for QUERY with NAME and shortcut KEY.
@ -116,8 +132,8 @@ with KEY."
(= (plist-get bm :key) key)) (= (plist-get bm :key) key))
(mu4e-bookmarks))) (mu4e-bookmarks)))
(cl-pushnew `(:name ,name (cl-pushnew `(:name ,name
:query ,query :query ,query
:key ,key) :key ,key)
mu4e-bookmarks :test 'equal)) mu4e-bookmarks :test 'equal))
(defun mu4e-bookmarks () (defun mu4e-bookmarks ()
@ -125,9 +141,159 @@ with KEY."
Convert from the old format if needed." Convert from the old format if needed."
(seq-map (lambda (item) (seq-map (lambda (item)
(if (and (listp item) (= (length item) 3)) (if (and (listp item) (= (length item) 3))
`(:name ,(nth 1 item) :query ,(nth 0 item) `(:name ,(nth 1 item) :query ,(nth 0 item)
:key ,(nth 2 item)) :key ,(nth 2 item))
item)) mu4e-bookmarks)) 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 <total number matching count>
:unread <number of unread messages in count>
[:favorite t]
:baseline ( ;; baseline results
:count <total number matching count>
:unread <number of unread messages in count>)) 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) (provide 'mu4e-bookmarks)
;;; mu4e-bookmarks.el ends here ;;; mu4e-bookmarks.el ends here