X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch-show.el;h=aa6ddd1a0ac6130969ee65204b84191c76622880;hp=f3150af520912376419b9fb8db7a59a6d97797af;hb=933011ccaf4cb4ca2e92cff8a4a1ded6567db37c;hpb=74cb76a69d4fb47bb1c03f2d688807793d39ab73 diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index f3150af5..aa6ddd1a 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -26,11 +26,13 @@ (require 'message) (require 'mm-decode) (require 'mailcap) +(require 'icalendar) (require 'notmuch-lib) (require 'notmuch-query) (require 'notmuch-wash) (require 'notmuch-mua) +(require 'notmuch-crypto) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-fontify-headers "notmuch" nil) @@ -63,6 +65,17 @@ any given message." :group 'notmuch :type 'boolean) +(defcustom notmuch-show-elide-same-subject nil + "Do not show the subject of a collapsed message if it is the +same as that of the previous message." + :group 'notmuch + :type 'boolean) + +(defcustom notmuch-show-always-show-subject t + "Should a collapsed message show the `Subject:' line?" + :group 'notmuch + :type 'boolean) + (defvar notmuch-show-markup-headers-hook '(notmuch-show-colour-headers) "A list of functions called to decorate the headers listed in `notmuch-message-headers'.") @@ -82,6 +95,18 @@ any given message." notmuch-wash-elide-blank-lines notmuch-wash-excerpt-citations)) +;; Mostly useful for debugging. +(defcustom notmuch-show-all-multipart/alternative-parts nil + "Should all parts of multipart/alternative parts be shown?" + :group 'notmuch + :type 'boolean) + +(defcustom notmuch-show-indent-multipart nil + "Should the sub-parts of a multipart/* part be indented?" + ;; dme: Not sure which is a good default. + :group 'notmuch + :type 'boolean) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -198,12 +223,30 @@ any given message." 'face 'notmuch-tag-face) ")")))))) +(defun notmuch-show-clean-address (address) + "Clean a single email address for display." + (let* ((parsed (mail-header-parse-address address)) + (address (car parsed)) + (name (cdr parsed))) + ;; Remove double quotes. They might be required during transport, + ;; but we don't need to see them. + (when name + (setq name (replace-regexp-in-string "\"" "" name))) + ;; If the address is 'foo@bar.com ' then show just + ;; 'foo@bar.com'. + (when (string= name address) + (setq name nil)) + + (if (not name) + address + (concat name " <" address ">")))) + (defun notmuch-show-insert-headerline (headers date tags depth) "Insert a notmuch style headerline based on HEADERS for a message at DEPTH in the current thread." (let ((start (point))) (insert (notmuch-show-spaces-n depth) - (plist-get headers :From) + (notmuch-show-clean-address (plist-get headers :From)) " (" date ") (" @@ -236,7 +279,7 @@ message at DEPTH in the current thread." 'follow-link t 'face 'message-mml) -(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name) +(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment) (insert-button (concat "[ " (if name (concat name ": ") "") @@ -244,6 +287,7 @@ message at DEPTH in the current thread." (if (not (string-equal declared-type content-type)) (concat " (as " content-type ")") "") + (or comment "") " ]\n") :type 'notmuch-show-part-button-type :notmuch-part nth @@ -252,18 +296,20 @@ message at DEPTH in the current thread." ;; Functions handling particular MIME parts. (defun notmuch-show-save-part (message-id nth &optional filename) - (with-temp-buffer - ;; Always acquires the part via `notmuch part', even if it is - ;; available in the JSON output. - (insert (notmuch-show-get-bodypart-internal message-id nth)) - (let ((file (read-file-name - "Filename to save as: " - (or mailcap-download-directory "~/") - nil nil - filename)) - (require-final-newline nil) - (coding-system-for-write 'no-conversion)) - (write-region (point-min) (point-max) file)))) + (let ((process-crypto notmuch-show-process-crypto)) + (with-temp-buffer + (setq notmuch-show-process-crypto process-crypto) + ;; Always acquires the part via `notmuch part', even if it is + ;; available in the JSON output. + (insert (notmuch-show-get-bodypart-internal message-id nth)) + (let ((file (read-file-name + "Filename to save as: " + (or mailcap-download-directory "~/") + nil nil + filename)) + (require-final-newline nil) + (coding-system-for-write 'no-conversion)) + (write-region (point-min) (point-max) file))))) (defun notmuch-show-mm-display-part-inline (msg part content-type content) "Use the mm-decode/mm-view functions to display a part in the @@ -280,6 +326,157 @@ current buffer, if possible." t) nil))))) +(defvar notmuch-show-multipart/alternative-discouraged + '( + ;; Avoid HTML parts. + "text/html" + ;; multipart/related usually contain a text/html part and some associated graphics. + "multipart/related" + )) + +(defun notmuch-show-multipart/*-to-list (part) + (mapcar '(lambda (inner-part) (plist-get inner-part :content-type)) + (plist-get part :content))) + +(defun notmuch-show-multipart/alternative-choose (types) + ;; Based on `mm-preferred-alternative-precedence'. + (let ((seq types)) + (dolist (pref (reverse notmuch-show-multipart/alternative-discouraged)) + (dolist (elem (copy-sequence seq)) + (when (string-match pref elem) + (setq seq (nconc (delete elem seq) (list elem)))))) + seq)) + +(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth declared-type) + (notmuch-show-insert-part-header nth declared-type content-type nil) + (let ((chosen-type (car (notmuch-show-multipart/alternative-choose (notmuch-show-multipart/*-to-list part)))) + (inner-parts (plist-get part :content)) + (start (point))) + ;; This inserts all parts of the chosen type rather than just one, + ;; but it's not clear that this is the wrong thing to do - which + ;; should be chosen if there are more than one that match? + (mapc (lambda (inner-part) + (let ((inner-type (plist-get inner-part :content-type))) + (if (or notmuch-show-all-multipart/alternative-parts + (string= chosen-type inner-type)) + (notmuch-show-insert-bodypart msg inner-part depth) + (notmuch-show-insert-part-header (plist-get inner-part :id) inner-type inner-type nil " (not shown)")))) + inner-parts) + + (when notmuch-show-indent-multipart + (indent-rigidly start (point) 1))) + t) + +(defun notmuch-show-setup-w3m () + "Instruct w3m how to retrieve content from a \"related\" part of a message." + (interactive) + (if (boundp 'w3m-cid-retrieve-function-alist) + (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist) + (push (cons 'notmuch-show-mode 'notmuch-show-w3m-cid-retrieve) + w3m-cid-retrieve-function-alist))) + (setq mm-inline-text-html-with-images t)) + +(defvar w3m-current-buffer) ;; From `w3m.el'. +(defvar notmuch-show-w3m-cid-store nil) +(make-variable-buffer-local 'notmuch-show-w3m-cid-store) + +(defun notmuch-show-w3m-cid-store-internal (content-id + message-id + part-number + content-type + content) + (push (list content-id + message-id + part-number + content-type + content) + notmuch-show-w3m-cid-store)) + +(defun notmuch-show-w3m-cid-store (msg part) + (let ((content-id (plist-get part :content-id))) + (when content-id + (notmuch-show-w3m-cid-store-internal (concat "cid:" content-id) + (plist-get msg :id) + (plist-get part :id) + (plist-get part :content-type) + nil)))) + +(defun notmuch-show-w3m-cid-retrieve (url &rest args) + (let ((matching-part (with-current-buffer w3m-current-buffer + (assoc url notmuch-show-w3m-cid-store)))) + (if matching-part + (let ((message-id (nth 1 matching-part)) + (part-number (nth 2 matching-part)) + (content-type (nth 3 matching-part)) + (content (nth 4 matching-part))) + ;; If we don't already have the content, get it and cache + ;; it, as some messages reference the same cid: part many + ;; times (hundreds!), which results in many calls to + ;; `notmuch part'. + (unless content + (setq content (notmuch-show-get-bodypart-internal (concat "id:" message-id) + part-number)) + (with-current-buffer w3m-current-buffer + (notmuch-show-w3m-cid-store-internal url + message-id + part-number + content-type + content))) + (insert content) + content-type) + nil))) + +(defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth declared-type) + (notmuch-show-insert-part-header nth declared-type content-type nil) + (let ((inner-parts (plist-get part :content)) + (start (point))) + + ;; We assume that the first part is text/html and the remainder + ;; things that it references. + + ;; Stash the non-primary parts. + (mapc (lambda (part) + (notmuch-show-w3m-cid-store msg part)) + (cdr inner-parts)) + + ;; Render the primary part. + (notmuch-show-insert-bodypart msg (car inner-parts) depth) + + (when notmuch-show-indent-multipart + (indent-rigidly start (point) 1))) + t) + +(defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth declared-type) + (notmuch-show-insert-part-header nth declared-type content-type nil) + (let ((inner-parts (plist-get part :content)) + (start (point))) + ;; Show all of the parts. + (mapc (lambda (inner-part) + (notmuch-show-insert-bodypart msg inner-part depth)) + inner-parts) + + (when notmuch-show-indent-multipart + (indent-rigidly start (point) 1))) + t) + +(defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth declared-type) + (let* ((message-part (plist-get part :content)) + (inner-parts (plist-get message-part :content))) + (notmuch-show-insert-part-header nth declared-type content-type nil) + ;; Override `notmuch-message-headers' to force `From' to be + ;; displayed. + (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date"))) + (notmuch-show-insert-headers (plist-get part :headers))) + ;; Blank line after headers to be compatible with the normal + ;; message display. + (insert "\n") + + ;; Show all of the parts. + (mapc (lambda (inner-part) + (notmuch-show-insert-bodypart msg inner-part depth)) + inner-parts)) + t) + (defun notmuch-show-insert-part-text/plain (msg part content-type nth depth declared-type) (let ((start (point))) ;; If this text/plain part is not the first part in the message, @@ -293,6 +490,24 @@ current buffer, if possible." (run-hook-with-args 'notmuch-show-insert-text/plain-hook depth)))) t) +(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth declared-type) + (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename)) + (insert (with-temp-buffer + (insert (notmuch-show-get-bodypart-content msg part nth)) + (goto-char (point-min)) + (let ((file (make-temp-file "notmuch-ical")) + result) + (icalendar--convert-ical-to-diary + (icalendar--read-element nil nil) + file t) + (set-buffer (get-file-buffer file)) + (setq result (buffer-substring (point-min) (point-max))) + (set-buffer-modified-p nil) + (kill-buffer (current-buffer)) + (delete-file file) + result))) + t) + (defun notmuch-show-insert-part-application/octet-stream (msg part content-type nth depth declared-type) ;; If we can deduce a MIME type from the filename of the attachment, ;; do so and pass it on to the handler for that type. @@ -309,6 +524,11 @@ current buffer, if possible." nil)) nil)))) +(defun notmuch-show-insert-part-application/* (msg part content-type nth depth declared-type +) + ;; do not render random "application" parts + (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))) + (defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type) ;; This handler _must_ succeed - it is the handler of last resort. (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename)) @@ -339,13 +559,20 @@ current buffer, if possible." ;; Helper for parts which are generally not included in the default ;; JSON output. - +;; Uses the buffer-local variable notmuch-show-process-crypto to +;; determine if parts should be decrypted first. (defun notmuch-show-get-bodypart-internal (message-id part-number) - (with-temp-buffer - (let ((coding-system-for-read 'no-conversion)) - (call-process notmuch-command nil t nil - "part" (format "--part=%s" part-number) message-id) - (buffer-string)))) + (let ((args '("show" "--format=raw")) + (part-arg (format "--part=%s" part-number))) + (setq args (append args (list part-arg))) + (if notmuch-show-process-crypto + (setq args (append args '("--decrypt")))) + (setq args (append args (list message-id))) + (with-temp-buffer + (let ((coding-system-for-read 'no-conversion)) + (progn + (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args)) + (buffer-string)))))) (defun notmuch-show-get-bodypart-content (msg part nth) (or (plist-get part :content) @@ -366,6 +593,16 @@ current buffer, if possible." "Insert the body part PART at depth DEPTH in the current thread." (let ((content-type (downcase (plist-get part :content-type))) (nth (plist-get part :id))) + ;; add encryption status button if encstatus specified + (if (plist-member part :encstatus) + (let* ((encstatus (car (plist-get part :encstatus)))) + (notmuch-crypto-insert-encstatus-button encstatus))) + ;; add signature status button if sigstatus specified + (if (plist-member part :sigstatus) + (let* ((headers (plist-get msg :headers)) + (from (plist-get headers :From)) + (sigstatus (car (plist-get part :sigstatus)))) + (notmuch-crypto-insert-sigstatus-button sigstatus from))) (notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type)) ;; Some of the body part handlers leave point somewhere up in the ;; part, so we make sure that we're down at the end. @@ -438,8 +675,9 @@ current buffer, if possible." ;; If the subject of this message is the same as that of the ;; previous message, don't display it when this message is ;; collapsed. - (when (not (string= notmuch-show-previous-subject - bare-subject)) + (when (and notmuch-show-elide-same-subject + (not (string= notmuch-show-previous-subject + bare-subject))) (forward-line 1)) (setq headers-start (point-marker))) (setq headers-end (point-marker)) @@ -498,9 +736,10 @@ current buffer, if possible." (mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest)) (defvar notmuch-show-parent-buffer nil) +(make-variable-buffer-local 'notmuch-show-parent-buffer) ;;;###autoload -(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name) +(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name crypto-switch) "Run \"notmuch show\" with the given thread ID and display results. The optional PARENT-BUFFER is the notmuch-search buffer from @@ -520,10 +759,14 @@ function is used. " (let ((buffer (get-buffer-create (generate-new-buffer-name (or buffer-name (concat "*notmuch-" thread-id "*"))))) + (process-crypto (if crypto-switch + (not notmuch-crypto-process-mime) + notmuch-crypto-process-mime)) (inhibit-read-only t)) (switch-to-buffer buffer) (notmuch-show-mode) - (set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer) + (setq notmuch-show-parent-buffer parent-buffer) + (setq notmuch-show-process-crypto process-crypto) (erase-buffer) (goto-char (point-min)) (save-excursion @@ -579,7 +822,7 @@ function is used. " (define-key map (kbd "") 'notmuch-show-previous-button) (define-key map (kbd "TAB") 'notmuch-show-next-button) (define-key map "s" 'notmuch-search) - (define-key map "m" 'notmuch-mua-mail) + (define-key map "m" 'notmuch-mua-new-mail) (define-key map "f" 'notmuch-show-forward-message) (define-key map "r" 'notmuch-show-reply) (define-key map "|" 'notmuch-show-pipe-message) @@ -885,16 +1128,16 @@ any effects from previous calls to ;; Move to the previous message. (notmuch-show-previous-message))))) -(defun notmuch-show-reply () +(defun notmuch-show-reply (&optional prompt-for-sender) "Reply to the current message." - (interactive) - (notmuch-mua-reply (notmuch-show-get-message-id))) + (interactive "P") + (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender)) -(defun notmuch-show-forward-message () +(defun notmuch-show-forward-message (&optional prompt-for-sender) "Forward the current message." - (interactive) + (interactive "P") (with-current-notmuch-show-message - (notmuch-mua-forward-message))) + (notmuch-mua-new-forward-message prompt-for-sender))) (defun notmuch-show-next-message () "Show the next message." @@ -1005,10 +1248,8 @@ the result." (new-tags (notmuch-show-add-tags-worker current-tags toadd))) (unless (equal current-tags new-tags) - (apply 'notmuch-call-notmuch-process - (append (cons "tag" - (mapcar (lambda (s) (concat "+" s)) toadd)) - (cons (notmuch-show-get-message-id) nil))) + (apply 'notmuch-tag (notmuch-show-get-message-id) + (mapcar (lambda (s) (concat "+" s)) toadd)) (notmuch-show-set-tags new-tags)))) (defun notmuch-show-remove-tag (&rest toremove) @@ -1021,10 +1262,8 @@ the result." (new-tags (notmuch-show-del-tags-worker current-tags toremove))) (unless (equal current-tags new-tags) - (apply 'notmuch-call-notmuch-process - (append (cons "tag" - (mapcar (lambda (s) (concat "-" s)) toremove)) - (cons (notmuch-show-get-message-id) nil))) + (apply 'notmuch-tag (notmuch-show-get-message-id) + (mapcar (lambda (s) (concat "-" s)) toremove)) (notmuch-show-set-tags new-tags)))) (defun notmuch-show-toggle-headers ()