(require 'notmuch-mua)
(require 'notmuch-crypto)
(require 'notmuch-print)
+(require 'notmuch-draft)
(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
(declare-function notmuch-search-next-thread "notmuch" nil)
(&optional query query-context target buffer-name open-target))
(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
(declare-function notmuch-read-query "notmuch" (prompt))
+(declare-function notmuch-draft-resume "notmuch-draft" (id))
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order.
:type 'function
:group 'notmuch-show)
+(defcustom notmuch-show-imenu-indent nil
+ "Should Imenu display messages indented.
+
+By default, Imenu (see Info node `(emacs) Imenu') in a
+notmuch-show buffer displays all messages straight. This is
+because the default Emacs frontend for Imenu makes it difficult
+to select an Imenu entry with spaces in front. Other imenu
+frontends such as counsel-imenu does not have this limitation.
+In these cases, Imenu entries can be indented to reflect the
+position of the message in the thread."
+ :type 'boolean
+ :group 'notmuch-show)
+
(defmacro with-current-notmuch-show-message (&rest body)
"Evaluate body with current buffer set to the text of current message"
`(save-excursion
(with-temp-buffer
(insert all)
(if indenting
- (indent-rigidly (point-min) (point-max) (- depth)))
+ (indent-rigidly (point-min) (point-max) (- (* notmuch-show-indent-messages-width depth))))
;; Remove the original header.
(goto-char (point-min))
(re-search-forward "^$" (point-max) nil)
(indent-rigidly start (point) 1)))
t)
+(defun notmuch-show-insert-part-application/pgp-encrypted (msg part content-type nth depth button)
+ t)
+
(defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth button)
(let ((inner-parts (plist-get part :content))
(start (point)))
(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button)
(notmuch-show-insert-part-text/calendar msg part content-type nth depth button))
+;; https://bugs.gnu.org/28350
+(defun notmuch-show--enriched-decode-display-prop (start end &optional param)
+ (list start end))
+
+(defun notmuch-show-insert-part-text/enriched (msg part content-type nth depth button)
+ (advice-add 'enriched-decode-display-prop :override
+ #'notmuch-show--enriched-decode-display-prop)
+ nil)
+
(defun notmuch-show-get-mime-type-of-application/octet-stream (part)
;; If we can deduce a MIME type from the filename of the attachment,
;; we return that.
(narrow-to-region part-beg part-end)
(delete-region part-beg part-end)
(apply #'notmuch-show-insert-bodypart-internal part-args)
- (indent-rigidly part-beg part-end depth))
+ (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth)))
(goto-char part-end)
(delete-char 1)
(notmuch-show-record-part-information (second part-args)
buttons for a corresponding notmuch search."
(goto-address-fontify-region start end)
(save-excursion
- (let (links)
- (goto-char start)
- (while (re-search-forward notmuch-id-regexp end t)
+ (let (links
+ (beg-line (progn (goto-char start) (line-beginning-position)))
+ (end-line (progn (goto-char end) (line-end-position))))
+ (goto-char beg-line)
+ (while (re-search-forward notmuch-id-regexp end-line t)
(push (list (match-beginning 0) (match-end 0)
(match-string-no-properties 0)) links))
- (goto-char start)
- (while (re-search-forward notmuch-mid-regexp end t)
+ (goto-char beg-line)
+ (while (re-search-forward notmuch-mid-regexp end-line t)
(let* ((mid-cid (match-string-no-properties 1))
(mid (save-match-data
(string-match "^[^/]*" mid-cid)
(interactive "sNotmuch show: \nP")
(let ((buffer-name (generate-new-buffer-name
(or buffer-name
- (concat "*notmuch-" thread-id "*")))))
+ (concat "*notmuch-" thread-id "*"))))
+ ;; We override mm-inline-override-types to stop application/*
+ ;; parts from being displayed unless the user has customized
+ ;; it themselves.
+ (mm-inline-override-types
+ (if (equal mm-inline-override-types
+ (eval (car (get 'mm-inline-override-types 'standard-value))))
+ (cons "application/*" mm-inline-override-types)
+ mm-inline-override-types)))
(switch-to-buffer (get-buffer-create buffer-name))
;; No need to track undo information for this buffer.
(setq buffer-undo-list t)
(message "No messages matched the query!")
nil))))
+(defun notmuch-show--build-queries (thread context)
+ "Return a list of queries to try for this search.
+
+THREAD and CONTEXT are both strings, though CONTEXT may be nil.
+When CONTEXT is not nil, the first query is the conjunction of it
+and THREAD. The next query is THREAD alone, and serves as a
+fallback if the prior matches no messages."
+ (let (queries)
+ (push (list thread) queries)
+ (if context (push (list thread "and (" context ")") queries))
+ queries))
+
(defun notmuch-show--build-buffer (&optional state)
"Display messages matching the current buffer context.
first relevant message.
If no messages match the query return NIL."
- (let* ((basic-args (list notmuch-show-thread-id))
- (args (if notmuch-show-query-context
- (append (list "\'") basic-args
- (list "and (" notmuch-show-query-context ")\'"))
- (append (list "\'") basic-args (list "\'"))))
- (cli-args (cons "--exclude=false"
+ (let* ((cli-args (cons "--exclude=false"
(when notmuch-show-elide-non-matching-messages
(list "--entire-thread=false"))))
-
- (forest (or (notmuch-query-get-threads (append cli-args args))
- ;; If a query context reduced the number of
- ;; results to zero, try again without it.
- (and notmuch-show-query-context
- (notmuch-query-get-threads (append cli-args basic-args)))))
-
+ (queries (notmuch-show--build-queries
+ notmuch-show-thread-id notmuch-show-query-context))
+ (forest nil)
;; Must be reset every time we are going to start inserting
;; messages into the buffer.
(notmuch-show-previous-subject ""))
-
+ ;; Use results from the first query that returns some.
+ (while (and (not forest) queries)
+ (setq forest (notmuch-query-get-threads
+ (append cli-args (list "'") (car queries) (list "'"))))
+ (setq queries (cdr queries)))
(when forest
(notmuch-show-insert-forest forest)
This includes:
- the list of open messages,
- - the current message."
- (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
+ - the combination of current message id with/for each visible window."
+ (let* ((win-list (get-buffer-window-list (current-buffer) nil t))
+ (win-id-combo (mapcar (lambda (win)
+ (with-selected-window win
+ (list win (notmuch-show-get-message-id))))
+ win-list)))
+ (list win-id-combo (notmuch-show-get-message-ids-for-open-messages))))
(defun notmuch-show-get-query ()
"Return the current query in this show buffer"
This includes:
- opening the messages previously opened,
- closing all other messages,
- - moving to the correct current message."
- (let ((current (car state))
+ - moving to the correct current message in every displayed window."
+ (let ((win-msg-alist (car state))
(open (cadr state)))
;; Open those that were open.
(member (notmuch-show-get-message-id) open))
until (not (notmuch-show-goto-message-next)))
- ;; Go to the previously open message.
- (notmuch-show-goto-message current)))
+ (dolist (win-msg-pair win-msg-alist)
+ (with-selected-window (car win-msg-pair)
+ ;; Go to the previously open message in this window
+ (notmuch-show-goto-message (cadr win-msg-pair))))))
(defun notmuch-show-refresh-view (&optional reset-state)
"Refresh the current view.
(define-key map "v" 'notmuch-show-view-part)
(define-key map "o" 'notmuch-show-interactively-view-part)
(define-key map "|" 'notmuch-show-pipe-part)
+ (define-key map "m" 'notmuch-show-choose-mime-of-part)
(define-key map "?" 'notmuch-subkeymap-help)
map)
"Submap for part commands")
(define-key map "|" 'notmuch-show-pipe-message)
(define-key map "w" 'notmuch-show-save-attachments)
(define-key map "V" 'notmuch-show-view-raw-message)
+ (define-key map "e" 'notmuch-show-resume-message)
(define-key map "c" 'notmuch-show-stash-map)
(define-key map "h" 'notmuch-show-toggle-visibility-headers)
+ (define-key map "k" 'notmuch-tag-jump)
(define-key map "*" 'notmuch-show-tag-all)
(define-key map "-" 'notmuch-show-remove-tag)
(define-key map "+" 'notmuch-show-add-tag)
"Keymap for \"notmuch show\" buffers.")
(fset 'notmuch-show-mode-map notmuch-show-mode-map)
-(defun notmuch-show-mode ()
+(define-derived-mode notmuch-show-mode fundamental-mode "notmuch-show"
"Major mode for viewing a thread with notmuch.
This buffer contains the results of the \"notmuch show\" command
All currently available key bindings:
\\{notmuch-show-mode-map}"
- (interactive)
- (kill-all-local-variables)
(setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
- (use-local-map notmuch-show-mode-map)
- (setq major-mode 'notmuch-show-mode
- mode-name "notmuch-show")
(setq buffer-read-only t
- truncate-lines t))
+ truncate-lines t)
+ (setq imenu-prev-index-position-function
+ #'notmuch-show-imenu-prev-index-position-function)
+ (setq imenu-extract-index-name-function
+ #'notmuch-show-imenu-extract-index-name-function))
(defun notmuch-tree-from-show-current-query ()
"Call notmuch tree with the current query"
;; dme: Would it make sense to use a macro for many of these?
+;; XXX TODO figure out what to do about multiple filenames
(defun notmuch-show-get-filename ()
"Return the filename of the current message."
- (notmuch-show-get-prop :filename))
+ (car (notmuch-show-get-prop :filename)))
(defun notmuch-show-get-header (header &optional props)
"Return the named header of the current message, if any."
(defun notmuch-show-get-date ()
(notmuch-show-get-header :Date))
+(defun notmuch-show-get-timestamp ()
+ (notmuch-show-get-prop :timestamp))
+
(defun notmuch-show-get-from ()
(notmuch-show-get-header :From))
(notmuch-show-mark-read)
(notmuch-show-set-prop :seen t)))
+(defvar notmuch-show--seen-has-errored nil)
+(make-variable-buffer-local 'notmuch-show--seen-has-errored)
+
(defun notmuch-show-command-hook ()
(when (eq major-mode 'notmuch-show-mode)
;; We need to redisplay to get window-start and window-end correct.
(redisplay)
(save-excursion
- (funcall notmuch-show-mark-read-function (window-start) (window-end)))))
+ (condition-case err
+ (funcall notmuch-show-mark-read-function (window-start) (window-end))
+ ((debug error)
+ (unless notmuch-show--seen-has-errored
+ (setq notmuch-show--seen-has-errored 't)
+ (setq header-line-format
+ (concat header-line-format
+ (propertize " [some mark read tag changes may have failed]"
+ 'face font-lock-warning-face)))))))))
(defun notmuch-show-filter-thread (query)
"Filter or LIMIT the current thread based on a new query string.
(setq buffer-read-only t)
(view-buffer buf 'kill-buffer-if-not-modified)))
+(defun notmuch-show-resume-message ()
+ "Resume EDITING the current draft message."
+ (interactive)
+ (notmuch-draft-resume (notmuch-show-get-message-id)))
+
(put 'notmuch-show-pipe-message 'notmuch-doc
"Pipe the contents of the current message to a command.")
(put 'notmuch-show-pipe-message 'notmuch-prefix-doc
(interactive)
(notmuch-common-do-stash (notmuch-show-get-cc)))
-(defun notmuch-show-stash-date ()
- "Copy date of current message to kill-ring."
- (interactive)
- (notmuch-common-do-stash (notmuch-show-get-date)))
+(put 'notmuch-show-stash-date 'notmuch-prefix-doc
+ "Copy timestamp of current message to kill-ring.")
+(defun notmuch-show-stash-date (&optional stash-timestamp)
+ "Copy date of current message to kill-ring.
+
+If invoked with a prefix argument, copy timestamp of current
+message to kill-ring."
+ (interactive "P")
+ (if stash-timestamp
+ (notmuch-common-do-stash (format "%d" (notmuch-show-get-timestamp)))
+ (notmuch-common-do-stash (notmuch-show-get-date))))
(defun notmuch-show-stash-filename ()
"Copy filename of current message to kill-ring."
(insert (notmuch-get-bodypart-binary msg part process-crypto)))
buf))
-(defun notmuch-show-current-part-handle ()
+(defun notmuch-show-current-part-handle (&optional mime-type)
"Return an mm-handle for the part containing point.
This creates a temporary buffer for the part's content; the
-caller is responsible for killing this buffer as appropriate."
+caller is responsible for killing this buffer as appropriate. If
+MIME-TYPE is given then set the handle's mime-type to MIME-TYPE."
(let* ((msg (notmuch-show-get-message-properties))
(part (notmuch-show-get-part-properties))
(buf (notmuch-show-generate-part-buffer msg part))
- (computed-type (plist-get part :computed-type))
+ (computed-type (or mime-type (plist-get part :computed-type)))
(filename (plist-get part :filename))
(disposition (if filename `(attachment (filename . ,filename)))))
(mm-make-handle buf (list computed-type) nil nil disposition)))
-(defun notmuch-show-apply-to-current-part-handle (fn)
+(defun notmuch-show-apply-to-current-part-handle (fn &optional mime-type)
"Apply FN to an mm-handle for the part containing point.
This ensures that the temporary buffer created for the mm-handle
-is destroyed when FN returns."
- (let ((handle (notmuch-show-current-part-handle)))
+is destroyed when FN returns. If MIME-TYPE is given then force
+part to be treated as if it had that mime-type."
+ (let ((handle (notmuch-show-current-part-handle mime-type)))
;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
;; call it from a temp-buffer, unless
;; notmuch-show-attachment-debug is non-nil in which case we put
(notmuch-show-apply-to-current-part-handle #'mm-pipe-part))
+(defun notmuch-show--mm-display-part (handle)
+ "Use mm-display-part to display HANDLE in a new buffer.
+
+If the part is displayed in an external application then close
+the new buffer."
+ (let ((buf (get-buffer-create (generate-new-buffer-name
+ (concat " *notmuch-internal-part*")))))
+ (switch-to-buffer buf)
+ (if (eq (mm-display-part handle) 'external)
+ (kill-buffer buf)
+ (goto-char (point-min))
+ (set-buffer-modified-p nil)
+ (view-buffer buf 'kill-buffer-if-not-modified))))
+
+(defun notmuch-show-choose-mime-of-part (mime-type)
+ "Choose the mime type to use for displaying part"
+ (interactive
+ (list (completing-read "Mime type to use (default text/plain): "
+ (mailcap-mime-types) nil nil nil nil "text/plain")))
+ (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part mime-type))
+
+(defun notmuch-show-imenu-prev-index-position-function ()
+ "Move point to previous message in notmuch-show buffer.
+This function is used as a value for
+`imenu-prev-index-position-function'."
+ (if (bobp)
+ nil
+ (notmuch-show-previous-message)
+ t))
+
+(defun notmuch-show-imenu-extract-index-name-function ()
+ "Return imenu name for line at point.
+This function is used as a value for
+`imenu-extract-index-name-function'. Point should be at the
+beginning of the line."
+ (back-to-indentation)
+ (buffer-substring-no-properties (if notmuch-show-imenu-indent
+ (line-beginning-position)
+ (point))
+ (line-end-position)))
+
(provide 'notmuch-show)
;;; notmuch-show.el ends here