2019-08-19 12:41:33 +02:00
|
|
|
|
;;; mu4e-view.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2020-02-06 19:28:24 +01:00
|
|
|
|
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
|
|
|
|
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
|
|
|
|
|
;; This file is not part of GNU Emacs.
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2011-09-18 13:39:36 +02:00
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2012-03-31 16:20:03 +02:00
|
|
|
|
;; In this file we define mu4e-view-mode (+ helper functions), which is used for
|
|
|
|
|
;; viewing e-mail messages
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
|
|
|
|
;;; Code:
|
2020-02-11 15:10:35 +01:00
|
|
|
|
|
2018-09-18 02:53:27 +02:00
|
|
|
|
(require 'cl-lib)
|
2012-09-19 15:56:13 +02:00
|
|
|
|
(require 'mu4e-utils) ;; utility functions
|
2012-04-09 15:34:52 +02:00
|
|
|
|
(require 'mu4e-vars)
|
2012-04-23 18:07:20 +02:00
|
|
|
|
(require 'mu4e-mark)
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(require 'mu4e-proc)
|
2012-05-01 21:45:54 +02:00
|
|
|
|
(require 'mu4e-compose)
|
2012-04-24 17:13:12 +02:00
|
|
|
|
(require 'mu4e-actions)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(require 'mu4e-message)
|
2012-04-09 15:34:52 +02:00
|
|
|
|
|
2020-01-27 15:24:39 +01:00
|
|
|
|
(eval-when-compile (require 'gnus-art))
|
2020-05-28 09:21:16 +02:00
|
|
|
|
|
2011-11-27 15:23:14 +01:00
|
|
|
|
(require 'comint)
|
2012-06-19 13:12:54 +02:00
|
|
|
|
(require 'browse-url)
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(require 'button)
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(require 'epa)
|
|
|
|
|
(require 'epg)
|
2015-03-21 09:28:01 +01:00
|
|
|
|
(require 'thingatpt)
|
2015-12-04 22:19:32 +01:00
|
|
|
|
(require 'calendar)
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2018-06-13 00:16:58 +02:00
|
|
|
|
(declare-function mu4e-view-mode "mu4e-view")
|
2019-08-19 12:41:33 +02:00
|
|
|
|
(defvar gnus-icalendar-additional-identities)
|
|
|
|
|
(defvar mu4e~headers-view-win)
|
2018-06-13 00:16:58 +02:00
|
|
|
|
|
2020-02-11 15:10:35 +01:00
|
|
|
|
;;; Options
|
|
|
|
|
|
2012-04-24 17:13:12 +02:00
|
|
|
|
(defgroup mu4e-view nil
|
|
|
|
|
"Settings for the message view."
|
|
|
|
|
:group 'mu4e)
|
|
|
|
|
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(defcustom mu4e-view-use-gnus nil
|
2020-03-06 13:23:30 +01:00
|
|
|
|
"Whether to (experimentally) use Gnus' article view.
|
2018-12-01 11:29:07 +01:00
|
|
|
|
\(instead of mu4e's internal viewer)."
|
2018-04-24 13:21:15 +02:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2012-04-24 17:13:12 +02:00
|
|
|
|
(defcustom mu4e-view-fields
|
2014-10-19 02:20:21 +02:00
|
|
|
|
'(:from :to :cc :subject :flags :date :maildir :mailing-list :tags
|
2020-02-11 12:00:46 +01:00
|
|
|
|
:attachments :signature :decryption)
|
2012-11-05 17:16:46 +01:00
|
|
|
|
"Header fields to display in the message view buffer.
|
|
|
|
|
For the complete list of available headers, see `mu4e-header-info'."
|
2012-04-24 17:13:12 +02:00
|
|
|
|
:type (list 'symbol)
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2012-07-16 11:19:19 +02:00
|
|
|
|
(defcustom mu4e-view-show-addresses nil
|
2018-12-01 11:29:07 +01:00
|
|
|
|
"Whether to initially show full e-mail addresses for contacts.
|
|
|
|
|
Otherwise, just show their names."
|
2012-04-24 17:13:12 +02:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
2012-09-19 10:36:02 +02:00
|
|
|
|
|
2012-09-16 21:34:11 +02:00
|
|
|
|
(make-obsolete-variable 'mu4e-view-wrap-lines nil "0.9.9-dev7")
|
|
|
|
|
(make-obsolete-variable 'mu4e-view-hide-cited nil "0.9.9-dev7")
|
2012-04-24 17:13:12 +02:00
|
|
|
|
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(defcustom mu4e-view-date-format "%c"
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Date format to use in the message view.
|
|
|
|
|
In the format of `format-time-string'."
|
2012-08-01 16:07:11 +02:00
|
|
|
|
:type 'string
|
2012-04-24 17:13:12 +02:00
|
|
|
|
:group 'mu4e-view)
|
2012-09-19 10:36:02 +02:00
|
|
|
|
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(defcustom mu4e-view-image-max-width 800
|
2012-11-05 17:16:46 +01:00
|
|
|
|
"The maximum width for images to display.
|
2018-12-01 11:29:07 +01:00
|
|
|
|
This is only effective if you're using an Emacs with Imagemagick
|
2013-01-26 09:57:14 +01:00
|
|
|
|
support, and `mu4e-view-show-images' is non-nil."
|
2017-09-02 14:37:35 +02:00
|
|
|
|
:type 'integer
|
2012-05-16 19:47:13 +02:00
|
|
|
|
:group 'mu4e-view)
|
2012-06-26 21:49:34 +02:00
|
|
|
|
|
2013-07-06 10:51:33 +02:00
|
|
|
|
(defcustom mu4e-view-image-max-height 600
|
|
|
|
|
"The maximum height for images to display.
|
2018-12-01 11:29:07 +01:00
|
|
|
|
This is only effective if you're using an Emacs with Imagemagick
|
2013-07-06 10:51:33 +02:00
|
|
|
|
support, and `mu4e-view-show-images' is non-nil."
|
2017-09-02 14:37:35 +02:00
|
|
|
|
:type 'integer
|
2013-07-06 10:51:33 +02:00
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2012-10-17 11:07:57 +02:00
|
|
|
|
(defcustom mu4e-view-scroll-to-next t
|
2018-12-01 11:29:07 +01:00
|
|
|
|
"Move to the next message when calling
|
|
|
|
|
`mu4e-view-scroll-up-or-next' (typically bound to SPC) when at
|
|
|
|
|
the end of a message. Otherwise, don't move to the next message."
|
2017-09-02 14:37:35 +02:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
2012-10-17 11:07:57 +02:00
|
|
|
|
|
2020-02-17 12:26:25 +01:00
|
|
|
|
(defcustom mu4e-view-auto-mark-as-read t
|
|
|
|
|
"Automatically mark messages are 'read' when you read
|
|
|
|
|
them. This is typically the expected behavior, but can be turned
|
|
|
|
|
off, for example when using a read-only file-system."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2015-02-11 15:46:01 +01:00
|
|
|
|
(defcustom mu4e-save-multiple-attachments-without-asking nil
|
|
|
|
|
"If non-nil, saving multiple attachments asks once for a
|
|
|
|
|
directory and saves all attachments in the chosen directory."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2019-12-31 17:20:15 +01:00
|
|
|
|
(defcustom mu4e-view-actions
|
2015-11-07 06:36:31 +01:00
|
|
|
|
'( ("capture message" . mu4e-action-capture-message)
|
|
|
|
|
("view as pdf" . mu4e-action-view-as-pdf)
|
|
|
|
|
("show this thread" . mu4e-action-show-thread))
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"List of actions to perform on messages in view mode.
|
2017-11-04 14:06:45 +01:00
|
|
|
|
The actions are cons-cells of the form:
|
|
|
|
|
(NAME . FUNC)
|
2012-04-24 17:13:12 +02:00
|
|
|
|
where:
|
|
|
|
|
* NAME is the name of the action (e.g. \"Count lines\")
|
2012-07-10 10:51:54 +02:00
|
|
|
|
* FUNC is a function which receives a message plist as an argument.
|
|
|
|
|
|
2019-12-31 17:20:15 +01:00
|
|
|
|
The first letter of NAME is used as a shortcut character."
|
|
|
|
|
:group 'mu4e-view
|
|
|
|
|
:type '(alist :key-type string :value-type function))
|
2012-04-28 12:47:13 +02:00
|
|
|
|
|
2016-12-19 08:51:26 +01:00
|
|
|
|
(defcustom mu4e-view-attachment-assoc nil
|
|
|
|
|
"Alist of (EXTENSION . PROGRAM).
|
|
|
|
|
Specify which PROGRAM to use to open attachment with EXTENSION.
|
|
|
|
|
Args EXTENSION and PROGRAM should be specified as strings."
|
|
|
|
|
:group 'mu4e-view
|
|
|
|
|
:type '(alist :key-type string :value-type string))
|
|
|
|
|
|
|
|
|
|
(defcustom mu4e-view-attachment-actions
|
|
|
|
|
'( ("ssave" . mu4e-view-save-attachment-single)
|
|
|
|
|
("Ssave multi" . mu4e-view-save-attachment-multi)
|
|
|
|
|
("wopen-with" . mu4e-view-open-attachment-with)
|
2012-06-11 15:40:23 +02:00
|
|
|
|
("ein-emacs" . mu4e-view-open-attachment-emacs)
|
2015-02-03 18:14:58 +01:00
|
|
|
|
("dimport-in-diary" . mu4e-view-import-attachment-diary)
|
2020-03-04 11:15:52 +01:00
|
|
|
|
("kimport-public-key" . mu4e-view-import-public-key)
|
2012-06-11 15:40:23 +02:00
|
|
|
|
("|pipe" . mu4e-view-pipe-attachment))
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"List of actions to perform on message attachments.
|
2013-04-17 20:04:42 +02:00
|
|
|
|
The actions are cons-cells of the form:
|
|
|
|
|
(NAME . FUNC)
|
2012-04-24 17:13:12 +02:00
|
|
|
|
where:
|
2012-11-10 14:01:17 +01:00
|
|
|
|
* NAME is the name of the action (e.g. \"Count lines\")
|
|
|
|
|
* FUNC is a function which receives two arguments: the message
|
|
|
|
|
plist and the attachment number.
|
2016-12-19 08:51:26 +01:00
|
|
|
|
The first letter of NAME is used as a shortcut character."
|
|
|
|
|
:group 'mu4e-view
|
|
|
|
|
:type '(alist :key-type string :value-type function))
|
2013-10-13 14:25:12 +02:00
|
|
|
|
|
2020-02-11 15:10:35 +01:00
|
|
|
|
;;; Keymaps
|
|
|
|
|
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(defvar mu4e-view-header-field-keymap
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map [mouse-1] 'mu4e~view-header-field-fold)
|
|
|
|
|
(define-key map (kbd "TAB") 'mu4e~view-header-field-fold)
|
|
|
|
|
map)
|
|
|
|
|
"Keymap used for header fields.")
|
|
|
|
|
|
2014-02-07 03:04:26 +01:00
|
|
|
|
(defvar mu4e-view-contacts-header-keymap
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map [mouse-2] 'mu4e~view-compose-contact)
|
|
|
|
|
(define-key map "C" 'mu4e~view-compose-contact)
|
|
|
|
|
(define-key map "c" 'mu4e~view-copy-contact)
|
|
|
|
|
map)
|
|
|
|
|
"Keymap used for the contacts in the header fields.")
|
|
|
|
|
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(defvar mu4e-view-clickable-urls-keymap
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map [mouse-1] 'mu4e~view-browse-url-from-binding)
|
|
|
|
|
(define-key map [?\M-\r] 'mu4e~view-browse-url-from-binding)
|
2014-02-07 03:04:26 +01:00
|
|
|
|
map)
|
|
|
|
|
"Keymap used for the urls inside the body.")
|
2014-02-06 09:01:13 +01:00
|
|
|
|
|
|
|
|
|
(defvar mu4e-view-attachments-header-keymap
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(define-key map [mouse-1] 'mu4e~view-open-attach-from-binding)
|
|
|
|
|
(define-key map [?\M-\r] 'mu4e~view-open-attach-from-binding)
|
|
|
|
|
(define-key map [mouse-2] 'mu4e~view-save-attach-from-binding)
|
|
|
|
|
(define-key map (kbd "<S-return>") 'mu4e~view-save-attach-from-binding)
|
2014-02-07 03:04:26 +01:00
|
|
|
|
map)
|
2020-05-12 23:56:55 +02:00
|
|
|
|
"Keymap used in the \"Attachments\" header field.")
|
2014-02-07 02:38:33 +01:00
|
|
|
|
|
2020-02-11 15:10:35 +01:00
|
|
|
|
;;; Variables
|
|
|
|
|
|
2020-04-23 04:37:23 +02:00
|
|
|
|
;; It's useful to have the current view message available to
|
|
|
|
|
;; `mu4e-view-mode-hooks' functions, and we set up this variable
|
|
|
|
|
;; before calling `mu4e-view-mode'. However, changing the major mode
|
|
|
|
|
;; clobbers any local variables. Work around that by declaring the
|
|
|
|
|
;; variable permanent-local.
|
2020-02-17 12:26:25 +01:00
|
|
|
|
(defvar-local mu4e~view-message nil
|
|
|
|
|
"The message being viewed in view mode.")
|
2020-04-23 04:37:23 +02:00
|
|
|
|
(put 'mu4e~view-message 'permanent-local t)
|
2020-02-17 12:26:25 +01:00
|
|
|
|
|
|
|
|
|
(defvar mu4e-view-fill-headers t
|
|
|
|
|
"If non-nil, automatically fill the headers when viewing them.")
|
|
|
|
|
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(defvar mu4e~view-cited-hidden nil "Whether cited lines are hidden.")
|
2018-12-01 11:27:39 +01:00
|
|
|
|
(put 'mu4e~view-cited-hidden 'permanent-local t)
|
2017-05-18 03:42:52 +02:00
|
|
|
|
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(defvar mu4e~view-link-map nil
|
|
|
|
|
"A map of some number->url so we can jump to url by number.")
|
2018-12-01 11:27:39 +01:00
|
|
|
|
(put 'mu4e~view-link-map 'permanent-local t)
|
2012-05-13 12:10:56 +02:00
|
|
|
|
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(defvar mu4e~path-parent-docid-map (make-hash-table :test 'equal)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"A map of msg paths --> parent-docids.
|
|
|
|
|
This is to determine what is the parent docid for embedded
|
|
|
|
|
message extracted at some path.")
|
2018-12-01 11:27:39 +01:00
|
|
|
|
(put 'mu4e~path-parent-docid-map 'permanent-local t)
|
2012-09-23 10:42:00 +02:00
|
|
|
|
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(defvar mu4e~view-attach-map nil
|
|
|
|
|
"A mapping of user-visible attachment number to the actual part index.")
|
2018-12-01 11:27:39 +01:00
|
|
|
|
(put 'mu4e~view-attach-map 'permanent-local t)
|
2019-08-19 12:41:33 +02:00
|
|
|
|
|
|
|
|
|
(defvar mu4e~view-rendering nil)
|
|
|
|
|
|
|
|
|
|
(defvar mu4e~view-html-text nil
|
|
|
|
|
"Should we prefer html or text just this once? A symbol `text'
|
|
|
|
|
or `html' or nil.")
|
2020-02-11 15:10:35 +01:00
|
|
|
|
|
2020-02-18 22:39:30 +01:00
|
|
|
|
;;; Main
|
2012-05-13 12:10:56 +02:00
|
|
|
|
|
2016-02-23 22:19:54 +01:00
|
|
|
|
(defun mu4e-view-message-with-message-id (msgid)
|
|
|
|
|
"View message with message-id MSGID. This (re)creates a
|
|
|
|
|
headers-buffer with a search for MSGID, then open a view for that
|
|
|
|
|
message."
|
|
|
|
|
(mu4e-headers-search (concat "msgid:" msgid) nil nil t msgid t))
|
|
|
|
|
|
|
|
|
|
(define-obsolete-function-alias 'mu4e-view-message-with-msgid
|
|
|
|
|
'mu4e-view-message-with-message-id "0.9.17")
|
2012-01-06 11:31:28 +01:00
|
|
|
|
|
2013-10-05 19:09:26 +02:00
|
|
|
|
(defun mu4e~view-custom-field (msg field)
|
|
|
|
|
"Show some custom header field, or raise an error if it is not
|
|
|
|
|
found."
|
|
|
|
|
(let* ((item (or (assoc field mu4e-header-info-custom)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mu4e-error "field %S not found" field)))
|
|
|
|
|
(func (or (plist-get (cdr-safe item) :function)
|
|
|
|
|
(mu4e-error "no :function defined for field %S %S"
|
|
|
|
|
field (cdr item)))))
|
2013-10-05 19:09:26 +02:00
|
|
|
|
(funcall func msg)))
|
|
|
|
|
|
2012-04-16 21:06:49 +02:00
|
|
|
|
(defun mu4e-view-message-text (msg)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Return the message to display (as a string), based on the MSG plist."
|
2012-04-16 21:06:49 +02:00
|
|
|
|
(concat
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (field)
|
|
|
|
|
(let ((fieldval (mu4e-message-field msg field)))
|
|
|
|
|
(cl-case field
|
|
|
|
|
(:subject (mu4e~view-construct-header field fieldval))
|
|
|
|
|
(:path (mu4e~view-construct-header field fieldval))
|
|
|
|
|
(:maildir (mu4e~view-construct-header field fieldval))
|
|
|
|
|
(:user-agent (mu4e~view-construct-header field fieldval))
|
|
|
|
|
((:flags :tags) (mu4e~view-construct-flags-tags-header
|
|
|
|
|
field fieldval))
|
|
|
|
|
|
|
|
|
|
;; contact fields
|
|
|
|
|
(:to (mu4e~view-construct-contacts-header msg field))
|
|
|
|
|
(:from (mu4e~view-construct-contacts-header msg field))
|
|
|
|
|
(:cc (mu4e~view-construct-contacts-header msg field))
|
|
|
|
|
(:bcc (mu4e~view-construct-contacts-header msg field))
|
|
|
|
|
|
|
|
|
|
;; if we (`user-mail-address' are the From, show To, otherwise,
|
|
|
|
|
;; show From
|
|
|
|
|
(:from-or-to
|
|
|
|
|
(let* ((from (mu4e-message-field msg :from))
|
|
|
|
|
(from (and from (cdar from))))
|
|
|
|
|
(if (mu4e-user-mail-address-p from)
|
|
|
|
|
(mu4e~view-construct-contacts-header msg :to)
|
|
|
|
|
(mu4e~view-construct-contacts-header msg :from))))
|
|
|
|
|
;; date
|
|
|
|
|
(:date
|
|
|
|
|
(let ((datestr
|
|
|
|
|
(when fieldval (format-time-string mu4e-view-date-format
|
|
|
|
|
fieldval))))
|
|
|
|
|
(if datestr (mu4e~view-construct-header field datestr) "")))
|
|
|
|
|
;; size
|
|
|
|
|
(:size
|
|
|
|
|
(mu4e~view-construct-header field (mu4e-display-size fieldval)))
|
|
|
|
|
(:mailing-list
|
|
|
|
|
(mu4e~view-construct-header field fieldval))
|
|
|
|
|
(:message-id
|
|
|
|
|
(mu4e~view-construct-header field fieldval))
|
|
|
|
|
;; attachments
|
|
|
|
|
(:attachments (mu4e~view-construct-attachments-header msg))
|
|
|
|
|
;; pgp-signatures
|
|
|
|
|
(:signature (mu4e~view-construct-signature-header msg))
|
|
|
|
|
;; pgp-decryption
|
|
|
|
|
(:decryption (mu4e~view-construct-decryption-header msg))
|
|
|
|
|
(t (mu4e~view-construct-header field
|
|
|
|
|
(mu4e~view-custom-field msg field))))))
|
|
|
|
|
mu4e-view-fields "")
|
|
|
|
|
"\n"
|
|
|
|
|
(let* ((prefer-html
|
|
|
|
|
(cond
|
|
|
|
|
((eq mu4e~view-html-text 'html) t)
|
|
|
|
|
((eq mu4e~view-html-text 'text) nil)
|
|
|
|
|
(t mu4e-view-prefer-html)))
|
|
|
|
|
(body (mu4e-message-body-text msg prefer-html)))
|
|
|
|
|
(setq mu4e~view-html-text nil)
|
|
|
|
|
(when (fboundp 'add-face-text-property)
|
|
|
|
|
(add-face-text-property 0 (length body) 'mu4e-view-body-face t body))
|
|
|
|
|
body)))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
|
2015-03-21 09:28:01 +01:00
|
|
|
|
(defun mu4e~view-embedded-winbuf ()
|
2012-09-23 10:42:00 +02:00
|
|
|
|
"Get a buffer (shown in a window) for the embedded message."
|
|
|
|
|
(let* ((buf (get-buffer-create mu4e~view-embedded-buffer-name))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(win (or (get-buffer-window buf) (split-window-vertically))))
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(select-window win)
|
|
|
|
|
(switch-to-buffer buf)))
|
2014-04-27 10:54:15 +02:00
|
|
|
|
|
|
|
|
|
(defun mu4e~delete-all-overlays ()
|
|
|
|
|
"`delete-all-overlays' with compatibility fallback."
|
|
|
|
|
(if (functionp 'delete-all-overlays)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(delete-all-overlays)
|
2014-04-27 10:54:15 +02:00
|
|
|
|
(remove-overlays)))
|
|
|
|
|
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(defun mu4e-view (msg)
|
2011-09-18 13:39:36 +02:00
|
|
|
|
"Display the message MSG in a new buffer, and keep in sync with HDRSBUF.
|
|
|
|
|
'In sync' here means that moving to the next/previous message in
|
2012-04-29 21:03:27 +02:00
|
|
|
|
the the message view affects HDRSBUF, as does marking etc.
|
2014-10-10 17:34:57 +02:00
|
|
|
|
|
2011-09-18 13:39:36 +02:00
|
|
|
|
As a side-effect, a message that is being viewed loses its 'unread'
|
2018-04-24 13:21:15 +02:00
|
|
|
|
marking if it still had that.
|
|
|
|
|
|
|
|
|
|
Depending on the value of `mu4e-view-use-gnus', either use mu4e's
|
2020-03-06 13:23:30 +01:00
|
|
|
|
internal display mode, or a display mode based on Gnus'
|
2018-04-24 13:21:15 +02:00
|
|
|
|
article-mode."
|
2018-06-08 09:01:55 +02:00
|
|
|
|
(mu4e~view-define-mode)
|
2019-04-27 07:38:53 +02:00
|
|
|
|
|
|
|
|
|
;; XXX(djcb): only called for the side-effect of setting up
|
|
|
|
|
;; `mu4e~view-attach-map'. Instead, we should split that function
|
|
|
|
|
;; into setting up the map, and actually producing the header.
|
|
|
|
|
(mu4e~view-construct-attachments-header msg)
|
|
|
|
|
|
2018-08-14 21:20:32 +02:00
|
|
|
|
;; When MSG is unread, mu4e~view-mark-as-read-maybe will trigger
|
|
|
|
|
;; another call to mu4e-view (via mu4e~headers-update-handler as
|
|
|
|
|
;; the reply handler to mu4e~proc-move)
|
|
|
|
|
(unless (mu4e~view-mark-as-read-maybe msg)
|
|
|
|
|
(if mu4e-view-use-gnus
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mu4e~view-gnus msg)
|
2018-08-14 21:20:32 +02:00
|
|
|
|
(mu4e~view-internal msg))))
|
2018-04-24 13:21:15 +02:00
|
|
|
|
|
|
|
|
|
(defun mu4e~view-internal (msg)
|
2018-12-01 11:27:39 +01:00
|
|
|
|
"Display MSG using mu4e's internal view mode."
|
2013-10-20 15:55:36 +02:00
|
|
|
|
(let* ((embedded ;; is it as an embedded msg (ie. message/rfc822 att)?
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(when (gethash (mu4e-message-field msg :path)
|
|
|
|
|
mu4e~path-parent-docid-map) t))
|
|
|
|
|
(buf (if embedded
|
|
|
|
|
(mu4e~view-embedded-winbuf)
|
|
|
|
|
(get-buffer-create mu4e~view-buffer-name))))
|
2011-09-18 13:39:36 +02:00
|
|
|
|
(with-current-buffer buf
|
2017-10-29 06:54:13 +01:00
|
|
|
|
(let ((inhibit-read-only t))
|
2017-11-04 05:50:03 +01:00
|
|
|
|
(when (or embedded (not (mu4e~view-mark-as-read-maybe msg)))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(mu4e~delete-all-overlays)
|
2018-11-07 20:24:02 +01:00
|
|
|
|
(insert (mu4e-view-message-text msg))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(mu4e~fontify-cited)
|
|
|
|
|
(mu4e~fontify-signature)
|
|
|
|
|
(mu4e~view-make-urls-clickable)
|
|
|
|
|
(mu4e~view-show-images-maybe msg)
|
2020-04-23 04:37:23 +02:00
|
|
|
|
(when (not embedded) (setq mu4e~view-message msg))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mu4e-view-mode)
|
2020-04-23 04:37:23 +02:00
|
|
|
|
(when embedded (local-set-key "q" 'kill-buffer-and-window))))
|
2018-08-14 21:20:32 +02:00
|
|
|
|
(switch-to-buffer buf))))
|
2014-02-03 05:40:01 +01:00
|
|
|
|
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(defun mu4e~view-gnus (msg)
|
2020-03-06 13:23:30 +01:00
|
|
|
|
"View MSG using Gnus' article mode. Experimental."
|
2020-01-21 09:49:40 +01:00
|
|
|
|
(require 'gnus-art)
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(let ((marked-read (mu4e~view-mark-as-read-maybe msg))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(path (mu4e-message-field msg :path))
|
|
|
|
|
(inhibit-read-only t)
|
|
|
|
|
;; support signature verification
|
|
|
|
|
(mm-verify-option 'known)
|
|
|
|
|
(mm-decrypt-option 'known)
|
|
|
|
|
(gnus-article-emulate-mime t)
|
|
|
|
|
(gnus-buttonized-mime-types (append (list "multipart/signed"
|
|
|
|
|
"multipart/encrypted")
|
|
|
|
|
gnus-buttonized-mime-types)))
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(switch-to-buffer (get-buffer-create mu4e~view-buffer-name))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(unless marked-read
|
2019-02-02 16:01:36 +01:00
|
|
|
|
;; when we're being marked as read, no need to start rendering
|
|
|
|
|
;; the messages; just the minimal so (update... ) can find us.
|
2018-09-02 15:43:06 +02:00
|
|
|
|
(insert-file-contents-literally path)
|
2019-02-02 16:01:36 +01:00
|
|
|
|
(unless (message-fetch-field "Content-Type" t)
|
|
|
|
|
;; For example, for messages in `mu4e-drafts-folder'
|
|
|
|
|
(let ((coding (or (default-value 'buffer-file-coding-system)
|
|
|
|
|
'prefer-utf-8)))
|
|
|
|
|
(recode-region (point-min) (point-max) coding 'no-conversion)))
|
2018-06-07 12:08:53 +02:00
|
|
|
|
(setq
|
2020-02-11 12:00:46 +01:00
|
|
|
|
gnus-summary-buffer (get-buffer-create " *appease-gnus*")
|
|
|
|
|
gnus-original-article-buffer (current-buffer))
|
2019-02-02 12:44:49 +01:00
|
|
|
|
(run-hooks 'gnus-article-decode-hook)
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(let ((mu4e~view-rendering t) ; customize gnus in mu4e
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(max-specpdl-size mu4e-view-max-specpdl-size)
|
2020-02-26 23:44:49 +01:00
|
|
|
|
(gnus-blocked-images ".") ;; don't load external images.
|
2020-02-19 19:30:14 +01:00
|
|
|
|
;; Possibly add headers (before "Attachments")
|
|
|
|
|
(gnus-display-mime-function (mu4e~view-gnus-display-mime msg))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(gnus-icalendar-additional-identities (mu4e-personal-addresses)))
|
2019-02-02 15:31:41 +01:00
|
|
|
|
(gnus-article-prepare-display))
|
2019-02-25 20:49:41 +01:00
|
|
|
|
(setq mu4e~view-message msg)
|
2020-04-23 04:37:23 +02:00
|
|
|
|
(mu4e-view-mode)
|
2018-06-13 00:16:58 +02:00
|
|
|
|
(setq gnus-article-decoded-p gnus-article-decode-hook)
|
2018-06-07 12:08:53 +02:00
|
|
|
|
(set-buffer-modified-p nil)
|
|
|
|
|
(read-only-mode))))
|
2018-04-24 13:21:15 +02:00
|
|
|
|
|
2020-02-19 19:30:14 +01:00
|
|
|
|
(defun mu4e~view-gnus-display-mime (msg)
|
|
|
|
|
"Same as `gnus-display-mime' but add a mu4e headers to MSG."
|
|
|
|
|
(lambda (&optional ihandles)
|
|
|
|
|
(gnus-display-mime ihandles)
|
|
|
|
|
(unless ihandles
|
|
|
|
|
(save-restriction
|
|
|
|
|
(article-goto-body)
|
|
|
|
|
(forward-line -1)
|
|
|
|
|
(narrow-to-region (point) (point))
|
|
|
|
|
(dolist (field mu4e-view-fields)
|
|
|
|
|
(let ((fieldval (mu4e-message-field msg field)))
|
|
|
|
|
(cl-case field
|
|
|
|
|
((:path :maildir :user-agent :mailing-list :message-id)
|
|
|
|
|
(mu4e~view-gnus-insert-header field fieldval))
|
|
|
|
|
((:flags :tags)
|
|
|
|
|
(let ((flags (mapconcat (lambda (flag)
|
|
|
|
|
(if (symbolp flag)
|
|
|
|
|
(symbol-name flag)
|
|
|
|
|
flag)) fieldval ", ")))
|
|
|
|
|
(mu4e~view-gnus-insert-header field flags)))
|
|
|
|
|
(:size (mu4e~view-gnus-insert-header
|
|
|
|
|
field (mu4e-display-size fieldval)))
|
|
|
|
|
((:subject :to :from :cc :bcc :from-or-to :date :attachments
|
|
|
|
|
:signature :decryption)) ; handled by Gnus
|
|
|
|
|
(t
|
|
|
|
|
(mu4e~view-gnus-insert-header-custom msg field))
|
|
|
|
|
)))
|
|
|
|
|
(let ((gnus-treatment-function-alist
|
|
|
|
|
'((gnus-treat-highlight-headers
|
|
|
|
|
gnus-article-highlight-headers))))
|
|
|
|
|
(gnus-treat-article 'head))))))
|
|
|
|
|
|
|
|
|
|
(defun mu4e~view-gnus-insert-header (field val)
|
|
|
|
|
"Insert a header FIELD with value VAL in Gnus article view."
|
|
|
|
|
(let* ((info (cdr (assoc field mu4e-header-info)))
|
|
|
|
|
(key (plist-get info :name))
|
|
|
|
|
(help (plist-get info :help)))
|
|
|
|
|
(if (and val (> (length val) 0))
|
|
|
|
|
(insert (propertize (concat key ":") 'help-echo help)
|
|
|
|
|
" " val "\n"))))
|
|
|
|
|
|
|
|
|
|
(defun mu4e~view-gnus-insert-header-custom (msg field)
|
|
|
|
|
"Insert the custom FIELD in Gnus article view."
|
|
|
|
|
(let* ((info (cdr-safe (or (assoc field mu4e-header-info-custom)
|
|
|
|
|
(mu4e-error "custom field %S not found" field))))
|
|
|
|
|
(key (plist-get info :name))
|
|
|
|
|
(func (or (plist-get info :function)
|
|
|
|
|
(mu4e-error "no :function defined for custom field %S %S"
|
|
|
|
|
field info)))
|
|
|
|
|
(val (funcall func msg))
|
|
|
|
|
(help (plist-get info :help)))
|
|
|
|
|
(when (and val (> (length val) 0))
|
|
|
|
|
(insert (propertize (concat key ":") 'help-echo help) " " val "\n"))))
|
|
|
|
|
|
2020-04-05 22:57:34 +02:00
|
|
|
|
(define-advice gnus-icalendar-event-from-handle
|
|
|
|
|
(:filter-args (handle-attendee) mu4e~view-fix-missing-charset)
|
|
|
|
|
"Do not trigger an error when displaying an ical attachment
|
|
|
|
|
with no charset."
|
|
|
|
|
(if (and (boundp 'mu4e~view-rendering) mu4e~view-rendering)
|
|
|
|
|
(let* ((handle (car handle-attendee))
|
2020-04-08 05:59:16 +02:00
|
|
|
|
(attendee (cadr handle-attendee))
|
2020-04-05 22:57:34 +02:00
|
|
|
|
(buf (mm-handle-buffer handle))
|
|
|
|
|
(ty (mm-handle-type handle))
|
|
|
|
|
(rest (cddr handle)))
|
|
|
|
|
;; Put the fallback at the end:
|
|
|
|
|
(setq ty (append ty '((charset . "utf-8"))))
|
|
|
|
|
(setq handle (cons buf (cons ty rest)))
|
|
|
|
|
(list handle attendee))
|
|
|
|
|
handle-attendee))
|
|
|
|
|
|
|
|
|
|
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(defun mu4e~view-get-property-from-event (prop)
|
2014-02-07 03:04:26 +01:00
|
|
|
|
"Get the property PROP at point, or the location of the mouse.
|
|
|
|
|
The action is chosen based on the `last-command-event'.
|
|
|
|
|
Meant to be evoked from interactive commands."
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(if (and (eventp last-command-event)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(mouse-event-p last-command-event))
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(let ((posn (event-end last-command-event)))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(when (numberp (posn-point posn))
|
|
|
|
|
(get-text-property
|
|
|
|
|
(posn-point posn)
|
|
|
|
|
prop
|
|
|
|
|
(window-buffer (posn-window posn)))
|
|
|
|
|
))
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(get-text-property (point) prop)))
|
2014-10-10 17:34:57 +02:00
|
|
|
|
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(defun mu4e~view-construct-header (field val &optional dont-propertize-val)
|
|
|
|
|
"Return header field FIELD (as in `mu4e-header-info') with value
|
|
|
|
|
VAL if VAL is non-nil. If DONT-PROPERTIZE-VAL is non-nil, do not
|
|
|
|
|
add text-properties to VAL."
|
2013-10-05 19:09:26 +02:00
|
|
|
|
(let* ((info (cdr (assoc field
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(append mu4e-header-info mu4e-header-info-custom))))
|
|
|
|
|
(key (plist-get info :name))
|
|
|
|
|
(val (if val (propertize val 'field 'mu4e-header-field-value
|
|
|
|
|
'front-sticky '(field))))
|
|
|
|
|
(help (plist-get info :help)))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(if (and val (> (length val) 0))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert (propertize (concat key ":")
|
|
|
|
|
'field 'mu4e-header-field-key
|
|
|
|
|
'front-sticky '(field)
|
|
|
|
|
'keymap mu4e-view-header-field-keymap
|
|
|
|
|
'face 'mu4e-header-key-face
|
|
|
|
|
'help-echo help) " "
|
|
|
|
|
(if dont-propertize-val
|
|
|
|
|
val
|
|
|
|
|
(propertize val 'face 'mu4e-header-value-face)) "\n")
|
|
|
|
|
(when mu4e-view-fill-headers
|
|
|
|
|
;; temporarily set the fill column <margin> positions to the right, so
|
|
|
|
|
;; we can indent the following lines correctly
|
|
|
|
|
(let* ((margin 1)
|
|
|
|
|
(fill-column (max (- fill-column margin) 0)))
|
|
|
|
|
(fill-region (point-min) (point-max))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (and (zerop (forward-line 1)) (not (looking-at "^$")))
|
|
|
|
|
(indent-to-column margin))))
|
|
|
|
|
(buffer-string))
|
|
|
|
|
"")))
|
2014-10-10 17:34:57 +02:00
|
|
|
|
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(defun mu4e~view-header-field-fold ()
|
2020-05-25 14:43:06 +02:00
|
|
|
|
"Fold/unfold headers' value if there is more than one line."
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(interactive)
|
|
|
|
|
(let ((name-pos (field-beginning))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(value-pos (1+ (field-end))))
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(if (and name-pos value-pos
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(eq (get-text-property name-pos 'field) 'mu4e-header-field-key))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(let* ((folded))
|
|
|
|
|
(mapc (lambda (o)
|
|
|
|
|
(when (overlay-get o 'mu4e~view-header-field-folded)
|
|
|
|
|
(delete-overlay o)
|
|
|
|
|
(setq folded t)))
|
|
|
|
|
(overlays-at value-pos))
|
|
|
|
|
(unless folded
|
|
|
|
|
(let* ((o (make-overlay value-pos (field-end value-pos)))
|
|
|
|
|
(vals (split-string (field-string value-pos) "\n" t))
|
|
|
|
|
(val (if (= (length vals) 1)
|
|
|
|
|
(car vals)
|
2020-05-25 14:43:06 +02:00
|
|
|
|
(truncate-string-to-width (car vals)
|
|
|
|
|
(- (length (car vals)) 1) 0 nil t)))) |