X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-show.el;h=9f6fe077df0caa5843454c91cd93a27d87db50f6;hp=1ac80cac9cba3559a002da14c78b4a6835047ff3;hb=71fb37d48236e49b64e300c3764c0e17e02c8f9e;hpb=39408d0f7d71334c539327db5bbfe248cbab8e82 diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 1ac80cac..9f6fe077 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -46,6 +46,7 @@ (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp)) (declare-function notmuch-tree "notmuch-tree" (&optional query query-context target buffer-name open-target)) +(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") "Headers that should be shown in a message, in this order. @@ -171,7 +172,7 @@ each attachment handler is logged in buffers with names beginning (defcustom notmuch-show-stash-mlarchive-link-alist '(("Gmane" . "http://mid.gmane.org/") ("MARC" . "http://marc.info/?i=") - ("Mail Archive, The" . "http://mail-archive.com/search?l=mid&q=") + ("Mail Archive, The" . "http://mid.mail-archive.com/") ("LKML" . "http://lkml.kernel.org/r/") ;; FIXME: can these services be searched by `Message-Id' ? ;; ("MarkMail" . "http://markmail.org/") @@ -180,10 +181,21 @@ each attachment handler is logged in buffers with names beginning ) "List of Mailing List Archives to use when stashing links. -These URIs are concatenated with the current message's -Message-Id in `notmuch-show-stash-mlarchive-link'." +This list is used for generating a Mailing List Archive reference +URI with the current message's Message-Id in +`notmuch-show-stash-mlarchive-link'. + +If the cdr of the alist element is not a function, the cdr is +expected to contain a URI that is concatenated with the current +message's Message-Id to create a ML archive reference URI. + +If the cdr is a function, the function is called with the +Message-Id as the argument, and the function is expected to +return the ML archive reference URI." :type '(alist :key-type (string :tag "Name") - :value-type (string :tag "URL")) + :value-type (choice + (string :tag "URL") + (function :tag "Function returning the URL"))) :group 'notmuch-show) (defcustom notmuch-show-stash-mlarchive-link-default "Gmane" @@ -211,6 +223,16 @@ For example, if you wanted to remove an \"unread\" tag and add a :type '(repeat string) :group 'notmuch-show) +(defcustom notmuch-show-mark-read-function #'notmuch-show-seen-current-message + "Function to control which messages are marked read. + +The function should take two arguments START and END which will +be the start and end of the visible portion of the buffer and +should mark the appropriate messages read by applying +`notmuch-show-mark-read'. This function will be called after +every user interaction with notmuch." + :type 'function + :group 'notmuch-show) (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" @@ -219,9 +241,9 @@ For example, if you wanted to remove an \"unread\" tag and add a (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*")))) (with-current-buffer buf (let ((coding-system-for-read 'no-conversion)) - (call-process notmuch-command nil t nil "show" "--format=raw" id) - ,@body) - (kill-buffer buf)))))) + (call-process notmuch-command nil t nil "show" "--format=raw" id)) + ,@body) + (kill-buffer buf))))) (defun notmuch-show-turn-on-visual-line-mode () "Enable Visual Line mode." @@ -344,7 +366,7 @@ operation on the contents of the current buffer." (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) (let ((inhibit-read-only t)) (replace-match (concat "(" - (notmuch-tag-format-tags tags) + (notmuch-tag-format-tags tags (notmuch-show-get-prop :orig-tags)) ")")))))) (defun notmuch-clean-address (address) @@ -423,7 +445,7 @@ message at DEPTH in the current thread." " (" date ") (" - (notmuch-tag-format-tags tags) + (notmuch-tag-format-tags tags tags) ")\n") (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face))) @@ -695,7 +717,7 @@ message at DEPTH in the current thread." (let ((start (if button (button-start button) (point)))) - (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto)) + (insert (notmuch-get-bodypart-content msg part notmuch-show-process-crypto)) (save-excursion (save-restriction (narrow-to-region start (point-max)) @@ -704,7 +726,7 @@ message at DEPTH in the current thread." (defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth button) (insert (with-temp-buffer - (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto)) + (insert (notmuch-get-bodypart-content msg part notmuch-show-process-crypto)) ;; notmuch-get-bodypart-content provides "raw", non-converted ;; data. Replace CRLF with LF before icalendar can use it. (goto-char (point-min)) @@ -756,7 +778,7 @@ message at DEPTH in the current thread." (defun notmuch-show-insert-part-*/* (msg part content-type nth depth button) ;; This handler _must_ succeed - it is the handler of last resort. - (notmuch-mm-display-part-inline msg part nth content-type notmuch-show-process-crypto) + (notmuch-mm-display-part-inline msg part content-type notmuch-show-process-crypto) t) ;; Functions for determining how to handle MIME parts. @@ -785,7 +807,10 @@ message at DEPTH in the current thread." (while (and handlers (not (condition-case err (funcall (car handlers) msg part content-type nth depth button) - (error (progn + ;; Specifying `debug' here lets the debugger + ;; run if `debug-on-error' is non-nil. + ((debug error) + (progn (insert "!!! Bodypart insert error: ") (insert (error-message-string err)) (insert " !!!\n") nil))))) @@ -1142,9 +1167,12 @@ function is used." (let ((inhibit-read-only t)) (notmuch-show-mode) + (add-hook 'post-command-hook #'notmuch-show-command-hook nil t) + ;; Don't track undo information for this buffer (set 'buffer-undo-list t) + (notmuch-tag-clear-cache) (erase-buffer) (goto-char (point-min)) (save-excursion @@ -1167,6 +1195,8 @@ function is used." (jit-lock-register #'notmuch-show-buttonise-links) + (notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags)))) + ;; Set the header line to the subject of the first message. (setq header-line-format (notmuch-sanitize (notmuch-show-strip-re (notmuch-show-get-subject)))) @@ -1180,6 +1210,15 @@ This includes: - the current message." (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages))) +(defun notmuch-show-get-query () + "Return the current query in this show buffer" + (if notmuch-show-query-context + (concat notmuch-show-thread-id + " and (" + notmuch-show-query-context + ")") + notmuch-show-thread-id)) + (defun notmuch-show-apply-state (state) "Apply STATE to the current buffer. @@ -1241,6 +1280,7 @@ reset based on the original query." (define-key map "t" 'notmuch-show-stash-to) (define-key map "l" 'notmuch-show-stash-mlarchive-link) (define-key map "L" 'notmuch-show-stash-mlarchive-link-and-go) + (define-key map "G" 'notmuch-show-stash-git-send-email) (define-key map "?" 'notmuch-subkeymap-help) map) "Submap for stash commands") @@ -1258,46 +1298,46 @@ reset based on the original query." (fset 'notmuch-show-part-map notmuch-show-part-map) (defvar notmuch-show-mode-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map notmuch-common-keymap) - (define-key map "Z" 'notmuch-tree-from-show-current-query) - (define-key map (kbd "") 'widget-backward) - (define-key map (kbd "M-TAB") 'notmuch-show-previous-button) - (define-key map (kbd "") 'notmuch-show-previous-button) - (define-key map (kbd "TAB") 'notmuch-show-next-button) - (define-key map "f" 'notmuch-show-forward-message) - (define-key map "r" 'notmuch-show-reply-sender) - (define-key map "R" 'notmuch-show-reply) - (define-key map "|" 'notmuch-show-pipe-message) - (define-key map "w" 'notmuch-show-save-attachments) - (define-key map "V" 'notmuch-show-view-raw-message) - (define-key map "c" 'notmuch-show-stash-map) - (define-key map "h" 'notmuch-show-toggle-visibility-headers) - (define-key map "*" 'notmuch-show-tag-all) - (define-key map "-" 'notmuch-show-remove-tag) - (define-key map "+" 'notmuch-show-add-tag) - (define-key map "X" 'notmuch-show-archive-thread-then-exit) - (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit) - (define-key map "A" 'notmuch-show-archive-thread-then-next) - (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread) - (define-key map "N" 'notmuch-show-next-message) - (define-key map "P" 'notmuch-show-previous-message) - (define-key map "n" 'notmuch-show-next-open-message) - (define-key map "p" 'notmuch-show-previous-open-message) - (define-key map (kbd "M-n") 'notmuch-show-next-thread-show) - (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show) - (define-key map (kbd "DEL") 'notmuch-show-rewind) - (define-key map " " 'notmuch-show-advance-and-archive) - (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all) - (define-key map (kbd "RET") 'notmuch-show-toggle-message) - (define-key map "#" 'notmuch-show-print-message) - (define-key map "!" 'notmuch-show-toggle-elide-non-matching) - (define-key map "$" 'notmuch-show-toggle-process-crypto) - (define-key map "<" 'notmuch-show-toggle-thread-indentation) - (define-key map "t" 'toggle-truncate-lines) - (define-key map "." 'notmuch-show-part-map) - map) - "Keymap for \"notmuch show\" buffers.") + (let ((map (make-sparse-keymap))) + (set-keymap-parent map notmuch-common-keymap) + (define-key map "Z" 'notmuch-tree-from-show-current-query) + (define-key map (kbd "") 'widget-backward) + (define-key map (kbd "M-TAB") 'notmuch-show-previous-button) + (define-key map (kbd "") 'notmuch-show-previous-button) + (define-key map (kbd "TAB") 'notmuch-show-next-button) + (define-key map "f" 'notmuch-show-forward-message) + (define-key map "r" 'notmuch-show-reply-sender) + (define-key map "R" 'notmuch-show-reply) + (define-key map "|" 'notmuch-show-pipe-message) + (define-key map "w" 'notmuch-show-save-attachments) + (define-key map "V" 'notmuch-show-view-raw-message) + (define-key map "c" 'notmuch-show-stash-map) + (define-key map "h" 'notmuch-show-toggle-visibility-headers) + (define-key map "*" 'notmuch-show-tag-all) + (define-key map "-" 'notmuch-show-remove-tag) + (define-key map "+" 'notmuch-show-add-tag) + (define-key map "X" 'notmuch-show-archive-thread-then-exit) + (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit) + (define-key map "A" 'notmuch-show-archive-thread-then-next) + (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread) + (define-key map "N" 'notmuch-show-next-message) + (define-key map "P" 'notmuch-show-previous-message) + (define-key map "n" 'notmuch-show-next-open-message) + (define-key map "p" 'notmuch-show-previous-open-message) + (define-key map (kbd "M-n") 'notmuch-show-next-thread-show) + (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show) + (define-key map (kbd "DEL") 'notmuch-show-rewind) + (define-key map " " 'notmuch-show-advance-and-archive) + (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all) + (define-key map (kbd "RET") 'notmuch-show-toggle-message) + (define-key map "#" 'notmuch-show-print-message) + (define-key map "!" 'notmuch-show-toggle-elide-non-matching) + (define-key map "$" 'notmuch-show-toggle-process-crypto) + (define-key map "<" 'notmuch-show-toggle-thread-indentation) + (define-key map "t" 'toggle-truncate-lines) + (define-key map "." 'notmuch-show-part-map) + map) + "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) (defun notmuch-show-mode () @@ -1442,8 +1482,18 @@ an error if there is no part containing point." (notmuch-show-set-message-properties props))) (defun notmuch-show-get-prop (prop &optional props) + "Get property PROP from current message in show or tree mode. + +It gets property PROP from PROPS or, if PROPS is nil, the current +message in either tree or show. This means that several utility +functions in notmuch-show can be used directly by notmuch-tree as +they just need the correct message properties." (let ((props (or props - (notmuch-show-get-message-properties)))) + (cond ((eq major-mode 'notmuch-show-mode) + (notmuch-show-get-message-properties)) + ((eq major-mode 'notmuch-tree-mode) + (notmuch-tree-get-message-properties)) + (t nil))))) (plist-get props prop))) (defun notmuch-show-get-message-id (&optional bare) @@ -1527,6 +1577,23 @@ marked as unread, i.e. the tag changes in (apply 'notmuch-show-tag-message (notmuch-tag-change-list notmuch-show-mark-read-tags unread)))) +(defun notmuch-show-seen-current-message (start end) + "Mark the current message read if it is open. + +We only mark it read once: if it is changed back then that is a +user decision and we should not override it." + (when (and (notmuch-show-message-visible-p) + (not (notmuch-show-get-prop :seen))) + (notmuch-show-mark-read) + (notmuch-show-set-prop :seen t))) + +(defun notmuch-show-command-hook () + (when (eq major-mode 'notmuch-show-mode) + ;; We need to redisplay to get window-start and window-end correct. + (redisplay) + (save-excursion + (funcall notmuch-show-mark-read-function (window-start) (window-end))))) + ;; Functions for getting attributes of several messages in the current ;; thread. @@ -1662,9 +1729,7 @@ If a prefix argument is given and this is the last message in the thread, navigate to the next thread in the parent search buffer." (interactive "P") (if (notmuch-show-goto-message-next) - (progn - (notmuch-show-mark-read) - (notmuch-show-message-adjust)) + (notmuch-show-message-adjust) (if pop-at-end (notmuch-show-next-thread) (goto-char (point-max))))) @@ -1675,7 +1740,6 @@ thread, navigate to the next thread in the parent search buffer." (if (= (point) (notmuch-show-message-top)) (notmuch-show-goto-message-previous) (notmuch-show-move-to-message-top)) - (notmuch-show-mark-read) (notmuch-show-message-adjust)) (defun notmuch-show-next-open-message (&optional pop-at-end) @@ -1690,9 +1754,7 @@ to show, nil otherwise." (while (and (setq r (notmuch-show-goto-message-next)) (not (notmuch-show-message-visible-p)))) (if r - (progn - (notmuch-show-mark-read) - (notmuch-show-message-adjust)) + (notmuch-show-message-adjust) (if pop-at-end (notmuch-show-next-thread) (goto-char (point-max)))) @@ -1705,9 +1767,7 @@ to show, nil otherwise." (while (and (setq r (notmuch-show-goto-message-next)) (not (notmuch-show-get-prop :match)))) (if r - (progn - (notmuch-show-mark-read) - (notmuch-show-message-adjust)) + (notmuch-show-message-adjust) (goto-char (point-max))))) (defun notmuch-show-open-if-matched () @@ -1718,8 +1778,7 @@ to show, nil otherwise." (defun notmuch-show-goto-first-wanted-message () "Move to the first open message and mark it read" (goto-char (point-min)) - (if (notmuch-show-message-visible-p) - (notmuch-show-mark-read) + (unless (notmuch-show-message-visible-p) (notmuch-show-next-open-message)) (when (eobp) ;; There are no matched non-excluded messages so open all matched @@ -1727,8 +1786,7 @@ to show, nil otherwise." (notmuch-show-mapc 'notmuch-show-open-if-matched) (force-window-update) (goto-char (point-min)) - (if (notmuch-show-message-visible-p) - (notmuch-show-mark-read) + (unless (notmuch-show-message-visible-p) (notmuch-show-next-open-message)))) (defun notmuch-show-previous-open-message () @@ -1738,15 +1796,15 @@ to show, nil otherwise." (notmuch-show-goto-message-previous) (notmuch-show-move-to-message-top)) (not (notmuch-show-message-visible-p)))) - (notmuch-show-mark-read) (notmuch-show-message-adjust)) (defun notmuch-show-view-raw-message () - "View the file holding the current message." + "View the original source of the current message." (interactive) (let* ((id (notmuch-show-get-message-id)) (buf (get-buffer-create (concat "*notmuch-raw-" id "*")))) - (call-process notmuch-command nil buf nil "show" "--format=raw" id) + (let ((coding-system-for-read 'no-conversion)) + (call-process notmuch-command nil buf nil "show" "--format=raw" id)) (switch-to-buffer buf) (goto-char (point-min)) (set-buffer-modified-p nil) @@ -1781,10 +1839,14 @@ message." (setq shell-command (concat notmuch-command " show --format=raw " (shell-quote-argument (notmuch-show-get-message-id)) " | " command))) - (let ((buf (get-buffer-create (concat "*notmuch-pipe*")))) + (let ((cwd default-directory) + (buf (get-buffer-create (concat "*notmuch-pipe*")))) (with-current-buffer buf (setq buffer-read-only nil) (erase-buffer) + ;; Use the originating buffer's working directory instead of + ;; that of the pipe buffer. + (cd cwd) (let ((exit-code (call-process-shell-command shell-command nil buf))) (goto-char (point-max)) (set-buffer-modified-p nil) @@ -1901,7 +1963,7 @@ buffer. If PREVIOUS is non-nil, move to the previous item in the search results instead." (interactive "P") (let ((parent-buffer notmuch-show-parent-buffer)) - (notmuch-kill-this-buffer) + (notmuch-bury-or-kill-this-buffer) (when (buffer-live-p parent-buffer) (switch-to-buffer parent-buffer) (and (if previous @@ -2045,16 +2107,19 @@ This presumes that the message is available at the selected Mailing List Archive If optional argument MLA is non-nil, use the provided key instead of prompting the user (see `notmuch-show-stash-mlarchive-link-alist')." (interactive) - (notmuch-common-do-stash - (concat (cdr (assoc - (or mla - (let ((completion-ignore-case t)) - (completing-read - "Mailing List Archive: " - notmuch-show-stash-mlarchive-link-alist - nil t nil nil notmuch-show-stash-mlarchive-link-default))) - notmuch-show-stash-mlarchive-link-alist)) - (notmuch-show-get-message-id t)))) + (let ((url (cdr (assoc + (or mla + (let ((completion-ignore-case t)) + (completing-read + "Mailing List Archive: " + notmuch-show-stash-mlarchive-link-alist + nil t nil nil + notmuch-show-stash-mlarchive-link-default))) + notmuch-show-stash-mlarchive-link-alist)))) + (notmuch-common-do-stash + (if (functionp url) + (funcall url (notmuch-show-get-message-id t)) + (concat url (notmuch-show-get-message-id t)))))) (defun notmuch-show-stash-mlarchive-link-and-go (&optional mla) "Copy an ML Archive URI for the current message to the kill-ring and visit it. @@ -2067,6 +2132,43 @@ the user (see `notmuch-show-stash-mlarchive-link-alist')." (notmuch-show-stash-mlarchive-link mla) (browse-url (current-kill 0 t))) +(defun notmuch-show-stash-git-helper (addresses prefix) + "Escape, trim, quote, and add PREFIX to each address in list of ADDRESSES, and return the result as a single string." + (mapconcat (lambda (x) + (concat prefix "\"" + ;; escape double-quotes + (replace-regexp-in-string + "\"" "\\\\\"" + ;; trim leading and trailing spaces + (replace-regexp-in-string + "\\(^ *\\| *$\\)" "" + x)) "\"")) + addresses " ")) + +(put 'notmuch-show-stash-git-send-email 'notmuch-prefix-doc + "Copy From/To/Cc of current message to kill-ring in a form suitable for pasting to git send-email command line.") + +(defun notmuch-show-stash-git-send-email (&optional no-in-reply-to) + "Copy From/To/Cc/Message-Id of current message to kill-ring in a form suitable for pasting to git send-email command line. + +If invoked with a prefix argument (or NO-IN-REPLY-TO is non-nil), +omit --in-reply-to=." + (interactive "P") + (notmuch-common-do-stash + (mapconcat 'identity + (remove "" + (list + (notmuch-show-stash-git-helper + (message-tokenize-header (notmuch-show-get-from)) "--to=") + (notmuch-show-stash-git-helper + (message-tokenize-header (notmuch-show-get-to)) "--to=") + (notmuch-show-stash-git-helper + (message-tokenize-header (notmuch-show-get-cc)) "--cc=") + (unless no-in-reply-to + (notmuch-show-stash-git-helper + (list (notmuch-show-get-message-id t)) "--in-reply-to=")))) + " "))) + ;; Interactive part functions and their helpers (defun notmuch-show-generate-part-buffer (message-id nth)