]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-show.el
emacs: add stash support for git send-email command line
[notmuch] / emacs / notmuch-show.el
index 7325792be71f99dde301e7020b507b91e833200a..9f6fe077df0caa5843454c91cd93a27d87db50f6 100644 (file)
@@ -44,6 +44,9 @@
 (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))
+(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
 
 (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
   "Headers that should be shown in a message, in this order.
@@ -157,10 +160,19 @@ indentation."
 (make-variable-buffer-local 'notmuch-show-indent-content)
 (put 'notmuch-show-indent-content 'permanent-local t)
 
+(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.")
+
 (defcustom notmuch-show-stash-mlarchive-link-alist
   '(("Gmane" . "http://mid.gmane.org/")
     ("MARC" . "http://marc.info/?i=")
-    ("Mail Archive, The" . "http://mail-archive.com/search?l=mid&q=")
+    ("Mail Archive, The" . "http://mid.mail-archive.com/")
     ("LKML" . "http://lkml.kernel.org/r/")
     ;; FIXME: can these services be searched by `Message-Id' ?
     ;; ("MarkMail" . "http://markmail.org/")
@@ -169,10 +181,21 @@ indentation."
     )
   "List of Mailing List Archives to use when stashing links.
 
-These URIs are concatenated with the current message's
-Message-Id in `notmuch-show-stash-mlarchive-link'."
+This list is used for generating a Mailing List Archive reference
+URI with the current message's Message-Id in
+`notmuch-show-stash-mlarchive-link'.
+
+If the cdr of the alist element is not a function, the cdr is
+expected to contain a URI that is concatenated with the current
+message's Message-Id to create a ML archive reference URI.
+
+If the cdr is a function, the function is called with the
+Message-Id as the argument, and the function is expected to
+return the ML archive reference URI."
   :type '(alist :key-type (string :tag "Name")
-               :value-type (string :tag "URL"))
+               :value-type (choice
+                            (string :tag "URL")
+                            (function :tag "Function returning the URL")))
   :group 'notmuch-show)
 
 (defcustom notmuch-show-stash-mlarchive-link-default "Gmane"
@@ -200,6 +223,16 @@ For example, if you wanted to remove an \"unread\" tag and add a
   :type '(repeat string)
   :group 'notmuch-show)
 
+(defcustom notmuch-show-mark-read-function #'notmuch-show-seen-current-message
+  "Function to control which messages are marked read.
+
+The function should take two arguments START and END which will
+be the start and end of the visible portion of the buffer and
+should mark the appropriate messages read by applying
+`notmuch-show-mark-read'. This function will be called after
+every user interaction with notmuch."
+  :type 'function
+  :group 'notmuch-show)
 
 (defmacro with-current-notmuch-show-message (&rest body)
   "Evaluate body with current buffer set to the text of current message"
@@ -208,9 +241,9 @@ For example, if you wanted to remove an \"unread\" tag and add a
        (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
          (with-current-buffer buf
           (let ((coding-system-for-read 'no-conversion))
-            (call-process notmuch-command nil t nil "show" "--format=raw" id)
-            ,@body)
-          (kill-buffer buf))))))
+            (call-process notmuch-command nil t nil "show" "--format=raw" id))
+          ,@body)
+        (kill-buffer buf)))))
 
 (defun notmuch-show-turn-on-visual-line-mode ()
   "Enable Visual Line mode."
@@ -333,7 +366,7 @@ operation on the contents of the current buffer."
     (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
        (let ((inhibit-read-only t))
          (replace-match (concat "("
-                                (notmuch-tag-format-tags tags)
+                                (notmuch-tag-format-tags tags (notmuch-show-get-prop :orig-tags))
                                 ")"))))))
 
 (defun notmuch-clean-address (address)
@@ -407,17 +440,18 @@ unchanged ADDRESS if parsing fails."
 message at DEPTH in the current thread."
   (let ((start (point)))
     (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
-           (notmuch-show-clean-address (plist-get headers :From))
+           (notmuch-sanitize
+            (notmuch-show-clean-address (plist-get headers :From)))
            " ("
            date
            ") ("
-           (notmuch-tag-format-tags tags)
+           (notmuch-tag-format-tags tags tags)
            ")\n")
     (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
 
 (defun notmuch-show-insert-header (header header-value)
   "Insert a single header."
-  (insert header ": " header-value "\n"))
+  (insert header ": " (notmuch-sanitize header-value) "\n"))
 
 (defun notmuch-show-insert-headers (headers)
   "Insert the headers of the current message."
@@ -683,7 +717,7 @@ message at DEPTH in the current thread."
   (let ((start (if button
                   (button-start button)
                 (point))))
-    (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto))
+    (insert (notmuch-get-bodypart-content msg part notmuch-show-process-crypto))
     (save-excursion
       (save-restriction
        (narrow-to-region start (point-max))
@@ -692,7 +726,7 @@ message at DEPTH in the current thread."
 
 (defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth button)
   (insert (with-temp-buffer
-           (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto))
+           (insert (notmuch-get-bodypart-content msg part notmuch-show-process-crypto))
            ;; notmuch-get-bodypart-content provides "raw", non-converted
            ;; data. Replace CRLF with LF before icalendar can use it.
            (goto-char (point-min))
@@ -744,7 +778,7 @@ message at DEPTH in the current thread."
 
 (defun notmuch-show-insert-part-*/* (msg part content-type nth depth button)
   ;; This handler _must_ succeed - it is the handler of last resort.
-  (notmuch-mm-display-part-inline msg part nth content-type notmuch-show-process-crypto)
+  (notmuch-mm-display-part-inline msg part content-type notmuch-show-process-crypto)
   t)
 
 ;; Functions for determining how to handle MIME parts.
@@ -773,7 +807,10 @@ message at DEPTH in the current thread."
     (while (and handlers
                (not (condition-case err
                         (funcall (car handlers) msg part content-type nth depth button)
-                      (error (progn
+                      ;; Specifying `debug' here lets the debugger
+                      ;; run if `debug-on-error' is non-nil.
+                      ((debug error)
+                       (progn
                                (insert "!!! Bodypart insert error: ")
                                (insert (error-message-string err))
                                (insert " !!!\n") nil)))))
@@ -1080,15 +1117,17 @@ buttons for a corresponding notmuch search."
        (make-text-button (first link) (second link)
                          :type 'notmuch-button-type
                          'action `(lambda (arg)
-                                    (notmuch-show ,(third link)))
+                                    (notmuch-show ,(third link) current-prefix-arg))
                          '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 elide-toggle parent-buffer query-context buffer-name)
   "Run \"notmuch show\" with the given thread ID and display results.
 
+ELIDE-TOGGLE, if non-nil, inverts the default elide behavior.
+
 The optional PARENT-BUFFER is the notmuch-search buffer from
 which this notmuch-show command was executed, (so that the
 next thread from that buffer can be show when done with this
@@ -1102,7 +1141,7 @@ 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."
-  (interactive "sNotmuch show: ")
+  (interactive "sNotmuch show: \nP")
   (let ((buffer-name (generate-new-buffer-name
                      (or buffer-name
                          (concat "*notmuch-" thread-id "*")))))
@@ -1112,9 +1151,9 @@ function is used."
     (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
     ;; Set the default value for
     ;; `notmuch-show-elide-non-matching-messages' in this buffer. If
-    ;; there is a prefix argument, invert the default.
+    ;; elide-toggle is set, invert the default.
     (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages)
-    (if current-prefix-arg
+    (if elide-toggle
        (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)))
 
     (setq notmuch-show-thread-id thread-id
@@ -1128,9 +1167,12 @@ function is used."
   (let ((inhibit-read-only t))
 
     (notmuch-show-mode)
+    (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
+
     ;; Don't track undo information for this buffer
     (set 'buffer-undo-list t)
 
+    (notmuch-tag-clear-cache)
     (erase-buffer)
     (goto-char (point-min))
     (save-excursion
@@ -1153,8 +1195,10 @@ function is used."
 
       (jit-lock-register #'notmuch-show-buttonise-links)
 
+      (notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
+
       ;; Set the header line to the subject of the first message.
-      (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject)))
+      (setq header-line-format (notmuch-sanitize (notmuch-show-strip-re (notmuch-show-get-subject))))
 
       (run-hooks 'notmuch-show-hook))))
 
@@ -1166,6 +1210,15 @@ This includes:
  - the current message."
   (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
 
+(defun notmuch-show-get-query ()
+  "Return the current query in this show buffer"
+  (if notmuch-show-query-context
+      (concat notmuch-show-thread-id
+             " and ("
+             notmuch-show-query-context
+             ")")
+    notmuch-show-thread-id))
+
 (defun notmuch-show-apply-state (state)
   "Apply STATE to the current buffer.
 
@@ -1227,6 +1280,8 @@ reset based on the original query."
     (define-key map "t" 'notmuch-show-stash-to)
     (define-key map "l" 'notmuch-show-stash-mlarchive-link)
     (define-key map "L" 'notmuch-show-stash-mlarchive-link-and-go)
+    (define-key map "G" 'notmuch-show-stash-git-send-email)
+    (define-key map "?" 'notmuch-subkeymap-help)
     map)
   "Submap for stash commands")
 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
@@ -1237,50 +1292,52 @@ reset based on the original query."
     (define-key map "v" 'notmuch-show-view-part)
     (define-key map "o" 'notmuch-show-interactively-view-part)
     (define-key map "|" 'notmuch-show-pipe-part)
+    (define-key map "?" 'notmuch-subkeymap-help)
     map)
   "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 (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 "TAB") 'notmuch-show-next-button)
-       (define-key map "f" 'notmuch-show-forward-message)
-       (define-key map "r" 'notmuch-show-reply-sender)
-       (define-key map "R" 'notmuch-show-reply)
-       (define-key map "|" 'notmuch-show-pipe-message)
-       (define-key map "w" 'notmuch-show-save-attachments)
-       (define-key map "V" 'notmuch-show-view-raw-message)
-       (define-key map "c" 'notmuch-show-stash-map)
-       (define-key map "h" 'notmuch-show-toggle-visibility-headers)
-       (define-key map "*" 'notmuch-show-tag-all)
-       (define-key map "-" 'notmuch-show-remove-tag)
-       (define-key map "+" 'notmuch-show-add-tag)
-       (define-key map "X" 'notmuch-show-archive-thread-then-exit)
-       (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
-       (define-key map "A" 'notmuch-show-archive-thread-then-next)
-       (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
-       (define-key map "N" 'notmuch-show-next-message)
-       (define-key map "P" 'notmuch-show-previous-message)
-       (define-key map "n" 'notmuch-show-next-open-message)
-       (define-key map "p" 'notmuch-show-previous-open-message)
-       (define-key map (kbd "M-n") 'notmuch-show-next-thread-show)
-       (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show)
-       (define-key map (kbd "DEL") 'notmuch-show-rewind)
-       (define-key map " " 'notmuch-show-advance-and-archive)
-       (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
-       (define-key map (kbd "RET") 'notmuch-show-toggle-message)
-       (define-key map "#" 'notmuch-show-print-message)
-       (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
-       (define-key map "$" 'notmuch-show-toggle-process-crypto)
-       (define-key map "<" 'notmuch-show-toggle-thread-indentation)
-       (define-key map "t" 'toggle-truncate-lines)
-       (define-key map "." 'notmuch-show-part-map)
-       map)
-      "Keymap for \"notmuch show\" buffers.")
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map notmuch-common-keymap)
+    (define-key map "Z" 'notmuch-tree-from-show-current-query)
+    (define-key map (kbd "<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 "TAB") 'notmuch-show-next-button)
+    (define-key map "f" 'notmuch-show-forward-message)
+    (define-key map "r" 'notmuch-show-reply-sender)
+    (define-key map "R" 'notmuch-show-reply)
+    (define-key map "|" 'notmuch-show-pipe-message)
+    (define-key map "w" 'notmuch-show-save-attachments)
+    (define-key map "V" 'notmuch-show-view-raw-message)
+    (define-key map "c" 'notmuch-show-stash-map)
+    (define-key map "h" 'notmuch-show-toggle-visibility-headers)
+    (define-key map "*" 'notmuch-show-tag-all)
+    (define-key map "-" 'notmuch-show-remove-tag)
+    (define-key map "+" 'notmuch-show-add-tag)
+    (define-key map "X" 'notmuch-show-archive-thread-then-exit)
+    (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
+    (define-key map "A" 'notmuch-show-archive-thread-then-next)
+    (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
+    (define-key map "N" 'notmuch-show-next-message)
+    (define-key map "P" 'notmuch-show-previous-message)
+    (define-key map "n" 'notmuch-show-next-open-message)
+    (define-key map "p" 'notmuch-show-previous-open-message)
+    (define-key map (kbd "M-n") 'notmuch-show-next-thread-show)
+    (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show)
+    (define-key map (kbd "DEL") 'notmuch-show-rewind)
+    (define-key map " " 'notmuch-show-advance-and-archive)
+    (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
+    (define-key map (kbd "RET") 'notmuch-show-toggle-message)
+    (define-key map "#" 'notmuch-show-print-message)
+    (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
+    (define-key map "$" 'notmuch-show-toggle-process-crypto)
+    (define-key map "<" 'notmuch-show-toggle-thread-indentation)
+    (define-key map "t" 'toggle-truncate-lines)
+    (define-key map "." 'notmuch-show-part-map)
+    map)
+  "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
 
 (defun notmuch-show-mode ()
@@ -1320,6 +1377,13 @@ All currently available key bindings:
   (setq buffer-read-only t
        truncate-lines t))
 
+(defun notmuch-tree-from-show-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)))
+
 (defun notmuch-show-move-to-message-top ()
   (goto-char (notmuch-show-message-top)))
 
@@ -1418,8 +1482,18 @@ an error if there is no part containing point."
     (notmuch-show-set-message-properties props)))
 
 (defun notmuch-show-get-prop (prop &optional props)
+  "Get property PROP from current message in show or tree mode.
+
+It gets property PROP from PROPS or, if PROPS is nil, the current
+message in either tree or show. This means that several utility
+functions in notmuch-show can be used directly by notmuch-tree as
+they just need the correct message properties."
   (let ((props (or props
-                  (notmuch-show-get-message-properties))))
+                  (cond ((eq major-mode 'notmuch-show-mode)
+                         (notmuch-show-get-message-properties))
+                        ((eq major-mode 'notmuch-tree-mode)
+                         (notmuch-tree-get-message-properties))
+                        (t nil)))))
     (plist-get props prop)))
 
 (defun notmuch-show-get-message-id (&optional bare)
@@ -1503,6 +1577,23 @@ marked as unread, i.e. the tag changes in
     (apply 'notmuch-show-tag-message
           (notmuch-tag-change-list notmuch-show-mark-read-tags unread))))
 
+(defun notmuch-show-seen-current-message (start end)
+  "Mark the current message read if it is open.
+
+We only mark it read once: if it is changed back then that is a
+user decision and we should not override it."
+  (when (and (notmuch-show-message-visible-p)
+            (not (notmuch-show-get-prop :seen)))
+       (notmuch-show-mark-read)
+       (notmuch-show-set-prop :seen t)))
+
+(defun notmuch-show-command-hook ()
+  (when (eq major-mode 'notmuch-show-mode)
+    ;; We need to redisplay to get window-start and window-end correct.
+    (redisplay)
+    (save-excursion
+      (funcall notmuch-show-mark-read-function (window-start) (window-end)))))
+
 ;; Functions for getting attributes of several messages in the current
 ;; thread.
 
@@ -1638,9 +1729,7 @@ If a prefix argument is given and this is the last message in the
 thread, navigate to the next thread in the parent search buffer."
   (interactive "P")
   (if (notmuch-show-goto-message-next)
-      (progn
-       (notmuch-show-mark-read)
-       (notmuch-show-message-adjust))
+      (notmuch-show-message-adjust)
     (if pop-at-end
        (notmuch-show-next-thread)
       (goto-char (point-max)))))
@@ -1651,7 +1740,6 @@ thread, navigate to the next thread in the parent search buffer."
   (if (= (point) (notmuch-show-message-top))
       (notmuch-show-goto-message-previous)
     (notmuch-show-move-to-message-top))
-  (notmuch-show-mark-read)
   (notmuch-show-message-adjust))
 
 (defun notmuch-show-next-open-message (&optional pop-at-end)
@@ -1666,9 +1754,7 @@ to show, nil otherwise."
     (while (and (setq r (notmuch-show-goto-message-next))
                (not (notmuch-show-message-visible-p))))
     (if r
-       (progn
-         (notmuch-show-mark-read)
-         (notmuch-show-message-adjust))
+       (notmuch-show-message-adjust)
       (if pop-at-end
          (notmuch-show-next-thread)
        (goto-char (point-max))))
@@ -1681,9 +1767,7 @@ to show, nil otherwise."
     (while (and (setq r (notmuch-show-goto-message-next))
                (not (notmuch-show-get-prop :match))))
     (if r
-       (progn
-         (notmuch-show-mark-read)
-         (notmuch-show-message-adjust))
+       (notmuch-show-message-adjust)
       (goto-char (point-max)))))
 
 (defun notmuch-show-open-if-matched ()
@@ -1694,8 +1778,7 @@ to show, nil otherwise."
 (defun notmuch-show-goto-first-wanted-message ()
   "Move to the first open message and mark it read"
   (goto-char (point-min))
-  (if (notmuch-show-message-visible-p)
-      (notmuch-show-mark-read)
+  (unless (notmuch-show-message-visible-p)
     (notmuch-show-next-open-message))
   (when (eobp)
     ;; There are no matched non-excluded messages so open all matched
@@ -1703,8 +1786,7 @@ to show, nil otherwise."
     (notmuch-show-mapc 'notmuch-show-open-if-matched)
     (force-window-update)
     (goto-char (point-min))
-    (if (notmuch-show-message-visible-p)
-       (notmuch-show-mark-read)
+    (unless (notmuch-show-message-visible-p)
       (notmuch-show-next-open-message))))
 
 (defun notmuch-show-previous-open-message ()
@@ -1714,15 +1796,15 @@ to show, nil otherwise."
                  (notmuch-show-goto-message-previous)
                (notmuch-show-move-to-message-top))
              (not (notmuch-show-message-visible-p))))
-  (notmuch-show-mark-read)
   (notmuch-show-message-adjust))
 
 (defun notmuch-show-view-raw-message ()
-  "View the file holding the current message."
+  "View the original source of the current message."
   (interactive)
   (let* ((id (notmuch-show-get-message-id))
         (buf (get-buffer-create (concat "*notmuch-raw-" id "*"))))
-    (call-process notmuch-command nil buf nil "show" "--format=raw" id)
+    (let ((coding-system-for-read 'no-conversion))
+      (call-process notmuch-command nil buf nil "show" "--format=raw" id))
     (switch-to-buffer buf)
     (goto-char (point-min))
     (set-buffer-modified-p nil)
@@ -1757,10 +1839,14 @@ message."
       (setq shell-command
            (concat notmuch-command " show --format=raw "
                    (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
-    (let ((buf (get-buffer-create (concat "*notmuch-pipe*"))))
+    (let ((cwd default-directory)
+         (buf (get-buffer-create (concat "*notmuch-pipe*"))))
       (with-current-buffer buf
        (setq buffer-read-only nil)
        (erase-buffer)
+       ;; Use the originating buffer's working directory instead of
+       ;; that of the pipe buffer.
+       (cd cwd)
        (let ((exit-code (call-process-shell-command shell-command nil buf)))
          (goto-char (point-max))
          (set-buffer-modified-p nil)
@@ -1780,23 +1866,28 @@ TAG-CHANGES is a list of tag operations for `notmuch-tag'."
       (notmuch-tag (notmuch-show-get-message-id) tag-changes)
       (notmuch-show-set-tags new-tags))))
 
-(defun notmuch-show-tag (&optional tag-changes)
+(defun notmuch-show-tag (tag-changes)
   "Change tags for the current message.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (let* ((tag-changes (notmuch-tag (notmuch-show-get-message-id) tag-changes))
-        (current-tags (notmuch-show-get-tags))
+  (interactive (list (notmuch-read-tag-changes (notmuch-show-get-tags)
+                                              "Tag message")))
+  (notmuch-tag (notmuch-show-get-message-id) tag-changes)
+  (let* ((current-tags (notmuch-show-get-tags))
         (new-tags (notmuch-update-tags current-tags tag-changes)))
     (unless (equal current-tags new-tags)
       (notmuch-show-set-tags new-tags))))
 
-(defun notmuch-show-tag-all (&optional tag-changes)
+(defun notmuch-show-tag-all (tag-changes)
   "Change tags for all messages in the current show buffer.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (setq tag-changes (notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes))
+  (interactive
+   (list (let (tags)
+          (notmuch-show-mapc
+           (lambda () (setq tags (append (notmuch-show-get-tags) tags))))
+          (notmuch-read-tag-changes tags "Tag thread"))))
+  (notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes)
   (notmuch-show-mapc
    (lambda ()
      (let* ((current-tags (notmuch-show-get-tags))
@@ -1804,19 +1895,21 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
        (unless (equal current-tags new-tags)
         (notmuch-show-set-tags new-tags))))))
 
-(defun notmuch-show-add-tag ()
+(defun notmuch-show-add-tag (tag-changes)
   "Change tags for the current message (defaulting to add).
 
 Same as `notmuch-show-tag' but sets initial input to '+'."
-  (interactive)
-  (notmuch-show-tag "+"))
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "+")))
+  (notmuch-show-tag tag-changes))
 
-(defun notmuch-show-remove-tag ()
+(defun notmuch-show-remove-tag (tag-changes)
   "Change tags for the current message (defaulting to remove).
 
 Same as `notmuch-show-tag' but sets initial input to '-'."
-  (interactive)
-  (notmuch-show-tag "-"))
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "-")))
+  (notmuch-show-tag tag-changes))
 
 (defun notmuch-show-toggle-visibility-headers ()
   "Toggle the visibility of the current message headers."
@@ -1870,7 +1963,7 @@ buffer. If PREVIOUS is non-nil, move to the previous item in the
 search results instead."
   (interactive "P")
   (let ((parent-buffer notmuch-show-parent-buffer))
-    (notmuch-kill-this-buffer)
+    (notmuch-bury-or-kill-this-buffer)
     (when (buffer-live-p parent-buffer)
       (switch-to-buffer parent-buffer)
       (and (if previous
@@ -2014,16 +2107,19 @@ This presumes that the message is available at the selected Mailing List Archive
 If optional argument MLA is non-nil, use the provided key instead of prompting
 the user (see `notmuch-show-stash-mlarchive-link-alist')."
   (interactive)
-  (notmuch-common-do-stash
-   (concat (cdr (assoc
-                (or mla
-                    (let ((completion-ignore-case t))
-                      (completing-read
-                       "Mailing List Archive: "
-                       notmuch-show-stash-mlarchive-link-alist
-                       nil t nil nil notmuch-show-stash-mlarchive-link-default)))
-                notmuch-show-stash-mlarchive-link-alist))
-          (notmuch-show-get-message-id t))))
+  (let ((url (cdr (assoc
+                  (or mla
+                      (let ((completion-ignore-case t))
+                        (completing-read
+                         "Mailing List Archive: "
+                         notmuch-show-stash-mlarchive-link-alist
+                         nil t nil nil
+                         notmuch-show-stash-mlarchive-link-default)))
+                  notmuch-show-stash-mlarchive-link-alist))))
+    (notmuch-common-do-stash
+     (if (functionp url)
+        (funcall url (notmuch-show-get-message-id t))
+       (concat url (notmuch-show-get-message-id t))))))
 
 (defun notmuch-show-stash-mlarchive-link-and-go (&optional mla)
   "Copy an ML Archive URI for the current message to the kill-ring and visit it.
@@ -2036,6 +2132,43 @@ the user (see `notmuch-show-stash-mlarchive-link-alist')."
   (notmuch-show-stash-mlarchive-link mla)
   (browse-url (current-kill 0 t)))
 
+(defun notmuch-show-stash-git-helper (addresses prefix)
+  "Escape, trim, quote, and add PREFIX to each address in list of ADDRESSES, and return the result as a single string."
+  (mapconcat (lambda (x)
+              (concat prefix "\""
+                      ;; escape double-quotes
+                      (replace-regexp-in-string
+                       "\"" "\\\\\""
+                       ;; trim leading and trailing spaces
+                       (replace-regexp-in-string
+                        "\\(^ *\\| *$\\)" ""
+                        x)) "\""))
+            addresses " "))
+
+(put 'notmuch-show-stash-git-send-email 'notmuch-prefix-doc
+     "Copy From/To/Cc of current message to kill-ring in a form suitable for pasting to git send-email command line.")
+
+(defun notmuch-show-stash-git-send-email (&optional no-in-reply-to)
+  "Copy From/To/Cc/Message-Id of current message to kill-ring in a form suitable for pasting to git send-email command line.
+
+If invoked with a prefix argument (or NO-IN-REPLY-TO is non-nil),
+omit --in-reply-to=<Message-Id>."
+  (interactive "P")
+  (notmuch-common-do-stash
+   (mapconcat 'identity
+             (remove ""
+                     (list
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-from)) "--to=")
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-to)) "--to=")
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-cc)) "--cc=")
+                      (unless no-in-reply-to
+                        (notmuch-show-stash-git-helper
+                         (list (notmuch-show-get-message-id t)) "--in-reply-to="))))
+             " ")))
+
 ;; Interactive part functions and their helpers
 
 (defun notmuch-show-generate-part-buffer (message-id nth)
@@ -2069,8 +2202,16 @@ caller is responsible for killing this buffer as appropriate."
 This ensures that the temporary buffer created for the mm-handle
 is destroyed when FN returns."
   (let ((handle (notmuch-show-current-part-handle)))
+    ;; 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*".
     (unwind-protect
-       (funcall fn handle)
+       (if notmuch-show-attachment-debug
+           (with-current-buffer (generate-new-buffer " *notmuch-part*")
+             (funcall fn handle))
+         (with-temp-buffer
+           (funcall fn handle)))
       (kill-buffer (mm-handle-buffer handle)))))
 
 (defun notmuch-show-part-button-default (&optional button)