mirror of https://github.com/djcb/mu.git
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.
This commit is contained in:
parent
fc867c9065
commit
10041eb2e1
|
@ -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',
|
||||
|
|
|
@ -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 <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 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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; 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
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
59
mu4e/mu4e.el
59
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."
|
||||
|
|
Loading…
Reference in New Issue