]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-show.el
emacs: Shorten long lines
[notmuch] / emacs / notmuch-show.el
index a0a58373aa675a330dc57c04f94bcde622a0f53e..41f31c4653e3fed5c0b6a8351c4ecfa9e93ef26c 100644 (file)
 
 ;;; Code:
 
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile
+  (require 'cl-lib)
+  (require 'pcase))
+
 (require 'mm-view)
 (require 'message)
 (require 'mm-decode)
 (require 'mm-view)
 (require 'message)
 (require 'mm-decode)
 (declare-function notmuch-count-attachments "notmuch" (mm-handle))
 (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp))
 (declare-function notmuch-tree "notmuch-tree"
 (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))
+                 (&optional query query-context target buffer-name
+                            open-target unthreaded))
 (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
 (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-read-query "notmuch" (prompt))
 (declare-function notmuch-draft-resume "notmuch-draft" (id))
 
 (declare-function notmuch-read-query "notmuch" (prompt))
 (declare-function notmuch-draft-resume "notmuch-draft" (id))
 
@@ -90,10 +96,11 @@ visible for any given message."
   :group 'notmuch-show
   :group 'notmuch-hooks)
 
   :group 'notmuch-show
   :group 'notmuch-hooks)
 
-(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)
+(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."
   :type 'hook
   :options '(notmuch-wash-convert-inline-patch-to-part
   "Functions used to improve the display of text/plain parts."
   :type 'hook
   :options '(notmuch-wash-convert-inline-patch-to-part
@@ -174,7 +181,7 @@ indentation."
 (make-variable-buffer-local 'notmuch-show-indent-content)
 
 (defvar notmuch-show-attachment-debug nil
 (make-variable-buffer-local 'notmuch-show-indent-content)
 
 (defvar notmuch-show-attachment-debug nil
-  "If t log stdout and stderr from attachment handlers
+  "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
 
 When set to nil (the default) stdout and stderr from attachment
 handlers is discarded. When set to t the stdout and stderr from
@@ -261,7 +268,7 @@ position of the message in the thread."
   :group 'notmuch-show)
 
 (defmacro with-current-notmuch-show-message (&rest body)
   :group 'notmuch-show)
 
 (defmacro with-current-notmuch-show-message (&rest body)
-  "Evaluate body with current buffer set to the text of current message"
+  "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 "*"))))
   `(save-excursion
      (let ((id (notmuch-show-get-message-id)))
        (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
@@ -343,7 +350,9 @@ operation on the contents of the current buffer."
     (with-temp-buffer
       (insert all)
       (if indenting
     (with-temp-buffer
       (insert all)
       (if indenting
-         (indent-rigidly (point-min) (point-max) (- (* notmuch-show-indent-messages-width depth))))
+         (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)
       ;; Remove the original header.
       (goto-char (point-min))
       (re-search-forward "^$" (point-max) nil)
@@ -390,7 +399,9 @@ operation on the contents of the current buffer."
     (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
        (let ((inhibit-read-only t))
          (replace-match (concat "("
     (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))
+                                (notmuch-tag-format-tags
+                                 tags
+                                 (notmuch-show-get-prop :orig-tags))
                                 ")"))))))
 
 (defun notmuch-clean-address (address)
                                 ")"))))))
 
 (defun notmuch-clean-address (address)
@@ -427,17 +438,16 @@ parsing fails."
        (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
 
        ;; Outer single and double quotes, which might be nested.
        (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
 
        ;; Outer single and double quotes, which might be nested.
-       (loop
-        with start-of-loop
-        do (setq start-of-loop p-name)
+       (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))
 
 
-        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)))
+                until (string= start-of-loop p-name)))
 
       ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
       ;; 'foo@bar.com'.
 
       ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
       ;; 'foo@bar.com'.
@@ -462,16 +472,23 @@ unchanged ADDRESS if parsing fails."
 (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."
 (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)))
+  (let ((start (point))
+       (from (notmuch-sanitize
+              (notmuch-show-clean-address (plist-get headers :From)))))
+    (when (string-match "\\cR" from)
+      ;; If the From header has a right-to-left character add
+      ;; 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 (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
-           (notmuch-sanitize
-            (notmuch-show-clean-address (plist-get headers :From)))
+           from
            " ("
            date
            ") ("
            (notmuch-tag-format-tags tags tags)
            ")\n")
            " ("
            date
            ") ("
            (notmuch-tag-format-tags tags tags)
            ")\n")
-    (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
+    (overlay-put (make-overlay start (point))
+                'face 'notmuch-message-summary-face)))
 
 (defun notmuch-show-insert-header (header header-value)
   "Insert a single header."
 
 (defun notmuch-show-insert-header (header header-value)
   "Insert a single header."
@@ -498,7 +515,8 @@ message at DEPTH in the current thread."
   'face 'message-mml
   :supertype 'notmuch-button-type)
 
   'face 'message-mml
   :supertype 'notmuch-button-type)
 
-(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
+(defun notmuch-show-insert-part-header (nth content-type declared-type
+                                           &optional name comment)
   (let ((button)
        (base-label (concat (when name (concat name ": "))
                            declared-type
   (let ((button)
        (base-label (concat (when name (concat name ": "))
                            declared-type
@@ -522,8 +540,9 @@ message at DEPTH in the current thread."
     (when button
       (let ((overlay (button-get button 'overlay))
            (lazy-part (button-get button :notmuch-lazy-part)))
     (when button
       (let ((overlay (button-get button 'overlay))
            (lazy-part (button-get button :notmuch-lazy-part)))
-       ;; We have a part to toggle if there is an overlay or if there is a lazy part.
-       ;; If neither is present we cannot toggle the part so we just return nil.
+       ;; We have a part to toggle if there is an overlay or if there
+       ;; is a lazy part.  If neither is present we cannot toggle the
+       ;; part so we just return nil.
        (when (or overlay lazy-part)
          (let* ((show (button-get button :notmuch-part-hidden))
                 (new-start (button-start button))
        (when (or overlay lazy-part)
          (let* ((show (button-get button :notmuch-part-hidden))
                 (new-start (button-start button))
@@ -571,13 +590,13 @@ message at DEPTH in the current thread."
   ;; Recurse on sub-parts
   (let ((ctype (notmuch-split-content-type
                (downcase (plist-get part :content-type)))))
   ;; Recurse on sub-parts
   (let ((ctype (notmuch-split-content-type
                (downcase (plist-get part :content-type)))))
-    (cond ((equal (first ctype) "multipart")
+    (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
           (mapc (apply-partially #'notmuch-show--register-cids msg)
                 (plist-get part :content)))
          ((equal ctype '("message" "rfc822"))
           (notmuch-show--register-cids
            msg
-           (first (plist-get (first (plist-get part :content)) :body)))))))
+           (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.
 
 (defun notmuch-show--get-cid-content (cid)
   "Return a list (CID-content content-type) or nil.
@@ -588,8 +607,8 @@ 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
 will return nil if the CID is unknown or cannot be retrieved."
   (let ((descriptor (cdr (assoc cid notmuch-show--cids))))
     (when descriptor
-      (let* ((msg (first descriptor))
-            (part (second 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
             ;; Request caching for this content, as some messages
             ;; reference the same cid: part many times (hundreds!).
             (content (notmuch-get-bodypart-binary
@@ -604,7 +623,7 @@ will return nil if the CID is unknown or cannot be retrieved."
     (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)))
     (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)))
-  (setq mm-inline-text-html-with-images t))
+  (setq mm-html-inhibit-images nil))
 
 (defvar w3m-current-buffer) ;; From `w3m.el'.
 (defun notmuch-show--cid-w3m-retrieve (url &rest args)
 
 (defvar w3m-current-buffer) ;; From `w3m.el'.
 (defun notmuch-show--cid-w3m-retrieve (url &rest args)
@@ -614,8 +633,8 @@ will return nil if the CID is unknown or cannot be retrieved."
          (with-current-buffer w3m-current-buffer
            (notmuch-show--get-cid-content cid))))
     (when content-and-type
          (with-current-buffer w3m-current-buffer
            (notmuch-show--get-cid-content cid))))
     (when content-and-type
-      (insert (first content-and-type))
-      (second content-and-type))))
+      (insert (car content-and-type))
+      (cadr content-and-type))))
 
 ;; MIME part renderers
 
 
 ;; MIME part renderers
 
@@ -624,7 +643,8 @@ will return nil if the CID is unknown or cannot be retrieved."
          (plist-get part :content)))
 
 (defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
          (plist-get part :content)))
 
 (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))))
+  (let ((chosen-type (car (notmuch-multipart/alternative-choose
+                          msg (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,
        (inner-parts (plist-get part :content))
        (start (point)))
     ;; This inserts all parts of the chosen type rather than just one,
@@ -760,7 +780,8 @@ will return nil if the CID is unknown or cannot be retrieved."
              (unwind-protect
                  (progn
                    (unless (icalendar-import-buffer file t)
              (unwind-protect
                  (progn
                    (unless (icalendar-import-buffer file t)
-                     (error "Icalendar import error. See *icalendar-errors* for more information"))
+                     (error "Icalendar import error. %s"
+                            "See *icalendar-errors* for more information"))
                    (set-buffer (get-file-buffer file))
                    (setq result (buffer-substring (point-min) (point-max)))
                    (set-buffer-modified-p nil)
                    (set-buffer (get-file-buffer file))
                    (setq result (buffer-substring (point-min) (point-max)))
                    (set-buffer-modified-p nil)
@@ -778,12 +799,15 @@ will return nil if the CID is unknown or cannot be retrieved."
     ;;
     ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
     ;; (see notmuch-show-handlers-for)
     ;;
     ;; 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).
+    (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)
       (require 'enriched)
-      (letf (((symbol-function 'enriched-decode-display-prop)
+      (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))))
 
                 (lambda (start end &optional param) (list start end))))
        (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
 
@@ -841,7 +865,7 @@ will return nil if the CID is unknown or cannot be retrieved."
           ;; shr strips the "cid:" part of URL, but doesn't
           ;; URL-decode it (see RFC 2392).
           (let ((cid (url-unhex-string url)))
           ;; shr strips the "cid:" part of URL, but doesn't
           ;; URL-decode it (see RFC 2392).
           (let ((cid (url-unhex-string url)))
-            (first (notmuch-show--get-cid-content cid))))))
+            (car (notmuch-show--get-cid-content cid))))))
     (shr-insert-document dom)
     t))
 
     (shr-insert-document dom)
     t))
 
@@ -871,18 +895,19 @@ will return nil if the CID is unknown or cannot be retrieved."
 
 (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
   ;; Run the handlers until one of them succeeds.
 
 (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
   ;; Run the handlers until one of them succeeds.
-  (loop for handler in (notmuch-show-handlers-for content-type)
-       until (condition-case err
-                 (funcall handler msg part content-type nth depth button)
-               ;; Specifying `debug' here lets the debugger run if
-               ;; `debug-on-error' is non-nil.
-               ((debug error)
-                (insert "!!! Bodypart handler `" (prin1-to-string handler) "' threw an error:\n"
-                        "!!! " (error-message-string err) "\n")
-                nil))))
+  (cl-loop for handler in (notmuch-show-handlers-for content-type)
+          until (condition-case err
+                    (funcall handler msg part content-type nth depth button)
+                  ;; Specifying `debug' here lets the debugger run if
+                  ;; `debug-on-error' is non-nil.
+                  ((debug error)
+                   (insert "!!! Bodypart handler `" (prin1-to-string handler)
+                           "' threw an error:\n"
+                           "!!! " (error-message-string err) "\n")
+                   nil))))
 
 (defun notmuch-show-create-part-overlays (button beg end)
 
 (defun notmuch-show-create-part-overlays (button beg end)
-  "Add an overlay to the part between BEG and END"
+  "Add an overlay to the part between BEG and END."
 
   ;; If there is no button (i.e., the part is text/plain and the first
   ;; part) or if the part has no content then we don't make the part
 
   ;; If there is no button (i.e., the part is text/plain and the first
   ;; part) or if the part has no content then we don't make the part
@@ -893,7 +918,7 @@ will return nil if the CID is unknown or cannot be retrieved."
     t))
 
 (defun notmuch-show-record-part-information (part beg end)
     t))
 
 (defun notmuch-show-record-part-information (part beg end)
-  "Store PART as a text property from BEG to END"
+  "Store PART as a text property from BEG to END."
 
   ;; Record part information.  Since we already inserted subparts,
   ;; don't override existing :notmuch-part properties.
 
   ;; Record part information.  Since we already inserted subparts,
   ;; don't override existing :notmuch-part properties.
@@ -905,13 +930,15 @@ will return nil if the CID is unknown or cannot be retrieved."
   ;; watch out for sticky specs of t, which means all properties are
   ;; front-sticky/rear-nonsticky.
   (notmuch-map-text-property beg end 'front-sticky
   ;; watch out for sticky specs of t, which means all properties are
   ;; front-sticky/rear-nonsticky.
   (notmuch-map-text-property beg end 'front-sticky
-                            (lambda (v) (if (listp v)
-                                            (pushnew :notmuch-part v)
-                                          v)))
+                            (lambda (v)
+                              (if (listp v)
+                                  (cl-pushnew :notmuch-part v)
+                                v)))
   (notmuch-map-text-property beg end 'rear-nonsticky
   (notmuch-map-text-property beg end 'rear-nonsticky
-                            (lambda (v) (if (listp v)
-                                            (pushnew :notmuch-part v)
-                                          v))))
+                            (lambda (v)
+                              (if (listp v)
+                                  (cl-pushnew :notmuch-part v)
+                                v))))
 
 (defun notmuch-show-lazy-part (part-args button)
   ;; Insert the lazy part after the button for the part. We would just
 
 (defun notmuch-show-lazy-part (part-args button)
   ;; Insert the lazy part after the button for the part. We would just
@@ -936,10 +963,12 @@ will return nil if the CID is unknown or cannot be retrieved."
        (narrow-to-region part-beg part-end)
        (delete-region part-beg part-end)
        (apply #'notmuch-show-insert-bodypart-internal part-args)
        (narrow-to-region part-beg part-end)
        (delete-region part-beg part-end)
        (apply #'notmuch-show-insert-bodypart-internal part-args)
-       (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth)))
+       (indent-rigidly part-beg
+                       part-end
+                       (* notmuch-show-indent-messages-width depth)))
       (goto-char part-end)
       (delete-char 1)
       (goto-char part-end)
       (delete-char 1)
-      (notmuch-show-record-part-information (second part-args)
+      (notmuch-show-record-part-information (cadr part-args)
                                            (button-start button)
                                            part-end)
       ;; Create the overlay. If the lazy-part turned out to be empty/not
                                            (button-start button)
                                            part-end)
       ;; Create the overlay. If the lazy-part turned out to be empty/not
@@ -994,12 +1023,14 @@ is t, hide the part initially and show the button."
         (nth (plist-get part :id))
         (long (and (notmuch-match-content-type mime-type "text/*")
                    (> notmuch-show-max-text-part-size 0)
         (nth (plist-get part :id))
         (long (and (notmuch-match-content-type mime-type "text/*")
                    (> notmuch-show-max-text-part-size 0)
-                   (> (length (plist-get part :content)) notmuch-show-max-text-part-size)))
+                   (> (length (plist-get part :content))
+                      notmuch-show-max-text-part-size)))
         (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)
         (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))))
+                  (notmuch-show-insert-part-header nth mime-type 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)
         ;; 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)
@@ -1035,7 +1066,7 @@ is t, hide the part initially and show the button."
   ;; Register all content IDs for this message.  According to RFC
   ;; 2392, content IDs are *global*, but it's okay if an MUA treats
   ;; them as only global within a message.
   ;; Register all content IDs for this message.  According to RFC
   ;; 2392, content IDs are *global*, but it's okay if an MUA treats
   ;; them as only global within a message.
-  (notmuch-show--register-cids msg (first body))
+  (notmuch-show--register-cids msg (car body))
 
   (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
 
 
   (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
 
@@ -1097,13 +1128,17 @@ is t, hide the part initially and show the button."
 
     ;; Indent according to the depth in the thread.
     (if notmuch-show-indent-content
 
     ;; 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)))
+       (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.
 
     (setq message-end (point-max-marker))
 
     ;; Save the extents of this message over the whole text of the
     ;; message.
-    (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
+    (put-text-property message-start message-end
+                      :notmuch-message-extent
+                      (cons message-start message-end))
 
     ;; Create overlays used to control visibility
     (plist-put msg :headers-overlay (make-overlay headers-start headers-end))
 
     ;; Create overlays used to control visibility
     (plist-put msg :headers-overlay (make-overlay headers-start headers-end))
@@ -1137,7 +1172,8 @@ is t, hide the part initially and show the button."
 (defun notmuch-show-toggle-elide-non-matching ()
   "Toggle the display of non-matching messages."
   (interactive)
 (defun notmuch-show-toggle-elide-non-matching ()
   "Toggle the display of non-matching messages."
   (interactive)
-  (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))
+  (setq notmuch-show-elide-non-matching-messages
+       (not notmuch-show-elide-non-matching-messages))
   (message (if notmuch-show-elide-non-matching-messages
               "Showing matching messages only."
             "Showing all messages."))
   (message (if notmuch-show-elide-non-matching-messages
               "Showing matching messages only."
             "Showing all messages."))
@@ -1218,13 +1254,13 @@ buttons for a corresponding notmuch search."
                      (url-unhex-string (match-string 0 mid-cid)))))
          (push (list (match-beginning 0) (match-end 0)
                      (notmuch-id-to-query mid)) links)))
                      (url-unhex-string (match-string 0 mid-cid)))))
          (push (list (match-beginning 0) (match-end 0)
                      (notmuch-id-to-query mid)) links)))
-      (dolist (link links)
+      (pcase-dolist (`(,beg ,end ,link) links)
        ;; Remove the overlay created by goto-address-mode
        ;; Remove the overlay created by goto-address-mode
-       (remove-overlays (first link) (second link) 'goto-address t)
-       (make-text-button (first link) (second link)
+       (remove-overlays beg end 'goto-address t)
+       (make-text-button beg end
                          :type 'notmuch-button-type
                          'action `(lambda (arg)
                          :type 'notmuch-button-type
                          'action `(lambda (arg)
-                                    (notmuch-show ,(third link) current-prefix-arg))
+                                    (notmuch-show ,link current-prefix-arg))
                          'follow-link t
                          'help-echo "Mouse-1, RET: search for this message"
                          'face goto-address-mail-face)))))
                          'follow-link t
                          'help-echo "Mouse-1, RET: search for this message"
                          'face goto-address-mail-face)))))
@@ -1374,7 +1410,7 @@ This includes:
     (list win-id-combo (notmuch-show-get-message-ids-for-open-messages))))
 
 (defun notmuch-show-get-query ()
     (list win-id-combo (notmuch-show-get-message-ids-for-open-messages))))
 
 (defun notmuch-show-get-query ()
-  "Return the current query in this show buffer"
+  "Return the current query in this show buffer."
   (if notmuch-show-query-context
       (concat notmuch-show-thread-id
              " and ("
   (if notmuch-show-query-context
       (concat notmuch-show-thread-id
              " and ("
@@ -1385,9 +1421,9 @@ This includes:
 (defun notmuch-show-goto-message (msg-id)
   "Go to message with msg-id."
   (goto-char (point-min))
 (defun notmuch-show-goto-message (msg-id)
   "Go to message with msg-id."
   (goto-char (point-min))
-  (unless (loop if (string= msg-id (notmuch-show-get-message-id))
-               return t
-               until (not (notmuch-show-goto-message-next)))
+  (unless (cl-loop if (string= msg-id (notmuch-show-get-message-id))
+                  return t
+                  until (not (notmuch-show-goto-message-next)))
     (goto-char (point-min))
     (message "Message-id not found."))
   (notmuch-show-message-adjust))
     (goto-char (point-min))
     (message "Message-id not found."))
   (notmuch-show-message-adjust))
@@ -1404,9 +1440,10 @@ This includes:
 
     ;; Open those that were open.
     (goto-char (point-min))
 
     ;; Open those that were open.
     (goto-char (point-min))
-    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
-                                          (member (notmuch-show-get-message-id) open))
-         until (not (notmuch-show-goto-message-next)))
+    (cl-loop do (notmuch-show-message-visible
+                (notmuch-show-get-message-properties)
+                (member (notmuch-show-get-message-id) open))
+            until (not (notmuch-show-goto-message-next)))
 
     (dolist (win-msg-pair win-msg-alist)
       (with-selected-window (car win-msg-pair)
 
     (dolist (win-msg-pair win-msg-alist)
       (with-selected-window (car win-msg-pair)
@@ -1452,7 +1489,7 @@ reset based on the original query."
     (define-key map "G" 'notmuch-show-stash-git-send-email)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
     (define-key map "G" 'notmuch-show-stash-git-send-email)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
-  "Submap for stash commands")
+  "Submap for stash commands.")
 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
 
 (defvar notmuch-show-part-map
 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
 
 (defvar notmuch-show-part-map
@@ -1464,13 +1501,14 @@ reset based on the original query."
     (define-key map "m" 'notmuch-show-choose-mime-of-part)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
     (define-key map "m" 'notmuch-show-choose-mime-of-part)
     (define-key map "?" 'notmuch-subkeymap-help)
     map)
-  "Submap for part commands")
+  "Submap for part commands.")
 (fset 'notmuch-show-part-map notmuch-show-part-map)
 
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
     (define-key map "Z" 'notmuch-tree-from-show-current-query)
 (fset 'notmuch-show-part-map notmuch-show-part-map)
 
 (defvar notmuch-show-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
     (define-key map "Z" 'notmuch-tree-from-show-current-query)
+    (define-key map "U" 'notmuch-unthreaded-from-show-current-query)
     (define-key map (kbd "<C-tab>") 'widget-backward)
     (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
     (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
     (define-key map (kbd "<C-tab>") 'widget-backward)
     (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
     (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
@@ -1511,6 +1549,7 @@ reset based on the original query."
     (define-key map "<" 'notmuch-show-toggle-thread-indentation)
     (define-key map "t" 'toggle-truncate-lines)
     (define-key map "." 'notmuch-show-part-map)
     (define-key map "<" 'notmuch-show-toggle-thread-indentation)
     (define-key map "t" 'toggle-truncate-lines)
     (define-key map "." 'notmuch-show-part-map)
+    (define-key map "B" 'notmuch-show-browse-urls)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
     map)
   "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
@@ -1552,12 +1591,19 @@ All currently available key bindings:
         #'notmuch-show-imenu-extract-index-name-function))
 
 (defun notmuch-tree-from-show-current-query ()
         #'notmuch-show-imenu-extract-index-name-function))
 
 (defun notmuch-tree-from-show-current-query ()
-  "Call notmuch tree with the current query"
+  "Call notmuch tree with the current query."
   (interactive)
   (notmuch-tree notmuch-show-thread-id
                notmuch-show-query-context
                (notmuch-show-get-message-id)))
 
   (interactive)
   (notmuch-tree notmuch-show-thread-id
                notmuch-show-query-context
                (notmuch-show-get-message-id)))
 
+(defun notmuch-unthreaded-from-show-current-query ()
+  "Call notmuch unthreaded with the current query."
+  (interactive)
+  (notmuch-unthreaded notmuch-show-thread-id
+                     notmuch-show-query-context
+                     (notmuch-show-get-message-id)))
+
 (defun notmuch-show-move-to-message-top ()
   (goto-char (notmuch-show-message-top)))
 
 (defun notmuch-show-move-to-message-top ()
   (goto-char (notmuch-show-message-top)))
 
@@ -1573,6 +1619,8 @@ All currently available key bindings:
 ;; region a->b is not found when point is at b. We walk backwards
 ;; until finding the property.
 (defun notmuch-show-message-extent ()
 ;; region a->b is not found when point is at b. We walk backwards
 ;; until finding the property.
 (defun notmuch-show-message-extent ()
+  "Return a cons cell containing the start and end buffer offset
+of the current message."
   (let (r)
     (save-excursion
       (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
   (let (r)
     (save-excursion
       (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
@@ -1607,8 +1655,8 @@ All currently available key bindings:
 effects."
   (save-excursion
     (goto-char (point-min))
 effects."
   (save-excursion
     (goto-char (point-min))
-    (loop do (funcall function)
-         while (notmuch-show-goto-message-next))))
+    (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.
@@ -1627,7 +1675,8 @@ effects."
 (defun notmuch-show-set-message-properties (props)
   (save-excursion
     (notmuch-show-move-to-message-top)
 (defun notmuch-show-set-message-properties (props)
   (save-excursion
     (notmuch-show-move-to-message-top)
-    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+    (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.
 
 (defun notmuch-show-get-message-properties ()
   "Return the properties of the current message as a plist.
@@ -1780,8 +1829,9 @@ user decision and we should not override it."
           (setq notmuch-show--seen-has-errored 't)
           (setq header-line-format
                 (concat header-line-format
           (setq notmuch-show--seen-has-errored 't)
           (setq header-line-format
                 (concat header-line-format
-                        (propertize "  [some mark read tag changes may have failed]"
-                                    'face font-lock-warning-face)))))))))
+                        (propertize
+                         "  [some mark read tag changes may have failed]"
+                         'face font-lock-warning-face)))))))))
 
 (defun notmuch-show-filter-thread (query)
   "Filter or LIMIT the current thread based on a new query string.
 
 (defun notmuch-show-filter-thread (query)
   "Filter or LIMIT the current thread based on a new query string.
@@ -1803,7 +1853,8 @@ Reshows the current thread with matches defined by the new query-string."
       (goto-char (point-min))
       (while (not done)
        (if (notmuch-show-message-visible-p)
       (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 message-ids
+                 (append message-ids (list (notmuch-show-get-message-id)))))
        (setq done (not (notmuch-show-goto-message-next)))
        )
       message-ids
        (setq done (not (notmuch-show-goto-message-next)))
        )
       message-ids
@@ -1867,7 +1918,8 @@ shown."
       (notmuch-show-archive-thread-then-next)))
 
 (defun notmuch-show-rewind ()
       (notmuch-show-archive-thread-then-next)))
 
 (defun notmuch-show-rewind ()
-  "Backup through the thread (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
+  "Backup through the thread (reverse scrolling compared to \
+\\[notmuch-show-advance-and-archive]).
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
 
 Specifically, if the beginning of the previous email is fewer
 than `window-height' lines from the current point, move to it
@@ -1993,7 +2045,7 @@ to show, nil otherwise."
     (notmuch-show-message-visible props (plist-get props :match))))
 
 (defun notmuch-show-goto-first-wanted-message ()
     (notmuch-show-message-visible props (plist-get props :match))))
 
 (defun notmuch-show-goto-first-wanted-message ()
-  "Move to the first open message and mark it read"
+  "Move to the first open message and mark it read."
   (goto-char (point-min))
   (unless (notmuch-show-message-visible-p)
     (notmuch-show-next-open-message))
   (goto-char (point-min))
   (unless (notmuch-show-message-visible-p)
     (notmuch-show-next-open-message))
@@ -2059,11 +2111,14 @@ message."
        (setq shell-command
              (concat notmuch-command " show --format=mbox --exclude=false "
                      (shell-quote-argument
        (setq shell-command
              (concat notmuch-command " show --format=mbox --exclude=false "
                      (shell-quote-argument
-                      (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR "))
+                      (mapconcat 'identity
+                                 (notmuch-show-get-message-ids-for-open-messages)
+                                 " OR "))
                      " | " command))
       (setq shell-command
            (concat notmuch-command " show --format=raw "
                      " | " command))
       (setq shell-command
            (concat notmuch-command " show --format=raw "
-                   (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
+                   (shell-quote-argument (notmuch-show-get-message-id))
+                   " | " command)))
     (let ((cwd default-directory)
          (buf (get-buffer-create (concat "*notmuch-pipe*"))))
       (with-current-buffer buf
     (let ((cwd default-directory)
          (buf (get-buffer-create (concat "*notmuch-pipe*"))))
       (with-current-buffer buf
@@ -2164,9 +2219,10 @@ argument, hide all of the messages."
   (interactive)
   (save-excursion
     (goto-char (point-min))
   (interactive)
   (save-excursion
     (goto-char (point-min))
-    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
-                                          (not current-prefix-arg))
-         until (not (notmuch-show-goto-message-next))))
+    (cl-loop do (notmuch-show-message-visible
+                (notmuch-show-get-message-properties)
+                (not current-prefix-arg))
+            until (not (notmuch-show-goto-message-next))))
   (force-window-update))
 
 (defun notmuch-show-next-button ()
   (force-window-update))
 
 (defun notmuch-show-next-button ()
@@ -2253,7 +2309,7 @@ message will be \"unarchived\", i.e. the tag changes in
           (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
 (defun notmuch-show-archive-message-then-next-or-exit ()
           (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
 (defun notmuch-show-archive-message-then-next-or-exit ()
-  "Archive the current message, then show the next open message in the current thread.
+  "Archive current message, then show next open message in current thread.
 
 If at the last open message in the current thread, then exit back
 to search results."
 
 If at the last open message in the current thread, then exit back
 to search results."
@@ -2262,7 +2318,7 @@ to search results."
   (notmuch-show-next-open-message t))
 
 (defun notmuch-show-archive-message-then-next-or-next-thread ()
   (notmuch-show-next-open-message t))
 
 (defun notmuch-show-archive-message-then-next-or-next-thread ()
-  "Archive the current message, then show the next open message in the current thread.
+  "Archive current message, then show next open message in current or next thread.
 
 If at the last open message in the current thread, then show next
 thread from search."
 
 If at the last open message in the current thread, then show next
 thread from search."
@@ -2493,11 +2549,12 @@ the new buffer."
       (view-buffer buf 'kill-buffer-if-not-modified))))
 
 (defun notmuch-show-choose-mime-of-part (mime-type)
       (view-buffer buf 'kill-buffer-if-not-modified))))
 
 (defun notmuch-show-choose-mime-of-part (mime-type)
-  "Choose the mime type to use for displaying part"
+  "Choose the mime type to use for displaying part."
   (interactive
    (list (completing-read "Mime type to use (default text/plain): "
                          (mailcap-mime-types) nil nil nil nil "text/plain")))
   (interactive
    (list (completing-read "Mime type to use (default text/plain): "
                          (mailcap-mime-types) nil nil nil nil "text/plain")))
-  (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part mime-type))
+  (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part
+                                            mime-type))
 
 (defun notmuch-show-imenu-prev-index-position-function ()
   "Move point to previous message in notmuch-show buffer.
 
 (defun notmuch-show-imenu-prev-index-position-function ()
   "Move point to previous message in notmuch-show buffer.
@@ -2519,6 +2576,36 @@ beginning of the line."
                                    (point))
                                  (line-end-position)))
 
                                    (point))
                                  (line-end-position)))
 
+(defmacro notmuch-show--with-currently-shown-message (&rest body)
+  "Evaluate BODY with display restricted to the currently shown
+message."
+  `(save-excursion
+     (save-restriction
+      (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."
+  (notmuch-show--with-currently-shown-message
+   (let (urls)
+     (goto-char (point-min))
+     (while (re-search-forward goto-address-url-regexp (point-max) t)
+       (push (match-string-no-properties 0) urls))
+     (reverse urls))))
+
+(defun notmuch-show-browse-urls (&optional kill)
+  "Offer to browse any URLs in the current message.
+With a prefix argument, copy the URL to the kill ring rather than
+browsing."
+  (interactive "P")
+  (let ((urls (notmuch-show--gather-urls))
+       (prompt (if kill "Copy URL to kill ring: " "Browse URL: "))
+       (fn (if kill #'kill-new #'browse-url)))
+    (if urls
+       (funcall fn (completing-read prompt urls nil nil nil nil (car urls)))
+      (message "No URLs found."))))
+
 (provide 'notmuch-show)
 
 ;;; notmuch-show.el ends here
 (provide 'notmuch-show)
 
 ;;; notmuch-show.el ends here