emacs: `notmuch-show-get-message-id': optionally return Message-Id sans prefix
[notmuch] / emacs / notmuch-show.el
1 ;; notmuch-show.el --- displaying notmuch forests.
2 ;;
3 ;; Copyright © Carl Worth
4 ;; Copyright © David Edmondson
5 ;;
6 ;; This file is part of Notmuch.
7 ;;
8 ;; Notmuch is free software: you can redistribute it and/or modify it
9 ;; under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation, either version 3 of the License, or
11 ;; (at your option) any later version.
12 ;;
13 ;; Notmuch is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 ;; General Public License for more details.
17 ;;
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
20 ;;
21 ;; Authors: Carl Worth <cworth@cworth.org>
22 ;;          David Edmondson <dme@dme.org>
23
24 (eval-when-compile (require 'cl))
25 (require 'mm-view)
26 (require 'message)
27 (require 'mm-decode)
28 (require 'mailcap)
29 (require 'icalendar)
30 (require 'goto-addr)
31
32 (require 'notmuch-lib)
33 (require 'notmuch-query)
34 (require 'notmuch-wash)
35 (require 'notmuch-mua)
36 (require 'notmuch-crypto)
37 (require 'notmuch-print)
38
39 (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
40 (declare-function notmuch-fontify-headers "notmuch" nil)
41 (declare-function notmuch-read-tag-changes "notmuch" (&optional initial-input &rest search-terms))
42 (declare-function notmuch-search-next-thread "notmuch" nil)
43 (declare-function notmuch-search-show-thread "notmuch" nil)
44 (declare-function notmuch-update-tags "notmuch" (current-tags tag-changes))
45
46 (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
47   "Headers that should be shown in a message, in this order.
48
49 For an open message, all of these headers will be made visible
50 according to `notmuch-message-headers-visible' or can be toggled
51 with `notmuch-show-toggle-headers'. For a closed message, only
52 the first header in the list will be visible."
53   :type '(repeat string)
54   :group 'notmuch-show)
55
56 (defcustom notmuch-message-headers-visible t
57   "Should the headers be visible by default?
58
59 If this value is non-nil, then all of the headers defined in
60 `notmuch-message-headers' will be visible by default in the display
61 of each message. Otherwise, these headers will be hidden and
62 `notmuch-show-toggle-headers' can be used to make the visible for
63 any given message."
64   :type 'boolean
65   :group 'notmuch-show)
66
67 (defcustom notmuch-show-relative-dates t
68   "Display relative dates in the message summary line."
69   :type 'boolean
70   :group 'notmuch-show)
71
72 (defvar notmuch-show-markup-headers-hook '(notmuch-show-colour-headers)
73   "A list of functions called to decorate the headers listed in
74 `notmuch-message-headers'.")
75
76 (defcustom notmuch-show-hook '(notmuch-show-turn-on-visual-line-mode)
77   "Functions called after populating a `notmuch-show' buffer."
78   :type 'hook
79   :options '(notmuch-show-turn-on-visual-line-mode)
80   :group 'notmuch-show
81   :group 'notmuch-hooks)
82
83 (defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines
84                                                  notmuch-wash-tidy-citations
85                                                  notmuch-wash-elide-blank-lines
86                                                  notmuch-wash-excerpt-citations)
87   "Functions used to improve the display of text/plain parts."
88   :type 'hook
89   :options '(notmuch-wash-convert-inline-patch-to-part
90              notmuch-wash-wrap-long-lines
91              notmuch-wash-tidy-citations
92              notmuch-wash-elide-blank-lines
93              notmuch-wash-excerpt-citations)
94   :group 'notmuch-show
95   :group 'notmuch-hooks)
96
97 ;; Mostly useful for debugging.
98 (defcustom notmuch-show-all-multipart/alternative-parts t
99   "Should all parts of multipart/alternative parts be shown?"
100   :type 'boolean
101   :group 'notmuch-show)
102
103 (defcustom notmuch-show-indent-messages-width 1
104   "Width of message indentation in threads.
105
106 Messages are shown indented according to their depth in a thread.
107 This variable determines the width of this indentation measured
108 in number of blanks.  Defaults to `1', choose `0' to disable
109 indentation."
110   :type 'integer
111   :group 'notmuch-show)
112
113 (defcustom notmuch-show-indent-multipart nil
114   "Should the sub-parts of a multipart/* part be indented?"
115   ;; dme: Not sure which is a good default.
116   :type 'boolean
117   :group 'notmuch-show)
118
119 (defcustom notmuch-show-part-button-default-action 'notmuch-show-save-part
120   "Default part header button action (on ENTER or mouse click)."
121   :group 'notmuch-show
122   :type '(choice (const :tag "Save part"
123                         notmuch-show-save-part)
124                  (const :tag "View part"
125                         notmuch-show-view-part)
126                  (const :tag "View interactively"
127                         notmuch-show-interactively-view-part)))
128
129 (defcustom notmuch-show-only-matching-messages nil
130   "Only matching messages are shown by default."
131   :type 'boolean
132   :group 'notmuch-show)
133
134 (defvar notmuch-show-thread-id nil)
135 (make-variable-buffer-local 'notmuch-show-thread-id)
136 (put 'notmuch-show-thread-id 'permanent-local t)
137
138 (defvar notmuch-show-parent-buffer nil)
139 (make-variable-buffer-local 'notmuch-show-parent-buffer)
140 (put 'notmuch-show-parent-buffer 'permanent-local t)
141
142 (defvar notmuch-show-query-context nil)
143 (make-variable-buffer-local 'notmuch-show-query-context)
144 (put 'notmuch-show-query-context 'permanent-local t)
145
146 (defvar notmuch-show-process-crypto nil)
147 (make-variable-buffer-local 'notmuch-show-process-crypto)
148 (put 'notmuch-show-process-crypto 'permanent-local t)
149
150 (defvar notmuch-show-elide-non-matching-messages nil)
151 (make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
152 (put 'notmuch-show-elide-non-matching-messages 'permanent-local t)
153
154 (defvar notmuch-show-indent-content t)
155 (make-variable-buffer-local 'notmuch-show-indent-content)
156 (put 'notmuch-show-indent-content 'permanent-local t)
157
158 (defmacro with-current-notmuch-show-message (&rest body)
159   "Evaluate body with current buffer set to the text of current message"
160   `(save-excursion
161      (let ((id (notmuch-show-get-message-id)))
162        (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
163          (with-current-buffer buf
164             (call-process notmuch-command nil t nil "show" "--format=raw" id)
165            ,@body)
166          (kill-buffer buf)))))
167
168 (defun notmuch-show-turn-on-visual-line-mode ()
169   "Enable Visual Line mode."
170   (visual-line-mode t))
171
172 (defun notmuch-show-view-all-mime-parts ()
173   "Use external viewers to view all attachments from the current message."
174   (interactive)
175   (with-current-notmuch-show-message
176    ;; We override the mm-inline-media-tests to indicate which message
177    ;; parts are already sufficiently handled by the original
178    ;; presentation of the message in notmuch-show mode. These parts
179    ;; will be inserted directly into the temporary buffer of
180    ;; with-current-notmuch-show-message and silently discarded.
181    ;;
182    ;; Any MIME part not explicitly mentioned here will be handled by an
183    ;; external viewer as configured in the various mailcap files.
184    (let ((mm-inline-media-tests '(
185                                   ("text/.*" ignore identity)
186                                   ("application/pgp-signature" ignore identity)
187                                   ("multipart/alternative" ignore identity)
188                                   ("multipart/mixed" ignore identity)
189                                   ("multipart/related" ignore identity)
190                                  )))
191      (mm-display-parts (mm-dissect-buffer)))))
192
193 (defun notmuch-foreach-mime-part (function mm-handle)
194   (cond ((stringp (car mm-handle))
195          (dolist (part (cdr mm-handle))
196            (notmuch-foreach-mime-part function part)))
197         ((bufferp (car mm-handle))
198          (funcall function mm-handle))
199         (t (dolist (part mm-handle)
200              (notmuch-foreach-mime-part function part)))))
201
202 (defun notmuch-count-attachments (mm-handle)
203   (let ((count 0))
204     (notmuch-foreach-mime-part
205      (lambda (p)
206        (let ((disposition (mm-handle-disposition p)))
207          (and (listp disposition)
208               (or (equal (car disposition) "attachment")
209                   (and (equal (car disposition) "inline")
210                        (assq 'filename disposition)))
211               (incf count))))
212      mm-handle)
213     count))
214
215 (defun notmuch-save-attachments (mm-handle &optional queryp)
216   (notmuch-foreach-mime-part
217    (lambda (p)
218      (let ((disposition (mm-handle-disposition p)))
219        (and (listp disposition)
220             (or (equal (car disposition) "attachment")
221                 (and (equal (car disposition) "inline")
222                      (assq 'filename disposition)))
223             (or (not queryp)
224                 (y-or-n-p
225                  (concat "Save '" (cdr (assq 'filename disposition)) "' ")))
226             (mm-save-part p))))
227    mm-handle))
228
229 (defun notmuch-show-save-attachments ()
230   "Save all attachments from the current message."
231   (interactive)
232   (with-current-notmuch-show-message
233    (let ((mm-handle (mm-dissect-buffer)))
234      (notmuch-save-attachments
235       mm-handle (> (notmuch-count-attachments mm-handle) 1))))
236   (message "Done"))
237
238 (defun notmuch-show-with-message-as-text (fn)
239   "Apply FN to a text representation of the current message.
240
241 FN is called with one argument, the message properties. It should
242 operation on the contents of the current buffer."
243
244   ;; Remake the header to ensure that all information is available.
245   (let* ((to (notmuch-show-get-to))
246          (cc (notmuch-show-get-cc))
247          (from (notmuch-show-get-from))
248          (subject (notmuch-show-get-subject))
249          (date (notmuch-show-get-date))
250          (tags (notmuch-show-get-tags))
251          (depth (notmuch-show-get-depth))
252
253          (header (concat
254                   "Subject: " subject "\n"
255                   "To: " to "\n"
256                   (if (not (string= cc ""))
257                       (concat "Cc: " cc "\n")
258                     "")
259                   "From: " from "\n"
260                   "Date: " date "\n"
261                   (if tags
262                       (concat "Tags: "
263                               (mapconcat #'identity tags ", ") "\n")
264                     "")))
265          (all (buffer-substring (notmuch-show-message-top)
266                                 (notmuch-show-message-bottom)))
267
268          (props (notmuch-show-get-message-properties))
269          (indenting notmuch-show-indent-content))
270     (with-temp-buffer
271       (insert all)
272       (if indenting
273           (indent-rigidly (point-min) (point-max) (- depth)))
274       ;; Remove the original header.
275       (goto-char (point-min))
276       (re-search-forward "^$" (point-max) nil)
277       (delete-region (point-min) (point))
278       (insert header)
279       (funcall fn props))))
280
281 (defun notmuch-show-print-message ()
282   "Print the current message."
283   (interactive)
284   (notmuch-show-with-message-as-text 'notmuch-print-message))
285
286 (defun notmuch-show-fontify-header ()
287   (let ((face (cond
288                ((looking-at "[Tt]o:")
289                 'message-header-to)
290                ((looking-at "[Bb]?[Cc][Cc]:")
291                 'message-header-cc)
292                ((looking-at "[Ss]ubject:")
293                 'message-header-subject)
294                ((looking-at "[Ff]rom:")
295                 'message-header-from)
296                (t
297                 'message-header-other))))
298
299     (overlay-put (make-overlay (point) (re-search-forward ":"))
300                  'face 'message-header-name)
301     (overlay-put (make-overlay (point) (re-search-forward ".*$"))
302                  'face face)))
303
304 (defun notmuch-show-colour-headers ()
305   "Apply some colouring to the current headers."
306   (goto-char (point-min))
307   (while (looking-at "^[A-Za-z][-A-Za-z0-9]*:")
308     (notmuch-show-fontify-header)
309     (forward-line)))
310
311 (defun notmuch-show-spaces-n (n)
312   "Return a string comprised of `n' spaces."
313   (make-string n ? ))
314
315 (defun notmuch-show-update-tags (tags)
316   "Update the displayed tags of the current message."
317   (save-excursion
318     (goto-char (notmuch-show-message-top))
319     (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
320         (let ((inhibit-read-only t))
321           (replace-match (concat "("
322                                  (propertize (mapconcat 'identity tags " ")
323                                              'face 'notmuch-tag-face)
324                                  ")"))))))
325
326 (defun notmuch-show-clean-address (address)
327   "Try to clean a single email ADDRESS for display.  Return
328 unchanged ADDRESS if parsing fails."
329   (condition-case nil
330     (let (p-name p-address)
331       ;; It would be convenient to use `mail-header-parse-address',
332       ;; but that expects un-decoded mailbox parts, whereas our
333       ;; mailbox parts are already decoded (and hence may contain
334       ;; UTF-8). Given that notmuch should handle most of the awkward
335       ;; cases, some simple string deconstruction should be sufficient
336       ;; here.
337       (cond
338        ;; "User <user@dom.ain>" style.
339        ((string-match "\\(.*\\) <\\(.*\\)>" address)
340         (setq p-name (match-string 1 address)
341               p-address (match-string 2 address)))
342
343        ;; "<user@dom.ain>" style.
344        ((string-match "<\\(.*\\)>" address)
345         (setq p-address (match-string 1 address)))
346
347        ;; Everything else.
348        (t
349         (setq p-address address)))
350
351       (when p-name
352         ;; Remove elements of the mailbox part that are not relevant for
353         ;; display, even if they are required during transport:
354         ;;
355         ;; Backslashes.
356         (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
357
358         ;; Outer single and double quotes, which might be nested.
359         (loop
360          with start-of-loop
361          do (setq start-of-loop p-name)
362
363          when (string-match "^\"\\(.*\\)\"$" p-name)
364          do (setq p-name (match-string 1 p-name))
365
366          when (string-match "^'\\(.*\\)'$" p-name)
367          do (setq p-name (match-string 1 p-name))
368
369          until (string= start-of-loop p-name)))
370
371       ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
372       ;; 'foo@bar.com'.
373       (when (string= p-name p-address)
374         (setq p-name nil))
375
376       ;; If no name results, return just the address.
377       (if (not p-name)
378           p-address
379         ;; Otherwise format the name and address together.
380         (concat p-name " <" p-address ">")))
381     (error address)))
382
383 (defun notmuch-show-insert-headerline (headers date tags depth)
384   "Insert a notmuch style headerline based on HEADERS for a
385 message at DEPTH in the current thread."
386   (let ((start (point)))
387     (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
388             (notmuch-show-clean-address (plist-get headers :From))
389             " ("
390             date
391             ") ("
392             (propertize (mapconcat 'identity tags " ")
393                         'face 'notmuch-tag-face)
394             ")\n")
395     (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
396
397 (defun notmuch-show-insert-header (header header-value)
398   "Insert a single header."
399   (insert header ": " header-value "\n"))
400
401 (defun notmuch-show-insert-headers (headers)
402   "Insert the headers of the current message."
403   (let ((start (point)))
404     (mapc (lambda (header)
405             (let* ((header-symbol (intern (concat ":" header)))
406                    (header-value (plist-get headers header-symbol)))
407               (if (and header-value
408                        (not (string-equal "" header-value)))
409                   (notmuch-show-insert-header header header-value))))
410           notmuch-message-headers)
411     (save-excursion
412       (save-restriction
413         (narrow-to-region start (point-max))
414         (run-hooks 'notmuch-show-markup-headers-hook)))))
415
416 (define-button-type 'notmuch-show-part-button-type
417   'action 'notmuch-show-part-button-default
418   'keymap 'notmuch-show-part-button-map
419   'follow-link t
420   'face 'message-mml)
421
422 (defvar notmuch-show-part-button-map
423   (let ((map (make-sparse-keymap)))
424     (set-keymap-parent map button-map)
425     (define-key map "s" 'notmuch-show-part-button-save)
426     (define-key map "v" 'notmuch-show-part-button-view)
427     (define-key map "o" 'notmuch-show-part-button-interactively-view)
428     map)
429   "Submap for button commands")
430 (fset 'notmuch-show-part-button-map notmuch-show-part-button-map)
431
432 (defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
433   (let ((button))
434     (setq button
435           (insert-button
436            (concat "[ "
437                    (if name (concat name ": ") "")
438                    declared-type
439                    (if (not (string-equal declared-type content-type))
440                        (concat " (as " content-type ")")
441                      "")
442                    (or comment "")
443                    " ]")
444            :type 'notmuch-show-part-button-type
445            :notmuch-part nth
446            :notmuch-filename name
447            :notmuch-content-type content-type))
448     (insert "\n")
449     ;; return button
450     button))
451
452 ;; Functions handling particular MIME parts.
453
454 (defmacro notmuch-with-temp-part-buffer (message-id nth &rest body)
455   (declare (indent 2))
456   (let ((process-crypto (make-symbol "process-crypto")))
457     `(let ((,process-crypto notmuch-show-process-crypto))
458        (with-temp-buffer
459          (setq notmuch-show-process-crypto ,process-crypto)
460          ;; Always acquires the part via `notmuch part', even if it is
461          ;; available in the JSON output.
462          (insert (notmuch-show-get-bodypart-internal ,message-id ,nth))
463          ,@body))))
464
465 (defun notmuch-show-save-part (message-id nth &optional filename content-type)
466   (notmuch-with-temp-part-buffer message-id nth
467     (let ((file (read-file-name
468                  "Filename to save as: "
469                  (or mailcap-download-directory "~/")
470                  nil nil
471                  filename)))
472       ;; Don't re-compress .gz & al.  Arguably we should make
473       ;; `file-name-handler-alist' nil, but that would chop
474       ;; ange-ftp, which is reasonable to use here.
475       (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t))))
476
477 (defun notmuch-show-view-part (message-id nth &optional filename content-type )
478   (notmuch-with-temp-part-buffer message-id nth
479     ;; set mm-inlined-types to nil to force an external viewer
480     (let ((handle (mm-make-handle (current-buffer) (list content-type)))
481           (mm-inlined-types nil))
482       ;; We override mm-save-part as notmuch-show-save-part is better
483       ;; since it offers the filename. We need to lexically bind
484       ;; everything we need for notmuch-show-save-part to prevent
485       ;; potential dynamic shadowing.
486       (lexical-let ((message-id message-id)
487                     (nth nth)
488                     (filename filename)
489                     (content-type content-type))
490         (flet ((mm-save-part (&rest args) (notmuch-show-save-part
491                                            message-id nth filename content-type)))
492           (mm-display-part handle))))))
493
494 (defun notmuch-show-interactively-view-part (message-id nth &optional filename content-type)
495   (notmuch-with-temp-part-buffer message-id nth
496     (let ((handle (mm-make-handle (current-buffer) (list content-type))))
497       (mm-interactively-view-part handle))))
498
499 (defun notmuch-show-mm-display-part-inline (msg part nth content-type)
500   "Use the mm-decode/mm-view functions to display a part in the
501 current buffer, if possible."
502   (let ((display-buffer (current-buffer)))
503     (with-temp-buffer
504       (let* ((charset (plist-get part :content-charset))
505              (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset)))))
506         ;; If the user wants the part inlined, insert the content and
507         ;; test whether we are able to inline it (which includes both
508         ;; capability and suitability tests).
509         (when (mm-inlined-p handle)
510           (insert (notmuch-show-get-bodypart-content msg part nth))
511           (when (mm-inlinable-p handle)
512             (set-buffer display-buffer)
513             (mm-display-part handle)
514             t))))))
515
516 (defvar notmuch-show-multipart/alternative-discouraged
517   '(
518     ;; Avoid HTML parts.
519     "text/html"
520     ;; multipart/related usually contain a text/html part and some associated graphics.
521     "multipart/related"
522     ))
523
524 (defun notmuch-show-multipart/*-to-list (part)
525   (mapcar (lambda (inner-part) (plist-get inner-part :content-type))
526           (plist-get part :content)))
527
528 (defun notmuch-show-multipart/alternative-choose (types)
529   ;; Based on `mm-preferred-alternative-precedence'.
530   (let ((seq types))
531     (dolist (pref (reverse notmuch-show-multipart/alternative-discouraged))
532       (dolist (elem (copy-sequence seq))
533         (when (string-match pref elem)
534           (setq seq (nconc (delete elem seq) (list elem))))))
535     seq))
536
537 (defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth declared-type)
538   (notmuch-show-insert-part-header nth declared-type content-type nil)
539   (let ((chosen-type (car (notmuch-show-multipart/alternative-choose (notmuch-show-multipart/*-to-list part))))
540         (inner-parts (plist-get part :content))
541         (start (point)))
542     ;; This inserts all parts of the chosen type rather than just one,
543     ;; but it's not clear that this is the wrong thing to do - which
544     ;; should be chosen if there are more than one that match?
545     (mapc (lambda (inner-part)
546             (let ((inner-type (plist-get inner-part :content-type)))
547               (if (or notmuch-show-all-multipart/alternative-parts
548                       (string= chosen-type inner-type))
549                   (notmuch-show-insert-bodypart msg inner-part depth)
550                 (notmuch-show-insert-part-header (plist-get inner-part :id) inner-type inner-type nil " (not shown)"))))
551           inner-parts)
552
553     (when notmuch-show-indent-multipart
554       (indent-rigidly start (point) 1)))
555   t)
556
557 (defun notmuch-show-setup-w3m ()
558   "Instruct w3m how to retrieve content from a \"related\" part of a message."
559   (interactive)
560   (if (boundp 'w3m-cid-retrieve-function-alist)
561     (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)
562       (push (cons 'notmuch-show-mode 'notmuch-show-w3m-cid-retrieve)
563             w3m-cid-retrieve-function-alist)))
564   (setq mm-inline-text-html-with-images t))
565
566 (defvar w3m-current-buffer) ;; From `w3m.el'.
567 (defvar notmuch-show-w3m-cid-store nil)
568 (make-variable-buffer-local 'notmuch-show-w3m-cid-store)
569
570 (defun notmuch-show-w3m-cid-store-internal (content-id
571                                             message-id
572                                             part-number
573                                             content-type
574                                             content)
575   (push (list content-id
576               message-id
577               part-number
578               content-type
579               content)
580         notmuch-show-w3m-cid-store))
581
582 (defun notmuch-show-w3m-cid-store (msg part)
583   (let ((content-id (plist-get part :content-id)))
584     (when content-id
585       (notmuch-show-w3m-cid-store-internal (concat "cid:" content-id)
586                                            (plist-get msg :id)
587                                            (plist-get part :id)
588                                            (plist-get part :content-type)
589                                            nil))))
590
591 (defun notmuch-show-w3m-cid-retrieve (url &rest args)
592   (let ((matching-part (with-current-buffer w3m-current-buffer
593                          (assoc url notmuch-show-w3m-cid-store))))
594     (if matching-part
595         (let ((message-id (nth 1 matching-part))
596               (part-number (nth 2 matching-part))
597               (content-type (nth 3 matching-part))
598               (content (nth 4 matching-part)))
599           ;; If we don't already have the content, get it and cache
600           ;; it, as some messages reference the same cid: part many
601           ;; times (hundreds!), which results in many calls to
602           ;; `notmuch part'.
603           (unless content
604             (setq content (notmuch-show-get-bodypart-internal (concat "id:" message-id)
605                                                               part-number))
606             (with-current-buffer w3m-current-buffer
607               (notmuch-show-w3m-cid-store-internal url
608                                                    message-id
609                                                    part-number
610                                                    content-type
611                                                    content)))
612           (insert content)
613           content-type)
614       nil)))
615
616 (defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth declared-type)
617   (notmuch-show-insert-part-header nth declared-type content-type nil)
618   (let ((inner-parts (plist-get part :content))
619         (start (point)))
620
621     ;; We assume that the first part is text/html and the remainder
622     ;; things that it references.
623
624     ;; Stash the non-primary parts.
625     (mapc (lambda (part)
626             (notmuch-show-w3m-cid-store msg part))
627           (cdr inner-parts))
628
629     ;; Render the primary part.
630     (notmuch-show-insert-bodypart msg (car inner-parts) depth)
631
632     (when notmuch-show-indent-multipart
633       (indent-rigidly start (point) 1)))
634   t)
635
636 (defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth declared-type)
637   (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
638     (button-put button 'face 'notmuch-crypto-part-header)
639     ;; add signature status button if sigstatus provided
640     (if (plist-member part :sigstatus)
641         (let* ((from (notmuch-show-get-header :From msg))
642                (sigstatus (car (plist-get part :sigstatus))))
643           (notmuch-crypto-insert-sigstatus-button sigstatus from))
644       ;; if we're not adding sigstatus, tell the user how they can get it
645       (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts.")))
646
647   (let ((inner-parts (plist-get part :content))
648         (start (point)))
649     ;; Show all of the parts.
650     (mapc (lambda (inner-part)
651             (notmuch-show-insert-bodypart msg inner-part depth))
652           inner-parts)
653
654     (when notmuch-show-indent-multipart
655       (indent-rigidly start (point) 1)))
656   t)
657
658 (defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth declared-type)
659   (let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
660     (button-put button 'face 'notmuch-crypto-part-header)
661     ;; add encryption status button if encstatus specified
662     (if (plist-member part :encstatus)
663         (let ((encstatus (car (plist-get part :encstatus))))
664           (notmuch-crypto-insert-encstatus-button encstatus)
665           ;; add signature status button if sigstatus specified
666           (if (plist-member part :sigstatus)
667               (let* ((from (notmuch-show-get-header :From msg))
668                      (sigstatus (car (plist-get part :sigstatus))))
669                 (notmuch-crypto-insert-sigstatus-button sigstatus from))))
670       ;; if we're not adding encstatus, tell the user how they can get it
671       (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts.")))
672
673   (let ((inner-parts (plist-get part :content))
674         (start (point)))
675     ;; Show all of the parts.
676     (mapc (lambda (inner-part)
677             (notmuch-show-insert-bodypart msg inner-part depth))
678           inner-parts)
679
680     (when notmuch-show-indent-multipart
681       (indent-rigidly start (point) 1)))
682   t)
683
684 (defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth declared-type)
685   (notmuch-show-insert-part-header nth declared-type content-type nil)
686   (let ((inner-parts (plist-get part :content))
687         (start (point)))
688     ;; Show all of the parts.
689     (mapc (lambda (inner-part)
690             (notmuch-show-insert-bodypart msg inner-part depth))
691           inner-parts)
692
693     (when notmuch-show-indent-multipart
694       (indent-rigidly start (point) 1)))
695   t)
696
697 (defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth declared-type)
698   (notmuch-show-insert-part-header nth declared-type content-type nil)
699   (let* ((message (car (plist-get part :content)))
700          (body (car (plist-get message :body)))
701          (start (point)))
702
703     ;; Override `notmuch-message-headers' to force `From' to be
704     ;; displayed.
705     (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
706       (notmuch-show-insert-headers (plist-get message :headers)))
707
708     ;; Blank line after headers to be compatible with the normal
709     ;; message display.
710     (insert "\n")
711
712     ;; Show the body
713     (notmuch-show-insert-bodypart msg body depth)
714
715     (when notmuch-show-indent-multipart
716       (indent-rigidly start (point) 1)))
717   t)
718
719 (defun notmuch-show-insert-part-text/plain (msg part content-type nth depth declared-type)
720   (let ((start (point)))
721     ;; If this text/plain part is not the first part in the message,
722     ;; insert a header to make this clear.
723     (if (> nth 1)
724         (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename)))
725     (insert (notmuch-show-get-bodypart-content msg part nth))
726     (save-excursion
727       (save-restriction
728         (narrow-to-region start (point-max))
729         (run-hook-with-args 'notmuch-show-insert-text/plain-hook msg depth))))
730   t)
731
732 (defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth declared-type)
733   (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename))
734   (insert (with-temp-buffer
735             (insert (notmuch-show-get-bodypart-content msg part nth))
736             (goto-char (point-min))
737             (let ((file (make-temp-file "notmuch-ical"))
738                   result)
739               (icalendar--convert-ical-to-diary
740                (icalendar--read-element nil nil)
741                file t)
742               (set-buffer (get-file-buffer file))
743               (setq result (buffer-substring (point-min) (point-max)))
744               (set-buffer-modified-p nil)
745               (kill-buffer (current-buffer))
746               (delete-file file)
747               result)))
748   t)
749
750 ;; For backwards compatibility.
751 (defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth declared-type)
752   (notmuch-show-insert-part-text/calendar msg part content-type nth depth declared-type))
753
754 (defun notmuch-show-insert-part-application/octet-stream (msg part content-type nth depth declared-type)
755   ;; If we can deduce a MIME type from the filename of the attachment,
756   ;; do so and pass it on to the handler for that type.
757   (if (plist-get part :filename)
758       (let ((extension (file-name-extension (plist-get part :filename)))
759             mime-type)
760         (if extension
761             (progn
762               (mailcap-parse-mimetypes)
763               (setq mime-type (mailcap-extension-to-mime extension))
764               (if (and mime-type
765                        (not (string-equal mime-type "application/octet-stream")))
766                   (notmuch-show-insert-bodypart-internal msg part mime-type nth depth content-type)
767                 nil))
768           nil))))
769
770 ;; Handler for wash generated inline patch fake parts.
771 (defun notmuch-show-insert-part-inline-patch-fake-part (msg part content-type nth depth declared-type)
772   (notmuch-show-insert-part-*/* msg part "text/x-diff" nth depth "inline patch"))
773
774 (defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type)
775   ;; This handler _must_ succeed - it is the handler of last resort.
776   (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))
777   (notmuch-show-mm-display-part-inline msg part nth content-type)
778   t)
779
780 ;; Functions for determining how to handle MIME parts.
781
782 (defun notmuch-show-split-content-type (content-type)
783   (split-string content-type "/"))
784
785 (defun notmuch-show-handlers-for (content-type)
786   "Return a list of content handlers for a part of type CONTENT-TYPE."
787   (let (result)
788     (mapc (lambda (func)
789             (if (functionp func)
790                 (push func result)))
791           ;; Reverse order of prefrence.
792           (list (intern (concat "notmuch-show-insert-part-*/*"))
793                 (intern (concat
794                          "notmuch-show-insert-part-"
795                          (car (notmuch-show-split-content-type content-type))
796                          "/*"))
797                 (intern (concat "notmuch-show-insert-part-" content-type))))
798     result))
799
800 ;; Helper for parts which are generally not included in the default
801 ;; JSON output.
802 (defun notmuch-show-get-bodypart-internal (message-id part-number)
803   (let ((args '("show" "--format=raw"))
804         (part-arg (format "--part=%s" part-number)))
805     (setq args (append args (list part-arg)))
806     (if notmuch-show-process-crypto
807         (setq args (append args '("--decrypt"))))
808     (setq args (append args (list message-id)))
809     (with-temp-buffer
810       (let ((coding-system-for-read 'no-conversion))
811         (progn
812           (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))
813           (buffer-string))))))
814
815 (defun notmuch-show-get-bodypart-content (msg part nth)
816   (or (plist-get part :content)
817       (notmuch-show-get-bodypart-internal (concat "id:" (plist-get msg :id)) nth)))
818
819 ;; \f
820
821 (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth declared-type)
822   (let ((handlers (notmuch-show-handlers-for content-type)))
823     ;; Run the content handlers until one of them returns a non-nil
824     ;; value.
825     (while (and handlers
826                 (not (funcall (car handlers) msg part content-type nth depth declared-type)))
827       (setq handlers (cdr handlers))))
828   t)
829
830 (defun notmuch-show-insert-bodypart (msg part depth)
831   "Insert the body part PART at depth DEPTH in the current thread."
832   (let ((content-type (downcase (plist-get part :content-type)))
833         (nth (plist-get part :id)))
834     (notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type))
835   ;; Some of the body part handlers leave point somewhere up in the
836   ;; part, so we make sure that we're down at the end.
837   (goto-char (point-max))
838   ;; Ensure that the part ends with a carriage return.
839   (unless (bolp)
840     (insert "\n")))
841
842 (defun notmuch-show-insert-body (msg body depth)
843   "Insert the body BODY at depth DEPTH in the current thread."
844   (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
845
846 (defun notmuch-show-make-symbol (type)
847   (make-symbol (concat "notmuch-show-" type)))
848
849 (defun notmuch-show-strip-re (string)
850   (replace-regexp-in-string "\\([Rr]e: *\\)+" "" string))
851
852 (defvar notmuch-show-previous-subject "")
853 (make-variable-buffer-local 'notmuch-show-previous-subject)
854
855 (defun notmuch-show-insert-msg (msg depth)
856   "Insert the message MSG at depth DEPTH in the current thread."
857   (let* ((headers (plist-get msg :headers))
858          ;; Indentation causes the buffer offset of the start/end
859          ;; points to move, so we must use markers.
860          message-start message-end
861          content-start content-end
862          headers-start headers-end
863          body-start body-end
864          (headers-invis-spec (notmuch-show-make-symbol "header"))
865          (message-invis-spec (notmuch-show-make-symbol "message"))
866          (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
867
868     ;; Set `buffer-invisibility-spec' to `nil' (a list), otherwise
869     ;; removing items from `buffer-invisibility-spec' (which is what
870     ;; `notmuch-show-headers-visible' and
871     ;; `notmuch-show-message-visible' do) is a no-op and has no
872     ;; effect. This caused threads with only matching messages to have
873     ;; those messages hidden initially because
874     ;; `buffer-invisibility-spec' stayed `t'.
875     ;;
876     ;; This needs to be set here (rather than just above the call to
877     ;; `notmuch-show-headers-visible') because some of the part
878     ;; rendering or body washing functions
879     ;; (e.g. `notmuch-wash-text/plain-citations') manipulate
880     ;; `buffer-invisibility-spec').
881     (when (eq buffer-invisibility-spec t)
882       (setq buffer-invisibility-spec nil))
883
884     (setq message-start (point-marker))
885
886     (notmuch-show-insert-headerline headers
887                                     (or (if notmuch-show-relative-dates
888                                             (plist-get msg :date_relative)
889                                           nil)
890                                         (plist-get headers :Date))
891                                     (plist-get msg :tags) depth)
892
893     (setq content-start (point-marker))
894
895     (plist-put msg :headers-invis-spec headers-invis-spec)
896     (plist-put msg :message-invis-spec message-invis-spec)
897
898     ;; Set `headers-start' to point after the 'Subject:' header to be
899     ;; compatible with the existing implementation. This just sets it
900     ;; to after the first header.
901     (notmuch-show-insert-headers headers)
902     (save-excursion
903       (goto-char content-start)
904       ;; If the subject of this message is the same as that of the
905       ;; previous message, don't display it when this message is
906       ;; collapsed.
907       (when (not (string= notmuch-show-previous-subject
908                           bare-subject))
909         (forward-line 1))
910       (setq headers-start (point-marker)))
911     (setq headers-end (point-marker))
912
913     (setq notmuch-show-previous-subject bare-subject)
914
915     (setq body-start (point-marker))
916     ;; A blank line between the headers and the body.
917     (insert "\n")
918     (notmuch-show-insert-body msg (plist-get msg :body)
919                               (if notmuch-show-indent-content depth 0))
920     ;; Ensure that the body ends with a newline.
921     (unless (bolp)
922       (insert "\n"))
923     (setq body-end (point-marker))
924     (setq content-end (point-marker))
925
926     ;; Indent according to the depth in the thread.
927     (if notmuch-show-indent-content
928         (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)))
929
930     (setq message-end (point-max-marker))
931
932     ;; Save the extents of this message over the whole text of the
933     ;; message.
934     (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
935
936     (let ((headers-overlay (make-overlay headers-start headers-end))
937           (invis-specs (list headers-invis-spec message-invis-spec)))
938       (overlay-put headers-overlay 'invisible invis-specs)
939       (overlay-put headers-overlay 'priority 10))
940     (overlay-put (make-overlay body-start body-end) 'invisible message-invis-spec)
941
942     (plist-put msg :depth depth)
943
944     ;; Save the properties for this message. Currently this saves the
945     ;; entire message (augmented it with other stuff), which seems
946     ;; like overkill. We might save a reduced subset (for example, not
947     ;; the content).
948     (notmuch-show-set-message-properties msg)
949
950     ;; Set header visibility.
951     (notmuch-show-headers-visible msg notmuch-message-headers-visible)
952
953     ;; Message visibility depends on whether it matched the search
954     ;; criteria.
955     (notmuch-show-message-visible msg (plist-get msg :match))))
956
957 (defun notmuch-show-toggle-process-crypto ()
958   "Toggle the processing of cryptographic MIME parts."
959   (interactive)
960   (setq notmuch-show-process-crypto (not notmuch-show-process-crypto))
961   (message (if notmuch-show-process-crypto
962                "Processing cryptographic MIME parts."
963              "Not processing cryptographic MIME parts."))
964   (notmuch-show-refresh-view t))
965
966 (defun notmuch-show-toggle-elide-non-matching ()
967   "Toggle the display of non-matching messages."
968   (interactive)
969   (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))
970   (message (if notmuch-show-elide-non-matching-messages
971                "Showing matching messages only."
972              "Showing all messages."))
973   (notmuch-show-refresh-view t))
974
975 (defun notmuch-show-toggle-thread-indentation ()
976   "Toggle the indentation of threads."
977   (interactive)
978   (setq notmuch-show-indent-content (not notmuch-show-indent-content))
979   (message (if notmuch-show-indent-content
980                "Content is indented."
981              "Content is not indented."))
982   (notmuch-show-refresh-view t))
983
984 (defun notmuch-show-insert-tree (tree depth)
985   "Insert the message tree TREE at depth DEPTH in the current thread."
986   (let ((msg (car tree))
987         (replies (cadr tree)))
988     (if (or (not notmuch-show-elide-non-matching-messages)
989             (plist-get msg :match))
990         (notmuch-show-insert-msg msg depth))
991     (notmuch-show-insert-thread replies (1+ depth))))
992
993 (defun notmuch-show-insert-thread (thread depth)
994   "Insert the thread THREAD at depth DEPTH in the current forest."
995   (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
996
997 (defun notmuch-show-insert-forest (forest)
998   "Insert the forest of threads FOREST."
999   (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
1000
1001 (defun notmuch-show-buttonise-links (start end)
1002   "Buttonise URLs and mail addresses between START and END.
1003
1004 This also turns id:\"<message id>\"-parts into buttons for
1005 a corresponding notmuch search."
1006   (goto-address-fontify-region start end)
1007   (save-excursion
1008     (goto-char start)
1009     (while (re-search-forward "id:\\(\"?\\)[^[:space:]\"]+\\1" end t)
1010       ;; remove the overlay created by goto-address-mode
1011       (remove-overlays (match-beginning 0) (match-end 0) 'goto-address t)
1012       (make-text-button (match-beginning 0) (match-end 0)
1013                         'action `(lambda (arg)
1014                                    (notmuch-show ,(match-string-no-properties 0)))
1015                         'follow-link t
1016                         'help-echo "Mouse-1, RET: search for this message"
1017                         'face goto-address-mail-face))))
1018
1019 ;;;###autoload
1020 (defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name)
1021   "Run \"notmuch show\" with the given thread ID and display results.
1022
1023 The optional PARENT-BUFFER is the notmuch-search buffer from
1024 which this notmuch-show command was executed, (so that the
1025 next thread from that buffer can be show when done with this
1026 one).
1027
1028 The optional QUERY-CONTEXT is a notmuch search term. Only
1029 messages from the thread matching this search term are shown if
1030 non-nil.
1031
1032 The optional BUFFER-NAME provides the name of the buffer in
1033 which the message thread is shown. If it is nil (which occurs
1034 when the command is called interactively) the argument to the
1035 function is used."
1036   (interactive "sNotmuch show: ")
1037   (let ((buffer-name (generate-new-buffer-name
1038                       (or buffer-name
1039                           (concat "*notmuch-" thread-id "*")))))
1040     (switch-to-buffer (get-buffer-create buffer-name))
1041     ;; Set the default value for `notmuch-show-process-crypto' in this
1042     ;; buffer.
1043     (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
1044     ;; Set the default value for
1045     ;; `notmuch-show-elide-non-matching-messages' in this buffer. If
1046     ;; there is a prefix argument, invert the default.
1047     (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages)
1048     (if current-prefix-arg
1049         (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)))
1050
1051     (setq notmuch-show-thread-id thread-id
1052           notmuch-show-parent-buffer parent-buffer
1053           notmuch-show-query-context query-context)
1054     (notmuch-show-worker)))
1055
1056 (defun notmuch-show-worker ()
1057   (let ((inhibit-read-only t))
1058
1059     (notmuch-show-mode)
1060     ;; Don't track undo information for this buffer
1061     (set 'buffer-undo-list t)
1062
1063     (erase-buffer)
1064     (goto-char (point-min))
1065     (save-excursion
1066       (let* ((basic-args (list notmuch-show-thread-id))
1067              (args (if notmuch-show-query-context
1068                        (append (list "\'") basic-args
1069                                (list "and (" notmuch-show-query-context ")\'"))
1070                      (append (list "\'") basic-args (list "\'")))))
1071         (notmuch-show-insert-forest (notmuch-query-get-threads args))
1072         ;; If the query context reduced the results to nothing, run
1073         ;; the basic query.
1074         (when (and (eq (buffer-size) 0)
1075                    notmuch-show-query-context)
1076           (notmuch-show-insert-forest
1077            (notmuch-query-get-threads basic-args))))
1078
1079       (jit-lock-register #'notmuch-show-buttonise-links)
1080
1081       (run-hooks 'notmuch-show-hook))
1082
1083     ;; Move straight to the first open message
1084     (unless (notmuch-show-message-visible-p)
1085       (notmuch-show-next-open-message))
1086
1087     ;; Set the header line to the subject of the first open message.
1088     (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject)))
1089
1090     (notmuch-show-mark-read)))
1091
1092 (defun notmuch-show-capture-state ()
1093   "Capture the state of the current buffer.
1094
1095 This includes:
1096  - the list of open messages,
1097  - the current message."
1098   (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
1099
1100 (defun notmuch-show-apply-state (state)
1101   "Apply STATE to the current buffer.
1102
1103 This includes:
1104  - opening the messages previously opened,
1105  - closing all other messages,
1106  - moving to the correct current message."
1107   (let ((current (car state))
1108         (open (cadr state)))
1109
1110     ;; Open those that were open.
1111     (goto-char (point-min))
1112     (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
1113                                            (member (notmuch-show-get-message-id) open))
1114           until (not (notmuch-show-goto-message-next)))
1115
1116     ;; Go to the previously open message.
1117     (goto-char (point-min))
1118     (unless (loop if (string= current (notmuch-show-get-message-id))
1119                   return t
1120                   until (not (notmuch-show-goto-message-next)))
1121       (goto-char (point-min))
1122       (message "Previously current message not found."))
1123     (notmuch-show-message-adjust)))
1124
1125 (defun notmuch-show-refresh-view (&optional retain-state)
1126   "Refresh the current view.
1127
1128 Refreshes the current view, observing changes in display
1129 preferences. If RETAIN-STATE is non-nil then the state of the
1130 buffer is stored and re-applied after the refresh."
1131   (interactive "P")
1132   (let ((inhibit-read-only t)
1133         state)
1134     (if retain-state
1135         (setq state (notmuch-show-capture-state)))
1136     (erase-buffer)
1137     (notmuch-show-worker)
1138     (if state
1139         (notmuch-show-apply-state state))))
1140
1141 (defvar notmuch-show-stash-map
1142   (let ((map (make-sparse-keymap)))
1143     (define-key map "c" 'notmuch-show-stash-cc)
1144     (define-key map "d" 'notmuch-show-stash-date)
1145     (define-key map "F" 'notmuch-show-stash-filename)
1146     (define-key map "f" 'notmuch-show-stash-from)
1147     (define-key map "i" 'notmuch-show-stash-message-id)
1148     (define-key map "I" 'notmuch-show-stash-message-id-stripped)
1149     (define-key map "s" 'notmuch-show-stash-subject)
1150     (define-key map "T" 'notmuch-show-stash-tags)
1151     (define-key map "t" 'notmuch-show-stash-to)
1152     map)
1153   "Submap for stash commands")
1154 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
1155
1156 (defvar notmuch-show-mode-map
1157       (let ((map (make-sparse-keymap)))
1158         (define-key map "?" 'notmuch-help)
1159         (define-key map "q" 'notmuch-kill-this-buffer)
1160         (define-key map (kbd "<C-tab>") 'widget-backward)
1161         (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
1162         (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
1163         (define-key map (kbd "TAB") 'notmuch-show-next-button)
1164         (define-key map "s" 'notmuch-search)
1165         (define-key map "m" 'notmuch-mua-new-mail)
1166         (define-key map "f" 'notmuch-show-forward-message)
1167         (define-key map "r" 'notmuch-show-reply-sender)
1168         (define-key map "R" 'notmuch-show-reply)
1169         (define-key map "|" 'notmuch-show-pipe-message)
1170         (define-key map "w" 'notmuch-show-save-attachments)
1171         (define-key map "V" 'notmuch-show-view-raw-message)
1172         (define-key map "v" 'notmuch-show-view-all-mime-parts)
1173         (define-key map "c" 'notmuch-show-stash-map)
1174         (define-key map "=" 'notmuch-show-refresh-view)
1175         (define-key map "h" 'notmuch-show-toggle-headers)
1176         (define-key map "*" 'notmuch-show-tag-all)
1177         (define-key map "-" 'notmuch-show-remove-tag)
1178         (define-key map "+" 'notmuch-show-add-tag)
1179         (define-key map "X" 'notmuch-show-archive-thread-then-exit)
1180         (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
1181         (define-key map "A" 'notmuch-show-archive-thread-then-next)
1182         (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
1183         (define-key map "N" 'notmuch-show-next-message)
1184         (define-key map "P" 'notmuch-show-previous-message)
1185         (define-key map "n" 'notmuch-show-next-open-message)
1186         (define-key map "p" 'notmuch-show-previous-open-message)
1187         (define-key map (kbd "DEL") 'notmuch-show-rewind)
1188         (define-key map " " 'notmuch-show-advance-and-archive)
1189         (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
1190         (define-key map (kbd "RET") 'notmuch-show-toggle-message)
1191         (define-key map "#" 'notmuch-show-print-message)
1192         (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
1193         (define-key map "$" 'notmuch-show-toggle-process-crypto)
1194         (define-key map "<" 'notmuch-show-toggle-thread-indentation)
1195         (define-key map "t" 'toggle-truncate-lines)
1196         map)
1197       "Keymap for \"notmuch show\" buffers.")
1198 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
1199
1200 (defun notmuch-show-mode ()
1201   "Major mode for viewing a thread with notmuch.
1202
1203 This buffer contains the results of the \"notmuch show\" command
1204 for displaying a single thread of email from your email archives.
1205
1206 By default, various components of email messages, (citations,
1207 signatures, already-read messages), are hidden. You can make
1208 these parts visible by clicking with the mouse button or by
1209 pressing RET after positioning the cursor on a hidden part, (for
1210 which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
1211
1212 Reading the thread sequentially is well-supported by pressing
1213 \\[notmuch-show-advance-and-archive]. This will scroll the current message (if necessary), advance
1214 to the next message, or advance to the next thread (if already on
1215 the last message of a thread).
1216
1217 Other commands are available to read or manipulate the thread
1218 more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages
1219 without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread
1220 without scrolling through with \\[notmuch-show-advance-and-archive]).
1221
1222 You can add or remove arbitrary tags from the current message with
1223 '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
1224
1225 All currently available key bindings:
1226
1227 \\{notmuch-show-mode-map}"
1228   (interactive)
1229   (kill-all-local-variables)
1230   (use-local-map notmuch-show-mode-map)
1231   (setq major-mode 'notmuch-show-mode
1232         mode-name "notmuch-show")
1233   (setq buffer-read-only t
1234         truncate-lines t))
1235
1236 (defun notmuch-show-move-to-message-top ()
1237   (goto-char (notmuch-show-message-top)))
1238
1239 (defun notmuch-show-move-to-message-bottom ()
1240   (goto-char (notmuch-show-message-bottom)))
1241
1242 (defun notmuch-show-message-adjust ()
1243   (recenter 0))
1244
1245 ;; Movement related functions.
1246
1247 ;; There's some strangeness here where a text property applied to a
1248 ;; region a->b is not found when point is at b. We walk backwards
1249 ;; until finding the property.
1250 (defun notmuch-show-message-extent ()
1251   (let (r)
1252     (save-excursion
1253       (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
1254         (backward-char)))
1255     r))
1256
1257 (defun notmuch-show-message-top ()
1258   (car (notmuch-show-message-extent)))
1259
1260 (defun notmuch-show-message-bottom ()
1261   (cdr (notmuch-show-message-extent)))
1262
1263 (defun notmuch-show-goto-message-next ()
1264   (let ((start (point)))
1265     (notmuch-show-move-to-message-bottom)
1266     (if (not (eobp))
1267         t
1268       (goto-char start)
1269       nil)))
1270
1271 (defun notmuch-show-goto-message-previous ()
1272   (notmuch-show-move-to-message-top)
1273   (if (bobp)
1274       nil
1275     (backward-char)
1276     (notmuch-show-move-to-message-top)
1277     t))
1278
1279 (defun notmuch-show-mapc (function)
1280   "Iterate through all messages in the current thread with
1281 `notmuch-show-goto-message-next' and call FUNCTION for side
1282 effects."
1283   (save-excursion
1284     (goto-char (point-min))
1285     (loop do (funcall function)
1286           while (notmuch-show-goto-message-next))))
1287
1288 ;; Functions relating to the visibility of messages and their
1289 ;; components.
1290
1291 (defun notmuch-show-element-visible (props visible-p spec-property)
1292   (let ((spec (plist-get props spec-property)))
1293     (if visible-p
1294         (remove-from-invisibility-spec spec)
1295       (add-to-invisibility-spec spec))))
1296
1297 (defun notmuch-show-message-visible (props visible-p)
1298   (notmuch-show-element-visible props visible-p :message-invis-spec)
1299   (notmuch-show-set-prop :message-visible visible-p props))
1300
1301 (defun notmuch-show-headers-visible (props visible-p)
1302   (notmuch-show-element-visible props visible-p :headers-invis-spec)
1303   (notmuch-show-set-prop :headers-visible visible-p props))
1304
1305 ;; Functions for setting and getting attributes of the current
1306 ;; message.
1307
1308 (defun notmuch-show-set-message-properties (props)
1309   (save-excursion
1310     (notmuch-show-move-to-message-top)
1311     (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
1312
1313 (defun notmuch-show-get-message-properties ()
1314   "Return the properties of the current message as a plist.
1315
1316 Some useful entries are:
1317 :headers - Property list containing the headers :Date, :Subject, :From, etc.
1318 :body - Body of the message
1319 :tags - Tags for this message"
1320   (save-excursion
1321     (notmuch-show-move-to-message-top)
1322     (get-text-property (point) :notmuch-message-properties)))
1323
1324 (defun notmuch-show-set-prop (prop val &optional props)
1325   (let ((inhibit-read-only t)
1326         (props (or props
1327                    (notmuch-show-get-message-properties))))
1328     (plist-put props prop val)
1329     (notmuch-show-set-message-properties props)))
1330
1331 (defun notmuch-show-get-prop (prop &optional props)
1332   (let ((props (or props
1333                    (notmuch-show-get-message-properties))))
1334     (plist-get props prop)))
1335
1336 (defun notmuch-show-get-message-id (&optional bare)
1337   "Return the Message-Id of the current message.
1338
1339 If optional argument BARE is non-nil, return
1340 the Message-Id without prefix and quotes."
1341   (if bare
1342       (notmuch-show-get-prop :id)
1343     (concat "id:\"" (notmuch-show-get-prop :id) "\"")))
1344
1345 (defun notmuch-show-get-messages-ids ()
1346   "Return all message ids of messages in the current thread."
1347   (let ((message-ids))
1348     (notmuch-show-mapc
1349      (lambda () (push (notmuch-show-get-message-id) message-ids)))
1350     message-ids))
1351
1352 (defun notmuch-show-get-messages-ids-search ()
1353   "Return a search string for all message ids of messages in the
1354 current thread."
1355   (mapconcat 'identity (notmuch-show-get-messages-ids) " or "))
1356
1357 ;; dme: Would it make sense to use a macro for many of these?
1358
1359 (defun notmuch-show-get-filename ()
1360   "Return the filename of the current message."
1361   (notmuch-show-get-prop :filename))
1362
1363 (defun notmuch-show-get-header (header &optional props)
1364   "Return the named header of the current message, if any."
1365   (plist-get (notmuch-show-get-prop :headers props) header))
1366
1367 (defun notmuch-show-get-cc ()
1368   (notmuch-show-get-header :Cc))
1369
1370 (defun notmuch-show-get-date ()
1371   (notmuch-show-get-header :Date))
1372
1373 (defun notmuch-show-get-from ()
1374   (notmuch-show-get-header :From))
1375
1376 (defun notmuch-show-get-subject ()
1377   (notmuch-show-get-header :Subject))
1378
1379 (defun notmuch-show-get-to ()
1380   (notmuch-show-get-header :To))
1381
1382 (defun notmuch-show-get-depth ()
1383   (notmuch-show-get-prop :depth))
1384
1385 (defun notmuch-show-get-pretty-subject ()
1386   (notmuch-prettify-subject (notmuch-show-get-subject)))
1387
1388 (defun notmuch-show-set-tags (tags)
1389   "Set the tags of the current message."
1390   (notmuch-show-set-prop :tags tags)
1391   (notmuch-show-update-tags tags))
1392
1393 (defun notmuch-show-get-tags ()
1394   "Return the tags of the current message."
1395   (notmuch-show-get-prop :tags))
1396
1397 (defun notmuch-show-message-visible-p ()
1398   "Is the current message visible?"
1399   (notmuch-show-get-prop :message-visible))
1400
1401 (defun notmuch-show-headers-visible-p ()
1402   "Are the headers of the current message visible?"
1403   (notmuch-show-get-prop :headers-visible))
1404
1405 (defun notmuch-show-mark-read ()
1406   "Mark the current message as read."
1407   (notmuch-show-tag-message "-unread"))
1408
1409 ;; Functions for getting attributes of several messages in the current
1410 ;; thread.
1411
1412 (defun notmuch-show-get-message-ids-for-open-messages ()
1413   "Return a list of all message IDs for open messages in the current thread."
1414   (save-excursion
1415     (let (message-ids done)
1416       (goto-char (point-min))
1417       (while (not done)
1418         (if (notmuch-show-message-visible-p)
1419             (setq message-ids (append message-ids (list (notmuch-show-get-message-id)))))
1420         (setq done (not (notmuch-show-goto-message-next)))
1421         )
1422       message-ids
1423       )))
1424
1425 ;; Commands typically bound to keys.
1426
1427 (defun notmuch-show-advance ()
1428   "Advance through thread.
1429
1430 If the current message in the thread is not yet fully visible,
1431 scroll by a near screenful to read more of the message.
1432
1433 Otherwise, (the end of the current message is already within the
1434 current window), advance to the next open message."
1435   (interactive)
1436   (let* ((end-of-this-message (notmuch-show-message-bottom))
1437          (visible-end-of-this-message (1- end-of-this-message))
1438          (ret nil))
1439     (while (invisible-p visible-end-of-this-message)
1440       (setq visible-end-of-this-message
1441             (max (point-min)
1442                  (1- (previous-single-char-property-change
1443                       visible-end-of-this-message 'invisible)))))
1444     (cond
1445      ;; Ideally we would test `end-of-this-message' against the result
1446      ;; of `window-end', but that doesn't account for the fact that
1447      ;; the end of the message might be hidden.
1448      ((and visible-end-of-this-message
1449            (> visible-end-of-this-message (window-end)))
1450       ;; The bottom of this message is not visible - scroll.
1451       (scroll-up nil))
1452
1453      ((not (= end-of-this-message (point-max)))
1454       ;; This is not the last message - move to the next visible one.
1455       (notmuch-show-next-open-message))
1456
1457      (t
1458       ;; This is the last message - change the return value
1459       (setq ret t)))
1460     ret))
1461
1462 (defun notmuch-show-advance-and-archive ()
1463   "Advance through thread and archive.
1464
1465 This command is intended to be one of the simplest ways to
1466 process a thread of email. It works exactly like
1467 notmuch-show-advance, in that it scrolls through messages in a
1468 show buffer, except that when it gets to the end of the buffer it
1469 archives the entire current thread, (remove the \"inbox\" tag
1470 from each message), kills the buffer, and displays the next
1471 thread from the search from which this thread was originally
1472 shown."
1473   (interactive)
1474   (if (notmuch-show-advance)
1475       (notmuch-show-archive-thread-then-next)))
1476
1477 (defun notmuch-show-rewind ()
1478   "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
1479
1480 Specifically, if the beginning of the previous email is fewer
1481 than `window-height' lines from the current point, move to it
1482 just like `notmuch-show-previous-message'.
1483
1484 Otherwise, just scroll down a screenful of the current message.
1485
1486 This command does not modify any message tags, (it does not undo
1487 any effects from previous calls to
1488 `notmuch-show-advance-and-archive'."
1489   (interactive)
1490   (let ((start-of-message (notmuch-show-message-top))
1491         (start-of-window (window-start)))
1492     (cond
1493       ;; Either this message is properly aligned with the start of the
1494       ;; window or the start of this message is not visible on the
1495       ;; screen - scroll.
1496      ((or (= start-of-message start-of-window)
1497           (< start-of-message start-of-window))
1498       (scroll-down)
1499       ;; If a small number of lines from the previous message are
1500       ;; visible, realign so that the top of the current message is at
1501       ;; the top of the screen.
1502       (when (<= (count-screen-lines (window-start) start-of-message)
1503                 next-screen-context-lines)
1504         (goto-char (notmuch-show-message-top))
1505         (notmuch-show-message-adjust))
1506       ;; Move to the top left of the window.
1507       (goto-char (window-start)))
1508      (t
1509       ;; Move to the previous message.
1510       (notmuch-show-previous-message)))))
1511
1512 (defun notmuch-show-reply (&optional prompt-for-sender)
1513   "Reply to the sender and all recipients of the current message."
1514   (interactive "P")
1515   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender t))
1516
1517 (defun notmuch-show-reply-sender (&optional prompt-for-sender)
1518   "Reply to the sender of the current message."
1519   (interactive "P")
1520   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender nil))
1521
1522 (defun notmuch-show-forward-message (&optional prompt-for-sender)
1523   "Forward the current message."
1524   (interactive "P")
1525   (with-current-notmuch-show-message
1526    (notmuch-mua-new-forward-message prompt-for-sender)))
1527
1528 (defun notmuch-show-next-message (&optional pop-at-end)
1529   "Show the next message.
1530
1531 If a prefix argument is given and this is the last message in the
1532 thread, navigate to the next thread in the parent search buffer."
1533   (interactive "P")
1534   (if (notmuch-show-goto-message-next)
1535       (progn
1536         (notmuch-show-mark-read)
1537         (notmuch-show-message-adjust))
1538     (if pop-at-end
1539         (notmuch-show-next-thread)
1540       (goto-char (point-max)))))
1541
1542 (defun notmuch-show-previous-message ()
1543   "Show the previous message."
1544   (interactive)
1545   (notmuch-show-goto-message-previous)
1546   (notmuch-show-mark-read)
1547   (notmuch-show-message-adjust))
1548
1549 (defun notmuch-show-next-open-message (&optional pop-at-end)
1550   "Show the next open message.
1551
1552 If a prefix argument is given and this is the last open message
1553 in the thread, navigate to the next thread in the parent search
1554 buffer. Return t if there was a next open message in the thread
1555 to show, nil otherwise."
1556   (interactive "P")
1557   (let (r)
1558     (while (and (setq r (notmuch-show-goto-message-next))
1559                 (not (notmuch-show-message-visible-p))))
1560     (if r
1561         (progn
1562           (notmuch-show-mark-read)
1563           (notmuch-show-message-adjust))
1564       (if pop-at-end
1565           (notmuch-show-next-thread)
1566         (goto-char (point-max))))
1567     r))
1568
1569 (defun notmuch-show-previous-open-message ()
1570   "Show the previous open message."
1571   (interactive)
1572   (while (and (notmuch-show-goto-message-previous)
1573               (not (notmuch-show-message-visible-p))))
1574   (notmuch-show-mark-read)
1575   (notmuch-show-message-adjust))
1576
1577 (defun notmuch-show-view-raw-message ()
1578   "View the file holding the current message."
1579   (interactive)
1580   (let* ((id (notmuch-show-get-message-id))
1581          (buf (get-buffer-create (concat "*notmuch-raw-" id "*"))))
1582     (call-process notmuch-command nil buf nil "show" "--format=raw" id)
1583     (switch-to-buffer buf)
1584     (goto-char (point-min))
1585     (set-buffer-modified-p nil)
1586     (view-buffer buf 'kill-buffer-if-not-modified)))
1587
1588 (defun notmuch-show-pipe-message (entire-thread command)
1589   "Pipe the contents of the current message (or thread) to the given command.
1590
1591 The given command will be executed with the raw contents of the
1592 current email message as stdin. Anything printed by the command
1593 to stdout or stderr will appear in the *notmuch-pipe* buffer.
1594
1595 When invoked with a prefix argument, the command will receive all
1596 open messages in the current thread (formatted as an mbox) rather
1597 than only the current message."
1598   (interactive "P\nsPipe message to command: ")
1599   (let (shell-command)
1600     (if entire-thread
1601         (setq shell-command
1602               (concat notmuch-command " show --format=mbox "
1603                       (shell-quote-argument
1604                        (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR "))
1605                       " | " command))
1606       (setq shell-command
1607             (concat notmuch-command " show --format=raw "
1608                     (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
1609     (let ((buf (get-buffer-create (concat "*notmuch-pipe*"))))
1610       (with-current-buffer buf
1611         (setq buffer-read-only nil)
1612         (erase-buffer)
1613         (let ((exit-code (call-process-shell-command shell-command nil buf)))
1614           (goto-char (point-max))
1615           (set-buffer-modified-p nil)
1616           (setq buffer-read-only t)
1617           (unless (zerop exit-code)
1618             (switch-to-buffer-other-window buf)
1619             (message (format "Command '%s' exited abnormally with code %d"
1620                              shell-command exit-code))))))))
1621
1622 (defun notmuch-show-tag-message (&rest tag-changes)
1623   "Change tags for the current message.
1624
1625 TAG-CHANGES is a list of tag operations for `notmuch-tag'."
1626   (let* ((current-tags (notmuch-show-get-tags))
1627          (new-tags (notmuch-update-tags current-tags tag-changes)))
1628     (unless (equal current-tags new-tags)
1629       (apply 'notmuch-tag (notmuch-show-get-message-id) tag-changes)
1630       (notmuch-show-set-tags new-tags))))
1631
1632 (defun notmuch-show-tag (&optional initial-input)
1633   "Change tags for the current message, read input from the minibuffer."
1634   (interactive)
1635   (let ((tag-changes (notmuch-read-tag-changes
1636                       initial-input (notmuch-show-get-message-id))))
1637     (apply 'notmuch-show-tag-message tag-changes)))
1638
1639 (defun notmuch-show-tag-all (&rest tag-changes)
1640   "Change tags for all messages in the current thread.
1641
1642 TAG-CHANGES is a list of tag operations for `notmuch-tag'."
1643   (interactive (notmuch-read-tag-changes nil notmuch-show-thread-id))
1644   (apply 'notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes)
1645   (notmuch-show-mapc
1646    (lambda ()
1647      (let* ((current-tags (notmuch-show-get-tags))
1648             (new-tags (notmuch-update-tags current-tags tag-changes)))
1649        (unless (equal current-tags new-tags)
1650          (notmuch-show-set-tags new-tags))))))
1651
1652 (defun notmuch-show-add-tag ()
1653   "Same as `notmuch-show-tag' but sets initial input to '+'."
1654   (interactive)
1655   (notmuch-show-tag "+"))
1656
1657 (defun notmuch-show-remove-tag ()
1658   "Same as `notmuch-show-tag' but sets initial input to '-'."
1659   (interactive)
1660   (notmuch-show-tag "-"))
1661
1662 (defun notmuch-show-toggle-headers ()
1663   "Toggle the visibility of the current message headers."
1664   (interactive)
1665   (let ((props (notmuch-show-get-message-properties)))
1666     (notmuch-show-headers-visible
1667      props
1668      (not (plist-get props :headers-visible))))
1669   (force-window-update))
1670
1671 (defun notmuch-show-toggle-message ()
1672   "Toggle the visibility of the current message."
1673   (interactive)
1674   (let ((props (notmuch-show-get-message-properties)))
1675     (notmuch-show-message-visible
1676      props
1677      (not (plist-get props :message-visible))))
1678   (force-window-update))
1679
1680 (defun notmuch-show-open-or-close-all ()
1681   "Set the visibility all of the messages in the current thread.
1682 By default make all of the messages visible. With a prefix
1683 argument, hide all of the messages."
1684   (interactive)
1685   (save-excursion
1686     (goto-char (point-min))
1687     (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
1688                                            (not current-prefix-arg))
1689           until (not (notmuch-show-goto-message-next))))
1690   (force-window-update))
1691
1692 (defun notmuch-show-next-button ()
1693   "Advance point to the next button in the buffer."
1694   (interactive)
1695   (forward-button 1))
1696
1697 (defun notmuch-show-previous-button ()
1698   "Move point back to the previous button in the buffer."
1699   (interactive)
1700   (backward-button 1))
1701
1702 (defun notmuch-show-next-thread (&optional show-next)
1703   "Move to the next item in the search results, if any."
1704   (interactive "P")
1705   (let ((parent-buffer notmuch-show-parent-buffer))
1706     (notmuch-kill-this-buffer)
1707     (when (buffer-live-p parent-buffer)
1708       (switch-to-buffer parent-buffer)
1709       (notmuch-search-next-thread)
1710       (if show-next
1711           (notmuch-search-show-thread)))))
1712
1713 (defun notmuch-show-archive-thread (&optional unarchive)
1714   "Archive each message in thread.
1715
1716 If a prefix argument is given, the messages will be
1717 \"unarchived\" (ie. the \"inbox\" tag will be added instead of
1718 removed).
1719
1720 Archive each message currently shown by removing the \"inbox\"
1721 tag from each. Then kill this buffer and show the next thread
1722 from the search from which this thread was originally shown.
1723
1724 Note: This command is safe from any race condition of new messages
1725 being delivered to the same thread. It does not archive the
1726 entire thread, but only the messages shown in the current
1727 buffer."
1728   (interactive "P")
1729   (let ((op (if unarchive "+" "-")))
1730     (notmuch-show-tag-all (concat op "inbox"))))
1731
1732 (defun notmuch-show-archive-thread-then-next ()
1733   "Archive each message in thread, then show next thread from search."
1734   (interactive)
1735   (notmuch-show-archive-thread)
1736   (notmuch-show-next-thread t))
1737
1738 (defun notmuch-show-archive-thread-then-exit ()
1739   "Archive each message in thread, then exit back to search results."
1740   (interactive)
1741   (notmuch-show-archive-thread)
1742   (notmuch-show-next-thread))
1743
1744 (defun notmuch-show-archive-message (&optional unarchive)
1745   "Archive the current message.
1746
1747 If a prefix argument is given, the message will be
1748 \"unarchived\" (ie. the \"inbox\" tag will be added instead of
1749 removed)."
1750   (interactive "P")
1751   (let ((op (if unarchive "+" "-")))
1752     (notmuch-show-tag-message (concat op "inbox"))))
1753
1754 (defun notmuch-show-archive-message-then-next-or-exit ()
1755   "Archive the current message, then show the next open message in the current thread.
1756
1757 If at the last open message in the current thread, then exit back
1758 to search results."
1759   (interactive)
1760   (notmuch-show-archive-message)
1761   (notmuch-show-next-open-message t))
1762
1763 (defun notmuch-show-archive-message-then-next-or-next-thread ()
1764   "Archive the current message, then show the next open message in the current thread.
1765
1766 If at the last open message in the current thread, then show next
1767 thread from search."
1768   (interactive)
1769   (notmuch-show-archive-message)
1770   (unless (notmuch-show-next-open-message)
1771     (notmuch-show-next-thread t)))
1772
1773 (defun notmuch-show-stash-cc ()
1774   "Copy CC field of current message to kill-ring."
1775   (interactive)
1776   (notmuch-common-do-stash (notmuch-show-get-cc)))
1777
1778 (defun notmuch-show-stash-date ()
1779   "Copy date of current message to kill-ring."
1780   (interactive)
1781   (notmuch-common-do-stash (notmuch-show-get-date)))
1782
1783 (defun notmuch-show-stash-filename ()
1784   "Copy filename of current message to kill-ring."
1785   (interactive)
1786   (notmuch-common-do-stash (notmuch-show-get-filename)))
1787
1788 (defun notmuch-show-stash-from ()
1789   "Copy From address of current message to kill-ring."
1790   (interactive)
1791   (notmuch-common-do-stash (notmuch-show-get-from)))
1792
1793 (defun notmuch-show-stash-message-id ()
1794   "Copy message ID of current message to kill-ring."
1795   (interactive)
1796   (notmuch-common-do-stash (notmuch-show-get-message-id)))
1797
1798 (defun notmuch-show-stash-message-id-stripped ()
1799   "Copy message ID of current message (sans `id:' prefix) to kill-ring."
1800   (interactive)
1801   (notmuch-common-do-stash (notmuch-show-get-message-id t)))
1802
1803 (defun notmuch-show-stash-subject ()
1804   "Copy Subject field of current message to kill-ring."
1805   (interactive)
1806   (notmuch-common-do-stash (notmuch-show-get-subject)))
1807
1808 (defun notmuch-show-stash-tags ()
1809   "Copy tags of current message to kill-ring as a comma separated list."
1810   (interactive)
1811   (notmuch-common-do-stash (mapconcat 'identity (notmuch-show-get-tags) ",")))
1812
1813 (defun notmuch-show-stash-to ()
1814   "Copy To address of current message to kill-ring."
1815   (interactive)
1816   (notmuch-common-do-stash (notmuch-show-get-to)))
1817
1818 ;; Commands typically bound to buttons.
1819
1820 (defun notmuch-show-part-button-default (&optional button)
1821   (interactive)
1822   (notmuch-show-part-button-internal button notmuch-show-part-button-default-action))
1823
1824 (defun notmuch-show-part-button-save (&optional button)
1825   (interactive)
1826   (notmuch-show-part-button-internal button #'notmuch-show-save-part))
1827
1828 (defun notmuch-show-part-button-view (&optional button)
1829   (interactive)
1830   (notmuch-show-part-button-internal button #'notmuch-show-view-part))
1831
1832 (defun notmuch-show-part-button-interactively-view (&optional button)
1833   (interactive)
1834   (notmuch-show-part-button-internal button #'notmuch-show-interactively-view-part))
1835
1836 (defun notmuch-show-part-button-internal (button handler)
1837   (let ((button (or button (button-at (point)))))
1838     (if button
1839         (let ((nth (button-get button :notmuch-part)))
1840           (if nth
1841               (funcall handler (notmuch-show-get-message-id) nth
1842                        (button-get button :notmuch-filename)
1843                        (button-get button :notmuch-content-type)))))))
1844
1845 ;;
1846
1847 (provide 'notmuch-show)