-;;; notmuch-show.el --- displaying notmuch forests.
+;;; notmuch-show.el --- displaying notmuch forests -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
;;; Code:
-(eval-when-compile
- (require 'cl-lib)
- (require 'pcase))
-
(require 'mm-view)
(require 'message)
(require 'mm-decode)
(require 'notmuch-print)
(require 'notmuch-draft)
-(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
+(declare-function notmuch-call-notmuch-process "notmuch-lib" (&rest args))
(declare-function notmuch-search-next-thread "notmuch" nil)
(declare-function notmuch-search-previous-thread "notmuch" nil)
-(declare-function notmuch-search-show-thread "notmuch" nil)
+(declare-function notmuch-search-show-thread "notmuch")
(declare-function notmuch-foreach-mime-part "notmuch" (function mm-handle))
(declare-function notmuch-count-attachments "notmuch" (mm-handle))
(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 unthreaded))
+ open-target unthreaded parent-buffer))
(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
-(declare-function notmuch-unthreaded
- (&optional query query-context target buffer-name open-target))
+(declare-function notmuch-unthreaded "notmuch-tree"
+ (&optional query query-context target buffer-name
+ open-target))
(declare-function notmuch-read-query "notmuch" (prompt))
(declare-function notmuch-draft-resume "notmuch-draft" (id))
+(defvar shr-blocked-images)
+(defvar gnus-blocked-images)
+(defvar shr-content-function)
+(defvar w3m-ignored-image-url-regexp)
+
+;;; Options
+
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order.
:type '(choice (const nil) regexp)
:group 'notmuch-show)
-(defvar notmuch-show-thread-id nil)
-(make-variable-buffer-local 'notmuch-show-thread-id)
+;;; Variables
+
+(defvar-local notmuch-show-thread-id nil)
+
+(defvar-local notmuch-show-parent-buffer nil)
-(defvar notmuch-show-parent-buffer nil)
-(make-variable-buffer-local 'notmuch-show-parent-buffer)
+(defvar-local notmuch-show-query-context nil)
-(defvar notmuch-show-query-context nil)
-(make-variable-buffer-local 'notmuch-show-query-context)
+(defvar-local notmuch-show-process-crypto nil)
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+(defvar-local notmuch-show-elide-non-matching-messages nil)
-(defvar notmuch-show-elide-non-matching-messages nil)
-(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
+(defvar-local notmuch-show-indent-content t)
-(defvar notmuch-show-indent-content t)
-(make-variable-buffer-local 'notmuch-show-indent-content)
+(defvar-local notmuch-show-single-message nil)
(defvar notmuch-show-attachment-debug nil
"If t log stdout and stderr from attachment handlers.
When set to nil (the default) stdout and stderr from attachment
handlers is discarded. When set to t the stdout and stderr from
each attachment handler is logged in buffers with names beginning
-\" *notmuch-part*\". This option requires emacs version at least
-24.3 to work.")
+\" *notmuch-part*\".")
+
+;;; Options
(defcustom notmuch-show-stash-mlarchive-link-alist
- '(("Gmane" . "https://mid.gmane.org/")
- ("MARC" . "https://marc.info/?i=")
+ '(("MARC" . "https://marc.info/?i=")
("Mail Archive, The" . "https://mid.mail-archive.com/")
- ("LKML" . "https://lkml.kernel.org/r/")
+ ("Lore" . "https://lore.kernel.org/r/")
+ ("Notmuch" . "https://nmbug.notmuchmail.org/nmweb/show/")
;; FIXME: can these services be searched by `Message-Id' ?
;; ("MarkMail" . "http://markmail.org/")
;; ("Nabble" . "http://nabble.com/")
(function :tag "Function returning the URL")))
:group 'notmuch-show)
-(defcustom notmuch-show-stash-mlarchive-link-default "Gmane"
+(defcustom notmuch-show-stash-mlarchive-link-default "MARC"
"Default Mailing List Archive to use when stashing links.
This is used when `notmuch-show-stash-mlarchive-link' isn't
:type 'boolean
:group 'notmuch-show)
+;;; Utilities
+
(defmacro with-current-notmuch-show-message (&rest body)
"Evaluate body with current buffer set to the text of current message."
`(save-excursion
(let ((id (notmuch-show-get-message-id)))
(let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
- (with-current-buffer buf
+ (with-current-buffer buf
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
,@body)
(kill-buffer buf)))))
"Enable Visual Line mode."
(visual-line-mode t))
+;;; Commands
+
;; DEPRECATED in Notmuch 0.16 since we now have convenient part
;; commands. We'll keep the command around for a version or two in
;; case people want to bind it themselves.
;;
;; Any MIME part not explicitly mentioned here will be handled by an
;; external viewer as configured in the various mailcap files.
- (let ((mm-inline-media-tests '(
- ("text/.*" ignore identity)
- ("application/pgp-signature" ignore identity)
- ("multipart/alternative" ignore identity)
- ("multipart/mixed" ignore identity)
- ("multipart/related" ignore identity)
- )))
+ (let ((mm-inline-media-tests
+ '(("text/.*" ignore identity)
+ ("application/pgp-signature" ignore identity)
+ ("multipart/alternative" ignore identity)
+ ("multipart/mixed" ignore identity)
+ ("multipart/related" ignore identity))))
(mm-display-parts (mm-dissect-buffer)))))
(defun notmuch-show-save-attachments ()
(header (concat
"Subject: " subject "\n"
"To: " to "\n"
- (if (not (string= cc ""))
+ (if (not (string-empty-p cc))
(concat "Cc: " cc "\n")
"")
"From: " from "\n"
(indenting notmuch-show-indent-content))
(with-temp-buffer
(insert all)
- (if indenting
- (indent-rigidly (point-min)
- (point-max)
- (- (* notmuch-show-indent-messages-width depth))))
+ (when indenting
+ (indent-rigidly (point-min)
+ (point-max)
+ (- (* notmuch-show-indent-messages-width depth))))
;; Remove the original header.
(goto-char (point-min))
(re-search-forward "^$" (point-max) nil)
(interactive)
(notmuch-show-with-message-as-text 'notmuch-print-message))
+;;; Headers
+
(defun notmuch-show-fontify-header ()
(let ((face (cond
((looking-at "[Tt]o:")
"Update the displayed tags of the current message."
(save-excursion
(goto-char (notmuch-show-message-top))
- (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
- (let ((inhibit-read-only t))
- (replace-match (concat "("
- (notmuch-tag-format-tags
- tags
- (notmuch-show-get-prop :orig-tags))
- ")"))))))
+ (when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
+ (let ((inhibit-read-only t))
+ (replace-match (concat "("
+ (notmuch-tag-format-tags
+ tags
+ (notmuch-show-get-prop :orig-tags))
+ ")"))))))
(defun notmuch-clean-address (address)
"Try to clean a single email ADDRESS for display. Return a cons
cell of (AUTHOR_EMAIL AUTHOR_NAME). Return (ADDRESS nil) if
parsing fails."
(condition-case nil
- (let (p-name p-address)
- ;; It would be convenient to use `mail-header-parse-address',
- ;; but that expects un-decoded mailbox parts, whereas our
- ;; mailbox parts are already decoded (and hence may contain
- ;; UTF-8). Given that notmuch should handle most of the awkward
- ;; cases, some simple string deconstruction should be sufficient
- ;; here.
- (cond
- ;; "User <user@dom.ain>" style.
- ((string-match "\\(.*\\) <\\(.*\\)>" address)
- (setq p-name (match-string 1 address)
- p-address (match-string 2 address)))
- ;; "<user@dom.ain>" style.
- ((string-match "<\\(.*\\)>" address)
- (setq p-address (match-string 1 address)))
- ;; Everything else.
- (t
- (setq p-address address)))
- (when p-name
- ;; Remove elements of the mailbox part that are not relevant for
- ;; display, even if they are required during transport:
- ;;
- ;; Backslashes.
- (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
- ;; Outer single and double quotes, which might be nested.
- (cl-loop with start-of-loop
- do (setq start-of-loop p-name)
- when (string-match "^\"\\(.*\\)\"$" p-name)
- do (setq p-name (match-string 1 p-name))
- when (string-match "^'\\(.*\\)'$" p-name)
- do (setq p-name (match-string 1 p-name))
- until (string= start-of-loop p-name)))
- ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
- ;; 'foo@bar.com'.
- (when (string= p-name p-address)
- (setq p-name nil))
- (cons p-address p-name))
+ (let (p-name p-address)
+ ;; It would be convenient to use `mail-header-parse-address',
+ ;; but that expects un-decoded mailbox parts, whereas our
+ ;; mailbox parts are already decoded (and hence may contain
+ ;; UTF-8). Given that notmuch should handle most of the awkward
+ ;; cases, some simple string deconstruction should be sufficient
+ ;; here.
+ (cond
+ ;; "User <user@dom.ain>" style.
+ ((string-match "\\(.*\\) <\\(.*\\)>" address)
+ (setq p-name (match-string 1 address))
+ (setq p-address (match-string 2 address)))
+
+ ;; "<user@dom.ain>" style.
+ ((string-match "<\\(.*\\)>" address)
+ (setq p-address (match-string 1 address)))
+ ;; Everything else.
+ (t
+ (setq p-address address)))
+ (when p-name
+ ;; Remove elements of the mailbox part that are not relevant for
+ ;; display, even if they are required during transport:
+ ;;
+ ;; Backslashes.
+ (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
+ ;; Outer single and double quotes, which might be nested.
+ (cl-loop with start-of-loop
+ do (setq start-of-loop p-name)
+ when (string-match "^\"\\(.*\\)\"$" p-name)
+ do (setq p-name (match-string 1 p-name))
+ when (string-match "^'\\(.*\\)'$" p-name)
+ do (setq p-name (match-string 1 p-name))
+ until (string= start-of-loop p-name)))
+ ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
+ ;; 'foo@bar.com'.
+ (when (string= p-name p-address)
+ (setq p-name nil))
+ (cons p-address p-name))
(error (cons address nil))))
(defun notmuch-show-clean-address (address)
- "Try to clean a single email ADDRESS for display. Return
-unchanged ADDRESS if parsing fails."
+ "Try to clean a single email ADDRESS for display.
+Return unchanged ADDRESS if parsing fails."
(let* ((clean-address (notmuch-clean-address address))
(p-address (car clean-address))
(p-name (cdr clean-address)))
;; invisible U+200E LEFT-TO-RIGHT MARK character which forces
;; the header paragraph as left-to-right text.
(insert (propertize (string ?\x200e) 'invisible t)))
- (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
+ (insert (if notmuch-show-indent-content
+ (notmuch-show-spaces-n (* notmuch-show-indent-messages-width
+ depth))
+ "")
from
" ("
date
(mapc (lambda (header)
(let* ((header-symbol (intern (concat ":" header)))
(header-value (plist-get headers header-symbol)))
- (if (and header-value
- (not (string-equal "" header-value)))
- (notmuch-show-insert-header header header-value))))
+ (when (and header-value
+ (not (string-equal "" header-value)))
+ (notmuch-show-insert-header header header-value))))
notmuch-message-headers)
(save-excursion
(save-restriction
(narrow-to-region start (point-max))
(run-hooks 'notmuch-show-markup-headers-hook)))))
+;;; Parts
+
(define-button-type 'notmuch-show-part-button-type
'action 'notmuch-show-part-button-default
'follow-link t
'face 'message-mml
:supertype 'notmuch-button-type)
-(defun notmuch-show-insert-part-header (nth content-type declared-type
+(defun notmuch-show-insert-part-header (_nth content-type declared-type
&optional name comment)
- (let ((button)
- (base-label (concat (when name (concat name ": "))
+ (let ((base-label (concat (and name (concat name ": "))
declared-type
- (unless (string-equal declared-type content-type)
- (concat " (as " content-type ")"))
+ (and (not (string-equal declared-type content-type))
+ (concat " (as " content-type ")"))
comment)))
- (setq button
- (insert-button
- (concat "[ " base-label " ]")
- :base-label base-label
- :type 'notmuch-show-part-button-type
- :notmuch-part-hidden nil))
- (insert "\n")
- ;; return button
- button))
+ (prog1 (insert-button
+ (concat "[ " base-label " ]")
+ :base-label base-label
+ :type 'notmuch-show-part-button-type
+ :notmuch-part-hidden nil)
+ (insert "\n"))))
(defun notmuch-show-toggle-part-invisibility (&optional button)
(interactive)
(overlay-put overlay 'invisible (not show))
t)))))))
-;; Part content ID handling
+;;; Part content ID handling
(defvar notmuch-show--cids nil
"Alist from raw content ID to (MSG PART).")
;; alternative (even if we can't render it).
(push (list content-id msg part) notmuch-show--cids)))
;; Recurse on sub-parts
- (let ((ctype (notmuch-split-content-type
- (downcase (plist-get part :content-type)))))
- (cond ((equal (car ctype) "multipart")
- (mapc (apply-partially #'notmuch-show--register-cids msg)
- (plist-get part :content)))
- ((equal ctype '("message" "rfc822"))
- (notmuch-show--register-cids
- msg
- (car (plist-get (car (plist-get part :content)) :body)))))))
+ (when-let ((type (plist-get part :content-type)))
+ (pcase-let ((`(,type ,subtype)
+ (split-string (downcase type) "/")))
+ (cond ((equal type "multipart")
+ (mapc (apply-partially #'notmuch-show--register-cids msg)
+ (plist-get part :content)))
+ ((and (equal type "message")
+ (equal subtype "rfc822"))
+ (notmuch-show--register-cids
+ msg
+ (car (plist-get (car (plist-get part :content)) :body))))))))
(defun notmuch-show--get-cid-content (cid)
"Return a list (CID-content content-type) or nil.
into the current buffer. CID must be a raw content ID, without
enclosing angle brackets, a cid: prefix, or URL encoding. This
will return nil if the CID is unknown or cannot be retrieved."
- (let ((descriptor (cdr (assoc cid notmuch-show--cids))))
- (when descriptor
- (let* ((msg (car descriptor))
- (part (cadr descriptor))
- ;; Request caching for this content, as some messages
- ;; reference the same cid: part many times (hundreds!).
- (content (notmuch-get-bodypart-binary
- msg part notmuch-show-process-crypto 'cache))
- (content-type (plist-get part :content-type)))
- (list content content-type)))))
+ (when-let ((descriptor (cdr (assoc cid notmuch-show--cids))))
+ (pcase-let ((`(,msg ,part) descriptor))
+ ;; Request caching for this content, as some messages
+ ;; reference the same cid: part many times (hundreds!).
+ (list (notmuch-get-bodypart-binary
+ msg part notmuch-show-process-crypto 'cache)
+ (plist-get part :content-type)))))
(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--cid-w3m-retrieve)
- w3m-cid-retrieve-function-alist)))
+ (when (and (boundp 'w3m-cid-retrieve-function-alist)
+ (not (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)))
+ (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve)
+ w3m-cid-retrieve-function-alist))
(setq mm-html-inhibit-images nil))
(defvar w3m-current-buffer) ;; From `w3m.el'.
-(defun notmuch-show--cid-w3m-retrieve (url &rest args)
+(defun notmuch-show--cid-w3m-retrieve (url &rest _args)
;; url includes the cid: prefix and is URL encoded (see RFC 2392).
(let* ((cid (url-unhex-string (substring url 4)))
(content-and-type
(mapcar (lambda (inner-part) (plist-get inner-part :content-type))
(plist-get part :content)))
-(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/alternative (msg part _content-type _nth depth _button)
(let ((chosen-type (car (notmuch-multipart/alternative-choose
msg (notmuch-show-multipart/*-to-list part))))
(inner-parts (plist-get part :content))
;; should be chosen if there are more than one that match?
(mapc (lambda (inner-part)
(let* ((inner-type (plist-get inner-part :content-type))
- (hide (not (or notmuch-show-all-multipart/alternative-parts
- (string= chosen-type inner-type)))))
+ (hide (not (or notmuch-show-all-multipart/alternative-parts
+ (string= chosen-type inner-type)))))
(notmuch-show-insert-bodypart msg inner-part depth hide)))
inner-parts)
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/related (msg part _content-type _nth depth _button)
(let ((inner-parts (plist-get part :content))
(start (point)))
;; Render the primary part. FIXME: Support RFC 2387 Start header.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/signed (msg part _content-type _nth depth button)
(when button
(button-put button 'face 'notmuch-crypto-part-header))
;; Insert a button detailing the signature status.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/encrypted (msg part _content-type _nth depth button)
(when button
(button-put button 'face 'notmuch-crypto-part-header))
;; Insert a button detailing the encryption status.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-application/pgp-encrypted (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-application/pgp-encrypted (_msg _part _content-type _nth _depth _button)
t)
-(defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/* (msg part _content-type _nth depth _button)
(let ((inner-parts (plist-get part :content))
(start (point)))
;; Show all of the parts.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth button)
- (let* ((message (car (plist-get part :content)))
- (body (car (plist-get message :body)))
- (start (point)))
- ;; 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 message :headers)))
- ;; Blank line after headers to be compatible with the normal
- ;; message display.
- (insert "\n")
- ;; Show the body
- (notmuch-show-insert-bodypart msg body depth)
- (when notmuch-show-indent-multipart
- (indent-rigidly start (point) 1)))
- t)
-
-(defun notmuch-show-insert-part-text/plain (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-message/rfc822 (msg part _content-type _nth depth _button)
+ (let ((message (car (plist-get part :content))))
+ (and
+ message
+ (let ((body (car (plist-get message :body)))
+ (start (point)))
+ ;; 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 message :headers)))
+ ;; Blank line after headers to be compatible with the normal
+ ;; message display.
+ (insert "\n")
+ ;; Show the body
+ (notmuch-show-insert-bodypart msg body depth)
+ (when notmuch-show-indent-multipart
+ (indent-rigidly start (point) 1))
+ t))))
+
+(defun notmuch-show-insert-part-text/plain (msg part _content-type _nth depth button)
;; For backward compatibility we want to apply the text/plain hook
;; to the whole of the part including the part button if there is
;; one.
(run-hook-with-args 'notmuch-show-insert-text/plain-hook msg depth))))
t)
-(defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-text/calendar (msg part _content-type _nth _depth _button)
(insert (with-temp-buffer
(insert (notmuch-get-bodypart-text msg part notmuch-show-process-crypto))
;; notmuch-get-bodypart-text does no newline conversion.
t)
;; For backwards compatibility.
-(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button)
- (notmuch-show-insert-part-text/calendar msg part content-type nth depth button))
-
-(if (version< emacs-version "25.3")
- ;; https://bugs.gnu.org/28350
- ;;
- ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
- ;; (see notmuch-show-handlers-for)
- (defun notmuch-show-insert-part-text/enriched
- (msg part content-type nth depth button)
- ;; By requiring enriched below, we ensure that the function
- ;; enriched-decode-display-prop is defined before it will be
- ;; shadowed by the letf below. Otherwise the version in
- ;; enriched.el may be loaded a bit later and used instead (for
- ;; the first time).
- (require 'enriched)
- (cl-letf (((symbol-function 'enriched-decode-display-prop)
- (lambda (start end &optional param) (list start end))))
- (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
+(defun notmuch-show-insert-part-text/x-vcalendar (msg part _content-type _nth depth _button)
+ (notmuch-show-insert-part-text/calendar msg part nil nil depth nil))
+
+(when (version< emacs-version "25.3")
+ ;; https://bugs.gnu.org/28350
+ ;;
+ ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
+ ;; (see notmuch-show-handlers-for)
+ (defun notmuch-show-insert-part-text/enriched
+ (msg part content-type nth depth button)
+ ;; By requiring enriched below, we ensure that the function
+ ;; enriched-decode-display-prop is defined before it will be
+ ;; shadowed by the letf below. Otherwise the version in
+ ;; enriched.el may be loaded a bit later and used instead (for
+ ;; the first time).
+ (require 'enriched)
+ (cl-letf (((symbol-function 'enriched-decode-display-prop)
+ (lambda (start end &optional _param) (list start end))))
+ (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
(defun notmuch-show-get-mime-type-of-application/octet-stream (part)
;; If we can deduce a MIME type from the filename of the attachment,
;; we return that.
- (if (plist-get part :filename)
- (let ((extension (file-name-extension (plist-get part :filename)))
- mime-type)
- (if extension
- (progn
- (mailcap-parse-mimetypes)
- (setq mime-type (mailcap-extension-to-mime extension))
- (if (and mime-type
- (not (string-equal mime-type "application/octet-stream")))
- mime-type
- nil))
- nil))))
+ (and (plist-get part :filename)
+ (let ((extension (file-name-extension (plist-get part :filename))))
+ (and extension
+ (progn
+ (mailcap-parse-mimetypes)
+ (let ((mime-type (mailcap-extension-to-mime extension)))
+ (and mime-type
+ (not (string-equal mime-type "application/octet-stream"))
+ mime-type)))))))
(defun notmuch-show-insert-part-text/html (msg part content-type nth depth button)
(if (eq mm-text-html-renderer 'shr)
(let ((mm-inline-text-html-with-w3m-keymap nil)
;; FIXME: If we block an image, offer a button to load external
;; images.
- (gnus-blocked-images notmuch-show-text/html-blocked-images))
+ (gnus-blocked-images notmuch-show-text/html-blocked-images)
+ (w3m-ignored-image-url-regexp notmuch-show-text/html-blocked-images))
(notmuch-show-insert-part-*/* msg part content-type nth depth button))))
-;; These functions are used by notmuch-show--insert-part-text/html-shr
+;;; Functions used by notmuch-show--insert-part-text/html-shr
+
(declare-function libxml-parse-html-region "xml.c")
(declare-function shr-insert-document "shr")
(shr-insert-document dom)
t))
-(defun notmuch-show-insert-part-*/* (msg part content-type nth depth button)
+(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 content-type notmuch-show-process-crypto)
t)
-;; Functions for determining how to handle MIME parts.
+;;; Functions for determining how to handle MIME parts.
(defun notmuch-show-handlers-for (content-type)
"Return a list of content handlers for a part of type CONTENT-TYPE."
(let (result)
(mapc (lambda (func)
- (if (functionp func)
- (push func result)))
+ (when (functionp func)
+ (push func result)))
;; Reverse order of prefrence.
(list (intern (concat "notmuch-show-insert-part-*/*"))
- (intern (concat
- "notmuch-show-insert-part-"
- (car (notmuch-split-content-type content-type))
- "/*"))
+ (intern (concat "notmuch-show-insert-part-"
+ (car (split-string content-type "/"))
+ "/*"))
(intern (concat "notmuch-show-insert-part-" content-type))))
result))
-;; \f
+;;; Parts
(defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
;; Run the handlers until one of them succeeds.
(defun notmuch-show-mime-type (part)
"Return the correct mime-type to use for PART."
- (let ((content-type (downcase (plist-get part :content-type))))
+ (when-let ((content-type (plist-get part :content-type)))
+ (setq content-type (downcase content-type))
(or (and (string= content-type "application/octet-stream")
(notmuch-show-get-mime-type-of-application/octet-stream part))
(and (string= content-type "inline patch")
should return non-NIL if a header button should be inserted for
this part.")
-(defun notmuch-show-insert-header-p (part hide)
+(defun notmuch-show-insert-header-p (part _hide)
;; Show all part buttons except for the first part if it is text/plain.
(let ((mime-type (notmuch-show-mime-type part)))
(not (and (string= mime-type "text/plain")
(<= (plist-get part :id) 1)))))
-(defun notmuch-show-reply-insert-header-p-never (part hide)
+(defun notmuch-show-reply-insert-header-p-never (_part _hide)
nil)
(defun notmuch-show-reply-insert-header-p-trimmed (part hide)
HIDE determines whether to show or hide the part and the button
as follows: If HIDE is nil, show the part and the button. If HIDE
is t, hide the part initially and show the button."
- (let* ((content-type (downcase (plist-get part :content-type)))
+ (let* ((content-type (plist-get part :content-type))
(mime-type (notmuch-show-mime-type part))
(nth (plist-get part :id))
(long (and (notmuch-match-content-type mime-type "text/*")
(beg (point))
;; This default header-p function omits the part button for
;; the first (or only) part if this is text/plain.
- (button (when (funcall notmuch-show-insert-header-p-function part hide)
- (notmuch-show-insert-part-header nth mime-type content-type
- (plist-get part :filename))))
+ (button (and (funcall notmuch-show-insert-header-p-function part hide)
+ (notmuch-show-insert-part-header
+ nth mime-type
+ (and content-type (downcase content-type))
+ (plist-get part :filename))))
;; Hide the part initially if HIDE is t, or if it is too long
;; and we have a button to allow toggling.
(show-part (not (or (equal hide t)
;; Store the computed mime-type for later use (e.g. by attachment handlers).
(plist-put part :computed-type mime-type)
(if show-part
- (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
+ (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
(when button
(button-put button :notmuch-lazy-part
(list msg part mime-type nth depth button))))
(bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
(setq message-start (point-marker))
(notmuch-show-insert-headerline headers
- (or (if notmuch-show-relative-dates
- (plist-get msg :date_relative)
- nil)
+ (or (and notmuch-show-relative-dates
+ (plist-get msg :date_relative))
(plist-get headers :Date))
(plist-get msg :tags) depth)
(setq content-start (point-marker))
;; 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))
+ (unless (string= notmuch-show-previous-subject bare-subject)
(forward-line 1))
(setq headers-start (point-marker)))
(setq headers-end (point-marker))
(insert "\n"))
(setq content-end (point-marker))
;; Indent according to the depth in the thread.
- (if notmuch-show-indent-content
- (indent-rigidly content-start
- content-end
- (* notmuch-show-indent-messages-width depth)))
+ (when notmuch-show-indent-content
+ (indent-rigidly content-start
+ content-end
+ (* notmuch-show-indent-messages-width depth)))
(setq message-end (point-max-marker))
;; Save the extents of this message over the whole text of the
;; message.
(notmuch-show-message-visible msg (and (plist-get msg :match)
(not (plist-get msg :excluded))))))
+;;; Toggle commands
+
(defun notmuch-show-toggle-process-crypto ()
"Toggle the processing of cryptographic MIME parts."
(interactive)
"Content is not indented."))
(notmuch-show-refresh-view))
+;;; Main insert functions
+
(defun notmuch-show-insert-tree (tree depth)
"Insert the message tree TREE at depth DEPTH in the current thread."
(let ((msg (car tree))
"Insert the forest of threads FOREST."
(mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
+;;; Link buttons
+
(defvar notmuch-id-regexp
(concat
;; Match the id: prefix only if it begins a word (to disallow, for
'help-echo "Mouse-1, RET: search for this message"
'face goto-address-mail-face)))))
+;;; Show command
+
;;;###autoload
(defun notmuch-show (thread-id &optional elide-toggle parent-buffer query-context buffer-name)
"Run \"notmuch show\" with the given thread ID and display results.
(eval (car (get 'mm-inline-override-types 'standard-value))))
(cons "application/*" mm-inline-override-types)
mm-inline-override-types)))
- (switch-to-buffer (get-buffer-create buffer-name))
+ (pop-to-buffer-same-window (get-buffer-create buffer-name))
;; No need to track undo information for this buffer.
(setq buffer-undo-list t)
(notmuch-show-mode)
;; Set various buffer local variables to their appropriate initial
;; state. Do this after enabling `notmuch-show-mode' so that they
;; aren't wiped out.
- (setq notmuch-show-thread-id thread-id
- notmuch-show-parent-buffer parent-buffer
- notmuch-show-query-context (if (or (string= query-context "")
- (string= query-context "*"))
- nil query-context)
- notmuch-show-process-crypto notmuch-crypto-process-mime
- ;; If `elide-toggle', invert the default value.
- notmuch-show-elide-non-matching-messages
+ (setq notmuch-show-thread-id thread-id)
+ (setq notmuch-show-parent-buffer parent-buffer)
+ (setq notmuch-show-query-context
+ (if (or (string= query-context "")
+ (string= query-context "*"))
+ nil
+ query-context))
+ (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
+ ;; If `elide-toggle', invert the default value.
+ (setq notmuch-show-elide-non-matching-messages
(if elide-toggle
(not notmuch-show-only-matching-messages)
notmuch-show-only-matching-messages))
fallback if the prior matches no messages."
(let (queries)
(push (list thread) queries)
- (if context (push (list thread "and (" context ")") queries))
+ (when context
+ (push (list thread "and (" context ")") queries))
queries))
(defun notmuch-show--build-buffer (&optional state)
first relevant message.
If no messages match the query return NIL."
- (let* ((cli-args (cons "--exclude=false"
- (when notmuch-show-elide-non-matching-messages
- (list "--entire-thread=false"))))
+ (let* ((cli-args (list "--exclude=false"))
+ (cli-args (if notmuch-show-elide-non-matching-messages (cons "--entire-thread=false" cli-args) cli-args))
+ ;; "part 0 is the whole message (headers and body)" notmuch-show(1)
+ (cli-args (if notmuch-show-single-message (cons "--part=0" cli-args) cli-args))
(queries (notmuch-show--build-queries
notmuch-show-thread-id notmuch-show-query-context))
(forest nil)
(while (and (not forest) queries)
(setq forest (notmuch-query-get-threads
(append cli-args (list "'") (car queries) (list "'"))))
+ (when (and forest notmuch-show-single-message)
+ (setq forest (list (list (list forest)))))
(setq queries (cdr queries)))
(when forest
(notmuch-show-insert-forest forest)
;; Report back to the caller whether any messages matched.
forest))
+;;; Refresh command
+
(defun notmuch-show-capture-state ()
"Capture the state of the current buffer.
(ding)
(message "Refreshing the buffer resulted in no messages!"))))
+;;; Keymaps
+
(defvar notmuch-show-stash-map
(let ((map (make-sparse-keymap)))
(define-key map "c" 'notmuch-show-stash-cc)
(define-key map "B" 'notmuch-show-browse-urls)
map)
"Keymap for \"notmuch show\" buffers.")
-(fset 'notmuch-show-mode-map notmuch-show-mode-map)
+
+;;; Mode
(define-derived-mode notmuch-show-mode fundamental-mode "notmuch-show"
"Major mode for viewing a thread with notmuch.
\\{notmuch-show-mode-map}"
(setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
- (setq buffer-read-only t
- truncate-lines t)
+ (setq buffer-read-only t)
+ (setq truncate-lines t)
(setq imenu-prev-index-position-function
- #'notmuch-show-imenu-prev-index-position-function)
+ #'notmuch-show-imenu-prev-index-position-function)
(setq imenu-extract-index-name-function
- #'notmuch-show-imenu-extract-index-name-function))
+ #'notmuch-show-imenu-extract-index-name-function))
+
+;;; Tree commands
(defun notmuch-tree-from-show-current-query ()
"Call notmuch tree with the current query."
notmuch-show-query-context
(notmuch-show-get-message-id)))
+;;; Movement related functions.
+
(defun notmuch-show-move-to-message-top ()
(goto-char (notmuch-show-message-top)))
(defun notmuch-show-move-to-message-bottom ()
(goto-char (notmuch-show-message-bottom)))
-(defun notmuch-show-message-adjust ()
- (recenter 0))
-
-;; Movement related functions.
-
;; There's some strangeness here where a text property applied to a
;; region a->b is not found when point is at b. We walk backwards
;; until finding the property.
(cl-loop do (funcall function)
while (notmuch-show-goto-message-next))))
-;; Functions relating to the visibility of messages and their
-;; components.
+;;; Functions relating to the visibility of messages and their components.
(defun notmuch-show-message-visible (props visible-p)
(overlay-put (plist-get props :message-overlay) 'invisible (not visible-p))
(overlay-put (plist-get props :headers-overlay) 'invisible (not visible-p))
(notmuch-show-set-prop :headers-visible visible-p props))
-;; Functions for setting and getting attributes of the current
-;; message.
+;;; Functions for setting and getting attributes of the current message.
(defun notmuch-show-set-message-properties (props)
(save-excursion
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
- (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)))
+ (plist-get (or props
+ (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)))
+ prop))
(defun notmuch-show-get-message-id (&optional bare)
"Return an id: query for the Message-Id of the current message.
(apply 'notmuch-show-tag-message
(notmuch-tag-change-list notmuch-show-mark-read-tags unread))))
-(defun notmuch-show-seen-current-message (start end)
+(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)))
+ (notmuch-show-mark-read)
+ (notmuch-show-set-prop :seen t)))
(defvar notmuch-show--seen-has-errored nil)
(make-variable-buffer-local 'notmuch-show--seen-has-errored)
;; We need to redisplay to get window-start and window-end correct.
(redisplay)
(save-excursion
- (condition-case err
+ (condition-case nil
(funcall notmuch-show-mark-read-function (window-start) (window-end))
((debug error)
(unless notmuch-show--seen-has-errored
- (setq notmuch-show--seen-has-errored 't)
+ (setq notmuch-show--seen-has-errored t)
(setq header-line-format
(concat header-line-format
(propertize
Reshows the current thread with matches defined by the new query-string."
(interactive (list (notmuch-read-query "Filter thread: ")))
(let ((msg-id (notmuch-show-get-message-id)))
- (setq notmuch-show-query-context (if (string= query "") nil query))
+ (setq notmuch-show-query-context (if (string-empty-p query) nil query))
(notmuch-show-refresh-view t)
(notmuch-show-goto-message msg-id)))
-;; Functions for getting attributes of several messages in the current
-;; thread.
+;;; Functions for getting attributes of several messages in the current thread.
(defun notmuch-show-get-message-ids-for-open-messages ()
"Return a list of all id: queries for open messages in the current thread."
(let (message-ids done)
(goto-char (point-min))
(while (not done)
- (if (notmuch-show-message-visible-p)
- (setq message-ids
- (append message-ids (list (notmuch-show-get-message-id)))))
- (setq done (not (notmuch-show-goto-message-next)))
- )
- message-ids
- )))
+ (when (notmuch-show-message-visible-p)
+ (setq message-ids
+ (append message-ids (list (notmuch-show-get-message-id)))))
+ (setq done (not (notmuch-show-goto-message-next))))
+ message-ids)))
-;; Commands typically bound to keys.
+;;; Commands typically bound to keys.
(defun notmuch-show-advance ()
"Advance through thread.
thread from the search from which this thread was originally
shown."
(interactive)
- (if (notmuch-show-advance)
- (notmuch-show-archive-thread-then-next)))
+ (when (notmuch-show-advance)
+ (notmuch-show-archive-thread-then-next)))
(defun notmuch-show-rewind ()
"Backup through the thread (reverse scrolling compared to \
(let ((start-of-message (notmuch-show-message-top))
(start-of-window (window-start)))
(cond
- ;; Either this message is properly aligned with the start of the
- ;; window or the start of this message is not visible on the
- ;; screen - scroll.
+ ;; Either this message is properly aligned with the start of the
+ ;; window or the start of this message is not visible on the
+ ;; screen - scroll.
((or (= start-of-message start-of-window)
(< start-of-message start-of-window))
(scroll-down)
(message-resend addresses)
(notmuch-bury-or-kill-this-buffer)))
+(defun notmuch-show-message-adjust ()
+ (recenter 0))
+
(defun notmuch-show-next-message (&optional pop-at-end)
"Show the next message.
(let* ((id (notmuch-show-get-message-id))
(buf (get-buffer-create (concat "*notmuch-raw-" id "*")))
(inhibit-read-only t))
- (switch-to-buffer buf)
+ (pop-to-buffer-same-window buf)
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
(goto-char (point-min))
(set-buffer-modified-p nil)
(setq buffer-read-only t)
(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)
- (setq buffer-read-only t)
- (unless (zerop exit-code)
- (switch-to-buffer-other-window buf)
- (message (format "Command '%s' exited abnormally with code %d"
- shell-command exit-code))))))))
+ (setq buffer-read-only t)
+ (let ((inhibit-read-only t))
+ (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)
+ (unless (zerop exit-code)
+ (pop-to-buffer buf)
+ (message (format "Command '%s' exited abnormally with code %d"
+ shell-command exit-code)))))))))
(defun notmuch-show-tag-message (&rest tag-changes)
"Change tags for the current message.
(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."
+ "Normalize all ADDRESSES while adding 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
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.")
+ "Copy From/To/Cc of current message to kill-ring.
+Use 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.
+ "Copy From/To/Cc/Message-Id of current message to kill-ring.
+Use 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=<Message-Id>."
(list (notmuch-show-get-message-id t)) "--in-reply-to="))))
" ")))
-;; Interactive part functions and their helpers
+;;; Interactive part functions and their helpers
(defun notmuch-show-generate-part-buffer (msg part)
"Return a temporary buffer containing the specified part's content."
(buf (notmuch-show-generate-part-buffer msg part))
(computed-type (or mime-type (plist-get part :computed-type)))
(filename (plist-get part :filename))
- (disposition (if filename `(attachment (filename . ,filename)))))
+ (disposition (and filename `(attachment (filename . ,filename)))))
(mm-make-handle buf (list computed-type) nil nil disposition)))
(defun notmuch-show-apply-to-current-part-handle (fn &optional mime-type)
is destroyed when FN returns. If MIME-TYPE is given then force
part to be treated as if it had that mime-type."
(let ((handle (notmuch-show-current-part-handle mime-type)))
- ;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
- ;; call it from a temp-buffer, unless
- ;; notmuch-show-attachment-debug is non-nil in which case we put
- ;; it in " *notmuch-part*".
+ ;; Emacs puts stdout/stderr into the calling buffer so we call
+ ;; it from a temp-buffer, unless notmuch-show-attachment-debug
+ ;; is non-nil, in which case we put it in " *notmuch-part*".
(unwind-protect
(if notmuch-show-attachment-debug
(with-current-buffer (generate-new-buffer " *notmuch-part*")
the new buffer."
(let ((buf (get-buffer-create (generate-new-buffer-name
(concat " *notmuch-internal-part*")))))
- (switch-to-buffer buf)
+ (pop-to-buffer-same-window buf)
(if (eq (mm-display-part handle) 'external)
(kill-buffer buf)
(goto-char (point-min))
message."
`(save-excursion
(save-restriction
- (let ((extent (notmuch-show-message-extent)))
- (narrow-to-region (car extent) (cdr extent))
- ,@body))))
+ (let ((extent (notmuch-show-message-extent)))
+ (narrow-to-region (car extent) (cdr extent))
+ ,@body))))
(defun notmuch-show--gather-urls ()
"Gather any URLs in the current message."
(funcall fn (completing-read prompt urls nil nil nil nil (car urls)))
(message "No URLs found."))))
+;;; _
+
(provide 'notmuch-show)
;;; notmuch-show.el ends here