]> git.notmuchmail.org Git - notmuch/blob - emacs/notmuch-show.el
emacs: Add `notmuch-show-only-matching-messages'.
[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/x-vcalendar (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 (defun notmuch-show-insert-part-application/octet-stream (msg part content-type nth depth declared-type)
751   ;; If we can deduce a MIME type from the filename of the attachment,
752   ;; do so and pass it on to the handler for that type.
753   (if (plist-get part :filename)
754       (let ((extension (file-name-extension (plist-get part :filename)))
755             mime-type)
756         (if extension
757             (progn
758               (mailcap-parse-mimetypes)
759               (setq mime-type (mailcap-extension-to-mime extension))
760               (if (and mime-type
761                        (not (string-equal mime-type "application/octet-stream")))
762                   (notmuch-show-insert-bodypart-internal msg part mime-type nth depth content-type)
763                 nil))
764           nil))))
765
766 ;; Handler for wash generated inline patch fake parts.
767 (defun notmuch-show-insert-part-inline-patch-fake-part (msg part content-type nth depth declared-type)
768   (notmuch-show-insert-part-*/* msg part "text/x-diff" nth depth "inline patch"))
769
770 (defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type)
771   ;; This handler _must_ succeed - it is the handler of last resort.
772   (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))
773   (notmuch-show-mm-display-part-inline msg part nth content-type)
774   t)
775
776 ;; Functions for determining how to handle MIME parts.
777
778 (defun notmuch-show-split-content-type (content-type)
779   (split-string content-type "/"))
780
781 (defun notmuch-show-handlers-for (content-type)
782   "Return a list of content handlers for a part of type CONTENT-TYPE."
783   (let (result)
784     (mapc (lambda (func)
785             (if (functionp func)
786                 (push func result)))
787           ;; Reverse order of prefrence.
788           (list (intern (concat "notmuch-show-insert-part-*/*"))
789                 (intern (concat
790                          "notmuch-show-insert-part-"
791                          (car (notmuch-show-split-content-type content-type))
792                          "/*"))
793                 (intern (concat "notmuch-show-insert-part-" content-type))))
794     result))
795
796 ;; Helper for parts which are generally not included in the default
797 ;; JSON output.
798 (defun notmuch-show-get-bodypart-internal (message-id part-number)
799   (let ((args '("show" "--format=raw"))
800         (part-arg (format "--part=%s" part-number)))
801     (setq args (append args (list part-arg)))
802     (if notmuch-show-process-crypto
803         (setq args (append args '("--decrypt"))))
804     (setq args (append args (list message-id)))
805     (with-temp-buffer
806       (let ((coding-system-for-read 'no-conversion))
807         (progn
808           (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))
809           (buffer-string))))))
810
811 (defun notmuch-show-get-bodypart-content (msg part nth)
812   (or (plist-get part :content)
813       (notmuch-show-get-bodypart-internal (concat "id:" (plist-get msg :id)) nth)))
814
815 ;; \f
816
817 (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth declared-type)
818   (let ((handlers (notmuch-show-handlers-for content-type)))
819     ;; Run the content handlers until one of them returns a non-nil
820     ;; value.
821     (while (and handlers
822                 (not (funcall (car handlers) msg part content-type nth depth declared-type)))
823       (setq handlers (cdr handlers))))
824   t)
825
826 (defun notmuch-show-insert-bodypart (msg part depth)
827   "Insert the body part PART at depth DEPTH in the current thread."
828   (let ((content-type (downcase (plist-get part :content-type)))
829         (nth (plist-get part :id)))
830     (notmuch-show-insert-bodypart-internal msg part content-type nth depth content-type))
831   ;; Some of the body part handlers leave point somewhere up in the
832   ;; part, so we make sure that we're down at the end.
833   (goto-char (point-max))
834   ;; Ensure that the part ends with a carriage return.
835   (unless (bolp)
836     (insert "\n")))
837
838 (defun notmuch-show-insert-body (msg body depth)
839   "Insert the body BODY at depth DEPTH in the current thread."
840   (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
841
842 (defun notmuch-show-make-symbol (type)
843   (make-symbol (concat "notmuch-show-" type)))
844
845 (defun notmuch-show-strip-re (string)
846   (replace-regexp-in-string "\\([Rr]e: *\\)+" "" string))
847
848 (defvar notmuch-show-previous-subject "")
849 (make-variable-buffer-local 'notmuch-show-previous-subject)
850
851 (defun notmuch-show-insert-msg (msg depth)
852   "Insert the message MSG at depth DEPTH in the current thread."
853   (let* ((headers (plist-get msg :headers))
854          ;; Indentation causes the buffer offset of the start/end
855          ;; points to move, so we must use markers.
856          message-start message-end
857          content-start content-end
858          headers-start headers-end
859          body-start body-end
860          (headers-invis-spec (notmuch-show-make-symbol "header"))
861          (message-invis-spec (notmuch-show-make-symbol "message"))
862          (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
863
864     ;; Set `buffer-invisibility-spec' to `nil' (a list), otherwise
865     ;; removing items from `buffer-invisibility-spec' (which is what
866     ;; `notmuch-show-headers-visible' and
867     ;; `notmuch-show-message-visible' do) is a no-op and has no
868     ;; effect. This caused threads with only matching messages to have
869     ;; those messages hidden initially because
870     ;; `buffer-invisibility-spec' stayed `t'.
871     ;;
872     ;; This needs to be set here (rather than just above the call to
873     ;; `notmuch-show-headers-visible') because some of the part
874     ;; rendering or body washing functions
875     ;; (e.g. `notmuch-wash-text/plain-citations') manipulate
876     ;; `buffer-invisibility-spec').
877     (when (eq buffer-invisibility-spec t)
878       (setq buffer-invisibility-spec nil))
879
880     (setq message-start (point-marker))
881
882     (notmuch-show-insert-headerline headers
883                                     (or (if notmuch-show-relative-dates
884                                             (plist-get msg :date_relative)
885                                           nil)
886                                         (plist-get headers :Date))
887                                     (plist-get msg :tags) depth)
888
889     (setq content-start (point-marker))
890
891     (plist-put msg :headers-invis-spec headers-invis-spec)
892     (plist-put msg :message-invis-spec message-invis-spec)
893
894     ;; Set `headers-start' to point after the 'Subject:' header to be
895     ;; compatible with the existing implementation. This just sets it
896     ;; to after the first header.
897     (notmuch-show-insert-headers headers)
898     (save-excursion
899       (goto-char content-start)
900       ;; If the subject of this message is the same as that of the
901       ;; previous message, don't display it when this message is
902       ;; collapsed.
903       (when (not (string= notmuch-show-previous-subject
904                           bare-subject))
905         (forward-line 1))
906       (setq headers-start (point-marker)))
907     (setq headers-end (point-marker))
908
909     (setq notmuch-show-previous-subject bare-subject)
910
911     (setq body-start (point-marker))
912     ;; A blank line between the headers and the body.
913     (insert "\n")
914     (notmuch-show-insert-body msg (plist-get msg :body)
915                               (if notmuch-show-indent-content depth 0))
916     ;; Ensure that the body ends with a newline.
917     (unless (bolp)
918       (insert "\n"))
919     (setq body-end (point-marker))
920     (setq content-end (point-marker))
921
922     ;; Indent according to the depth in the thread.
923     (if notmuch-show-indent-content
924         (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)))
925
926     (setq message-end (point-max-marker))
927
928     ;; Save the extents of this message over the whole text of the
929     ;; message.
930     (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
931
932     (let ((headers-overlay (make-overlay headers-start headers-end))
933           (invis-specs (list headers-invis-spec message-invis-spec)))
934       (overlay-put headers-overlay 'invisible invis-specs)
935       (overlay-put headers-overlay 'priority 10))
936     (overlay-put (make-overlay body-start body-end) 'invisible message-invis-spec)
937
938     (plist-put msg :depth depth)
939
940     ;; Save the properties for this message. Currently this saves the
941     ;; entire message (augmented it with other stuff), which seems
942     ;; like overkill. We might save a reduced subset (for example, not
943     ;; the content).
944     (notmuch-show-set-message-properties msg)
945
946     ;; Set header visibility.
947     (notmuch-show-headers-visible msg notmuch-message-headers-visible)
948
949     ;; Message visibility depends on whether it matched the search
950     ;; criteria.
951     (notmuch-show-message-visible msg (plist-get msg :match))))
952
953 (defun notmuch-show-toggle-process-crypto ()
954   "Toggle the processing of cryptographic MIME parts."
955   (interactive)
956   (setq notmuch-show-process-crypto (not notmuch-show-process-crypto))
957   (message (if notmuch-show-process-crypto
958                "Processing cryptographic MIME parts."
959              "Not processing cryptographic MIME parts."))
960   (notmuch-show-refresh-view t))
961
962 (defun notmuch-show-toggle-elide-non-matching ()
963   "Toggle the display of non-matching messages."
964   (interactive)
965   (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))
966   (message (if notmuch-show-elide-non-matching-messages
967                "Showing matching messages only."
968              "Showing all messages."))
969   (notmuch-show-refresh-view t))
970
971 (defun notmuch-show-toggle-thread-indentation ()
972   "Toggle the indentation of threads."
973   (interactive)
974   (setq notmuch-show-indent-content (not notmuch-show-indent-content))
975   (message (if notmuch-show-indent-content
976                "Content is indented."
977              "Content is not indented."))
978   (notmuch-show-refresh-view t))
979
980 (defun notmuch-show-insert-tree (tree depth)
981   "Insert the message tree TREE at depth DEPTH in the current thread."
982   (let ((msg (car tree))
983         (replies (cadr tree)))
984     (if (or (not notmuch-show-elide-non-matching-messages)
985             (plist-get msg :match))
986         (notmuch-show-insert-msg msg depth))
987     (notmuch-show-insert-thread replies (1+ depth))))
988
989 (defun notmuch-show-insert-thread (thread depth)
990   "Insert the thread THREAD at depth DEPTH in the current forest."
991   (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
992
993 (defun notmuch-show-insert-forest (forest)
994   "Insert the forest of threads FOREST."
995   (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
996
997 (defun notmuch-show-buttonise-links (start end)
998   "Buttonise URLs and mail addresses between START and END.
999
1000 This also turns id:\"<message id>\"-parts into buttons for
1001 a corresponding notmuch search."
1002   (goto-address-fontify-region start end)
1003   (save-excursion
1004     (goto-char start)
1005     (while (re-search-forward "id:\\(\"?\\)[^[:space:]\"]+\\1" end t)
1006       ;; remove the overlay created by goto-address-mode
1007       (remove-overlays (match-beginning 0) (match-end 0) 'goto-address t)
1008       (make-text-button (match-beginning 0) (match-end 0)
1009                         'action `(lambda (arg)
1010                                    (notmuch-show ,(match-string-no-properties 0)))
1011                         'follow-link t
1012                         'help-echo "Mouse-1, RET: search for this message"
1013                         'face goto-address-mail-face))))
1014
1015 ;;;###autoload
1016 (defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name)
1017   "Run \"notmuch show\" with the given thread ID and display results.
1018
1019 The optional PARENT-BUFFER is the notmuch-search buffer from
1020 which this notmuch-show command was executed, (so that the
1021 next thread from that buffer can be show when done with this
1022 one).
1023
1024 The optional QUERY-CONTEXT is a notmuch search term. Only
1025 messages from the thread matching this search term are shown if
1026 non-nil.
1027
1028 The optional BUFFER-NAME provides the name of the buffer in
1029 which the message thread is shown. If it is nil (which occurs
1030 when the command is called interactively) the argument to the
1031 function is used."
1032   (interactive "sNotmuch show: ")
1033   (let ((buffer-name (generate-new-buffer-name
1034                       (or buffer-name
1035                           (concat "*notmuch-" thread-id "*")))))
1036     (switch-to-buffer (get-buffer-create buffer-name))
1037     ;; Set the default value for `notmuch-show-process-crypto' in this
1038     ;; buffer.
1039     (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
1040     ;; Set the default value for
1041     ;; `notmuch-show-elide-non-matching-messages' in this buffer.
1042     (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages)
1043
1044     (setq notmuch-show-thread-id thread-id
1045           notmuch-show-parent-buffer parent-buffer
1046           notmuch-show-query-context query-context)
1047     (notmuch-show-worker)))
1048
1049 (defun notmuch-show-worker ()
1050   (let ((inhibit-read-only t))
1051
1052     (notmuch-show-mode)
1053     ;; Don't track undo information for this buffer
1054     (set 'buffer-undo-list t)
1055
1056     (erase-buffer)
1057     (goto-char (point-min))
1058     (save-excursion
1059       (let* ((basic-args (list notmuch-show-thread-id))
1060              (args (if notmuch-show-query-context
1061                        (append (list "\'") basic-args
1062                                (list "and (" notmuch-show-query-context ")\'"))
1063                      (append (list "\'") basic-args (list "\'")))))
1064         (notmuch-show-insert-forest (notmuch-query-get-threads args))
1065         ;; If the query context reduced the results to nothing, run
1066         ;; the basic query.
1067         (when (and (eq (buffer-size) 0)
1068                    notmuch-show-query-context)
1069           (notmuch-show-insert-forest
1070            (notmuch-query-get-threads basic-args))))
1071
1072       (jit-lock-register #'notmuch-show-buttonise-links)
1073
1074       (run-hooks 'notmuch-show-hook))
1075
1076     ;; Move straight to the first open message
1077     (unless (notmuch-show-message-visible-p)
1078       (notmuch-show-next-open-message))
1079
1080     ;; Set the header line to the subject of the first open message.
1081     (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject)))
1082
1083     (notmuch-show-mark-read)))
1084
1085 (defun notmuch-show-capture-state ()
1086   "Capture the state of the current buffer.
1087
1088 This includes:
1089  - the list of open messages,
1090  - the current message."
1091   (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
1092
1093 (defun notmuch-show-apply-state (state)
1094   "Apply STATE to the current buffer.
1095
1096 This includes:
1097  - opening the messages previously opened,
1098  - closing all other messages,
1099  - moving to the correct current message."
1100   (let ((current (car state))
1101         (open (cadr state)))
1102
1103     ;; Open those that were open.
1104     (goto-char (point-min))
1105     (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
1106                                            (member (notmuch-show-get-message-id) open))
1107           until (not (notmuch-show-goto-message-next)))
1108
1109     ;; Go to the previously open message.
1110     (goto-char (point-min))
1111     (unless (loop if (string= current (notmuch-show-get-message-id))
1112                   return t
1113                   until (not (notmuch-show-goto-message-next)))
1114       (goto-char (point-min))
1115       (message "Previously current message not found."))
1116     (notmuch-show-message-adjust)))
1117
1118 (defun notmuch-show-refresh-view (&optional retain-state)
1119   "Refresh the current view.
1120
1121 Refreshes the current view, observing changes in display
1122 preferences. If RETAIN-STATE is non-nil then the state of the
1123 buffer is stored and re-applied after the refresh."
1124   (interactive)
1125   (let ((inhibit-read-only t)
1126         state)
1127     (if retain-state
1128         (setq state (notmuch-show-capture-state)))
1129     (erase-buffer)
1130     (notmuch-show-worker)
1131     (if state
1132         (notmuch-show-apply-state state))))
1133
1134 (defvar notmuch-show-stash-map
1135   (let ((map (make-sparse-keymap)))
1136     (define-key map "c" 'notmuch-show-stash-cc)
1137     (define-key map "d" 'notmuch-show-stash-date)
1138     (define-key map "F" 'notmuch-show-stash-filename)
1139     (define-key map "f" 'notmuch-show-stash-from)
1140     (define-key map "i" 'notmuch-show-stash-message-id)
1141     (define-key map "I" 'notmuch-show-stash-message-id-stripped)
1142     (define-key map "s" 'notmuch-show-stash-subject)
1143     (define-key map "T" 'notmuch-show-stash-tags)
1144     (define-key map "t" 'notmuch-show-stash-to)
1145     map)
1146   "Submap for stash commands")
1147 (fset 'notmuch-show-stash-map notmuch-show-stash-map)
1148
1149 (defvar notmuch-show-mode-map
1150       (let ((map (make-sparse-keymap)))
1151         (define-key map "?" 'notmuch-help)
1152         (define-key map "q" 'notmuch-kill-this-buffer)
1153         (define-key map (kbd "<C-tab>") 'widget-backward)
1154         (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
1155         (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
1156         (define-key map (kbd "TAB") 'notmuch-show-next-button)
1157         (define-key map "s" 'notmuch-search)
1158         (define-key map "m" 'notmuch-mua-new-mail)
1159         (define-key map "f" 'notmuch-show-forward-message)
1160         (define-key map "r" 'notmuch-show-reply-sender)
1161         (define-key map "R" 'notmuch-show-reply)
1162         (define-key map "|" 'notmuch-show-pipe-message)
1163         (define-key map "w" 'notmuch-show-save-attachments)
1164         (define-key map "V" 'notmuch-show-view-raw-message)
1165         (define-key map "v" 'notmuch-show-view-all-mime-parts)
1166         (define-key map "c" 'notmuch-show-stash-map)
1167         (define-key map "=" 'notmuch-show-refresh-view)
1168         (define-key map "h" 'notmuch-show-toggle-headers)
1169         (define-key map "*" 'notmuch-show-tag-all)
1170         (define-key map "-" 'notmuch-show-remove-tag)
1171         (define-key map "+" 'notmuch-show-add-tag)
1172         (define-key map "X" 'notmuch-show-archive-thread-then-exit)
1173         (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
1174         (define-key map "A" 'notmuch-show-archive-thread-then-next)
1175         (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
1176         (define-key map "N" 'notmuch-show-next-message)
1177         (define-key map "P" 'notmuch-show-previous-message)
1178         (define-key map "n" 'notmuch-show-next-open-message)
1179         (define-key map "p" 'notmuch-show-previous-open-message)
1180         (define-key map (kbd "DEL") 'notmuch-show-rewind)
1181         (define-key map " " 'notmuch-show-advance-and-archive)
1182         (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
1183         (define-key map (kbd "RET") 'notmuch-show-toggle-message)
1184         (define-key map "#" 'notmuch-show-print-message)
1185         (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
1186         (define-key map "$" 'notmuch-show-toggle-process-crypto)
1187         (define-key map "<" 'notmuch-show-toggle-thread-indentation)
1188         (define-key map "t" 'toggle-truncate-lines)
1189         map)
1190       "Keymap for \"notmuch show\" buffers.")
1191 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
1192
1193 (defun notmuch-show-mode ()
1194   "Major mode for viewing a thread with notmuch.
1195
1196 This buffer contains the results of the \"notmuch show\" command
1197 for displaying a single thread of email from your email archives.
1198
1199 By default, various components of email messages, (citations,
1200 signatures, already-read messages), are hidden. You can make
1201 these parts visible by clicking with the mouse button or by
1202 pressing RET after positioning the cursor on a hidden part, (for
1203 which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
1204
1205 Reading the thread sequentially is well-supported by pressing
1206 \\[notmuch-show-advance-and-archive]. This will scroll the current message (if necessary), advance
1207 to the next message, or advance to the next thread (if already on
1208 the last message of a thread).
1209
1210 Other commands are available to read or manipulate the thread
1211 more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages
1212 without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread
1213 without scrolling through with \\[notmuch-show-advance-and-archive]).
1214
1215 You can add or remove arbitrary tags from the current message with
1216 '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
1217
1218 All currently available key bindings:
1219
1220 \\{notmuch-show-mode-map}"
1221   (interactive)
1222   (kill-all-local-variables)
1223   (use-local-map notmuch-show-mode-map)
1224   (setq major-mode 'notmuch-show-mode
1225         mode-name "notmuch-show")
1226   (setq buffer-read-only t
1227         truncate-lines t))
1228
1229 (defun notmuch-show-move-to-message-top ()
1230   (goto-char (notmuch-show-message-top)))
1231
1232 (defun notmuch-show-move-to-message-bottom ()
1233   (goto-char (notmuch-show-message-bottom)))
1234
1235 (defun notmuch-show-message-adjust ()
1236   (recenter 0))
1237
1238 ;; Movement related functions.
1239
1240 ;; There's some strangeness here where a text property applied to a
1241 ;; region a->b is not found when point is at b. We walk backwards
1242 ;; until finding the property.
1243 (defun notmuch-show-message-extent ()
1244   (let (r)
1245     (save-excursion
1246       (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
1247         (backward-char)))
1248     r))
1249
1250 (defun notmuch-show-message-top ()
1251   (car (notmuch-show-message-extent)))
1252
1253 (defun notmuch-show-message-bottom ()
1254   (cdr (notmuch-show-message-extent)))
1255
1256 (defun notmuch-show-goto-message-next ()
1257   (let ((start (point)))
1258     (notmuch-show-move-to-message-bottom)
1259     (if (not (eobp))
1260         t
1261       (goto-char start)
1262       nil)))
1263
1264 (defun notmuch-show-goto-message-previous ()
1265   (notmuch-show-move-to-message-top)
1266   (if (bobp)
1267       nil
1268     (backward-char)
1269     (notmuch-show-move-to-message-top)
1270     t))
1271
1272 (defun notmuch-show-mapc (function)
1273   "Iterate through all messages in the current thread with
1274 `notmuch-show-goto-message-next' and call FUNCTION for side
1275 effects."
1276   (save-excursion
1277     (goto-char (point-min))
1278     (loop do (funcall function)
1279           while (notmuch-show-goto-message-next))))
1280
1281 ;; Functions relating to the visibility of messages and their
1282 ;; components.
1283
1284 (defun notmuch-show-element-visible (props visible-p spec-property)
1285   (let ((spec (plist-get props spec-property)))
1286     (if visible-p
1287         (remove-from-invisibility-spec spec)
1288       (add-to-invisibility-spec spec))))
1289
1290 (defun notmuch-show-message-visible (props visible-p)
1291   (notmuch-show-element-visible props visible-p :message-invis-spec)
1292   (notmuch-show-set-prop :message-visible visible-p props))
1293
1294 (defun notmuch-show-headers-visible (props visible-p)
1295   (notmuch-show-element-visible props visible-p :headers-invis-spec)
1296   (notmuch-show-set-prop :headers-visible visible-p props))
1297
1298 ;; Functions for setting and getting attributes of the current
1299 ;; message.
1300
1301 (defun notmuch-show-set-message-properties (props)
1302   (save-excursion
1303     (notmuch-show-move-to-message-top)
1304     (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
1305
1306 (defun notmuch-show-get-message-properties ()
1307   "Return the properties of the current message as a plist.
1308
1309 Some useful entries are:
1310 :headers - Property list containing the headers :Date, :Subject, :From, etc.
1311 :body - Body of the message
1312 :tags - Tags for this message"
1313   (save-excursion
1314     (notmuch-show-move-to-message-top)
1315     (get-text-property (point) :notmuch-message-properties)))
1316
1317 (defun notmuch-show-set-prop (prop val &optional props)
1318   (let ((inhibit-read-only t)
1319         (props (or props
1320                    (notmuch-show-get-message-properties))))
1321     (plist-put props prop val)
1322     (notmuch-show-set-message-properties props)))
1323
1324 (defun notmuch-show-get-prop (prop &optional props)
1325   (let ((props (or props
1326                    (notmuch-show-get-message-properties))))
1327     (plist-get props prop)))
1328
1329 (defun notmuch-show-get-message-id ()
1330   "Return the message id of the current message."
1331   (concat "id:\"" (notmuch-show-get-prop :id) "\""))
1332
1333 (defun notmuch-show-get-messages-ids ()
1334   "Return all message ids of messages in the current thread."
1335   (let ((message-ids))
1336     (notmuch-show-mapc
1337      (lambda () (push (notmuch-show-get-message-id) message-ids)))
1338     message-ids))
1339
1340 (defun notmuch-show-get-messages-ids-search ()
1341   "Return a search string for all message ids of messages in the
1342 current thread."
1343   (mapconcat 'identity (notmuch-show-get-messages-ids) " or "))
1344
1345 ;; dme: Would it make sense to use a macro for many of these?
1346
1347 (defun notmuch-show-get-filename ()
1348   "Return the filename of the current message."
1349   (notmuch-show-get-prop :filename))
1350
1351 (defun notmuch-show-get-header (header &optional props)
1352   "Return the named header of the current message, if any."
1353   (plist-get (notmuch-show-get-prop :headers props) header))
1354
1355 (defun notmuch-show-get-cc ()
1356   (notmuch-show-get-header :Cc))
1357
1358 (defun notmuch-show-get-date ()
1359   (notmuch-show-get-header :Date))
1360
1361 (defun notmuch-show-get-from ()
1362   (notmuch-show-get-header :From))
1363
1364 (defun notmuch-show-get-subject ()
1365   (notmuch-show-get-header :Subject))
1366
1367 (defun notmuch-show-get-to ()
1368   (notmuch-show-get-header :To))
1369
1370 (defun notmuch-show-get-depth ()
1371   (notmuch-show-get-prop :depth))
1372
1373 (defun notmuch-show-get-pretty-subject ()
1374   (notmuch-prettify-subject (notmuch-show-get-subject)))
1375
1376 (defun notmuch-show-set-tags (tags)
1377   "Set the tags of the current message."
1378   (notmuch-show-set-prop :tags tags)
1379   (notmuch-show-update-tags tags))
1380
1381 (defun notmuch-show-get-tags ()
1382   "Return the tags of the current message."
1383   (notmuch-show-get-prop :tags))
1384
1385 (defun notmuch-show-message-visible-p ()
1386   "Is the current message visible?"
1387   (notmuch-show-get-prop :message-visible))
1388
1389 (defun notmuch-show-headers-visible-p ()
1390   "Are the headers of the current message visible?"
1391   (notmuch-show-get-prop :headers-visible))
1392
1393 (defun notmuch-show-mark-read ()
1394   "Mark the current message as read."
1395   (notmuch-show-tag-message "-unread"))
1396
1397 ;; Functions for getting attributes of several messages in the current
1398 ;; thread.
1399
1400 (defun notmuch-show-get-message-ids-for-open-messages ()
1401   "Return a list of all message IDs for open messages in the current thread."
1402   (save-excursion
1403     (let (message-ids done)
1404       (goto-char (point-min))
1405       (while (not done)
1406         (if (notmuch-show-message-visible-p)
1407             (setq message-ids (append message-ids (list (notmuch-show-get-message-id)))))
1408         (setq done (not (notmuch-show-goto-message-next)))
1409         )
1410       message-ids
1411       )))
1412
1413 ;; Commands typically bound to keys.
1414
1415 (defun notmuch-show-advance ()
1416   "Advance through thread.
1417
1418 If the current message in the thread is not yet fully visible,
1419 scroll by a near screenful to read more of the message.
1420
1421 Otherwise, (the end of the current message is already within the
1422 current window), advance to the next open message."
1423   (interactive)
1424   (let* ((end-of-this-message (notmuch-show-message-bottom))
1425          (visible-end-of-this-message (1- end-of-this-message))
1426          (ret nil))
1427     (while (invisible-p visible-end-of-this-message)
1428       (setq visible-end-of-this-message
1429             (max (point-min)
1430                  (1- (previous-single-char-property-change
1431                       visible-end-of-this-message 'invisible)))))
1432     (cond
1433      ;; Ideally we would test `end-of-this-message' against the result
1434      ;; of `window-end', but that doesn't account for the fact that
1435      ;; the end of the message might be hidden.
1436      ((and visible-end-of-this-message
1437            (> visible-end-of-this-message (window-end)))
1438       ;; The bottom of this message is not visible - scroll.
1439       (scroll-up nil))
1440
1441      ((not (= end-of-this-message (point-max)))
1442       ;; This is not the last message - move to the next visible one.
1443       (notmuch-show-next-open-message))
1444
1445      (t
1446       ;; This is the last message - change the return value
1447       (setq ret t)))
1448     ret))
1449
1450 (defun notmuch-show-advance-and-archive ()
1451   "Advance through thread and archive.
1452
1453 This command is intended to be one of the simplest ways to
1454 process a thread of email. It works exactly like
1455 notmuch-show-advance, in that it scrolls through messages in a
1456 show buffer, except that when it gets to the end of the buffer it
1457 archives the entire current thread, (remove the \"inbox\" tag
1458 from each message), kills the buffer, and displays the next
1459 thread from the search from which this thread was originally
1460 shown."
1461   (interactive)
1462   (if (notmuch-show-advance)
1463       (notmuch-show-archive-thread-then-next)))
1464
1465 (defun notmuch-show-rewind ()
1466   "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
1467
1468 Specifically, if the beginning of the previous email is fewer
1469 than `window-height' lines from the current point, move to it
1470 just like `notmuch-show-previous-message'.
1471
1472 Otherwise, just scroll down a screenful of the current message.
1473
1474 This command does not modify any message tags, (it does not undo
1475 any effects from previous calls to
1476 `notmuch-show-advance-and-archive'."
1477   (interactive)
1478   (let ((start-of-message (notmuch-show-message-top))
1479         (start-of-window (window-start)))
1480     (cond
1481       ;; Either this message is properly aligned with the start of the
1482       ;; window or the start of this message is not visible on the
1483       ;; screen - scroll.
1484      ((or (= start-of-message start-of-window)
1485           (< start-of-message start-of-window))
1486       (scroll-down)
1487       ;; If a small number of lines from the previous message are
1488       ;; visible, realign so that the top of the current message is at
1489       ;; the top of the screen.
1490       (when (<= (count-screen-lines (window-start) start-of-message)
1491                 next-screen-context-lines)
1492         (goto-char (notmuch-show-message-top))
1493         (notmuch-show-message-adjust))
1494       ;; Move to the top left of the window.
1495       (goto-char (window-start)))
1496      (t
1497       ;; Move to the previous message.
1498       (notmuch-show-previous-message)))))
1499
1500 (defun notmuch-show-reply (&optional prompt-for-sender)
1501   "Reply to the sender and all recipients of the current message."
1502   (interactive "P")
1503   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender t))
1504
1505 (defun notmuch-show-reply-sender (&optional prompt-for-sender)
1506   "Reply to the sender of the current message."
1507   (interactive "P")
1508   (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender nil))
1509
1510 (defun notmuch-show-forward-message (&optional prompt-for-sender)
1511   "Forward the current message."
1512   (interactive "P")
1513   (with-current-notmuch-show-message
1514    (notmuch-mua-new-forward-message prompt-for-sender)))
1515
1516 (defun notmuch-show-next-message (&optional pop-at-end)
1517   "Show the next message.
1518
1519 If a prefix argument is given and this is the last message in the
1520 thread, navigate to the next thread in the parent search buffer."
1521   (interactive "P")
1522   (if (notmuch-show-goto-message-next)
1523       (progn
1524         (notmuch-show-mark-read)
1525         (notmuch-show-message-adjust))
1526     (if pop-at-end
1527         (notmuch-show-next-thread)
1528       (goto-char (point-max)))))
1529
1530 (defun notmuch-show-previous-message ()
1531   "Show the previous message."
1532   (interactive)
1533   (notmuch-show-goto-message-previous)
1534   (notmuch-show-mark-read)
1535   (notmuch-show-message-adjust))
1536
1537 (defun notmuch-show-next-open-message (&optional pop-at-end)
1538   "Show the next open message.
1539
1540 If a prefix argument is given and this is the last open message
1541 in the thread, navigate to the next thread in the parent search
1542 buffer. Return t if there was a next open message in the thread
1543 to show, nil otherwise."
1544   (interactive "P")
1545   (let (r)
1546     (while (and (setq r (notmuch-show-goto-message-next))
1547                 (not (notmuch-show-message-visible-p))))
1548     (if r
1549         (progn
1550           (notmuch-show-mark-read)
1551           (notmuch-show-message-adjust))
1552       (if pop-at-end
1553           (notmuch-show-next-thread)
1554         (goto-char (point-max))))
1555     r))
1556
1557 (defun notmuch-show-previous-open-message ()
1558   "Show the previous open message."
1559   (interactive)
1560   (while (and (notmuch-show-goto-message-previous)
1561               (not (notmuch-show-message-visible-p))))
1562   (notmuch-show-mark-read)
1563   (notmuch-show-message-adjust))
1564
1565 (defun notmuch-show-view-raw-message ()
1566   "View the file holding the current message."
1567   (interactive)
1568   (let* ((id (notmuch-show-get-message-id))
1569          (buf (get-buffer-create (concat "*notmuch-raw-" id "*"))))
1570     (call-process notmuch-command nil buf nil "show" "--format=raw" id)
1571     (switch-to-buffer buf)
1572     (goto-char (point-min))
1573     (set-buffer-modified-p nil)
1574     (view-buffer buf 'kill-buffer-if-not-modified)))
1575
1576 (defun notmuch-show-pipe-message (entire-thread command)
1577   "Pipe the contents of the current message (or thread) to the given command.
1578
1579 The given command will be executed with the raw contents of the
1580 current email message as stdin. Anything printed by the command
1581 to stdout or stderr will appear in the *notmuch-pipe* buffer.
1582
1583 When invoked with a prefix argument, the command will receive all
1584 open messages in the current thread (formatted as an mbox) rather
1585 than only the current message."
1586   (interactive "P\nsPipe message to command: ")
1587   (let (shell-command)
1588     (if entire-thread
1589         (setq shell-command
1590               (concat notmuch-command " show --format=mbox "
1591                       (shell-quote-argument
1592                        (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR "))
1593                       " | " command))
1594       (setq shell-command
1595             (concat notmuch-command " show --format=raw "
1596                     (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
1597     (let ((buf (get-buffer-create (concat "*notmuch-pipe*"))))
1598       (with-current-buffer buf
1599         (setq buffer-read-only nil)
1600         (erase-buffer)
1601         (let ((exit-code (call-process-shell-command shell-command nil buf)))
1602           (goto-char (point-max))
1603           (set-buffer-modified-p nil)
1604           (setq buffer-read-only t)
1605           (unless (zerop exit-code)
1606             (switch-to-buffer-other-window buf)
1607             (message (format "Command '%s' exited abnormally with code %d"
1608                              shell-command exit-code))))))))
1609
1610 (defun notmuch-show-tag-message (&rest tag-changes)
1611   "Change tags for the current message.
1612
1613 TAG-CHANGES is a list of tag operations for `notmuch-tag'."
1614   (let* ((current-tags (notmuch-show-get-tags))
1615          (new-tags (notmuch-update-tags current-tags tag-changes)))
1616     (unless (equal current-tags new-tags)
1617       (apply 'notmuch-tag (notmuch-show-get-message-id) tag-changes)
1618       (notmuch-show-set-tags new-tags))))
1619
1620 (defun notmuch-show-tag (&optional initial-input)
1621   "Change tags for the current message, read input from the minibuffer."
1622   (interactive)
1623   (let ((tag-changes (notmuch-read-tag-changes
1624                       initial-input (notmuch-show-get-message-id))))
1625     (apply 'notmuch-show-tag-message tag-changes)))
1626
1627 (defun notmuch-show-tag-all (&rest tag-changes)
1628   "Change tags for all messages in the current thread.
1629
1630 TAG-CHANGES is a list of tag operations for `notmuch-tag'."
1631   (interactive (notmuch-read-tag-changes nil notmuch-show-thread-id))
1632   (apply 'notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes)
1633   (notmuch-show-mapc
1634    (lambda ()
1635      (let* ((current-tags (notmuch-show-get-tags))
1636             (new-tags (notmuch-update-tags current-tags tag-changes)))
1637        (unless (equal current-tags new-tags)
1638          (notmuch-show-set-tags new-tags))))))
1639
1640 (defun notmuch-show-add-tag ()
1641   "Same as `notmuch-show-tag' but sets initial input to '+'."
1642   (interactive)
1643   (notmuch-show-tag "+"))
1644
1645 (defun notmuch-show-remove-tag ()
1646   "Same as `notmuch-show-tag' but sets initial input to '-'."
1647   (interactive)
1648   (notmuch-show-tag "-"))
1649
1650 (defun notmuch-show-toggle-headers ()
1651   "Toggle the visibility of the current message headers."
1652   (interactive)
1653   (let ((props (notmuch-show-get-message-properties)))
1654     (notmuch-show-headers-visible
1655      props
1656      (not (plist-get props :headers-visible))))
1657   (force-window-update))
1658
1659 (defun notmuch-show-toggle-message ()
1660   "Toggle the visibility of the current message."
1661   (interactive)
1662   (let ((props (notmuch-show-get-message-properties)))
1663     (notmuch-show-message-visible
1664      props
1665      (not (plist-get props :message-visible))))
1666   (force-window-update))
1667
1668 (defun notmuch-show-open-or-close-all ()
1669   "Set the visibility all of the messages in the current thread.
1670 By default make all of the messages visible. With a prefix
1671 argument, hide all of the messages."
1672   (interactive)
1673   (save-excursion
1674     (goto-char (point-min))
1675     (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
1676                                            (not current-prefix-arg))
1677           until (not (notmuch-show-goto-message-next))))
1678   (force-window-update))
1679
1680 (defun notmuch-show-next-button ()
1681   "Advance point to the next button in the buffer."
1682   (interactive)
1683   (forward-button 1))
1684
1685 (defun notmuch-show-previous-button ()
1686   "Move point back to the previous button in the buffer."
1687   (interactive)
1688   (backward-button 1))
1689
1690 (defun notmuch-show-tag-thread-internal (tag &optional remove)
1691   "Add tag to the current set of messages.
1692
1693 If the remove switch is given, tags will be removed instead of
1694 added."
1695   (goto-char (point-min))
1696   (let ((op (if remove "-" "+")))
1697     (loop do (notmuch-show-tag-message (concat op tag))
1698           until (not (notmuch-show-goto-message-next)))))
1699
1700 (defun notmuch-show-add-tag-thread (tag)
1701   "Add tag to all messages in the current thread."
1702   (interactive)
1703   (notmuch-show-tag-thread-internal tag))
1704
1705 (defun notmuch-show-remove-tag-thread (tag)
1706   "Remove tag from all messages in the current thread."
1707   (interactive)
1708   (notmuch-show-tag-thread-internal tag t))
1709
1710 (defun notmuch-show-next-thread (&optional show-next)
1711   "Move to the next item in the search results, if any."
1712   (interactive "P")
1713   (let ((parent-buffer notmuch-show-parent-buffer))
1714     (notmuch-kill-this-buffer)
1715     (when (buffer-live-p parent-buffer)
1716       (switch-to-buffer parent-buffer)
1717       (notmuch-search-next-thread)
1718       (if show-next
1719           (notmuch-search-show-thread)))))
1720
1721 (defun notmuch-show-archive-thread (&optional unarchive)
1722   "Archive each message in thread.
1723
1724 If a prefix argument is given, the messages will be
1725 \"unarchived\" (ie. the \"inbox\" tag will be added instead of
1726 removed).
1727
1728 Archive each message currently shown by removing the \"inbox\"
1729 tag from each. Then kill this buffer and show the next thread
1730 from the search from which this thread was originally shown.
1731
1732 Note: This command is safe from any race condition of new messages
1733 being delivered to the same thread. It does not archive the
1734 entire thread, but only the messages shown in the current
1735 buffer."
1736   (interactive "P")
1737   (if unarchive
1738       (notmuch-show-add-tag-thread "inbox")
1739     (notmuch-show-remove-tag-thread "inbox")))
1740
1741 (defun notmuch-show-archive-thread-then-next ()
1742   "Archive each message in thread, then show next thread from search."
1743   (interactive)
1744   (notmuch-show-archive-thread)
1745   (notmuch-show-next-thread t))
1746
1747 (defun notmuch-show-archive-thread-then-exit ()
1748   "Archive each message in thread, then exit back to search results."
1749   (interactive)
1750   (notmuch-show-archive-thread)
1751   (notmuch-show-next-thread))
1752
1753 (defun notmuch-show-archive-message (&optional unarchive)
1754   "Archive the current message.
1755
1756 If a prefix argument is given, the message will be
1757 \"unarchived\" (ie. the \"inbox\" tag will be added instead of
1758 removed)."
1759   (interactive "P")
1760   (let ((op (if unarchive "+" "-")))
1761     (notmuch-show-tag-message (concat op "inbox"))))
1762
1763 (defun notmuch-show-archive-message-then-next-or-exit ()
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 exit back
1767 to search results."
1768   (interactive)
1769   (notmuch-show-archive-message)
1770   (notmuch-show-next-open-message t))
1771
1772 (defun notmuch-show-archive-message-then-next-or-next-thread ()
1773   "Archive the current message, then show the next open message in the current thread.
1774
1775 If at the last open message in the current thread, then show next
1776 thread from search."
1777   (interactive)
1778   (notmuch-show-archive-message)
1779   (unless (notmuch-show-next-open-message)
1780     (notmuch-show-next-thread t)))
1781
1782 (defun notmuch-show-stash-cc ()
1783   "Copy CC field of current message to kill-ring."
1784   (interactive)
1785   (notmuch-common-do-stash (notmuch-show-get-cc)))
1786
1787 (defun notmuch-show-stash-date ()
1788   "Copy date of current message to kill-ring."
1789   (interactive)
1790   (notmuch-common-do-stash (notmuch-show-get-date)))
1791
1792 (defun notmuch-show-stash-filename ()
1793   "Copy filename of current message to kill-ring."
1794   (interactive)
1795   (notmuch-common-do-stash (notmuch-show-get-filename)))
1796
1797 (defun notmuch-show-stash-from ()
1798   "Copy From address of current message to kill-ring."
1799   (interactive)
1800   (notmuch-common-do-stash (notmuch-show-get-from)))
1801
1802 (defun notmuch-show-stash-message-id ()
1803   "Copy message ID of current message to kill-ring."
1804   (interactive)
1805   (notmuch-common-do-stash (notmuch-show-get-message-id)))
1806
1807 (defun notmuch-show-stash-message-id-stripped ()
1808   "Copy message ID of current message (sans `id:' prefix) to kill-ring."
1809   (interactive)
1810   (notmuch-common-do-stash (substring (notmuch-show-get-message-id) 4 -1)))
1811
1812 (defun notmuch-show-stash-subject ()
1813   "Copy Subject field of current message to kill-ring."
1814   (interactive)
1815   (notmuch-common-do-stash (notmuch-show-get-subject)))
1816
1817 (defun notmuch-show-stash-tags ()
1818   "Copy tags of current message to kill-ring as a comma separated list."
1819   (interactive)
1820   (notmuch-common-do-stash (mapconcat 'identity (notmuch-show-get-tags) ",")))
1821
1822 (defun notmuch-show-stash-to ()
1823   "Copy To address of current message to kill-ring."
1824   (interactive)
1825   (notmuch-common-do-stash (notmuch-show-get-to)))
1826
1827 ;; Commands typically bound to buttons.
1828
1829 (defun notmuch-show-part-button-default (&optional button)
1830   (interactive)
1831   (notmuch-show-part-button-internal button notmuch-show-part-button-default-action))
1832
1833 (defun notmuch-show-part-button-save (&optional button)
1834   (interactive)
1835   (notmuch-show-part-button-internal button #'notmuch-show-save-part))
1836
1837 (defun notmuch-show-part-button-view (&optional button)
1838   (interactive)
1839   (notmuch-show-part-button-internal button #'notmuch-show-view-part))
1840
1841 (defun notmuch-show-part-button-interactively-view (&optional button)
1842   (interactive)
1843   (notmuch-show-part-button-internal button #'notmuch-show-interactively-view-part))
1844
1845 (defun notmuch-show-part-button-internal (button handler)
1846   (let ((button (or button (button-at (point)))))
1847     (if button
1848         (let ((nth (button-get button :notmuch-part)))
1849           (if nth
1850               (funcall handler (notmuch-show-get-message-id) nth
1851                        (button-get button :notmuch-filename)
1852                        (button-get button :notmuch-content-type)))))))
1853
1854 ;;
1855
1856 (provide 'notmuch-show)