]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-show.el
Merge branch 'release'
[notmuch] / emacs / notmuch-show.el
index e08497d98b089486efd792c702182c2808d984dc..2806879e53cfff412e07c4b175478f8ed728998e 100644 (file)
 (require 'message)
 (require 'mm-decode)
 (require 'mailcap)
+(require 'icalendar)
+(require 'goto-addr)
 
 (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)
@@ -72,7 +75,10 @@ any given message."
   :group 'notmuch
   :type 'hook)
 
-(defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-excerpt-citations)
+(defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines
+                                                notmuch-wash-tidy-citations
+                                                notmuch-wash-elide-blank-lines
+                                                notmuch-wash-excerpt-citations)
   "Functions used to improve the display of text/plain parts."
   :group 'notmuch
   :type 'hook
@@ -82,6 +88,28 @@ any given message."
             notmuch-wash-elide-blank-lines
             notmuch-wash-excerpt-citations))
 
+;; Mostly useful for debugging.
+(defcustom notmuch-show-all-multipart/alternative-parts t
+  "Should all parts of multipart/alternative parts be shown?"
+  :group 'notmuch
+  :type 'boolean)
+
+(defcustom notmuch-show-indent-messages-width 1
+  "Width of message indentation in threads.
+
+Messages are shown indented according to their depth in a thread.
+This variable determines the width of this indentation measured
+in number of blanks.  Defaults to `1', choose `0' to disable
+indentation."
+  :group 'notmuch
+  :type 'integer)
+
+(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
@@ -96,7 +124,7 @@ any given message."
   "Use external viewers to view all attachments from the current message."
   (interactive)
   (with-current-notmuch-show-message
-   ; We ovverride the mm-inline-media-tests to indicate which message
+   ; We override the mm-inline-media-tests to indicate which message
    ; parts are already sufficiently handled by the original
    ; presentation of the message in notmuch-show mode. These parts
    ; will be inserted directly into the temporary buffer of
@@ -198,12 +226,33 @@ any given message."
                                             'face 'notmuch-tag-face)
                                 ")"))))))
 
+(defun notmuch-show-clean-address (address)
+  "Try to clean a single email ADDRESS for display.  Return
+unchanged ADDRESS if parsing fails."
+  (condition-case nil
+    (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 <foo@bar.com>' then show just
+      ;; 'foo@bar.com'.
+      (when (string= name address)
+        (setq name nil))
+
+      (if (not name)
+        address
+        (concat name " <" address ">")))
+    (error 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)
+    (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
+           (notmuch-show-clean-address (plist-get headers :From))
            " ("
            date
            ") ("
@@ -219,12 +268,12 @@ message at DEPTH in the current thread."
 (defun notmuch-show-insert-headers (headers)
   "Insert the headers of the current message."
   (let ((start (point)))
-    (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))))
+    (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))))
          notmuch-message-headers)
     (save-excursion
       (save-restriction
@@ -236,90 +285,260 @@ 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)
-  (insert-button
-   (concat "[ "
-          (if name (concat name ": ") "")
-          declared-type
-          (if (not (string-equal declared-type content-type))
-              (concat " (as " content-type ")")
-            "")
-          " ]\n")
-   :type 'notmuch-show-part-button-type
-   :notmuch-part nth
-   :notmuch-filename name))
+(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
+  (let ((button))
+    (setq button
+         (insert-button
+          (concat "[ "
+                  (if name (concat name ": ") "")
+                  declared-type
+                  (if (not (string-equal declared-type content-type))
+                      (concat " (as " content-type ")")
+                    "")
+                  (or comment "")
+                  " ]")
+          :type 'notmuch-show-part-button-type
+          :notmuch-part nth
+          :notmuch-filename name))
+    (insert "\n")
+    ;; return button
+    button))
 
 ;; 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))))
-
-(defun notmuch-show-mm-display-part-inline (msg part content-type content)
+  (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)))
+       ;; Don't re-compress .gz & al.  Arguably we should make
+       ;; `file-name-handler-alist' nil, but that would chop
+       ;; ange-ftp, which is reasonable to use here.
+       (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t)))))
+
+(defun notmuch-show-mm-display-part-inline (msg part nth content-type)
   "Use the mm-decode/mm-view functions to display a part in the
 current buffer, if possible."
   (let ((display-buffer (current-buffer)))
     (with-temp-buffer
-      (insert content)
       (let ((handle (mm-make-handle (current-buffer) (list content-type))))
-       (set-buffer display-buffer)
        (if (and (mm-inlinable-p handle)
                 (mm-inlined-p handle))
-           (progn
+           (let ((content (notmuch-show-get-bodypart-content msg part nth)))
+             (insert content)
+             (set-buffer display-buffer)
              (mm-display-part handle)
              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)
-  (let ((inner-parts (plist-get part :content)))
-    (notmuch-show-insert-part-header nth declared-type content-type nil)
-    ;; In most cases, multipart/alternative is used to provide both
-    ;; text/plain and text/html (or multipart/related with text/html
-    ;; and image/*) parts. We might allow the user to express a
-    ;; preference about which part to show, but for the moment we just
-    ;; choose the first. This is usually the text/plain part.
+  (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/signed (msg part content-type nth depth declared-type)
+  (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
+    (button-put button 'face 'notmuch-crypto-part-header)
+    ;; add signature status button if sigstatus provided
+    (if (plist-member part :sigstatus)
+       (let* ((from (notmuch-show-get-header :From msg))
+              (sigstatus (car (plist-get part :sigstatus))))
+         (notmuch-crypto-insert-sigstatus-button sigstatus from))
+      ;; if we're not adding sigstatus, tell the user how they can get it
+      (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts.")))
+
+  (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-multipart/encrypted (msg part content-type nth depth declared-type)
+  (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
+    (button-put button 'face 'notmuch-crypto-part-header)
+    ;; 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* ((from (notmuch-show-get-header :From msg))
+                    (sigstatus (car (plist-get part :sigstatus))))
+               (notmuch-crypto-insert-sigstatus-button sigstatus from))))
+      ;; if we're not adding encstatus, tell the user how they can get it
+      (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts.")))
+
+  (let ((inner-parts (plist-get part :content))
+       (start (point)))
+    ;; Show all of the parts.
     (mapc (lambda (inner-part)
-           (let ((inner-type (concat (plist-get inner-part :content-type) " (not shown)")))
-             (notmuch-show-insert-part-header (plist-get inner-part :id) inner-type inner-type nil)))
-         (cdr inner-parts)))
+           (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-multipart/* (msg part content-type nth depth declared-type)
-  (let ((inner-parts (plist-get part :content)))
-    (notmuch-show-insert-part-header nth declared-type content-type nil)
+  (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))
+         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)
+  (notmuch-show-insert-part-header nth declared-type content-type nil)
+  (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 part :headers)))
+      (notmuch-show-insert-headers (plist-get message :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))
+    ;; 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 declared-type)
@@ -332,7 +551,25 @@ current buffer, if possible."
     (save-excursion
       (save-restriction
        (narrow-to-region start (point-max))
-       (run-hook-with-args 'notmuch-show-insert-text/plain-hook depth))))
+       (run-hook-with-args 'notmuch-show-insert-text/plain-hook msg 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)
@@ -351,12 +588,14 @@ current buffer, if possible."
                nil))
          nil))))
 
+;; Handler for wash generated inline patch fake parts.
+(defun notmuch-show-insert-part-inline-patch-fake-part (msg part content-type nth depth declared-type)
+  (notmuch-show-insert-part-*/* msg part "text/x-diff" nth depth "inline patch"))
+
 (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))
-  (let ((content (notmuch-show-get-bodypart-content msg part nth)))
-    (if content
-       (notmuch-show-mm-display-part-inline msg part content-type content)))
+  (notmuch-show-mm-display-part-inline msg part nth content-type)
   t)
 
 ;; Functions for determining how to handle MIME parts.
@@ -381,13 +620,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)
@@ -418,7 +664,7 @@ current buffer, if possible."
 
 (defun notmuch-show-insert-body (msg body depth)
   "Insert the body BODY at depth DEPTH in the current thread."
-  (mapc '(lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
+  (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
 
 (defun notmuch-show-make-symbol (type)
   (make-symbol (concat "notmuch-show-" type)))
@@ -469,6 +715,9 @@ current buffer, if possible."
 
     (setq content-start (point-marker))
 
+    (plist-put msg :headers-invis-spec headers-invis-spec)
+    (plist-put msg :message-invis-spec message-invis-spec)
+
     ;; Set `headers-start' to point after the 'Subject:' header to be
     ;; compatible with the existing implementation. This just sets it
     ;; to after the first header.
@@ -497,7 +746,7 @@ current buffer, if possible."
     (setq content-end (point-marker))
 
     ;; Indent according to the depth in the thread.
-    (indent-rigidly content-start content-end depth)
+    (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth))
 
     (setq message-end (point-max-marker))
 
@@ -505,10 +754,10 @@ current buffer, if possible."
     ;; message.
     (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
 
-    (plist-put msg :headers-invis-spec headers-invis-spec)
-    (overlay-put (make-overlay headers-start headers-end) 'invisible headers-invis-spec)
-
-    (plist-put msg :message-invis-spec message-invis-spec)
+    (let ((headers-overlay (make-overlay headers-start headers-end))
+          (invis-specs (list headers-invis-spec message-invis-spec)))
+      (overlay-put headers-overlay 'invisible invis-specs)
+      (overlay-put headers-overlay 'priority 10))
     (overlay-put (make-overlay body-start body-end) 'invisible message-invis-spec)
 
     ;; Save the properties for this message. Currently this saves the
@@ -533,16 +782,41 @@ current buffer, if possible."
 
 (defun notmuch-show-insert-thread (thread depth)
   "Insert the thread THREAD at depth DEPTH in the current forest."
-  (mapc '(lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
+  (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
 
 (defun notmuch-show-insert-forest (forest)
   "Insert the forest of threads FOREST."
-  (mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
+  (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
 
+(defvar notmuch-show-thread-id nil)
+(make-variable-buffer-local 'notmuch-show-thread-id)
 (defvar notmuch-show-parent-buffer nil)
+(make-variable-buffer-local 'notmuch-show-parent-buffer)
+(defvar notmuch-show-query-context nil)
+(make-variable-buffer-local 'notmuch-show-query-context)
+(defvar notmuch-show-buffer-name nil)
+(make-variable-buffer-local 'notmuch-show-buffer-name)
+
+(defun notmuch-show-buttonise-links (start end)
+  "Buttonise URLs and mail addresses between START and END.
+
+This also turns id:\"<message id>\"-parts into buttons for
+a corresponding notmuch search."
+  (goto-address-fontify-region start end)
+  (save-excursion
+    (goto-char start)
+    (while (re-search-forward "id:\\(\"?\\)[^[:space:]\"]+\\1" end t)
+      ;; remove the overlay created by goto-address-mode
+      (remove-overlays (match-beginning 0) (match-end 0) 'goto-address t)
+      (make-text-button (match-beginning 0) (match-end 0)
+                       'action `(lambda (arg)
+                                  (notmuch-show ,(match-string-no-properties 0)))
+                       'follow-link t
+                       'help-echo "Mouse-1, RET: search for this message"
+                       'face goto-address-mail-face))))
 
 ;;;###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
@@ -554,18 +828,37 @@ The optional QUERY-CONTEXT is a notmuch search term. Only
 messages from the thread matching this search term are shown if
 non-nil.
 
-The optional BUFFER-NAME provides the neame of the buffer in
+The optional BUFFER-NAME provides the name of the buffer in
 which the message thread is shown. If it is nil (which occurs
 when the command is called interactively) the argument to the
-function is used. "
+function is used.
+
+The optional CRYPTO-SWITCH toggles the value of the
+notmuch-crypto-process-mime customization variable for this show
+buffer."
   (interactive "sNotmuch show: ")
-  (let ((buffer (get-buffer-create (generate-new-buffer-name
-                                   (or buffer-name
-                                       (concat "*notmuch-" thread-id "*")))))
-       (inhibit-read-only t))
+  (let* ((process-crypto (if crypto-switch
+                            (not notmuch-crypto-process-mime)
+                          notmuch-crypto-process-mime)))
+    (notmuch-show-worker thread-id parent-buffer query-context buffer-name process-crypto)))
+
+(defun notmuch-show-worker (thread-id parent-buffer query-context buffer-name process-crypto)
+  (let* ((buffer-name (generate-new-buffer-name
+                      (or buffer-name
+                          (concat "*notmuch-" thread-id "*"))))
+        (buffer (get-buffer-create buffer-name))
+        (inhibit-read-only t))
     (switch-to-buffer buffer)
     (notmuch-show-mode)
-    (set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer)
+    ;; Don't track undo information for this buffer
+    (set 'buffer-undo-list t)
+
+    (setq notmuch-show-thread-id thread-id)
+    (setq notmuch-show-parent-buffer parent-buffer)
+    (setq notmuch-show-query-context query-context)
+    (setq notmuch-show-buffer-name buffer-name)
+    (setq notmuch-show-process-crypto process-crypto)
+
     (erase-buffer)
     (goto-char (point-min))
     (save-excursion
@@ -581,9 +874,8 @@ function is used. "
          (notmuch-show-insert-forest
           (notmuch-query-get-threads basic-args))))
 
-      ;; Enable buttonisation of URLs and email addresses in the
-      ;; buffer.
-      (goto-address-mode t)
+      (jit-lock-register #'notmuch-show-buttonise-links)
+
       ;; Act on visual lines rather than logical lines.
       (visual-line-mode t)
 
@@ -598,6 +890,22 @@ function is used. "
 
     (notmuch-show-mark-read)))
 
+(defun notmuch-show-refresh-view (&optional crypto-switch)
+  "Refresh the current view (with crypto switch if prefix given).
+
+Kills the current buffer and reruns notmuch show with the same
+thread id.  If a prefix is given, crypto processing is toggled."
+  (interactive "P")
+  (let ((thread-id notmuch-show-thread-id)
+       (parent-buffer notmuch-show-parent-buffer)
+       (query-context notmuch-show-query-context)
+       (buffer-name notmuch-show-buffer-name)
+       (process-crypto (if crypto-switch
+                           (not notmuch-show-process-crypto)
+                         notmuch-show-process-crypto)))
+    (notmuch-kill-this-buffer)
+    (notmuch-show-worker thread-id parent-buffer query-context buffer-name process-crypto)))
+
 (defvar notmuch-show-stash-map
   (let ((map (make-sparse-keymap)))
     (define-key map "c" 'notmuch-show-stash-cc)
@@ -605,6 +913,7 @@ function is used. "
     (define-key map "F" 'notmuch-show-stash-filename)
     (define-key map "f" 'notmuch-show-stash-from)
     (define-key map "i" 'notmuch-show-stash-message-id)
+    (define-key map "I" 'notmuch-show-stash-message-id-stripped)
     (define-key map "s" 'notmuch-show-stash-subject)
     (define-key map "T" 'notmuch-show-stash-tags)
     (define-key map "t" 'notmuch-show-stash-to)
@@ -621,7 +930,7 @@ function is used. "
        (define-key map (kbd "<backtab>") '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)
@@ -629,6 +938,7 @@ function is used. "
        (define-key map "V" 'notmuch-show-view-raw-message)
        (define-key map "v" 'notmuch-show-view-all-mime-parts)
        (define-key map "c" 'notmuch-show-stash-map)
+       (define-key map "=" 'notmuch-show-refresh-view)
        (define-key map "h" 'notmuch-show-toggle-headers)
        (define-key map "-" 'notmuch-show-remove-tag)
        (define-key map "+" 'notmuch-show-add-tag)
@@ -668,7 +978,7 @@ more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-
 without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread
 without scrolling through with \\[notmuch-show-advance-and-archive]).
 
-You can add or remove arbitary tags from the current message with
+You can add or remove arbitrary tags from the current message with
 '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
 
 All currently available key bindings:
@@ -724,14 +1034,6 @@ All currently available key bindings:
     (notmuch-show-move-to-message-top)
     t))
 
-(defun notmuch-show-move-past-invisible-forward ()
-  (while (point-invisible-p)
-    (forward-char)))
-
-(defun notmuch-show-move-past-invisible-backward ()
-  (while (point-invisible-p)
-    (backward-char)))
-
 ;; Functions relating to the visibility of messages and their
 ;; components.
 
@@ -742,20 +1044,11 @@ All currently available key bindings:
       (add-to-invisibility-spec spec))))
 
 (defun notmuch-show-message-visible (props visible-p)
-  (if visible-p
-      ;; When making the message visible, the headers may or not be
-      ;; visible. So we check that property separately.
-      (let ((headers-visible (plist-get props :headers-visible)))
-       (notmuch-show-element-visible props headers-visible :headers-invis-spec)
-       (notmuch-show-element-visible props t :message-invis-spec))
-    (notmuch-show-element-visible props nil :headers-invis-spec)
-    (notmuch-show-element-visible props nil :message-invis-spec))
-
+  (notmuch-show-element-visible props visible-p :message-invis-spec)
   (notmuch-show-set-prop :message-visible visible-p props))
 
 (defun notmuch-show-headers-visible (props visible-p)
-  (if (plist-get props :message-visible)
-      (notmuch-show-element-visible props visible-p :headers-invis-spec))
+  (notmuch-show-element-visible props visible-p :headers-invis-spec)
   (notmuch-show-set-prop :headers-visible visible-p props))
 
 ;; Functions for setting and getting attributes of the current
@@ -767,6 +1060,12 @@ All currently available key bindings:
     (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
 
 (defun notmuch-show-get-message-properties ()
+  "Return the properties of the current message as a plist.
+
+Some useful entries are:
+:headers - Property list containing the headers :Date, :Subject, :From, etc.
+:body - Body of the message
+:tags - Tags for this message"
   (save-excursion
     (notmuch-show-move-to-message-top)
     (get-text-property (point) :notmuch-message-properties)))
@@ -793,9 +1092,9 @@ All currently available key bindings:
   "Return the filename of the current message."
   (notmuch-show-get-prop :filename))
 
-(defun notmuch-show-get-header (header)
+(defun notmuch-show-get-header (header &optional props)
   "Return the named header of the current message, if any."
-  (plist-get (notmuch-show-get-prop :headers) header))
+  (plist-get (notmuch-show-get-prop :headers props) header))
 
 (defun notmuch-show-get-cc ()
   (notmuch-show-get-header :Cc))
@@ -851,35 +1150,29 @@ All currently available key bindings:
 
 ;; Commands typically bound to keys.
 
-(defun notmuch-show-advance-and-archive ()
-  "Advance through thread and archive.
-
-This command is intended to be one of the simplest ways to
-process a thread of email. It does the following:
+(defun notmuch-show-advance ()
+  "Advance through thread.
 
 If the current message in the thread is not yet fully visible,
 scroll by a near screenful to read more of the message.
 
 Otherwise, (the end of the current message is already within the
-current window), advance to the next open message.
-
-Finally, if there is no further message to advance to, and this
-last message is already read, then archive the entire current
-thread, (remove the \"inbox\" tag from each message). Also kill
-this buffer, and display the next thread from the search from
-which this thread was originally shown."
+current window), advance to the next open message."
   (interactive)
-  (let ((end-of-this-message (notmuch-show-message-bottom)))
+  (let* ((end-of-this-message (notmuch-show-message-bottom))
+        (visible-end-of-this-message (1- end-of-this-message))
+        (ret nil))
+    (while (invisible-p visible-end-of-this-message)
+      (setq visible-end-of-this-message
+           (max (point-min)
+                (1- (previous-single-char-property-change
+                     visible-end-of-this-message 'invisible)))))
     (cond
      ;; Ideally we would test `end-of-this-message' against the result
      ;; of `window-end', but that doesn't account for the fact that
-     ;; the end of the message might be hidden, so we have to actually
-     ;; go to the end, walk back over invisible text and then see if
-     ;; point is visible.
-     ((save-excursion
-       (goto-char (- end-of-this-message 1))
-       (notmuch-show-move-past-invisible-backward)
-       (> (point) (window-end)))
+     ;; the end of the message might be hidden.
+     ((and visible-end-of-this-message
+          (> visible-end-of-this-message (window-end)))
       ;; The bottom of this message is not visible - scroll.
       (scroll-up nil))
 
@@ -888,8 +1181,24 @@ which this thread was originally shown."
       (notmuch-show-next-open-message))
 
      (t
-      ;; This is the last message - archive the thread.
-      (notmuch-show-archive-thread)))))
+      ;; This is the last message - change the return value
+      (setq ret t)))
+    ret))
+
+(defun notmuch-show-advance-and-archive ()
+  "Advance through thread and archive.
+
+This command is intended to be one of the simplest ways to
+process a thread of email. It works exactly like
+notmuch-show-advance, in that it scrolls through messages in a
+show buffer, except that when it gets to the end of the buffer it
+archives the entire current thread, (remove the \"inbox\" tag
+from each message), kills the buffer, and displays the next
+thread from the search from which this thread was originally
+shown."
+  (interactive)
+  (if (notmuch-show-advance)
+      (notmuch-show-archive-thread)))
 
 (defun notmuch-show-rewind ()
   "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
@@ -927,16 +1236,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."
@@ -1047,10 +1356,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)
@@ -1063,10 +1370,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 ()
@@ -1168,6 +1473,11 @@ buffer."
   (interactive)
   (notmuch-common-do-stash (notmuch-show-get-message-id)))
 
+(defun notmuch-show-stash-message-id-stripped ()
+  "Copy message ID of current message (sans `id:' prefix) to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (substring (notmuch-show-get-message-id) 4 -1)))
+
 (defun notmuch-show-stash-subject ()
   "Copy Subject field of current message to kill-ring."
   (interactive)