mu4e: emacs 28.1 modernization

We can now use setq-local, length>, string-empty-p, affixations
functions and "choice" defcustoms.
This commit is contained in:
Dirk-Jan C. Binnema
2026-05-25 20:24:37 +03:00
committed by Seth Ladygo
parent dca5334295
commit cbe64d4cae
10 changed files with 79 additions and 58 deletions

View File

@ -238,8 +238,8 @@ When enabled, this attempts to put mu4e's completions at the
start of the buffer-local `completion-at-point-functions'. Other start of the buffer-local `completion-at-point-functions'. Other
completion functions still apply." completion functions still apply."
(when mu4e-compose-complete-addresses (when mu4e-compose-complete-addresses
(set (make-local-variable 'completion-ignore-case) t) (setq-local completion-ignore-case t)
(set (make-local-variable 'completion-cycle-threshold) 7) (setq-local completion-cycle-threshold 7)
(add-to-list (make-local-variable 'completion-styles) 'substring) (add-to-list (make-local-variable 'completion-styles) 'substring)
(add-hook 'completion-at-point-functions (add-hook 'completion-at-point-functions
#'mu4e--compose-complete-contact-field -10 t))) #'mu4e--compose-complete-contact-field -10 t)))
@ -289,13 +289,13 @@ buffers; lets remap its faces so it uses the ones for mu4e."
(when (eq major-mode 'mu4e-compose-mode) (when (eq major-mode 'mu4e-compose-mode)
(mu4e-warn "Not available in mu4e"))) (mu4e-warn "Not available in mu4e")))
(defun mu4e--neutralize-undesirables () ;; Neutralize Gnus commands that do not work with mu4e. The advice is a no-op
"Beware Gnus commands that do not work with mu4e." ;; outside mu4e-compose-mode (see `mu4e--compose-unsupported'), so it is safe
;; the Field menu contains many items that don't apply. ;; to install unconditionally at load time.
(advice-add 'gnus-delay-article (advice-add 'gnus-delay-article
:before #'mu4e--compose-unsupported) ;; # XXX does not work?! :before #'mu4e--compose-unsupported) ;; # XXX does not work?!
(advice-add 'message-goto-newsgroups :before #'mu4e--compose-unsupported) (advice-add 'message-goto-newsgroups :before #'mu4e--compose-unsupported)
(advice-add 'message-insert-newsgroups :before #'mu4e--compose-unsupported)) (advice-add 'message-insert-newsgroups :before #'mu4e--compose-unsupported)
(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose" (define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
"Major mode for the mu4e message composition, derived from `message-mode'. "Major mode for the mu4e message composition, derived from `message-mode'.
@ -303,13 +303,12 @@ buffers; lets remap its faces so it uses the ones for mu4e."
(progn (progn
(use-local-map mu4e-compose-mode-map) (use-local-map mu4e-compose-mode-map)
(mu4e-context-minor-mode) (mu4e-context-minor-mode)
(mu4e--neutralize-undesirables)
(mu4e--compose-remap-faces) (mu4e--compose-remap-faces)
(setq-local nobreak-char-display nil) (setq-local nobreak-char-display nil)
;; set this to allow mu4e to work when gnus-agent is unplugged in gnus ;; set this to allow mu4e to work when gnus-agent is unplugged in gnus
(set (make-local-variable 'message-send-mail-real-function) nil) (setq-local message-send-mail-real-function nil)
;; Set to nil to enable `electric-quote-local-mode' to work: ;; Set to nil to enable `electric-quote-local-mode' to work:
(set (make-local-variable 'comment-use-syntax) nil) (setq-local comment-use-syntax nil)
(mu4e--compose-setup-completion) ;; maybe offer address completion (mu4e--compose-setup-completion) ;; maybe offer address completion
(if mu4e-compose-format-flowed ;; format-flowed (if mu4e-compose-format-flowed ;; format-flowed
(progn (progn

View File

@ -280,7 +280,7 @@ case a phrase contains a quote, it will be escaped."
"Get the full combination of name and email address from CONTACT." "Get the full combination of name and email address from CONTACT."
(let* ((email (mu4e-contact-email contact)) (let* ((email (mu4e-contact-email contact))
(name (mu4e-contact-name contact))) (name (mu4e-contact-name contact)))
(if (and name (> (length name) 0)) (if (and name (not (string-empty-p name)))
(format "%s <%s>" (mu4e--rfc822-quote-phrase name) email) (format "%s <%s>" (mu4e--rfc822-quote-phrase name) email)
email))) email)))

View File

@ -1185,7 +1185,7 @@ The following specs are supported:
(use-local-map mu4e-headers-mode-map) (use-local-map mu4e-headers-mode-map)
(make-local-variable 'mu4e~headers-proc) (make-local-variable 'mu4e~headers-proc)
(make-local-variable 'mu4e~highlighted-docid) (make-local-variable 'mu4e~highlighted-docid)
(set (make-local-variable 'hl-line-face) 'mu4e-header-highlight-face) (setq-local hl-line-face 'mu4e-header-highlight-face)
;; Eldoc support ;; Eldoc support
(when (and (featurep 'eldoc) mu4e-eldoc-support) (when (and (featurep 'eldoc) mu4e-eldoc-support)
@ -1198,8 +1198,8 @@ The following specs are supported:
#'mu4e-headers-eldoc-function))) #'mu4e-headers-eldoc-function)))
;; support bookmarks. ;; support bookmarks.
(set (make-local-variable 'bookmark-make-record-function) (setq-local bookmark-make-record-function
'mu4e--make-bookmark-record) #'mu4e--make-bookmark-record)
;; maybe update the current headers upon indexing changes ;; maybe update the current headers upon indexing changes
(add-hook 'mu4e-index-updated-hook #'mu4e~headers-maybe-auto-update) (add-hook 'mu4e-index-updated-hook #'mu4e~headers-maybe-auto-update)
(setq (setq

View File

@ -721,7 +721,7 @@ shorter keys in some cases where there are multiple bindings."
;; not a perfect heuristic: e.g. '<up>' is longer that 'C-p' ;; not a perfect heuristic: e.g. '<up>' is longer that 'C-p'
(car-safe (car-safe
(seq-sort (lambda (b1 b2) (seq-sort (lambda (b1 b2)
(< (length b1) (length b2))) (length< b1 (length b2)))
(seq-map #'key-description (seq-map #'key-description
(where-is-internal cmd))))) (where-is-internal cmd)))))

View File

@ -205,7 +205,7 @@ binding representation, remove that first letter."
(let* ((bindstr (or bindstr (mu4e-key-description cmd) alt (let* ((bindstr (or bindstr (mu4e-key-description cmd) alt
(mu4e-error "No binding for %s" cmd))) (mu4e-error "No binding for %s" cmd)))
(bindstr (bindstr
(if (and alt (> (length bindstr) 1)) alt bindstr)) (if (and alt (length> bindstr 1)) alt bindstr))
(title ;; remove first letter afrer [] if it equal last of binding (title ;; remove first letter afrer [] if it equal last of binding
(string-replace (string-replace
(concat "[@]" (substring bindstr -1)) "[@]" title)) (concat "[@]" (substring bindstr -1)) "[@]" title))

View File

@ -106,7 +106,7 @@ is the target directory (for \"move\")")
(defun mu4e--mark-initialize () (defun mu4e--mark-initialize ()
"Initialize the marks-subsystem." "Initialize the marks-subsystem."
(set (make-local-variable 'mu4e--mark-map) (make-hash-table)) (setq-local mu4e--mark-map (make-hash-table))
;; ask user when kill buffer / emacs with live marks. ;; ask user when kill buffer / emacs with live marks.
;; (subject to mu4e-headers-leave-behavior) ;; (subject to mu4e-headers-leave-behavior)
(add-hook 'kill-buffer-query-functions (add-hook 'kill-buffer-query-functions

View File

@ -72,6 +72,17 @@ See `mu4e--uniquify-file-name' for an example."
(mm-destroy-parts mu4e--view-gnus-article-mime-handles) (mm-destroy-parts mu4e--view-gnus-article-mime-handles)
(setq mu4e--view-gnus-article-mime-handles nil))) (setq mu4e--view-gnus-article-mime-handles nil)))
;; Temp directories created to hand attachments to external openers; cleaned up
;; when the view buffer is killed.
(defvar-local mu4e--view-temp-dirs nil)
(put 'mu4e--view-temp-dirs 'permanent-local t)
(defun mu4e--view-kill-temp-dirs ()
"Delete temp dirs created for opening MIME-parts externally."
(dolist (dir mu4e--view-temp-dirs)
(ignore-errors (delete-directory dir)))
(setq mu4e--view-temp-dirs nil))
;;; MIME-parts ;;; MIME-parts
(defvar-local mu4e--view-mime-parts nil (defvar-local mu4e--view-mime-parts nil
"Cached MIME parts for this message.") "Cached MIME parts for this message.")
@ -229,7 +240,7 @@ COMPLETIONS is the list of completion strings to affixate."
'face 'mu4e-system-face)) 'face 'mu4e-system-face))
(target (propertize (or (plist-get part :target-dir) "") (target (propertize (or (plist-get part :target-dir) "")
'face 'mu4e-system-face)) 'face 'mu4e-system-face))
(icon (or (and (> (length raw-filename) 0) (icon (or (and (not (string-empty-p raw-filename))
(mu4e-file-name-to-icon raw-filename)) (mu4e-file-name-to-icon raw-filename))
(mu4e-mime-type-to-icon (mu4e-mime-type-to-icon
(plist-get part :mime-type)))) (plist-get part :mime-type))))
@ -430,32 +441,33 @@ Each of the actions is a plist with keys
The filename is deduced from the MIME-part's filename, or The filename is deduced from the MIME-part's filename, or
otherwise random; the result is placed in a temporary directory otherwise random; the result is placed in a temporary directory
with a unique name. Returns the full path for the file created. with a unique name. Returns the full path for the file created.
The directory and file are self-destructed." The directory is registered for cleanup when the current view
buffer is killed (see `mu4e--view-kill-temp-dirs')."
(let* ((tmpdir (make-temp-file "mu4e-temp-" t)) (let* ((tmpdir (make-temp-file "mu4e-temp-" t))
(fname (mm-handle-filename handle)) (fname (mm-handle-filename handle))
(fname (and fname (fname (and fname
(gnus-map-function mm-file-name-rewrite-functions (gnus-map-function mm-file-name-rewrite-functions
(file-name-nondirectory fname)))) (file-name-nondirectory fname))))
(fname (if fname (fname (if fname
(concat tmpdir "/" (replace-regexp-in-string "/" "-" fname)) (mu4e-join-paths
tmpdir (replace-regexp-in-string "/" "-" fname))
(let ((temporary-file-directory tmpdir)) (let ((temporary-file-directory tmpdir))
(make-temp-file "mimepart"))))) (make-temp-file "mimepart")))))
(mm-save-part-to-file handle fname) (mm-save-part-to-file handle fname)
(run-at-time "30 sec" nil (push tmpdir mu4e--view-temp-dirs)
(lambda () (ignore-errors (delete-directory tmpdir t))))
fname)) fname))
(defun mu4e--view-open-file (file &optional force-ask) (defun mu4e--view-open-file (file &optional force-ask)
"Open FILE with default handler, if any. "Open FILE with default handler, if any.
Otherwise, or if FORCE-ASK is set, ask user for the program to Otherwise, or if FORCE-ASK is set, ask user for the shell command
open with." to open with."
(if (and (not force-ask) (if (and (not force-ask)
(functionp mu4e-view-open-program)) (functionp mu4e-view-open-program))
(funcall mu4e-view-open-program file) (funcall mu4e-view-open-program file)
(let ((opener (let ((opener
(or (and (not force-ask) mu4e-view-open-program (or (and (not force-ask) mu4e-view-open-program
(executable-find mu4e-view-open-program)) (executable-find mu4e-view-open-program))
(read-shell-command "Open MIME-part with: ")))) (read-shell-command "Open MIME-part with shell command: "))))
(call-process opener nil 0 nil file)))) (call-process opener nil 0 nil file))))
(defun mu4e-view-mime-part-action (&optional n) (defun mu4e-view-mime-part-action (&optional n)
@ -515,17 +527,16 @@ the third MIME-part."
(cond (cond
((eq receives 'index) ((eq receives 'index)
(shell-command (shell-command
(concat handler " " (shell-quote-argument id)))) (concat handler " " id)))
((eq receives 'pipe) ((eq receives 'pipe)
(progn (progn
(mm-pipe-part handle handler))) (mm-pipe-part handle handler)))
((eq receives 'temp) ((eq receives 'temp)
(shell-command (shell-command
(shell-command (concat
(concat handler " "
handler " " (shell-quote-argument
(shell-quote-argument (mu4e--view-mime-part-to-temp-file handle)))))
(mu4e--view-mime-part-to-temp-file handle))))))
(t (mu4e-error "Invalid action %S" action)))))))) (t (mu4e-error "Invalid action %S" action))))))))
ids))) ids)))

View File

@ -281,7 +281,7 @@ limit the stack size."
(unless (and stack (string= (car stack) query)) (unless (and stack (string= (car stack) query))
(push query stack) (push query stack)
;; limit the stack to `mu4e--search-query-stack-size' elements ;; limit the stack to `mu4e--search-query-stack-size' elements
(when (> (length stack) mu4e--search-query-stack-size) (when (length> stack mu4e--search-query-stack-size)
(setq stack (cl-subseq stack 0 mu4e--search-query-stack-size))) (setq stack (cl-subseq stack 0 mu4e--search-query-stack-size)))
;; remove all duplicates of the new element ;; remove all duplicates of the new element
(seq-remove (lambda (elm) (string= elm (car stack))) (cdr stack)) (seq-remove (lambda (elm) (string= elm (car stack))) (cdr stack))
@ -607,24 +607,30 @@ query before submitting it."
(longest-query (longest-query
(seq-max (seq-map (lambda (c) (length (plist-get (cdr c) :query))) (seq-max (seq-map (lambda (c) (length (plist-get (cdr c) :query)))
candidates))) candidates)))
(affixation-func
(annotation-func (lambda (completions)
(lambda (candidate) (mapcar
(let* ((item (cdr-safe (assoc candidate candidates))) (lambda (candidate)
(name (propertize (or (plist-get item :name) "") (let* ((item (cdr-safe (assoc candidate candidates)))
'face 'mu4e-header-key-face)) (query (propertize (or (plist-get item :query) "")
(query (propertize (or (plist-get item :query) "") 'face 'mu4e-header-value-face))
'face 'mu4e-header-value-face))) (suffix
(concat (concat
" " " "
(make-string (- longest-name (length name)) ?\s) (make-string (- longest-name (length candidate)) ?\s)
query query
(make-string (- longest-query (length query)) ?\s) (make-string (- longest-query (length query)) ?\s)
" " " "
(mu4e--query-item-display-counts item))))) (mu4e--query-item-display-counts item))))
(completion-extra-properties (list candidate "" suffix)))
`(:annotation-function ,annotation-func)) completions)))
(chosen (completing-read "Query: " candidates)) (table (lambda (string pred action)
(if (eq action 'metadata)
`(metadata
(category . mu4e-query)
(affixation-function . ,affixation-func))
(complete-with-action action candidates string pred))))
(chosen (completing-read "Query: " table))
(query (or (plist-get (cdr-safe (assoc chosen candidates)) :query) (query (or (plist-get (cdr-safe (assoc chosen candidates)) :query)
(mu4e-warn "No query for %s" chosen)))) (mu4e-warn "No query for %s" chosen))))
(mu4e-search-bookmark query edit))) (mu4e-search-bookmark query edit)))

View File

@ -651,10 +651,11 @@ activates URLs (in plain-text mode only)."
(unless (mu4e--view-html-displayed-p) (unless (mu4e--view-html-displayed-p)
(mu4e--view-activate-urls)) (mu4e--view-activate-urls))
(kill-local-variable 'bookmark-make-record-function) (kill-local-variable 'bookmark-make-record-function)
(setq mu4e--gnus-article-mime-handles gnus-article-mime-handles (setq mu4e--view-gnus-article-mime-handles gnus-article-mime-handles
gnus-article-decoded-p gnus-article-decode-hook) gnus-article-decoded-p gnus-article-decode-hook)
(set-buffer-modified-p nil) (set-buffer-modified-p nil)
(add-hook 'kill-buffer-hook #'mu4e--view-kill-mime-handles)) (add-hook 'kill-buffer-hook #'mu4e--view-kill-mime-handles)
(add-hook 'kill-buffer-hook #'mu4e--view-kill-temp-dirs))
(epg-error (epg-error
(mu4e-message "EPG error: %s; fall back to raw view" (mu4e-message "EPG error: %s; fall back to raw view"
(error-message-string err)))))) (error-message-string err))))))
@ -789,7 +790,7 @@ filename."
;; (e.g. :user-agent): derive from the keyword name. ;; (e.g. :user-agent): derive from the keyword name.
(capitalize (substring (symbol-name field) 1)))) (capitalize (substring (symbol-name field) 1))))
(help (plist-get info :help))) (help (plist-get info :help)))
(if (and val (> (length val) 0)) (if (and val (not (string-empty-p val)))
(insert (propertize (concat key ":") 'help-echo help) (insert (propertize (concat key ":") 'help-echo help)
" " val "\n")))) " " val "\n"))))
@ -803,7 +804,7 @@ filename."
field info))) field info)))
(val (funcall func msg)) (val (funcall func msg))
(help (plist-get info :help))) (help (plist-get info :help)))
(when (and val (> (length val) 0)) (when (and val (not (string-empty-p val)))
(insert (propertize (concat key ":") 'help-echo help) " " val "\n")))) (insert (propertize (concat key ":") 'help-echo help) " " val "\n"))))
(define-advice gnus-icalendar-event-from-handle (define-advice gnus-icalendar-event-from-handle

View File

@ -103,7 +103,11 @@ A symbol:
For backward compatibility with `mu4e-compose-in-new-frame', t is For backward compatibility with `mu4e-compose-in-new-frame', t is
treated as =\\'frame." treated as =\\'frame."
:type 'symbol :type '(choice (const :tag "New buffer" nil)
(const :tag "New window" window)
(const :tag "New frame" frame)
(const :tag "New frame (compat)" t)
(const :tag "Use display-buffer" display-buffer))
:group 'mu4e-compose) :group 'mu4e-compose)
(declare-function mu4e-view-mode "mu4e-view") (declare-function mu4e-view-mode "mu4e-view")
@ -252,7 +256,7 @@ being created if CREATE is non-nil."
;; Required. The call chain of `mu4e-view-mode' ends up ;; Required. The call chain of `mu4e-view-mode' ends up
;; calling `kill-all-local-variables', which destroys the ;; calling `kill-all-local-variables', which destroys the
;; local binding. ;; local binding.
(set (make-local-variable 'mu4e-linked-headers-buffer) headers-buffer)) (setq-local mu4e-linked-headers-buffer headers-buffer))
buffer))) buffer)))
;; backward compat: `display-buffer-full-frame' only appears in emacs 29. ;; backward compat: `display-buffer-full-frame' only appears in emacs 29.