From 713ae1e98b143cdac0c97f2f5a13eb32dac33623 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 12:42:06 +0200 Subject: [PATCH 01/13] add .dir-locals.el With some basic settings --- .dir-locals.el | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 00000000..14e99441 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,19 @@ +;;; Directory Local Variables -*- no-byte-compile: t; -*- +;;; For more information see (info "(emacs) Directory Variables") +((nil . ((tab-width . 8) + (fill-column . 80) + ;; (commment-fill-column . 80) + (emacs-lisp-docstring-fill-column . 65) + (bug-reference-url-format . "https://github.com/djcb/mu/issues/%s"))) + (c-mode . ((c-file-style . "linux") + (indent-tabs-mode . t) + (mode . bug-reference-prog))) + (c++-mode . ((c-file-style . "linux") + (fill-column . 100) + ;; (comment-fill-column . 80) + (mode . bug-reference-prog))) + (emacs-lisp-mode . ((indent-tabs-mode . nil) + (mode . bug-reference-prog))) + (lisp-data-mode . ((indent-tabs-mode . nil))) + (texinfo-mode . ((mode . bug-reference-prog))) + (org-mode . ((mode . bug-reference)))) From 6278d1f18a19cc4df6a600a3d9c108d2d2be4ffd Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 12:42:48 +0200 Subject: [PATCH 02/13] mu4e-headers: whitespace/newline cleanups --- mu4e/mu4e-headers.el | 226 ++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 100 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index f7150911..d58cdd43 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -32,7 +32,8 @@ (require 'fringe) (require 'hl-line) (require 'mailcap) -(require 'mule-util) ;; seems _some_ people need this for truncate-string-ellipsis +(require 'mule-util) ;; seems _some_ people need this for + ;; truncate-string-ellipsis (require 'mu4e-update) @@ -142,7 +143,8 @@ Example that hides all trashed messages: (member \='trashed (mu4e-message-field msg :flags)))).") (defcustom mu4e-headers-visible-flags - '(draft flagged new passed replied trashed attach encrypted signed list personal) + '(draft flagged new passed replied trashed attach encrypted signed + list personal) "An ordered list of flags to show in the headers buffer. Each element is a symbol in the list. @@ -159,9 +161,9 @@ mostly covered by `new', and the display gets cluttered otherwise." (const :tag "Attach" attach) (const :tag "Encrypted" encrypted) (const :tag "Signed" signed) - (const :tag "List" list) - (const :tag "Personal" personal) - (const :tag "Calendar" calendar)) + (const :tag "List" list) + (const :tag "Personal" personal) + (const :tag "Calendar" calendar)) :group 'mu4e-headers) (defcustom mu4e-headers-found-hook nil @@ -346,7 +348,7 @@ Optionally, show TEXT." (with-current-buffer (mu4e-get-headers-buffer) (mu4e--mark-clear) (erase-buffer) - (when text + (when text (goto-char (point-min)) (insert (propertize text 'face 'mu4e-system-face 'intangible t))))))) @@ -359,7 +361,7 @@ into a string." (mapconcat (lambda (contact) (let ((name (mu4e-contact-name contact)) - (email (mu4e-contact-email contact))) + (email (mu4e-contact-email contact))) (or name email "?"))) contacts ", ")) (defun mu4e~headers-thread-prefix-map (type) @@ -374,7 +376,8 @@ into a string." (connection (funcall get-prefix mu4e-headers-thread-connection-prefix)) (blank (funcall get-prefix mu4e-headers-thread-blank-prefix)) (orphan (funcall get-prefix mu4e-headers-thread-orphan-prefix)) - (single-orphan (funcall get-prefix mu4e-headers-thread-single-orphan-prefix)) + (single-orphan (funcall get-prefix + mu4e-headers-thread-single-orphan-prefix)) (duplicate (funcall get-prefix mu4e-headers-thread-duplicate-prefix)) (t "?")))) @@ -481,7 +484,8 @@ with DOCID which must be present in the headers buffer." (if single-orphan 'single-orphan (if (and orphan (or first-child - (not (eq mu4e-headers-thread-mark-as-orphan 'first)))) + (not (eq mu4e-headers-thread-mark-as-orphan + 'first)))) 'orphan (if last-child 'last-child (if first-child 'first-child @@ -507,14 +511,14 @@ function is for display. (This difference is significant, since internally, the Maildir spec determines what the flags look like, while our display may be different)." (or (mapconcat - (lambda (flag) - (when (member flag mu4e-headers-visible-flags) - (if-let* ((mark (intern-soft - (format "mu4e-headers-%s-mark" (symbol-name flag)))) - (cell (symbol-value mark))) - (if mu4e-use-fancy-chars (cdr cell) (car cell)) - ""))) - flags "") + (lambda (flag) + (when (member flag mu4e-headers-visible-flags) + (if-let* ((mark (intern-soft + (format "mu4e-headers-%s-mark" (symbol-name flag)))) + (cell (symbol-value mark))) + (if mu4e-use-fancy-chars (cdr cell) (car cell)) + ""))) + flags "") "")) ;;; Special headers @@ -526,8 +530,8 @@ addresses, (as per `mu4e-personal-address-p'), show the To address. Otherwise, show the From address, prefixed with the appropriate `mu4e-headers-from-or-to-prefix'." (let* ((from1 (car-safe (mu4e-message-field msg :from))) - (from1-addr (and from1 (mu4e-contact-email from1))) - (is-user (and from1-addr (mu4e-personal-address-p from1-addr)))) + (from1-addr (and from1 (mu4e-contact-email from1))) + (is-user (and from1-addr (mu4e-personal-address-p from1-addr)))) (if is-user (concat (cdr mu4e-headers-from-or-to-prefix) (mu4e~headers-contact-str (mu4e-message-field msg :to))) @@ -591,8 +595,8 @@ found." (truncate-string-to-width val 600))) (:thread-subject ;; if not searching threads, fall back to :subject (if mu4e-search-threads - (mu4e~headers-thread-subject msg) - (mu4e~headers-field-value msg :subject))) + (mu4e~headers-thread-subject msg) + (mu4e~headers-field-value msg :subject))) ((:maildir :path :message-id) val) ((:to :from :cc :bcc) (mu4e~headers-contact-str val)) ;; if we (ie. `user-mail-address' is the 'From', show @@ -650,23 +654,26 @@ space propertized with a `display' text property which expands to (let* ((field (car f-w)) (width (cdr f-w)) (val (mu4e~headers-field-value msg field)) - (val (and val (if width (mu4e~headers-truncate-field field val width) val)))) + (val (and val + (if width + (mu4e~headers-truncate-field field val width) + val)))) val)) (defsubst mu4e~headers-apply-flags (msg fieldval) "Adjust FIELDVAL's face property based on flags in MSG." (let* ((flags (plist-get msg :flags)) - (meta (plist-get msg :meta)) - (face (cond - ((memq 'trashed flags) 'mu4e-trashed-face) + (meta (plist-get msg :meta)) + (face (cond + ((memq 'trashed flags) 'mu4e-trashed-face) ((memq 'draft flags) 'mu4e-draft-face) ((or (memq 'unread flags) (memq 'new flags)) 'mu4e-unread-face) ((memq 'flagged flags) 'mu4e-flagged-face) - ((plist-get meta :related) 'mu4e-related-face) + ((plist-get meta :related) 'mu4e-related-face) ((memq 'replied flags) 'mu4e-replied-face) ((memq 'passed flags) 'mu4e-forwarded-face) - (t 'mu4e-header-face)))) + (t 'mu4e-header-face)))) (add-face-text-property 0 (length fieldval) face t fieldval) fieldval)) @@ -684,7 +691,7 @@ displaying in the header view." (defsubst mu4e~headers-insert-header (msg pos) "Insert a header for MSG at point POS." (when-let ((line (mu4e~message-header-line msg)) - (docid (plist-get msg :docid))) + (docid (plist-get msg :docid))) (goto-char pos) (insert (propertize @@ -716,7 +723,8 @@ docid is not found." (defun mu4e~headers-view-this-message-p (docid) "Is DOCID currently being viewed?" - (mu4e-get-view-buffers (lambda (_) (eq docid (plist-get mu4e~view-message :docid))))) + (mu4e-get-view-buffers + (lambda (_) (eq docid (plist-get mu4e~view-message :docid))))) ;; note: this function is very performance-sensitive (defun mu4e~headers-append-handler (msglst) @@ -725,11 +733,11 @@ Do this at the end of the headers-buffer." (when (buffer-live-p (mu4e-get-headers-buffer)) (with-current-buffer (mu4e-get-headers-buffer) (save-excursion - (let ((inhibit-read-only t)) - (seq-do - (lambda (msg) - (mu4e~headers-insert-header msg (point-max))) - msglst)))))) + (let ((inhibit-read-only t)) + (seq-do + (lambda (msg) + (mu4e~headers-insert-header msg (point-max))) + msglst)))))) (defun mu4e~headers-update-handler (msg is-move maybe-view) @@ -741,7 +749,7 @@ headers." (let* ((docid (mu4e-message-field msg :docid)) (initial-message-at-point (mu4e~headers-docid-at-point)) (initial-column (current-column)) - (inhibit-read-only t) + (inhibit-read-only t) (point (mu4e~headers-docid-pos docid)) (markinfo (gethash docid mu4e--mark-map))) (when point ;; is the message present in this list? @@ -772,8 +780,8 @@ headers." ;; longer matches the query, but this seem a good heuristic. if it ;; was only a flag-change, show the message with its updated flags. (unless is-move - (save-excursion - (mu4e~headers-insert-header msg point))) + (save-excursion + (mu4e~headers-insert-header msg point))) ;; restore the mark, if any. See #2076. (when (and markinfo (mu4e~headers-goto-docid docid)) @@ -848,16 +856,16 @@ true, do *not* update the query history stack." (defun mu4e~headers-benchmark-message (count) "Get some report message for messaging search and rendering speed." (if (and mu4e-headers-report-render-time - mu4e~headers-search-start - mu4e~headers-render-start - (> count 0)) + mu4e~headers-search-start + mu4e~headers-render-start + (> count 0)) (let ((render-time-ms (* 1000(- (float-time) mu4e~headers-render-start))) - (search-time-ms (* 1000(- (float-time) mu4e~headers-search-start)))) - (format (concat - "; search: %0.1f ms (%0.2f ms/msg)" - "; render: %0.1f ms (%0.2f ms/msg)") - search-time-ms (/ search-time-ms count) - render-time-ms (/ render-time-ms count))) + (search-time-ms (* 1000(- (float-time) mu4e~headers-search-start)))) + (format (concat + "; search: %0.1f ms (%0.2f ms/msg)" + "; render: %0.1f ms (%0.2f ms/msg)") + search-time-ms (/ search-time-ms count) + render-time-ms (/ render-time-ms count))) "")) (defun mu4e~headers-found-handler (count) @@ -871,7 +879,7 @@ after the end of the search results." (str (if (zerop count) mu4e~no-matches mu4e~end-of-results)) (msg (format "Found %d matching message%s%s" count (if (= 1 count) "" "s") - (mu4e~headers-benchmark-message count)))) + (mu4e~headers-benchmark-message count)))) (insert (propertize str 'face 'mu4e-system-face 'intangible t)) (unless (zerop count) @@ -882,7 +890,8 @@ after the end of the search results." (when mu4e--search-msgid-target (if (eq (current-buffer) (window-buffer)) (mu4e-headers-goto-message-id mu4e--search-msgid-target) - (let* ((pos (mu4e-headers-goto-message-id mu4e--search-msgid-target))) + (let* ((pos (mu4e-headers-goto-message-id + mu4e--search-msgid-target))) (when pos (set-window-point (get-buffer-window nil t) pos))))) (when (and mu4e--search-view-target (mu4e-message-at-point 'noerror)) @@ -931,10 +940,10 @@ after the end of the search results." (define-key map "j" 'mu4e~headers-jump-to-maildir) (define-key map "O" 'mu4e-headers-change-sorting) - (define-key map "M" 'mu4e-headers-toggle-setting) + (define-key map "M" 'mu4e-headers-toggle-setting) - ;; these are impossible to remember; use mu4e-headers-toggle-setting - ;; instead :) + ;; these are impossible to remember; use mu4e-headers-toggle-setting + ;; instead :) (define-key map "P" 'mu4e-headers-toggle-threading) (define-key map "Q" 'mu4e-headers-toggle-full-search) (define-key map "W" 'mu4e-headers-toggle-include-related) @@ -947,7 +956,7 @@ after the end of the search results." (define-key map "t" 'mu4e-headers-mark-subthread) (define-key map "T" 'mu4e-headers-mark-thread) - (define-key map "," #'mu4e-sexp-at-point) + (define-key map "," #'mu4e-sexp-at-point) ;; navigation between messages (define-key map "p" 'mu4e-headers-prev) @@ -962,7 +971,8 @@ after the end of the search results." (define-key map (kbd "C-+") 'mu4e-headers-split-view-grow) (define-key map (kbd "C--") 'mu4e-headers-split-view-shrink) (define-key map (kbd "") 'mu4e-headers-split-view-grow) - (define-key map (kbd "") 'mu4e-headers-split-view-shrink) + (define-key map (kbd "") + 'mu4e-headers-split-view-shrink) ;; switching to view mode (if it's visible) (define-key map "y" 'mu4e-select-other-view) @@ -986,10 +996,14 @@ after the end of the search results." (define-key map (kbd "=") 'mu4e-headers-mark-for-untrash) (define-key map (kbd "&") 'mu4e-headers-mark-custom) - (define-key map (kbd "*") 'mu4e-headers-mark-for-something) - (define-key map (kbd "") 'mu4e-headers-mark-for-something) - (define-key map (kbd "") 'mu4e-headers-mark-for-something) - (define-key map (kbd "") 'mu4e-headers-mark-for-something) + (define-key map (kbd "*") + 'mu4e-headers-mark-for-something) + (define-key map (kbd "") + 'mu4e-headers-mark-for-something) + (define-key map (kbd "") + 'mu4e-headers-mark-for-something) + (define-key map (kbd "") + 'mu4e-headers-mark-for-something) (define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks) @@ -1104,7 +1118,7 @@ after the end of the search results." (field (car item)) (width (cdr item)) (info (cdr (assoc field (append mu4e-header-info mu4e-header-info-custom)))) - (sortable (plist-get info :sortable)) + (sortable (plist-get info :sortable)) ;; if sortable, it is either t (when field is sortable itself) ;; or a symbol (if another field is used for sorting) (this-field (when sortable (if (booleanp sortable) field sortable))) @@ -1143,8 +1157,8 @@ after the end of the search results." some changes, `mu4e-headers-auto-update' is non-nil and there is no user-interaction ongoing." (when (and mu4e-headers-auto-update ;; must be set - mu4e-index-update-status - (not (zerop (plist-get mu4e-index-update-status :updated))) + mu4e-index-update-status + (not (zerop (plist-get mu4e-index-update-status :updated))) ;; NOTE: `mu4e-mark-marks-num' can return nil. Is that intended? (zerop (or (mu4e-mark-marks-num) 0)) ;; non active marks (not (active-minibuffer-window))) ;; no user input only @@ -1287,21 +1301,23 @@ message plist, or nil if not found." (defvar mu4e~headers-mode-line-label "") (defun mu4e~headers-update-mode-line () "Update mode-line settings." - (let* ((flagstr - (mapconcat - (lambda (flag-cell) - (if (car flag-cell) - (if mu4e-use-fancy-chars - (cddr flag-cell) (cadr flag-cell) ) "")) - `((,mu4e-search-full . ,mu4e-headers-full-label) - (,mu4e-headers-include-related . ,mu4e-headers-related-label) - (,mu4e-search-threads . ,mu4e-headers-threaded-label) - (,mu4e-headers-skip-duplicates . ,mu4e-headers-skip-duplicates-label)) - "")) - (name "mu4e-headers")) + (let* ((flagstr + (mapconcat + (lambda (flag-cell) + (if (car flag-cell) + (if mu4e-use-fancy-chars + (cddr flag-cell) (cadr flag-cell) ) "")) + `((,mu4e-search-full . ,mu4e-headers-full-label) + (,mu4e-headers-include-related . ,mu4e-headers-related-label) + (,mu4e-search-threads . ,mu4e-headers-threaded-label) + (,mu4e-headers-skip-duplicates + . ,mu4e-headers-skip-duplicates-label)) + "")) + (name "mu4e-headers")) (setq mode-name name) - (setq mu4e~headers-mode-line-label (concat flagstr " " mu4e--search-last-query)) + (setq mu4e~headers-mode-line-label + (concat flagstr " " mu4e--search-last-query)) (make-local-variable 'global-mode-string) @@ -1405,9 +1421,10 @@ matching messages with that mark." (if (member field '(:to :from :cc :bcc :reply-to)) (cl-find-if (lambda (contact) (let ((name (mu4e-contact-name contact)) - (email (mu4e-contact-email contact))) + (email (mu4e-contact-email contact))) (or (and name (string-match pattern name)) - (and email (string-match pattern email))))) value) + (and email (string-match pattern email))))) + value) (string-match pattern (or value "")))))))) (defun mu4e-headers-mark-custom () @@ -1501,7 +1518,8 @@ user)." (or field (mu4e-read-option "Sortfield: " mu4e~headers-sort-field-choices))) ;; note: 'sortable' is either a boolean (meaning: if non-nil, this is - ;; sortable field), _or_ another field (meaning: sort by this other field). + ;; sortable field), _or_ another field (meaning: sort by this other + ;; field). (sortable (plist-get (cdr (assoc field mu4e-header-info)) :sortable)) ;; error check (sortable @@ -1533,21 +1551,22 @@ When prefix-argument DONT-REFRESH is non-nill, do not refresh the last search with the new setting." (interactive "P") (let* ((toggles '(("fFull-search" . mu4e-search-full) - ("rInclude-related" . mu4e-headers-include-related) - ("tShow threads" . mu4e-search-threads) - ("uSkip duplicates" . mu4e-headers-skip-duplicates))) - (toggles (seq-map - (lambda (cell) - (cons - (concat (car cell) (format" (%s)" - (if (symbol-value (cdr cell)) "on" "off"))) - (cdr cell))) toggles)) - (choice (mu4e-read-option "Toggle setting " toggles))) + ("rInclude-related" . mu4e-headers-include-related) + ("tShow threads" . mu4e-search-threads) + ("uSkip duplicates" . mu4e-headers-skip-duplicates))) + (toggles (seq-map + (lambda (cell) + (cons + (concat (car cell) + (format" (%s)" + (if (symbol-value (cdr cell)) "on" "off"))) + (cdr cell))) toggles)) + (choice (mu4e-read-option "Toggle setting " toggles))) (when choice (set choice (not (symbol-value choice))) (mu4e-message "Set `%s' to %s" (symbol-name choice) (symbol-value choice)) (unless dont-refresh - (mu4e-search-rerun))))) + (mu4e-search-rerun))))) (defun mu4e~headers-toggle (name togglevar dont-refresh) @@ -1595,9 +1614,9 @@ _not_ refresh the last search with the new setting for threading." (unless (eq major-mode 'mu4e-headers-mode) (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode)) (let* ((msg (mu4e-message-at-point)) - (path (mu4e-message-field msg :path)) - (_exists (or (file-readable-p path) - (mu4e-warn "No message at %s" path))) + (path (mu4e-message-field msg :path)) + (_exists (or (file-readable-p path) + (mu4e-warn "No message at %s" path))) (docid (or (mu4e-message-field msg :docid) (mu4e-warn "No message at point"))) (mark-as-read @@ -1622,12 +1641,12 @@ return nil." (condition-case _err (prog1 (let (line-move-visual) - (and (line-move arg) 0)) + (and (line-move arg) 0)) ;; Skip invisible text at BOL possibly hidden by ;; the end of another invisible overlay covering ;; previous EOL. (move-to-column 2)) - ((beginning-of-buffer end-of-buffer) + ((beginning-of-buffer end-of-buffer) 1)))) (let* ((succeeded (zerop (goto-next-line lines))) (docid (mu4e~headers-docid-at-point))) @@ -1637,7 +1656,8 @@ return nil." ;; update all windows showing the headers buffer (walk-windows (lambda (win) - (when (eq (window-buffer win) (mu4e-get-headers-buffer (buffer-name))) + (when (eq (window-buffer win) + (mu4e-get-headers-buffer (buffer-name))) (set-window-point win (point)))) nil t) ;; If the assigned (and buffer-local) `mu4e~headers-view-win' @@ -1649,8 +1669,8 @@ return nil." ;; attempt to highlight the new line, display the message (mu4e~headers-highlight docid) (if succeeded - docid - nil))))) + docid + nil))))) (defun mu4e-headers-next (&optional n) "Move point to the next message header. @@ -1701,7 +1721,8 @@ given, offer to edit the search query before executing it." (list maildir current-prefix-arg))) (when maildir (let* ((query (format "maildir:\"%s\"" maildir)) - (query (if edit (mu4e-search-read-query "Refine query: " query) query))) + (query (if edit + (mu4e-search-read-query "Refine query: " query) query))) (mu4e-mark-handle-when-leaving) (mu4e-search query)))) @@ -1741,7 +1762,8 @@ pass ACTIONFUNC, which is a function that takes a msg-plist argument." (interactive) (let ((msg (mu4e-message-at-point)) - (afunc (or actionfunc (mu4e-read-option "Action: " mu4e-headers-actions)))) + (afunc (or actionfunc + (mu4e-read-option "Action: " mu4e-headers-actions)))) (funcall afunc msg))) (defun mu4e-headers-mark-and-next (mark) @@ -1779,14 +1801,18 @@ other windows." (when mu4e-dim-when-loading (setq mu4e--loading-overlay-bg (let ((overlay (make-overlay (point-min) (point-max)))) - (overlay-put overlay 'face `(:foreground "gray22" :background - ,(face-attribute 'default :background))) + (overlay-put overlay 'face + `(:foreground "gray22" :background + ,(face-attribute 'default + :background))) (overlay-put overlay 'priority 9998) overlay))) (setq mu4e--loading-overlay-text (let ((overlay (make-overlay (point-min) (point-min)))) (overlay-put overlay 'priority 9999) - (overlay-put overlay 'before-string (propertize "Loading…\n" 'face 'mu4e-header-title-face)) + (overlay-put overlay 'before-string + (propertize "Loading…\n" + 'face 'mu4e-header-title-face)) overlay))) (when mu4e--loading-overlay-bg (delete-overlay mu4e--loading-overlay-bg)) From ff9fc73e5afe65858fc73e8c481d365f1dd008c3 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 13:31:13 +0200 Subject: [PATCH 03/13] mu4e-view: whitespace/newline fixes --- mu4e/mu4e-view.el | 582 +++++++++++++++++++++++----------------------- 1 file changed, 292 insertions(+), 290 deletions(-) diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index cbcdc675..3f0d5565 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -60,7 +60,7 @@ Otherwise, don't move to the next message." (defcustom mu4e-view-fields '(:from :to :cc :subject :flags :date :maildir :mailing-list :tags - :attachments :signature :decryption) + :attachments :signature :decryption) "Header fields to display in the message view buffer. For the complete list of available headers, see `mu4e-header-info'. @@ -75,11 +75,11 @@ details." (defcustom mu4e-view-actions (seq-filter 'identity - `( ("capture message" . mu4e-action-capture-message) - ("view in browser" . mu4e-action-view-in-browser) - ,(when (fboundp 'xwidget-webkit-browse-url) - '("xview in xwidget" . mu4e-action-view-in-xwidget)) - ("show this thread" . mu4e-action-show-thread))) + `( ("capture message" . mu4e-action-capture-message) + ("view in browser" . mu4e-action-view-in-browser) + ,(when (fboundp 'xwidget-webkit-browse-url) + '("xview in xwidget" . mu4e-action-view-in-xwidget)) + ("show this thread" . mu4e-action-show-thread))) "List of actions to perform on messages in view mode. The actions are cons-cells of the form: (NAME . FUNC) @@ -136,13 +136,13 @@ other windows." "Display the raw contents of message at point in a new buffer." (interactive) (let ((path (mu4e-message-readable-path)) - (buf (get-buffer-create mu4e~view-raw-buffer-name))) + (buf (get-buffer-create mu4e~view-raw-buffer-name))) (with-current-buffer buf (let ((inhibit-read-only t)) - (erase-buffer) + (erase-buffer) (mu4e-raw-view-mode) (insert-file-contents path) - (goto-char (point-min)))) + (goto-char (point-min)))) (mu4e-display-buffer buf t))) (defun mu4e-view-pipe (cmd) @@ -163,16 +163,17 @@ Then, display the results." ;; it have linked headers buffer? ((mu4e-current-buffer-type-p 'view) (when (mu4e--view-detached-p (current-buffer)) - (mu4e-error "You cannot navigate in a detached view buffer.")) + (mu4e-error + "Cannot navigate in a detached view buffer.")) (mu4e-get-headers-buffer)) ;; fallback; but what would trigger this? (t (mu4e-get-headers-buffer)))) - (docid (mu4e-message-field msg :docid))) + (docid (mu4e-message-field msg :docid))) (unless docid - (mu4e-error "Message without docid: action is not possible")) + (mu4e-error "Message without docid: action is not possible")) (with-current-buffer buffer - (mu4e-display-buffer buffer) - (if (or (mu4e~headers-goto-docid docid) + (mu4e-display-buffer buffer) + (if (or (mu4e~headers-goto-docid docid) ;; TODO: Is this the best way to find another ;; relevant docid for a view buffer? ;; @@ -185,8 +186,8 @@ Then, display the results." (mu4e~headers-goto-docid (with-current-buffer buffer (mu4e-message-field (mu4e-message-at-point) :docid)))) - ,@body - (mu4e-error "Cannot find message in headers buffer")))))) + ,@body + (mu4e-error "Cannot find message in headers buffer")))))) (defun mu4e-view-headers-next (&optional n) "Move point to the next message header. @@ -238,7 +239,7 @@ bymessage-at-point. The actions are specified in `mu4e-view-actions'." (interactive) (let* ((msg (or msg (mu4e-message-at-point))) - (actionfunc (mu4e-read-option "Action: " mu4e-view-actions))) + (actionfunc (mu4e-read-option "Action: " mu4e-view-actions))) (funcall actionfunc msg))) (defun mu4e-view-mark-pattern () @@ -289,25 +290,25 @@ Add this function to `mu4e-view-mode-hook' to enable this feature." (save-excursion (goto-char (point-min)) (while (re-search-forward - (concat "^" message-mark-insert-begin) nil t) - (setq ov-beg (match-beginning 0) - ov-end (match-end 0) - ov-inv (make-overlay ov-beg ov-end) - beg ov-end) - (overlay-put ov-inv 'invisible t) - (overlay-put ov-inv 'mu4e-overlay t) - (when (re-search-forward - (concat "^" message-mark-insert-end) nil t) - (setq ov-beg (match-beginning 0) - ov-end (match-end 0) - ov-inv (make-overlay ov-beg ov-end) - end ov-beg) - (overlay-put ov-inv 'invisible t)) - (when (and beg end) - (let ((ov (make-overlay beg end))) - (overlay-put ov 'mu4e-overlay t) - (overlay-put ov 'face 'mu4e-region-code)) - (setq beg nil end nil)))))) + (concat "^" message-mark-insert-begin) nil t) + (setq ov-beg (match-beginning 0) + ov-end (match-end 0) + ov-inv (make-overlay ov-beg ov-end) + beg ov-end) + (overlay-put ov-inv 'invisible t) + (overlay-put ov-inv 'mu4e-overlay t) + (when (re-search-forward + (concat "^" message-mark-insert-end) nil t) + (setq ov-beg (match-beginning 0) + ov-end (match-end 0) + ov-inv (make-overlay ov-beg ov-end) + end ov-beg) + (overlay-put ov-inv 'invisible t)) + (when (and beg end) + (let ((ov (make-overlay beg end))) + (overlay-put ov 'mu4e-overlay t) + (overlay-put ov 'face 'mu4e-region-code)) + (setq beg nil end nil)))))) ;;; View Utilities @@ -391,12 +392,12 @@ list." (defmacro mu4e~view-defun-mark-for (mark) "Define a function mu4e-view-mark-for- MARK." (let ((funcname (intern (format "mu4e-view-mark-for-%s" mark))) - (docstring (format "Mark the current message for %s." mark))) + (docstring (format "Mark the current message for %s." mark))) `(progn (defun ,funcname () ,docstring - (interactive) - (mu4e~view-in-headers-context - (mu4e-headers-mark-and-next ',mark))) + (interactive) + (mu4e~view-in-headers-context + (mu4e-headers-mark-and-next ',mark))) (put ',funcname 'definition-name ',mark)))) (mu4e~view-defun-mark-for move) @@ -446,8 +447,8 @@ If the url is mailto link, start writing an email to that address." (let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url)))) (when url (if (string-match-p "^mailto:" url) - (browse-url-mail url) - (browse-url url))))) + (browse-url-mail url) + (browse-url url))))) (defun mu4e~view-get-property-from-event (prop) @@ -455,13 +456,13 @@ If the url is mailto link, start writing an email to that address." The action is chosen based on the `last-command-event'. Meant to be evoked from interactive commands." (if (and (eventp last-command-event) - (mouse-event-p last-command-event)) + (mouse-event-p last-command-event)) (let ((posn (event-end last-command-event))) - (when (numberp (posn-point posn)) - (get-text-property - (posn-point posn) - prop - (window-buffer (posn-window posn))))) + (when (numberp (posn-point posn)) + (get-text-property + (posn-point posn) + prop + (window-buffer (posn-window posn))))) (get-text-property (point) prop))) ;; this is fairly simplistic... @@ -471,27 +472,27 @@ Also number them so they can be opened using `mu4e-view-go-to-url'." (let ((num 0)) (save-excursion (setq mu4e~view-link-map ;; buffer local - (make-hash-table :size 32 :weakness nil)) + (make-hash-table :size 32 :weakness nil)) (goto-char (point-min)) (while (re-search-forward mu4e~view-beginning-of-url-regexp nil t) - (let ((bounds (thing-at-point-bounds-of-url-at-point))) - (when bounds - (let* ((url (thing-at-point-url-at-point)) - (ov (make-overlay (car bounds) (cdr bounds)))) - (puthash (cl-incf num) url mu4e~view-link-map) - (add-text-properties - (car bounds) - (cdr bounds) - `(face mu4e-link-face - mouse-face highlight - mu4e-url ,url - keymap ,mu4e-view-active-urls-keymap - help-echo - "[mouse-1] or [M-RET] to open the link")) - (overlay-put ov 'mu4e-overlay t) - (overlay-put ov 'after-string - (propertize (format "\u200B[%d]" num) - 'face 'mu4e-url-number-face))))))))) + (let ((bounds (thing-at-point-bounds-of-url-at-point))) + (when bounds + (let* ((url (thing-at-point-url-at-point)) + (ov (make-overlay (car bounds) (cdr bounds)))) + (puthash (cl-incf num) url mu4e~view-link-map) + (add-text-properties + (car bounds) + (cdr bounds) + `(face mu4e-link-face + mouse-face highlight + mu4e-url ,url + keymap ,mu4e-view-active-urls-keymap + help-echo + "[mouse-1] or [M-RET] to open the link")) + (overlay-put ov 'mu4e-overlay t) + (overlay-put ov 'after-string + (propertize (format "\u200B[%d]" num) + 'face 'mu4e-url-number-face))))))))) (defun mu4e~view-get-urls-num (prompt &optional multi) @@ -504,21 +505,21 @@ string." (let* ((count (hash-table-count mu4e~view-link-map)) (def)) (when (zerop count) (mu4e-error "No links for this message")) (if (not multi) - (if (= count 1) - (read-number (mu4e-format "%s: " prompt) 1) - (read-number (mu4e-format "%s (1-%d): " prompt count))) + (if (= count 1) + (read-number (mu4e-format "%s: " prompt) 1) + (read-number (mu4e-format "%s (1-%d): " prompt count))) (progn - (setq def (if (= count 1) "1" (format "1-%d" count))) - (read-string (mu4e-format "%s (default %s): " prompt def) - nil nil def))))) + (setq def (if (= count 1) "1" (format "1-%d" count))) + (read-string (mu4e-format "%s (default %s): " prompt def) + nil nil def))))) (defun mu4e-view-go-to-url (&optional multi) "Offer to go visit one or more URLs. If MULTI (prefix-argument) is non-nil, offer to go to a range of URLs." (interactive "P") (mu4e~view-handle-urls "URL to visit" - multi - (lambda (url) (mu4e~view-browse-url-from-binding url)))) + multi + (lambda (url) (mu4e~view-browse-url-from-binding url)))) (defun mu4e-view-save-url (&optional multi) "Offer to save URLs to the kill ring. @@ -526,9 +527,9 @@ If MULTI (prefix-argument) is nil, save a single one, otherwise, offer to save a range of URLs." (interactive "P") (mu4e~view-handle-urls "URL to save" multi - (lambda (url) - (kill-new url) - (mu4e-message "Saved %s to the kill-ring" url)))) + (lambda (url) + (kill-new url) + (mu4e-message "Saved %s to the kill-ring" url)))) (defun mu4e-view-fetch-url (&optional multi) "Offer to fetch (download) URLs. @@ -540,7 +541,7 @@ URLs. The urls are fetched to `mu4e-attachment-dir'." "URL to fetch" multi (lambda (url) (let ((target (concat (mu4e~get-attachment-dir url) "/" - (file-name-nondirectory url)))) + (file-name-nondirectory url)))) (url-copy-file url target) (mu4e-message "Fetched %s -> %s" url target))))) @@ -556,7 +557,7 @@ it to a range of uris. PROMPT is the query to present to the user." "Apply URLFUNC to some URL with NUM in the current message. Prompting the user with PROMPT for the number." (let* ((num (or num (mu4e~view-get-urls-num prompt))) - (url (gethash num mu4e~view-link-map))) + (url (gethash num mu4e~view-link-map))) (unless url (mu4e-warn "Invalid number for URL")) (funcall urlfunc url))) @@ -572,9 +573,9 @@ of urls. You can type multiple values separated by space, e.g. 1 Furthermore, there is a shortcut \"a\" which means all urls, but as this is the default, you may not need it." (let* ((linkstr (mu4e~view-get-urls-num - "URL number range (or 'a' for 'all')" t)) - (count (hash-table-count mu4e~view-link-map)) - (linknums (mu4e-split-ranges-to-numbers linkstr count))) + "URL number range (or 'a' for 'all')" t)) + (count (hash-table-count mu4e~view-link-map)) + (linknums (mu4e-split-ranges-to-numbers linkstr count))) (dolist (num linknums) (mu4e~view-handle-single-url prompt urlfunc num)))) @@ -645,7 +646,8 @@ As a side-effect, a message that is being viewed loses its ;; ;; Otherwise, `mu4e-display-buffer' may adjust the view buffer's ;; window height based on a buffer that has no text in it yet! - (setq-local mu4e~headers-view-win (mu4e-display-buffer gnus-article-buffer nil)) + (setq-local mu4e~headers-view-win + (mu4e-display-buffer gnus-article-buffer nil)) (unless (window-live-p mu4e~headers-view-win) (mu4e-error "Cannot get a message view")) (select-window mu4e~headers-view-win))) @@ -673,17 +675,17 @@ determine which browser function to use." (mu4e-message-readable-path msg) nil nil nil t) (run-hooks 'gnus-article-decode-hook) (let ((header (unless skip-headers - (cl-loop for field in '("from" "to" "cc" "date" "subject") - when (message-fetch-field field) - concat (format "%s: %s\n" (capitalize field) it)))) - (parts (mm-dissect-buffer t t))) + (cl-loop for field in '("from" "to" "cc" "date" "subject") + when (message-fetch-field field) + concat (format "%s: %s\n" (capitalize field) it)))) + (parts (mm-dissect-buffer t t))) ;; If singlepart, enforce a list. (when (and (bufferp (car parts)) - (stringp (car (mm-handle-type parts)))) - (setq parts (list parts))) + (stringp (car (mm-handle-type parts)))) + (setq parts (list parts))) ;; Process the list (unless (gnus-article-browse-html-parts parts header) - (mu4e-warn "Message does not contain a \"text/html\" part")) + (mu4e-warn "Message does not contain a \"text/html\" part")) (mm-destroy-parts parts)))) (defun mu4e-action-view-in-xwidget (msg) @@ -700,38 +702,38 @@ determine which browser function to use." "Render current buffer with MSG using Gnus' article mode." (setq gnus-summary-buffer (get-buffer-create " *appease-gnus*")) (let* ((inhibit-read-only t) - (max-specpdl-size mu4e-view-max-specpdl-size) - (mm-decrypt-option 'known) - (ct (mail-fetch-field "Content-Type")) - (ct (and ct (mail-header-parse-content-type ct))) - (charset (mail-content-type-get ct 'charset)) - (charset (and charset (intern charset))) - (mu4e~view-rendering t); Needed if e.g. an ics file is buttonized - (gnus-article-emulate-mime t) - (gnus-unbuttonized-mime-types '(".*/.*")) - (gnus-buttonized-mime-types - (append (list "multipart/signed" "multipart/encrypted") - gnus-buttonized-mime-types)) - (gnus-newsgroup-charset - (if (and charset (coding-system-p charset)) charset - (detect-coding-region (point-min) (point-max) t))) - ;; Possibly add headers (before "Attachments") - (gnus-display-mime-function (mu4e~view-gnus-display-mime msg)) - (gnus-icalendar-additional-identities - (mu4e-personal-addresses 'no-regexp))) + (max-specpdl-size mu4e-view-max-specpdl-size) + (mm-decrypt-option 'known) + (ct (mail-fetch-field "Content-Type")) + (ct (and ct (mail-header-parse-content-type ct))) + (charset (mail-content-type-get ct 'charset)) + (charset (and charset (intern charset))) + (mu4e~view-rendering t); Needed if e.g. an ics file is buttonized + (gnus-article-emulate-mime t) + (gnus-unbuttonized-mime-types '(".*/.*")) + (gnus-buttonized-mime-types + (append (list "multipart/signed" "multipart/encrypted") + gnus-buttonized-mime-types)) + (gnus-newsgroup-charset + (if (and charset (coding-system-p charset)) charset + (detect-coding-region (point-min) (point-max) t))) + ;; Possibly add headers (before "Attachments") + (gnus-display-mime-function (mu4e~view-gnus-display-mime msg)) + (gnus-icalendar-additional-identities + (mu4e-personal-addresses 'no-regexp))) (condition-case err - (progn - (mm-enable-multibyte) - (run-hooks 'gnus-article-decode-hook) - (gnus-article-prepare-display) - (mu4e~view-activate-urls) - (setq mu4e~gnus-article-mime-handles gnus-article-mime-handles - gnus-article-decoded-p gnus-article-decode-hook) - (set-buffer-modified-p nil) - (add-hook 'kill-buffer-hook #'mu4e~view-kill-mime-handles)) + (progn + (mm-enable-multibyte) + (run-hooks 'gnus-article-decode-hook) + (gnus-article-prepare-display) + (mu4e~view-activate-urls) + (setq mu4e~gnus-article-mime-handles gnus-article-mime-handles + gnus-article-decoded-p gnus-article-decode-hook) + (set-buffer-modified-p nil) + (add-hook 'kill-buffer-hook #'mu4e~view-kill-mime-handles)) (epg-error (mu4e-warn "EPG error: %s; fall back to raw view" - (error-message-string err)))))) + (error-message-string err)))))) (defun mu4e~view-kill-mime-handles () "Kill cached MIME-handles, if any." @@ -750,7 +752,7 @@ determine which browser function to use." "Toggle whether to show all MIME-parts." (interactive) (setq gnus-inhibit-mime-unbuttonizing - (not gnus-inhibit-mime-unbuttonizing)) + (not gnus-inhibit-mime-unbuttonizing)) (mu4e-view-refresh)) (defun mu4e-view-toggle-fill-flowed() @@ -765,54 +767,54 @@ determine which browser function to use." (gnus-display-mime ihandles) (unless ihandles (save-restriction - (article-goto-body) - (forward-line -1) - (narrow-to-region (point) (point)) - (dolist (field mu4e-view-fields) - (let ((fieldval (mu4e-message-field msg field))) - (pcase field - ((or ':path ':maildir :list ':user-agent ':message-id) - (mu4e~view-gnus-insert-header field fieldval)) - (':mailing-list - (let ((list (plist-get msg :list))) - (if list (mu4e-get-mailing-list-shortname list) ""))) - ((or ':flags ':tags) - (let ((flags (mapconcat (lambda (flag) - (if (symbolp flag) - (symbol-name flag) - flag)) fieldval ", "))) - (mu4e~view-gnus-insert-header field flags))) - (':size (mu4e~view-gnus-insert-header - field (mu4e-display-size fieldval))) - ((or ':subject ':to ':from ':cc ':bcc ':from-or-to - ':date :attachments ':signature - ':decryption)) ; handled by Gnus - (_ - (mu4e~view-gnus-insert-header-custom msg field))))) - (let ((gnus-treatment-function-alist - '((gnus-treat-highlight-headers - gnus-article-highlight-headers)))) - (gnus-treat-article 'head)))))) + (article-goto-body) + (forward-line -1) + (narrow-to-region (point) (point)) + (dolist (field mu4e-view-fields) + (let ((fieldval (mu4e-message-field msg field))) + (pcase field + ((or ':path ':maildir :list ':user-agent ':message-id) + (mu4e~view-gnus-insert-header field fieldval)) + (':mailing-list + (let ((list (plist-get msg :list))) + (if list (mu4e-get-mailing-list-shortname list) ""))) + ((or ':flags ':tags) + (let ((flags (mapconcat (lambda (flag) + (if (symbolp flag) + (symbol-name flag) + flag)) fieldval ", "))) + (mu4e~view-gnus-insert-header field flags))) + (':size (mu4e~view-gnus-insert-header + field (mu4e-display-size fieldval))) + ((or ':subject ':to ':from ':cc ':bcc ':from-or-to + ':date :attachments ':signature + ':decryption)) ; handled by Gnus + (_ + (mu4e~view-gnus-insert-header-custom msg field))))) + (let ((gnus-treatment-function-alist + '((gnus-treat-highlight-headers + gnus-article-highlight-headers)))) + (gnus-treat-article 'head)))))) (defun mu4e~view-gnus-insert-header (field val) "Insert a header FIELD with value VAL." (let* ((info (cdr (assoc field mu4e-header-info))) - (key (plist-get info :name)) - (help (plist-get info :help))) + (key (plist-get info :name)) + (help (plist-get info :help))) (if (and val (> (length val) 0)) - (insert (propertize (concat key ":") 'help-echo help) - " " val "\n")))) + (insert (propertize (concat key ":") 'help-echo help) + " " val "\n")))) (defun mu4e~view-gnus-insert-header-custom (msg field) "Insert MSG's custom FIELD." (let* ((info (cdr-safe (or (assoc field mu4e-header-info-custom) - (mu4e-error "Custom field %S not found" field)))) - (key (plist-get info :name)) - (func (or (plist-get info :function) - (mu4e-error "No :function defined for custom field %S %S" - field info))) - (val (funcall func msg)) - (help (plist-get info :help))) + (mu4e-error "Custom field %S not found" field)))) + (key (plist-get info :name)) + (func (or (plist-get info :function) + (mu4e-error "No :function defined for custom field %S %S" + field info))) + (val (funcall func msg)) + (help (plist-get info :help))) (when (and val (> (length val) 0)) (insert (propertize (concat key ":") 'help-echo help) " " val "\n")))) @@ -821,14 +823,14 @@ determine which browser function to use." "Avoid error when displaying an ical attachment without a charset." (if (and (boundp 'mu4e~view-rendering) mu4e~view-rendering) (let* ((handle (car handle-attendee)) - (attendee (cadr handle-attendee)) - (buf (mm-handle-buffer handle)) - (ty (mm-handle-type handle)) - (rest (cddr handle))) - ;; Put the fallback at the end: - (setq ty (append ty '((charset . "utf-8")))) - (setq handle (cons buf (cons ty rest))) - (list handle attendee)) + (attendee (cadr handle-attendee)) + (buf (mm-handle-buffer handle)) + (ty (mm-handle-type handle)) + (rest (cddr handle))) + ;; Put the fallback at the end: + (setq ty (append ty '((charset . "utf-8")))) + (setq handle (cons buf (cons ty rest))) + (list handle attendee)) handle-attendee)) (defun mu4e~view-mode-p () @@ -961,26 +963,26 @@ This is useful for advising some Gnus-functionality that does not work in mu4e." (define-key map [menu-bar headers] (cons "Mu4e" menumap)) (define-key menumap [quit-buffer] - '("Quit view" . mu4e~view-quit-buffer)) + '("Quit view" . mu4e~view-quit-buffer)) (define-key menumap [display-help] '("Help" . mu4e-display-manual)) (define-key menumap [sepa0] '("--")) (define-key menumap [wrap-lines] - '("Toggle wrap lines" . visual-line-mode)) + '("Toggle wrap lines" . visual-line-mode)) (define-key menumap [raw-view] - '("View raw message" . mu4e-view-raw-message)) + '("View raw message" . mu4e-view-raw-message)) (define-key menumap [pipe] - '("Pipe through shell" . mu4e-view-pipe)) + '("Pipe through shell" . mu4e-view-pipe)) (define-key menumap [sepa1] '("--")) (define-key menumap [mark-delete] - '("Mark for deletion" . mu4e-view-mark-for-delete)) + '("Mark for deletion" . mu4e-view-mark-for-delete)) (define-key menumap [mark-untrash] - '("Mark for untrash" . mu4e-view-mark-for-untrash)) + '("Mark for untrash" . mu4e-view-mark-for-untrash)) (define-key menumap [mark-trash] - '("Mark for trash" . mu4e-view-mark-for-trash)) + '("Mark for trash" . mu4e-view-mark-for-trash)) (define-key menumap [mark-move] - '("Mark for move" . mu4e-view-mark-for-move)) + '("Mark for move" . mu4e-view-mark-for-move)) (define-key menumap [sepa2] '("--")) (define-key menumap [resend] '("Resend" . mu4e-compose-resend)) @@ -990,17 +992,17 @@ This is useful for advising some Gnus-functionality that does not work in mu4e." (define-key menumap [sepa3] '("--")) (define-key menumap [query-next] - '("Next query" . mu4e-headers-query-next)) + '("Next query" . mu4e-headers-query-next)) (define-key menumap [query-prev] - '("Previous query" . mu4e-headers-query-prev)) + '("Previous query" . mu4e-headers-query-prev)) (define-key menumap [narrow-search] - '("Narrow search" . mu4e-headers-search-narrow)) + '("Narrow search" . mu4e-headers-search-narrow)) (define-key menumap [bookmark] - '("Search bookmark" . mu4e-headers-search-bookmark)) + '("Search bookmark" . mu4e-headers-search-bookmark)) (define-key menumap [jump] - '("Jump to maildir" . mu4e~headers-jump-to-maildir)) + '("Jump to maildir" . mu4e~headers-jump-to-maildir)) (define-key menumap [search] - '("Search" . mu4e-headers-search)) + '("Search" . mu4e-headers-search)) (define-key menumap [sepa4] '("--")) (define-key menumap [next] '("Next" . mu4e-view-headers-next)) @@ -1048,9 +1050,9 @@ Based on Gnus' article-mode." ;; advice gnus-block-private-groups to always return "." ;; so that by default we block images. (advice-add 'gnus-block-private-groups :around - (lambda(func &rest args) - (if (mu4e~view-mode-p) - "." (apply func args)))) + (lambda(func &rest args) + (if (mu4e~view-mode-p) + "." (apply func args)))) (use-local-map mu4e-view-mode-map) (mu4e-context-minor-mode) (mu4e-search-minor-mode) @@ -1094,12 +1096,12 @@ The alist uniquely maps the number to the gnus-part." (save-excursion (goto-char (point-min)) (while (not (eobp)) - (let ((part (get-text-property (point) 'gnus-data)) - (index (get-text-property (point) 'gnus-part))) - (when (and part (numberp index) (not (assoc index parts)) - (push `(,index . ,part) parts))) - (goto-char (or (next-single-property-change (point) 'gnus-part) - (point-max)))))) + (let ((part (get-text-property (point) 'gnus-data)) + (index (get-text-property (point) 'gnus-part))) + (when (and part (numberp index) (not (assoc index parts)) + (push `(,index . ,part) parts))) + (goto-char (or (next-single-property-change (point) 'gnus-part) + (point-max)))))) parts)) @@ -1118,47 +1120,47 @@ Note, currently this does not work well with file names containing commas." (interactive "P") (cl-assert (and (eq major-mode 'mu4e-view-mode) - (derived-mode-p 'gnus-article-mode))) + (derived-mode-p 'gnus-article-mode))) (let* ((parts (mu4e~view-gather-mime-parts)) - (handles '()) - (files '()) - (compfn (if (and (boundp 'helm-mode) helm-mode) - #'completing-read - ;; Fallback to `completing-read-multiple' with poor - ;; completion - #'completing-read-multiple)) - dir) + (handles '()) + (files '()) + (compfn (if (and (boundp 'helm-mode) helm-mode) + #'completing-read + ;; Fallback to `completing-read-multiple' with poor + ;; completion + #'completing-read-multiple)) + dir) (dolist (part parts) (let ((fname (or (cdr (assoc 'filename (assoc "attachment" (cdr part)))) (cl-loop for item in part for name = (and (listp item) - (assoc-default 'name item)) + (assoc-default 'name item)) thereis (and (stringp name) name))))) - (when fname - (push `(,fname . ,(cdr part)) handles) - (push fname files)))) + (when fname + (push `(,fname . ,(cdr part)) handles) + (push fname files)))) (if files - (progn - (setq files (let ((helm-comp-read-use-marked t)) - (funcall compfn "Save part(s): " files)) - dir (if arg (read-directory-name "Save to directory: ") - mu4e-attachment-dir)) - (cl-loop for (f . h) in handles - when (member f files) - do (mm-save-part-to-file - h (let ((file (expand-file-name f dir))) - (if (file-exists-p file) - (let (newname (count 1)) - (while (and - (setq newname - (concat - (file-name-sans-extension file) - (format "(%s)" count) - (file-name-extension file t))) - (file-exists-p newname)) - (cl-incf count)) - newname) - file))))) + (progn + (setq files (let ((helm-comp-read-use-marked t)) + (funcall compfn "Save part(s): " files)) + dir (if arg (read-directory-name "Save to directory: ") + mu4e-attachment-dir)) + (cl-loop for (f . h) in handles + when (member f files) + do (mm-save-part-to-file + h (let ((file (expand-file-name f dir))) + (if (file-exists-p file) + (let (newname (count 1)) + (while (and + (setq newname + (concat + (file-name-sans-extension file) + (format "(%s)" count) + (file-name-extension file t))) + (file-exists-p newname)) + (cl-incf count)) + newname) + file))))) (mu4e-message "No attached files found")))) @@ -1176,7 +1178,7 @@ containing commas." (:name "open" :handler mu4e~view-open-file :receives temp) ;; open with some custom file. (:name "wopen-with" :handler (lambda (file)(mu4e~view-open-file file t)) - :receives temp) + :receives temp) ;; ;; some more examples @@ -1191,13 +1193,13 @@ containing commas." (:name "emacs" :handler find-file-read-only :receives temp) ;; open in this emacs instance, "raw" (:name "raw" :handler (lambda (str) - (let ((tmpbuf - (get-buffer-create " *mu4e-raw-mime*"))) - (with-current-buffer tmpbuf - (insert str) - (view-mode) - (goto-char (point-min))) - (display-buffer tmpbuf))) :receives pipe)) + (let ((tmpbuf + (get-buffer-create " *mu4e-raw-mime*"))) + (with-current-buffer tmpbuf + (insert str) + (view-mode) + (goto-char (point-min))) + (display-buffer tmpbuf))) :receives pipe)) "Specifies actions for MIME-parts. @@ -1205,16 +1207,16 @@ Each of the actions is a plist with keys `(:name ;; name of the action; shortcut is first letter of name :handler ;; one of: - ;; - a function receiving the index/temp/pipe - ;; - a string, which is taken as a shell command + ;; - a function receiving the index/temp/pipe + ;; - a string, which is taken as a shell command :receives ;; a symbol specifying what the handler receives - ;; - index: the index number of the mime part (default) - ;; - temp: the full path to the mime part in a - ;; temporary file, which is deleted immediately - ;; after invoking handler - ;; - pipe: the attachment is piped to some shell command - ;; or as a string parameter to a function + ;; - index: the index number of the mime part (default) + ;; - temp: the full path to the mime part in a + ;; temporary file, which is deleted immediately + ;; after invoking handler + ;; - pipe: the attachment is piped to some shell command + ;; or as a string parameter to a function ).") @@ -1225,17 +1227,17 @@ otherwise random; the result is placed in a temporary directory with a unique name. Returns the full path for the file created. The directory and file are self-destructed." (let* ((tmpdir (make-temp-file "mu4e-temp-" t)) - (fname (mm-handle-filename handle)) - (fname (and fname - (gnus-map-function mm-file-name-rewrite-functions - (file-name-nondirectory fname)))) - (fname (if fname - (concat tmpdir "/" (replace-regexp-in-string "/" "-" fname)) - (let ((temporary-file-directory tmpdir)) - (make-temp-file "mimepart"))))) + (fname (mm-handle-filename handle)) + (fname (and fname + (gnus-map-function mm-file-name-rewrite-functions + (file-name-nondirectory fname)))) + (fname (if fname + (concat tmpdir "/" (replace-regexp-in-string "/" "-" fname)) + (let ((temporary-file-directory tmpdir)) + (make-temp-file "mimepart"))))) (mm-save-part-to-file handle fname) (run-at-time "30 sec" nil - (lambda () (ignore-errors (delete-directory tmpdir t)))) + (lambda () (ignore-errors (delete-directory tmpdir t)))) fname)) @@ -1247,9 +1249,9 @@ open with." (functionp mu4e-view-open-program)) (funcall mu4e-view-open-program file) (let ((opener - (or (and (not force-ask) mu4e-view-open-program - (executable-find mu4e-view-open-program)) - (read-shell-command "Open MIME-part with: ")))) + (or (and (not force-ask) mu4e-view-open-program + (executable-find mu4e-view-open-program)) + (read-shell-command "Open MIME-part with: ")))) (call-process opener nil 0 nil file)))) (defun mu4e-view-mime-part-action (&optional n) @@ -1258,43 +1260,43 @@ If N is not specified, ask for it. For instance, '3 A o' opens the third MIME-part." (interactive "NNumber of MIME-part: ") (let* ((parts (mu4e~view-gather-mime-parts)) - (options - (mapcar (lambda (action) `(,(plist-get action :name) . ,action)) - mu4e-view-mime-part-actions)) - (handle - (or (cdr-safe (seq-find (lambda (part) (eq (car part) n)) parts)) - (mu4e-error "MIME-part %s not found" n))) - (action - (or (and options (mu4e-read-option "Action on MIME-part: " options)) - (mu4e-error "No such action"))) - (handler - (or (plist-get action :handler) - (mu4e-error "No :handler item found for action %S" action))) - (receives - (or (plist-get action :receives) - (mu4e-error "No :receives item found for action %S" action)))) + (options + (mapcar (lambda (action) `(,(plist-get action :name) . ,action)) + mu4e-view-mime-part-actions)) + (handle + (or (cdr-safe (seq-find (lambda (part) (eq (car part) n)) parts)) + (mu4e-error "MIME-part %s not found" n))) + (action + (or (and options (mu4e-read-option "Action on MIME-part: " options)) + (mu4e-error "No such action"))) + (handler + (or (plist-get action :handler) + (mu4e-error "No :handler item found for action %S" action))) + (receives + (or (plist-get action :receives) + (mu4e-error "No :receives item found for action %S" action)))) (save-excursion (cond ((functionp handler) - (cond - ((eq receives 'index) (funcall handler n)) - ((eq receives 'pipe) (funcall handler (mm-with-unibyte-buffer - (mm-insert-part handle) - (buffer-string)))) - ((eq receives 'temp) - (funcall handler (mu4e~view-mime-part-to-temp-file handle))) - (t (mu4e-error "Invalid :receive for %S" action)))) + (cond + ((eq receives 'index) (funcall handler n)) + ((eq receives 'pipe) (funcall handler (mm-with-unibyte-buffer + (mm-insert-part handle) + (buffer-string)))) + ((eq receives 'temp) + (funcall handler (mu4e~view-mime-part-to-temp-file handle))) + (t (mu4e-error "Invalid :receive for %S" action)))) ((stringp handler) - (cond - ((eq receives 'index) - (shell-command (concat handler " " (shell-quote-argument n)))) - ((eq receives 'pipe) (mm-pipe-part handle handler)) - ((eq receives 'temp) - (shell-command - (shell-command (concat handler " " - (shell-quote-argument - (mu4e~view-mime-part-to-temp-file handle)))))) - (t (mu4e-error "Invalid action %S" action)))))))) + (cond + ((eq receives 'index) + (shell-command (concat handler " " (shell-quote-argument n)))) + ((eq receives 'pipe) (mm-pipe-part handle handler)) + ((eq receives 'temp) + (shell-command + (shell-command (concat handler " " + (shell-quote-argument + (mu4e~view-mime-part-to-temp-file handle)))))) + (t (mu4e-error "Invalid action %S" action)))))))) (defun mu4e-view-toggle-html () "Toggle html-display of the first html-part found." @@ -1303,9 +1305,9 @@ the third MIME-part." ;; pertinence, i.e. the first HTML part found in it is the most important one. (save-excursion (if-let ((html-part - (seq-find (lambda (handle) - (equal (mm-handle-media-type (cdr handle)) "text/html")) - gnus-article-mime-handle-alist))) + (seq-find (lambda (handle) + (equal (mm-handle-media-type (cdr handle)) "text/html")) + gnus-article-mime-handle-alist))) (gnus-article-inline-part (car html-part)) (mu4e-warn "No html part in this message")))) @@ -1347,7 +1349,7 @@ value against HEADER-REGEXP in header-values)))) (add-hook 'bug-reference-auto-setup-functions - #'mu4e--view-try-setup-bug-reference-mode) + #'mu4e--view-try-setup-bug-reference-mode) (provide 'mu4e-view) From 85f60647b92485a8cc0bb5bc01532e3dcdaf3572 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 13:53:15 +0200 Subject: [PATCH 04/13] mu4e-view: bind 'P' to mu4e-headers-toggle-property And remove the obsolete older mu4e-headers-toggle-*. --- mu4e/mu4e-view.el | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index 3f0d5565..d735a04e 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -889,9 +889,7 @@ This is useful for advising some Gnus-functionality that does not work in mu4e." ;; toggle header settings (define-key map "O" #'mu4e-headers-change-sorting) - (define-key map "P" #'mu4e-headers-toggle-threading) - (define-key map "Q" #'mu4e-headers-toggle-full-search) - (define-key map "W" #'mu4e-headers-toggle-include-related) + (define-key map "P" #'mu4e-headers-toggle-property) ;; change the number of headers (define-key map (kbd "C-+") #'mu4e-headers-split-view-grow) From f0e973f8c5f3c77883f89fc3f2e13488ef288a84 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 13:55:38 +0200 Subject: [PATCH 05/13] mu4e-obsolete: whitespace fixes --- mu4e/mu4e-obsolete.el | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mu4e/mu4e-obsolete.el b/mu4e/mu4e-obsolete.el index 2fa82afe..af20b291 100644 --- a/mu4e/mu4e-obsolete.el +++ b/mu4e/mu4e-obsolete.el @@ -60,9 +60,9 @@ (make-obsolete-variable 'mu4e-html2text-command "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-view-prefer-html "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-view-html-plaintext-ratio-heuristic - "No longer in use" "1.7.0") + "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-message-body-rewrite-functions - "No longer in use" "1.7.0") + "No longer in use" "1.7.0") ;;; Html2Text (make-obsolete 'mu4e-shr2text "No longer in use" "1.7.0") @@ -70,32 +70,32 @@ ;; old message view (make-obsolete-variable 'mu4e-view-show-addresses - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-wrap-lines nil "0.9.9-dev7") (make-obsolete-variable 'mu4e-view-hide-cited nil "0.9.9-dev7") (make-obsolete-variable 'mu4e-view-date-format - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-image-max-width - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-image-max-height - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-save-multiple-attachments-without-asking - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-attachment-assoc - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-attachment-actions - "See mu4e-view-mime-part-actions" "1.7.0") + "See mu4e-view-mime-part-actions" "1.7.0") (make-obsolete-variable 'mu4e-view-header-field-keymap - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-header-field-keymap - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-contacts-header-keymap - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-view-attachments-header-keymap - "Unused with the new message view" "1.7.0") + "Unused with the new message view" "1.7.0") (make-obsolete-variable 'mu4e-imagemagick-identify nil "1.7.0") (make-obsolete-variable 'mu4e-view-show-images - "No longer used" "1.7.0") + "No longer used" "1.7.0") (make-obsolete-variable 'mu4e-view-gnus "Old view is gone" "1.7.0") (make-obsolete-variable 'mu4e-view-use-gnus "Gnus view is the default" "1.5.10") From 2dca07612cbc516202e3343019010c9c8756d512 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 13:56:12 +0200 Subject: [PATCH 06/13] mu4e-obsolete: make mu4e-headers-toggle-* obsolete ... for the new mu4e-headers-toggle-property --- mu4e/mu4e-obsolete.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mu4e/mu4e-obsolete.el b/mu4e/mu4e-obsolete.el index af20b291..97e15f1e 100644 --- a/mu4e/mu4e-obsolete.el +++ b/mu4e/mu4e-obsolete.el @@ -150,6 +150,16 @@ (make-obsolete-variable 'mu4e-headers-field-properties-function "not used" "1.6.1") +(define-obsolete-function-alias 'mu4e-headers-toggle-setting + 'mu4e-headers-toggle-property "1.9.5") +(define-obsolete-function-alias 'mu4e-headers-toggle-threading + 'mu4e-headers-toggle-property "1.9.5") +(define-obsolete-function-alias 'mu4e-headers-toggle-full-search + 'mu4e-headers-toggle-settin "1.9.5") +(define-obsolete-function-alias 'mu4e-headers-toggle-include-related + 'mu4e-headers-toggle-property "1.9.5") +(define-obsolete-function-alias 'mu4e-headers-toggle-skip-duplicates + 'mu4e-headers-toggle-property "1.9.5") ;; mu4e-main From b8861cdbfe2a082c4d2cbeb9701e41867af1df3f Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 13:59:26 +0200 Subject: [PATCH 07/13] mu4e-helpers: fix whitespace Follow .dir-locals. --- mu4e/mu4e-helpers.el | 64 +++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/mu4e/mu4e-helpers.el b/mu4e/mu4e-helpers.el index ef17d188..f746ca84 100644 --- a/mu4e/mu4e-helpers.el +++ b/mu4e/mu4e-helpers.el @@ -161,14 +161,16 @@ Does a local-exit and does not return." "Get PROP from plist LST and raise an error if not present." (or (plist-get lst prop) (if (plist-member lst prop) - nil - (mu4e-error "Missing property %s in %s" prop lst)))) + nil + (mu4e-error "Missing property %s in %s" prop lst)))) -(defun mu4e--read-char-choice (prompt choices) +(defun mu4e--read-char-choice (prompt choices &optional key) "Read and return one of CHOICES, prompting for PROMPT. Any input that is not one of CHOICES is ignored. This is mu4e's version of `read-char-choice' which becomes case-insentive after -trying an exact match." +trying an exact match. + +If optional KEY is provided, use that instead of asking user." (let ((choice) (chosen) (inhibit-quit nil)) (while (not chosen) (message nil);; this seems needed... @@ -369,18 +371,18 @@ http://cr.yp.to/proto/maildir.html." (seq-mapcat (lambda (flag) (pcase flag - (`draft "D") - (`flagged "F") - (`new "N") - (`passed "P") - (`replied "R") - (`seen "S") - (`trashed "T") - (`attach "a") - (`encrypted "x") - (`signed "s") - (`unread "u") - (_ ""))) + (`draft "D") + (`flagged "F") + (`new "N") + (`passed "P") + (`replied "R") + (`seen "S") + (`trashed "T") + (`attach "a") + (`encrypted "x") + (`signed "s") + (`unread "u") + (_ ""))) (seq-uniq flags) 'string))) (defun mu4e-string-to-flags (str) @@ -394,14 +396,14 @@ http://cr.yp.to/proto/maildir.html." (seq-mapcat (lambda (kar) (list - (pcase kar - ('?D 'draft) - ('?F 'flagged) - ('?P 'passed) - ('?R 'replied) - ('?S 'seen) - ('?T 'trashed) - (_ nil)))) + (pcase kar + ('?D 'draft) + ('?F 'flagged) + ('?P 'passed) + ('?R 'replied) + ('?S 'seen) + ('?T 'trashed) + (_ nil)))) str)))) @@ -470,7 +472,7 @@ The file will self-destruct in a short while, enough to open it in an external program." (let ((tmpfile (make-temp-file "mu4e-" nil (concat "." ext)))) (run-at-time "30 sec" nil - (lambda () (ignore-errors (delete-file tmpfile)))) + (lambda () (ignore-errors (delete-file tmpfile)))) tmpfile)) (defsubst mu4e-is-mode-or-derived-p (mode) @@ -492,12 +494,12 @@ Or go to the top level if there is none." (defun mu4e--make-bookmark-record () "Create a bookmark for the message at point." (let* ((msg (mu4e-message-at-point)) - (subject (or (plist-get msg :subject) "No subject")) - (date (plist-get msg :date)) - (date (if date (format-time-string "%F: " date) "")) - (title (format "%s%s" date subject)) - (msgid (or (plist-get msg :message-id) - (mu4e-error "Cannot bookmark message without message-id")))) + (subject (or (plist-get msg :subject) "No subject")) + (date (plist-get msg :date)) + (date (if date (format-time-string "%F: " date) "")) + (title (format "%s%s" date subject)) + (msgid (or (plist-get msg :message-id) + (mu4e-error "Cannot bookmark message without message-id")))) `(,title ,@(bookmark-make-record-default 'no-file 'no-context) (message-id . ,msgid) From e453f20ade4ed6d978eb4d867499e44d22330d93 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 14:00:19 +0200 Subject: [PATCH 08/13] mu4e-helpers: allow passing key to mu4e-read-option Can be useful for non-interactive use. --- mu4e/mu4e-helpers.el | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mu4e/mu4e-helpers.el b/mu4e/mu4e-helpers.el index f746ca84..bcf32ca1 100644 --- a/mu4e/mu4e-helpers.el +++ b/mu4e/mu4e-helpers.el @@ -174,14 +174,14 @@ If optional KEY is provided, use that instead of asking user." (let ((choice) (chosen) (inhibit-quit nil)) (while (not chosen) (message nil);; this seems needed... - (setq choice (read-char-exclusive prompt)) + (setq choice (or key (read-char-exclusive prompt))) (if (eq choice 27) (keyboard-quit)) ;; quit if ESC is pressed (setq chosen (or (member choice choices) (member (downcase choice) choices) (member (upcase choice) choices)))) (car chosen))) -(defun mu4e-read-option (prompt options) +(defun mu4e-read-option (prompt options &optional key) "Ask user for an option from a list on the input area. PROMPT describes a multiple-choice question to the user. OPTIONS describe the options, and is a list of cells describing @@ -202,6 +202,9 @@ user can then choose by typing CHAR. Example: User now will be presented with a list: \"Choose an animal: [M]onkey, [G]nu, [x]Moose\". +If optional character KEY is provied, use that instead of asking +the user. + Function returns the cdr of the list element." (let* ((prompt (mu4e-format "%s" prompt)) (optionsstr @@ -223,7 +226,8 @@ Function returns the cdr of the list element." " [" (propertize "C-g" 'face 'mu4e-highlight-face) " to cancel]") ;; the allowable chars - (seq-map (lambda(elm) (string-to-char (car elm))) options))) + (seq-map (lambda(elm) (string-to-char (car elm))) options) + key)) (chosen (seq-find (lambda (option) (eq response (string-to-char (car option)))) From 5a3d7e9c5684d30baf98aaeeacb7e373499fd836 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 14:01:52 +0200 Subject: [PATCH 09/13] mu4e-headers: add mu4e-headers-toggle-property, remove obsolete And add the 'P' one for mu4e-headers-toggle-property --- mu4e/mu4e-headers.el | 77 ++++++-------------------------------------- 1 file changed, 10 insertions(+), 67 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index d58cdd43..fe0545d6 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -940,14 +940,7 @@ after the end of the search results." (define-key map "j" 'mu4e~headers-jump-to-maildir) (define-key map "O" 'mu4e-headers-change-sorting) - (define-key map "M" 'mu4e-headers-toggle-setting) - - ;; these are impossible to remember; use mu4e-headers-toggle-setting - ;; instead :) - (define-key map "P" 'mu4e-headers-toggle-threading) - (define-key map "Q" 'mu4e-headers-toggle-full-search) - (define-key map "W" 'mu4e-headers-toggle-include-related) - (define-key map "V" 'mu4e-headers-toggle-skip-duplicates) + (define-key map "P" 'mu4e-headers-toggle-property) (define-key map "q" 'mu4e~headers-quit-buffer) (define-key map "g" 'mu4e-search-rerun) ;; for compatibility @@ -1037,18 +1030,6 @@ after the end of the search results." (define-key menumap [sepa0] '("--")) - (define-key menumap [toggle-include-related] - '(menu-item "Toggle related messages" - mu4e-headers-toggle-include-related - :button (:toggle . - (and (boundp 'mu4e-headers-include-related) - mu4e-headers-include-related)))) - (define-key menumap [toggle-threading] - '(menu-item "Toggle threading" mu4e-headers-toggle-threading - :button (:toggle . - (and (boundp 'mu4e-search-threads) - mu4e-search-threads)))) - (define-key menumap "|" '("Pipe through shell" . mu4e-view-pipe)) (define-key menumap [sepa1] '("--")) @@ -1311,7 +1292,8 @@ message plist, or nil if not found." (,mu4e-headers-include-related . ,mu4e-headers-related-label) (,mu4e-search-threads . ,mu4e-headers-threaded-label) (,mu4e-headers-skip-duplicates - . ,mu4e-headers-skip-duplicates-label)) + . ,mu4e-headers-skip-duplicates-label) + (,mu4e-headers-hide-enabled . ,mu4e-headers-hide-label)) "")) (name "mu4e-headers")) @@ -1544,16 +1526,17 @@ user)." (symbol-name mu4e-headers-sort-direction)) (mu4e-search-rerun))) - -(defun mu4e-headers-toggle-setting (&optional dont-refresh) +(defun mu4e-headers-toggle-property (&optional dont-refresh key) "Toggle some aspect of headers display. -When prefix-argument DONT-REFRESH is non-nill, do not refresh the -last search with the new setting." +When prefix-argument DONT-REFRESH is non-nil, do not refresh the +last search with the new setting. +If KEY is provided, use it instead of asking user." (interactive "P") (let* ((toggles '(("fFull-search" . mu4e-search-full) ("rInclude-related" . mu4e-headers-include-related) ("tShow threads" . mu4e-search-threads) - ("uSkip duplicates" . mu4e-headers-skip-duplicates))) + ("uSkip duplicates" . mu4e-headers-skip-duplicates) + ("pHide-predicate" . mu4e-headers-hide-enabled))) (toggles (seq-map (lambda (cell) (cons @@ -1561,53 +1544,13 @@ last search with the new setting." (format" (%s)" (if (symbol-value (cdr cell)) "on" "off"))) (cdr cell))) toggles)) - (choice (mu4e-read-option "Toggle setting " toggles))) + (choice (mu4e-read-option "Toggle property " toggles key))) (when choice (set choice (not (symbol-value choice))) (mu4e-message "Set `%s' to %s" (symbol-name choice) (symbol-value choice)) (unless dont-refresh (mu4e-search-rerun))))) - -(defun mu4e~headers-toggle (name togglevar dont-refresh) - "Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil, -re-run the last search." - (set togglevar (not (symbol-value togglevar))) - (mu4e-message "%s turned %s%s" - name - (if (symbol-value togglevar) "on" "off") - (if dont-refresh - " (press 'g' to refresh)" "")) - (unless dont-refresh - (mu4e-search-rerun))) - -(defun mu4e-headers-toggle-threading (&optional dont-refresh) - "Toggle `mu4e-search-threads'. With prefix-argument, do -_not_ refresh the last search with the new setting for threading." - (interactive "P") - (mu4e~headers-toggle "Threading" 'mu4e-search-threads dont-refresh)) - -(defun mu4e-headers-toggle-full-search (&optional dont-refresh) - "Toggle `mu4e-search-full'. With prefix-argument, do -_not_ refresh the last search with the new setting for threading." - (interactive "P") - (mu4e~headers-toggle "Full-search" - 'mu4e-search-full dont-refresh)) - -(defun mu4e-headers-toggle-include-related (&optional dont-refresh) - "Toggle `mu4e-headers-include-related'. With prefix-argument, do -_not_ refresh the last search with the new setting for threading." - (interactive "P") - (mu4e~headers-toggle "Include-related" - 'mu4e-headers-include-related dont-refresh)) - -(defun mu4e-headers-toggle-skip-duplicates (&optional dont-refresh) - "Toggle `mu4e-headers-skip-duplicates'. With prefix-argument, do -_not_ refresh the last search with the new setting for threading." - (interactive "P") - (mu4e~headers-toggle "Skip-duplicates" - 'mu4e-headers-skip-duplicates dont-refresh)) - (defun mu4e-headers-view-message () "View message at point." (interactive) From f4923c6f2a3378de3a284ddea66dcbd05ac02b2b Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 14:05:34 +0200 Subject: [PATCH 10/13] mu4e-headers: make hide-predicate togglable Like the other search properties. --- mu4e/mu4e-headers.el | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index fe0545d6..21dad885 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -130,11 +130,10 @@ next mail after marking a message in header view." (defvar mu4e-headers-hide-predicate nil - "Predicate function to hide matching heasders. -If the function evaluates to non-nil when applied a a message -plist, do not show the corresponding header. The function takes -one parameter MSG, which is the message plist for the message to -be hidden or not. + "Predicate function to hide matching headers. +Either nil or a function taking one message plist parameter and +which which return non-nil for messages that should be hidden from +the search results. Also see `mu4e-headers-hide-enabled'. Example that hides all trashed messages: @@ -142,6 +141,11 @@ Example that hides all trashed messages: (lambda (msg) (member \='trashed (mu4e-message-field msg :flags)))).") +(defvar mu4e-headers-hide-enabled t + "Whether `mu4e-headers-hide-predicate' should be active. +This can be used to toggle use of the predicate through + `mu4e-headers-toggle-property'.") + (defcustom mu4e-headers-visible-flags '(draft flagged new passed replied trashed attach encrypted signed list personal) @@ -271,6 +275,9 @@ Must have the same length as `mu4e-headers-thread-connection-prefix'.") "Non-fancy and fancy labels to indicate related search in the mode-line.") (defvar mu4e-headers-skip-duplicates-label '("U" . "Ⓤ") ;; 'U' for 'unique' "Non-fancy and fancy labels for include-related search in the mode-line.") +(defvar mu4e-headers-hide-label '("H" . "Ⓗ") + "Non-fancy and fancy labels to indicate header-hiding is active in +the mode-line.") ;;;; Various @@ -678,9 +685,10 @@ space propertized with a `display' text property which expands to fieldval)) (defsubst mu4e~message-header-line (msg) - "Return a propertized description of MSG suitable for + "Return a propertized description of message MSG suitable for displaying in the header view." - (unless (and mu4e-headers-hide-predicate + ;; should we hide it? + (unless (and mu4e-headers-hide-enabled mu4e-headers-hide-predicate (funcall mu4e-headers-hide-predicate msg)) (mu4e~headers-apply-flags msg From f46fe4ee69e6177153320897542cb88d9121c7b7 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 14:32:16 +0200 Subject: [PATCH 11/13] mu4e-headers: show number of hidden messages in footer --- mu4e/mu4e-headers.el | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index 21dad885..cb3e2d1a 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -342,6 +342,9 @@ In the format needed for `mu4e-read-option'.") "If non-nil, report on the time it took to render the messages. This is mostly useful for profiling.") +(defvar mu4e~headers-hidden 0 + "Number of headers hidden due to `mu4e-headers-hide-predicate'.") + ;;; Clear @@ -350,7 +353,8 @@ This is mostly useful for profiling.") "Clear the headers buffer and related data structures. Optionally, show TEXT." (when (buffer-live-p (mu4e-get-headers-buffer)) - (setq mu4e~headers-render-start (float-time)) + (setq mu4e~headers-render-start (float-time) + mu4e~headers-hidden 0) (let ((inhibit-read-only t)) (with-current-buffer (mu4e-get-headers-buffer) (mu4e--mark-clear) @@ -687,14 +691,16 @@ space propertized with a `display' text property which expands to (defsubst mu4e~message-header-line (msg) "Return a propertized description of message MSG suitable for displaying in the header view." - ;; should we hide it? - (unless (and mu4e-headers-hide-enabled mu4e-headers-hide-predicate - (funcall mu4e-headers-hide-predicate msg)) - (mu4e~headers-apply-flags - msg - (mapconcat (lambda (f-w) (mu4e~headers-field-handler f-w msg)) - mu4e-headers-fields " ")))) - + (if (and mu4e-headers-hide-enabled mu4e-headers-hide-predicate + (funcall mu4e-headers-hide-predicate msg)) + (progn + (cl-incf mu4e~headers-hidden) + nil) + (progn + (mu4e~headers-apply-flags + msg + (mapconcat (lambda (f-w) (mu4e~headers-field-handler f-w msg)) + mu4e-headers-fields " "))))) (defsubst mu4e~headers-insert-header (msg pos) "Insert a header for MSG at point POS." @@ -885,8 +891,9 @@ after the end of the search results." (goto-char (point-max)) (let ((inhibit-read-only t) (str (if (zerop count) mu4e~no-matches mu4e~end-of-results)) - (msg (format "Found %d matching message%s%s" + (msg (format "Found %d matching message%s; %d hidden%s" count (if (= 1 count) "" "s") + mu4e~headers-hidden (mu4e~headers-benchmark-message count)))) (insert (propertize str 'face 'mu4e-system-face 'intangible t)) From 87fb2787beff55f7ccf7c58b16331784d869d62e Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 14:06:23 +0200 Subject: [PATCH 12/13] NEWS.org: update --- NEWS.org | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/NEWS.org b/NEWS.org index 87e58118..3df2cba0 100644 --- a/NEWS.org +++ b/NEWS.org @@ -15,13 +15,31 @@ there's now 'z' binding (~mu4e-view-detach~), to 'detach' view and alllow for keeping multiple views around; see the Mu4e manual for details. + - One very visible (experimental) change is that the main-window is no + longer full-screen + - when moving messages (which includes changing flags), file-flags changes are propagated to duplicates of the messages; that is, e.g. the /Seen/ or /Replied/ status is propagated to all duplicates (earlier, this was only done when marking a message as read). Note, /Draft/, /Flagged/ and /Trashed/ flags are deliberately *not* propagated. - - teach ~mu4e-copy-thing-at-point~ about shr links + - teach ~mu4e-copy-thing-at-point~ about ~shr~ links + + - The ~mu4e-headers-toggle-setting~ has been renamed + ~mu4e-headers-toggle-property~ and has the new default binding ~P~, which + works in both the headers-view and message-view. The older functions + ~mu4e-headers-toggle-threading~, ~mu4e-headers-toggle-threading~, + ~mu4e-headers-toggle-full-search~ ~mu4e-headers-toggle-include-related~, + ~full-search~skip-duplicates~ have been removed (with their keybindings) in + favor of ~mu4e-headers-toggle-property~. + + - There's also a new property ~mu4e-headers-hide-enabled~, which controls + wheter ~mu4e-headers-hide-predicate~ is applied (when non-~nil~). This can be + used to temporarily turn the predicate off/on. + + - When searching, the number of hidden messages is now shown in the + message footer along with the number of Found messages - all the obsolete function and variable aliases have been moved to ~mu4e-obsolete.el~ so we can unclutter the non-obsolete code a bit. From a4698db84e5c9dca26dbdec8eca16f3e74519381 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 11 Dec 2022 15:25:11 +0200 Subject: [PATCH 13/13] mu4e.texi: update documentation Use the new toggles. --- mu4e/mu4e.texi | 95 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index 390d99a8..a7875f11 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -706,46 +706,46 @@ The main view looks something like the following: Basics - * [j]ump to some maildir - * enter a [s]earch query - * [C]ompose a new message + * [j]ump to some maildir + * enter a [s]earch query + * [C]ompose a new message Bookmarks - * [bu] Unread messages (13085/13085) - * [bt] Today's messages - * [bw] Last 7 days (53/128) - * [bp] Messages with images (75/2441) + * [bu] Unread messages (13085/13085) + * [bt] Today's messages + * [bw] Last 7 days (53/128) + * [bp] Messages with images (75/2441) Maildirs - * [ja] /archive (2101/18837) - * [ji] /inbox (1/2) - * [jb] /bulk (33/35) - * [jB] /bulkarchive (179/2090) - * [jm] /mu (694/17687) - * [jn] /sauron - * [js] /sent + * [ja] /archive (2101/18837) + * [ji] /inbox (1/2) + * [jb] /bulk (33/35) + * [jB] /bulkarchive (179/2090) + * [jm] /mu (694/17687) + * [jn] /sauron + * [js] /sent Misc - * [;]Switch context - * [U]pdate email & database - * toggle [m]ail sending mode (currently direct) - * [f]lush 1 queued mail + * [;]Switch context + * [U]pdate email & database + * toggle [m]ail sending mode (currently direct) + * [f]lush 1 queued mail - * [N]ews - * [A]bout mu4e - * [H]elp - * [q]uit + * [N]ews + * [A]bout mu4e + * [H]elp + * [q]uit Info - * last updated : Sat May 7 20:37:37 2022 - * database-path : /home/pam/.cache/mu/xapian - * maildir : /home/pam/Maildir - * in store : 86179 messages - * personal addresses : /.*example.com/, pam@fo + * last updated : Sat May 7 20:37:37 2022 + * database-path : /home/pam/.cache/mu/xapian + * maildir : /home/pam/Maildir + * in store : 86179 messages + * personal addresses : /.*example.com/, pam@fo @end verbatim @end cartouche @@ -950,10 +950,7 @@ M-left,\ previous query M-right next query O change sort order -P toggle threading -Q toggle full-search -V toggle skip-duplicates -W toggle include-related +P toggle search property marking ------- @@ -1037,14 +1034,14 @@ The header field used for sorting is indicated by ``@t{V}'' or variable @code{mu4e-use-fancy-chars}}, indicating the sort order (descending or ascending, respectively). -You can change the sort order by clicking the corresponding column with -the mouse, or with @kbd{M-x mu4e-headers-change-sorting} (@key{O}); note -that not all fields can be used for sorting. You can toggle threading -on/off using @kbd{M-x mu4e-headers-toggle-threading} or @key{P}. For -both of these functions, unless you provide a prefix argument -(@key{C-u}), the current search is updated immediately using the new -parameters. You can toggle full-search (@ref{Searching}) using @kbd{M-x -mu4e-headers-toggle-full-search} or @key{Q}. +You can change the sort order by clicking the corresponding column with the +mouse, or with @kbd{M-x mu4e-headers-change-sorting} (@key{O}); note that not +all fields can be used for sorting. You can toggle threading on/off through +@kbd{M-x mu4e-headers-toggle-property} or @key{Pt}. For both of these functions, +unless you provide a prefix argument (@key{C-u}), the current search is updated +immediately using the new parameters. You can toggle full-search +(@ref{Searching}) through @kbd{M-x mu4e-headers-toggle-property} as well; or +@key{Pf}. Note that with threading enabled, the sorting is exclusively by date, regardless of the column clicked. @@ -1274,6 +1271,9 @@ b search bookmark B edit bookmark before search j jump to maildir +O change sort order +P toggle search property + M-left previous query M-right next query @@ -1728,12 +1728,11 @@ order for this to work properly you need to pass your address to executing a query for messages that happen to have the property of being in a certain folder (maildir). -Normally, queries return up to @code{mu4e-headers-results-limit} (default: -500) results. That is usually more than enough, and makes things significantly +Normally, queries return up to @code{mu4e-headers-results-limit} (default: 500) +results. That is usually more than enough, and makes things significantly faster. Sometimes, however, you may want to show @emph{all} results; you can -enable this with @kbd{M-x mu4e-headers-toggle-full-search}, or by customizing -the variable @code{mu4e-headers-full-search}. This applies to all search -commands. +enable this with @kbd{M-x mu4e-headers-toggle-property}, or by customizing the +variable @code{mu4e-headers-full-search}. This applies to all search commands. You can also influence the sort order and whether threads are shown or not; see @ref{Sorting and threading}. @@ -3798,10 +3797,12 @@ completion. Set @code{mu4e-cache-maildir-list} to @code{t} (make sure to read its docstring). -@subsection How can I hide messages from the search results? -See the variable @code{mu4e-headers-hide-predicate}. +@subsection How can I hide certain messages from the search results? +See the variables @code{mu4e-headers-hide-predicate} and +@code{mu4e-headers-hide-enabled}. The latter can be toggled through +@code{mu4e-headers-toggle-property}. -For example, to filter out GMail's spam, set it to: +For example, to filter out GMail's spam folder, set it to: @lisp (setq mu4e-headers-hide-predicate (lambda (msg)