2012-04-08 19:28:49 +02:00
|
|
|
|
;;; mu4e-view.el -- part of mu4e, the mu mail user agent
|
2011-09-18 13:39:36 +02:00
|
|
|
|
;;
|
2018-04-24 13:21:15 +02:00
|
|
|
|
;; Copyright (C) 2011-2018 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.
|
|
|
|
|
;;
|
|
|
|
|
;; 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:
|
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
|
|
|
|
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(require 'gnus-art)
|
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
|
|
|
|
|
2012-06-10 15:14:21 +02:00
|
|
|
|
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
|
|
|
|
|
(require 'cl)
|
2012-06-12 19:58:32 +02:00
|
|
|
|
|
2012-04-24 17:13:12 +02:00
|
|
|
|
;; the message view
|
|
|
|
|
(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
|
|
|
|
|
"Whether to (experimentally) use Gnu's article view instead of
|
|
|
|
|
mu4e's internal viewer."
|
|
|
|
|
: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
|
2017-02-12 10:11:16 +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)
|
|
|
|
|
|
2013-01-26 09:57:14 +01:00
|
|
|
|
|
2012-07-16 11:19:19 +02:00
|
|
|
|
(defcustom mu4e-view-show-addresses nil
|
|
|
|
|
"Whether to initially show full e-mail addresses for contacts in
|
2014-05-25 03:16:34 +02:00
|
|
|
|
address fields, rather than only 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.
|
|
|
|
|
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.
|
|
|
|
|
This is only effective if you're using an emacs with Imagemagick
|
|
|
|
|
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
|
|
|
|
|
"If non-nil, move to the next message when calling
|
|
|
|
|
`mu4e-view-scroll-up-or-next' (typically bound to SPC) when at the
|
2017-09-02 14:37:35 +02:00
|
|
|
|
end of a message. Otherwise, don't move to the next message."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
2012-10-17 11:07:57 +02:00
|
|
|
|
|
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)
|
|
|
|
|
|
2012-04-24 17:13:12 +02:00
|
|
|
|
(defvar 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.
|
|
|
|
|
|
|
|
|
|
The first letter of NAME is used as a shortcut character.")
|
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)
|
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
|
|
|
|
|
|
|
|
|
(defvar mu4e-view-fill-headers t
|
|
|
|
|
"If non-nil, automatically fill the headers when viewing them.")
|
2014-02-06 09:01:13 +01:00
|
|
|
|
|
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)
|
|
|
|
|
"Keymap used in the \"Attachements\" header field.")
|
2014-02-07 02:38:33 +01:00
|
|
|
|
|
2015-12-27 13:30:02 +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)
|
|
|
|
|
|
|
|
|
|
|
2012-04-24 17:13:12 +02:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
|
|
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(defvar mu4e~view-cited-hidden nil "Whether cited lines are hidden.")
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(make-variable-buffer-local 'mu4e~view-cited-hidden)
|
|
|
|
|
|
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.")
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(make-variable-buffer-local 'mu4e~view-link-map)
|
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.")
|
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.")
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(make-variable-buffer-local 'mu4e~view-attach-map)
|
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)
|
|
|
|
|
(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)))))
|
|
|
|
|
(funcall func msg)))
|
|
|
|
|
|
2012-09-21 11:35:53 +02:00
|
|
|
|
|
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
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (field)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let ((fieldval (mu4e-message-field msg field)))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
(case field
|
2016-07-24 13:30:37 +02:00
|
|
|
|
(: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))
|
2016-01-10 12:08:18 +01:00
|
|
|
|
((:flags :tags) (mu4e~view-construct-flags-tags-header
|
|
|
|
|
field fieldval))
|
2012-12-22 20:12:36 +01:00
|
|
|
|
|
2012-04-16 21:06:49 +02:00
|
|
|
|
;; contact fields
|
2012-07-10 18:15:13 +02:00
|
|
|
|
(: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))
|
2012-04-19 18:00:09 +02:00
|
|
|
|
|
2012-04-16 21:06:49 +02:00
|
|
|
|
;; if we (`user-mail-address' are the From, show To, otherwise,
|
|
|
|
|
;; show From
|
|
|
|
|
(:from-or-to
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let* ((from (mu4e-message-field msg :from))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
(from (and from (cdar from))))
|
2012-10-19 15:01:55 +02:00
|
|
|
|
(if (mu4e-user-mail-address-p from)
|
2012-07-10 18:15:13 +02:00
|
|
|
|
(mu4e~view-construct-contacts-header msg :to)
|
|
|
|
|
(mu4e~view-construct-contacts-header msg :from))))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
;; date
|
|
|
|
|
(:date
|
|
|
|
|
(let ((datestr
|
|
|
|
|
(when fieldval (format-time-string mu4e-view-date-format
|
|
|
|
|
fieldval))))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(if datestr (mu4e~view-construct-header field datestr) "")))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
;; size
|
|
|
|
|
(:size
|
2012-09-15 17:24:03 +02:00
|
|
|
|
(mu4e~view-construct-header field (mu4e-display-size fieldval)))
|
2012-12-18 22:29:17 +01:00
|
|
|
|
(:mailing-list
|
|
|
|
|
(mu4e~view-construct-header field fieldval))
|
2013-09-28 19:36:18 +02:00
|
|
|
|
(:message-id
|
|
|
|
|
(mu4e~view-construct-header field fieldval))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
;; attachments
|
2012-07-10 18:15:13 +02:00
|
|
|
|
(:attachments (mu4e~view-construct-attachments-header msg))
|
2012-09-05 00:23:00 +02:00
|
|
|
|
;; pgp-signatures
|
2012-07-18 16:53:04 +02:00
|
|
|
|
(:signature (mu4e~view-construct-signature-header msg))
|
2014-10-19 02:20:21 +02:00
|
|
|
|
;; pgp-decryption
|
|
|
|
|
(:decryption (mu4e~view-construct-decryption-header msg))
|
2013-10-05 19:09:26 +02:00
|
|
|
|
(t (mu4e~view-construct-header field
|
2013-10-13 14:25:12 +02:00
|
|
|
|
(mu4e~view-custom-field msg field))))))
|
2012-04-16 21:06:49 +02:00
|
|
|
|
mu4e-view-fields "")
|
|
|
|
|
"\n"
|
2016-11-27 13:34:50 +01:00
|
|
|
|
(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)
|
2015-10-14 17:39:09 +02:00
|
|
|
|
(when (fboundp 'add-face-text-property)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(add-face-text-property 0 (length body) 'mu4e-view-body-face t body))
|
2015-10-14 17:39:09 +02:00
|
|
|
|
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))
|
|
|
|
|
(win (or (get-buffer-window buf) (split-window-vertically))))
|
|
|
|
|
(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)
|
|
|
|
|
(delete-all-overlays)
|
|
|
|
|
(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
|
|
|
|
|
internal display mode, or a display mode based on Gnu's
|
|
|
|
|
article-mode."
|
|
|
|
|
(if mu4e-view-use-gnus
|
|
|
|
|
(mu4e~view-gnus msg)
|
|
|
|
|
(mu4e~view-internal msg)))
|
|
|
|
|
|
|
|
|
|
(defun mu4e~view-internal (msg)
|
|
|
|
|
"Display a message 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)?
|
2017-10-29 11:51:28 +01:00
|
|
|
|
(when (gethash (mu4e-message-field msg :path)
|
|
|
|
|
mu4e~path-parent-docid-map) t))
|
|
|
|
|
(buf
|
|
|
|
|
(if embedded
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(mu4e~view-embedded-winbuf)
|
2017-10-29 11:51:28 +01:00
|
|
|
|
(get-buffer-create mu4e~view-buffer-name)))
|
2017-11-04 05:50:03 +01:00
|
|
|
|
mode-enabled)
|
2011-09-18 13:39:36 +02:00
|
|
|
|
(with-current-buffer buf
|
2017-11-04 05:50:03 +01:00
|
|
|
|
(unless (setq mode-enabled (eq major-mode 'mu4e-view-mode))
|
|
|
|
|
(let (mu4e-view-mode-hook) (mu4e-view-mode)))
|
2017-11-04 12:36:43 +01:00
|
|
|
|
(setq mu4e~view-msg msg)
|
2017-05-18 03:42:52 +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)
|
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)))
|
2013-10-20 15:55:36 +02:00
|
|
|
|
(erase-buffer)
|
2014-04-27 10:54:15 +02:00
|
|
|
|
(mu4e~delete-all-overlays)
|
2013-10-20 15:55:36 +02:00
|
|
|
|
(insert (mu4e-view-message-text msg))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(mu4e~fontify-cited)
|
|
|
|
|
(mu4e~fontify-signature)
|
2017-11-04 12:36:43 +01:00
|
|
|
|
(mu4e~view-make-urls-clickable)
|
2013-10-23 20:34:10 +02:00
|
|
|
|
(mu4e~view-show-images-maybe msg)
|
2017-11-04 05:50:03 +01:00
|
|
|
|
(when embedded (local-set-key "q" 'kill-buffer-and-window))
|
|
|
|
|
(unless mode-enabled (run-mode-hooks 'mu4e-view-mode-hook)))))
|
|
|
|
|
(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)
|
|
|
|
|
"View MSG using Gnu's article mode. Experimental."
|
|
|
|
|
(let ((marked-read (mu4e~view-mark-as-read-maybe msg))
|
|
|
|
|
(path (mu4e-message-field msg :path))
|
|
|
|
|
(inhibit-read-only t))
|
|
|
|
|
(switch-to-buffer (get-buffer-create mu4e~view-buffer-name))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(unless marked-read
|
|
|
|
|
;; when we're being marked as read, no need to start rendering the messages; just the minimail
|
|
|
|
|
;; so (update... ) can find us.
|
|
|
|
|
(insert-file-contents path)
|
|
|
|
|
(setq gnus-summary-buffer (get-buffer-create " *appease-gnus*"))
|
|
|
|
|
(let ((gnu-article-buffer (current-buffer)))
|
|
|
|
|
(gnus-article-prepare-display))
|
|
|
|
|
(mu4e~view-make-urls-clickable)
|
|
|
|
|
(mu4e~view-construct-attachments-header msg))
|
|
|
|
|
(mu4e-view-mode)
|
|
|
|
|
(setq mu4e~view-msg msg)
|
|
|
|
|
(setq gnus-article-buffer (current-buffer))
|
|
|
|
|
(set-buffer-modified-p nil)
|
|
|
|
|
(read-only-mode)))
|
|
|
|
|
|
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)
|
|
|
|
|
(mouse-event-p last-command-event))
|
|
|
|
|
(let ((posn (event-end last-command-event)))
|
2017-02-12 10:11:16 +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
|
|
|
|
|
(append mu4e-header-info mu4e-header-info-custom))))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(key (plist-get info :name))
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(val (if val (propertize val 'field 'mu4e-header-field-value
|
2017-02-12 10:11:16 +01:00
|
|
|
|
'front-sticky '(field))))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(help (plist-get info :help)))
|
|
|
|
|
(if (and val (> (length val) 0))
|
|
|
|
|
(with-temp-buffer
|
2013-02-21 19:52:58 +01:00
|
|
|
|
(insert (propertize (concat key ":")
|
2016-07-27 04:10:25 +02:00
|
|
|
|
'field 'mu4e-header-field-key
|
|
|
|
|
'front-sticky '(field)
|
|
|
|
|
'keymap mu4e-view-header-field-keymap
|
2013-10-19 11:05:05 +02:00
|
|
|
|
'face 'mu4e-header-key-face
|
2013-02-21 19:52:58 +01:00
|
|
|
|
'help-echo help) " "
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(if dont-propertize-val
|
|
|
|
|
val
|
2013-10-19 11:05:05 +02:00
|
|
|
|
(propertize val 'face 'mu4e-header-value-face)) "\n")
|
2013-10-13 14:25:12 +02:00
|
|
|
|
(when mu4e-view-fill-headers
|
|
|
|
|
;; temporarily set the fill column <margin> positions to the right, so
|
|
|
|
|
;; we can indent the following lines correctly
|
2013-10-13 22:24:56 +02:00
|
|
|
|
(let* ((margin 1)
|
|
|
|
|
(fill-column (max (- fill-column margin) 0)))
|
2013-10-13 14:25:12 +02:00
|
|
|
|
(fill-region (point-min) (point-max))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (and (zerop (forward-line 1)) (not (looking-at "^$")))
|
|
|
|
|
(indent-to-column margin))))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(buffer-string))
|
|
|
|
|
"")))
|
2014-10-10 17:34:57 +02:00
|
|
|
|
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(defun mu4e~view-header-field-fold ()
|
|
|
|
|
"Fold/unfold headers' value if there are more than one line."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((name-pos (field-beginning))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(value-pos (1+ (field-end))))
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(if (and name-pos value-pos
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(eq (get-text-property name-pos 'field) 'mu4e-header-field-key))
|
2016-07-27 04:10:25 +02:00
|
|
|
|
(save-excursion
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(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)
|
|
|
|
|
(concat (substring (car vals) 0 -3) "..."))))
|
|
|
|
|
(overlay-put o 'mu4e~view-header-field-folded t)
|
|
|
|
|
(overlay-put o 'display val))))))))
|
2016-07-27 04:10:25 +02:00
|
|
|
|
|
2012-07-16 11:19:19 +02:00
|
|
|
|
(defun mu4e~view-compose-contact (&optional point)
|
|
|
|
|
"Compose a message for the address at point."
|
|
|
|
|
(interactive)
|
|
|
|
|
(unless (get-text-property (or point (point)) 'email)
|
2013-04-15 22:41:26 +02:00
|
|
|
|
(mu4e-error "No address at point"))
|
2014-12-13 18:47:17 +01:00
|
|
|
|
(mu4e~compose-mail (get-text-property (or point (point)) 'long)))
|
2012-07-16 11:19:19 +02:00
|
|
|
|
|
2013-04-15 22:41:26 +02:00
|
|
|
|
(defun mu4e~view-copy-contact (&optional full)
|
|
|
|
|
"Compose a message for the address at (point)."
|
|
|
|
|
(interactive "P")
|
|
|
|
|
(let ((email (get-text-property (point) 'email))
|
2013-04-17 20:04:42 +02:00
|
|
|
|
(long (get-text-property (point) 'long)))
|
2013-04-15 22:41:26 +02:00
|
|
|
|
(unless email (mu4e-error "No address at point"))
|
|
|
|
|
(kill-new (if full long email))
|
|
|
|
|
(mu4e-message "Address copied.")))
|
2013-04-17 20:04:42 +02:00
|
|
|
|
|
2012-07-10 18:15:13 +02:00
|
|
|
|
(defun mu4e~view-construct-contacts-header (msg field)
|
2011-11-05 09:26:24 +01:00
|
|
|
|
"Add a header for a contact field (ie., :to, :from, :cc, :bcc)."
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(mu4e~view-construct-header field
|
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda(c)
|
2013-06-24 21:43:03 +02:00
|
|
|
|
(let* ((name (when (car c)
|
|
|
|
|
(replace-regexp-in-string "[[:cntrl:]]" "" (car c))))
|
|
|
|
|
(email (when (cdr c)
|
|
|
|
|
(replace-regexp-in-string "[[:cntrl:]]" "" (cdr c))))
|
2012-07-16 11:19:19 +02:00
|
|
|
|
(short (or name email)) ;; name may be nil
|
2014-02-07 02:38:33 +01:00
|
|
|
|
(long (if name (format "%s <%s>" name email) email)))
|
2012-07-10 10:51:54 +02:00
|
|
|
|
(propertize
|
2014-05-25 03:16:34 +02:00
|
|
|
|
(if mu4e-view-show-addresses long short)
|
2012-07-16 11:19:19 +02:00
|
|
|
|
'long long
|
|
|
|
|
'short short
|
|
|
|
|
'email email
|
2014-02-07 02:38:33 +01:00
|
|
|
|
'keymap mu4e-view-contacts-header-keymap
|
2013-10-19 11:05:05 +02:00
|
|
|
|
'face 'mu4e-contact-face
|
2012-07-16 11:19:19 +02:00
|
|
|
|
'mouse-face 'highlight
|
2014-05-25 03:16:34 +02:00
|
|
|
|
'help-echo (format "<%s>\n%s" email
|
|
|
|
|
"[mouse-2] or C to compose a mail for this recipient"))))
|
|
|
|
|
(mu4e-message-field msg field) ", ") t))
|
2012-07-16 11:19:19 +02:00
|
|
|
|
|
|
|
|
|
|
2012-10-22 22:52:50 +02:00
|
|
|
|
(defun mu4e~view-construct-flags-tags-header (field val)
|
2012-07-16 14:56:26 +02:00
|
|
|
|
"Construct a Flags: header."
|
|
|
|
|
(mu4e~view-construct-header
|
2012-10-22 22:52:50 +02:00
|
|
|
|
field
|
2012-07-16 14:56:26 +02:00
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (flag)
|
2012-10-22 22:52:50 +02:00
|
|
|
|
(propertize
|
|
|
|
|
(if (symbolp flag)
|
|
|
|
|
(symbol-name flag)
|
|
|
|
|
flag)
|
2013-10-19 11:05:05 +02:00
|
|
|
|
'face 'mu4e-special-header-value-face))
|
2012-10-22 22:52:50 +02:00
|
|
|
|
val
|
2013-10-19 11:05:05 +02:00
|
|
|
|
(propertize ", " 'face 'mu4e-header-value-face)) t))
|
2012-07-24 22:39:47 +02:00
|
|
|
|
|
2012-07-18 16:53:04 +02:00
|
|
|
|
(defun mu4e~view-construct-signature-header (msg)
|
|
|
|
|
"Construct a Signature: header, if there are any signed parts."
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let* ((parts (mu4e-message-field msg :parts))
|
2012-07-18 16:53:04 +02:00
|
|
|
|
(verdicts
|
|
|
|
|
(remove-if 'null
|
2012-09-28 16:10:34 +02:00
|
|
|
|
(mapcar (lambda (part) (mu4e-message-part-field part :signature))
|
|
|
|
|
parts)))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(signers
|
|
|
|
|
(mapconcat 'identity
|
|
|
|
|
(remove-if 'null
|
|
|
|
|
(mapcar (lambda (part) (mu4e-message-part-field part :signers))
|
|
|
|
|
parts)) ", "))
|
2012-07-18 16:53:04 +02:00
|
|
|
|
(val (when verdicts
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (v)
|
|
|
|
|
(propertize (symbol-name v)
|
2012-09-19 15:56:13 +02:00
|
|
|
|
'face (if (eq v 'verified)
|
|
|
|
|
'mu4e-ok-face 'mu4e-warning-face)))
|
2012-07-19 10:42:38 +02:00
|
|
|
|
verdicts ", ")))
|
2012-07-18 16:53:04 +02:00
|
|
|
|
(btn (when val
|
|
|
|
|
(with-temp-buffer
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(insert-text-button "Details"
|
|
|
|
|
'action (lambda (b)
|
2012-09-19 15:56:13 +02:00
|
|
|
|
(mu4e-view-verify-msg-popup
|
|
|
|
|
(button-get b 'msg))))
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(buffer-string))))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(val (when val (concat val signers " (" btn ")"))))
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(mu4e~view-construct-header :signature val t)))
|
2012-07-18 16:53:04 +02:00
|
|
|
|
|
2014-10-19 02:20:21 +02:00
|
|
|
|
(defun mu4e~view-construct-decryption-header (msg)
|
|
|
|
|
"Construct a Decryption: header, if there are any encrypted parts."
|
|
|
|
|
(let* ((parts (mu4e-message-field msg :parts))
|
|
|
|
|
(verdicts
|
|
|
|
|
(remove-if 'null
|
2016-01-10 12:08:18 +01:00
|
|
|
|
(mapcar (lambda (part)
|
|
|
|
|
(mu4e-message-part-field part :decryption))
|
|
|
|
|
parts)))
|
2014-10-19 02:20:21 +02:00
|
|
|
|
(succeeded (remove-if (lambda (v) (eq v 'failed)) verdicts))
|
|
|
|
|
(failed (remove-if (lambda (v) (eq v 'succeeded)) verdicts))
|
|
|
|
|
(succ (when succeeded
|
|
|
|
|
(propertize
|
|
|
|
|
(concat (number-to-string (length succeeded))
|
|
|
|
|
" part(s) decrypted")
|
|
|
|
|
'face 'mu4e-ok-face)))
|
|
|
|
|
(fail (when failed
|
|
|
|
|
(propertize
|
|
|
|
|
(concat (number-to-string (length failed))
|
|
|
|
|
" part(s) failed")
|
|
|
|
|
'face 'mu4e-warning-face)))
|
|
|
|
|
(val (concat succ fail)))
|
|
|
|
|
(mu4e~view-construct-header :decryption val t)))
|
|
|
|
|
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(defun mu4e~view-open-attach-from-binding ()
|
2014-02-07 03:04:26 +01:00
|
|
|
|
"Open the attachement at point, or click location."
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(interactive)
|
|
|
|
|
(let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
|
2014-02-07 06:21:59 +01:00
|
|
|
|
(when (and msg attnum)
|
|
|
|
|
(mu4e-view-open-attachment msg attnum))))
|
2012-09-19 15:56:13 +02:00
|
|
|
|
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(defun mu4e~view-save-attach-from-binding ()
|
2014-02-07 03:04:26 +01:00
|
|
|
|
"Save the attachement at point, or click location."
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(interactive)
|
|
|
|
|
(let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
|
2014-02-07 06:21:59 +01:00
|
|
|
|
(when (and msg attnum)
|
|
|
|
|
(mu4e-view-save-attachment-single msg attnum))))
|
2011-12-26 11:16:51 +01:00
|
|
|
|
|
2012-07-10 18:15:13 +02:00
|
|
|
|
(defun mu4e~view-construct-attachments-header (msg)
|
2011-09-19 23:20:59 +02:00
|
|
|
|
"Display attachment information; the field looks like something like:
|
2012-09-06 09:20:51 +02:00
|
|
|
|
:parts ((:index 1 :name \"1.part\" :mime-type \"text/plain\"
|
2017-02-12 10:11:16 +01:00
|
|
|
|
:type (leaf) :attachment nil :size 228)
|
|
|
|
|
(:index 2 :name \"analysis.doc\"
|
|
|
|
|
:mime-type \"application/msword\"
|
|
|
|
|
:type (leaf attachment) :attachment nil :size 605196))"
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(setq mu4e~view-attach-map ;; buffer local
|
2012-06-26 21:49:34 +02:00
|
|
|
|
(make-hash-table :size 64 :weakness nil))
|
2012-04-19 20:01:38 +02:00
|
|
|
|
(let* ((id 0)
|
2017-02-12 10:33:01 +01:00
|
|
|
|
(partcount (length (mu4e-message-field msg :parts)))
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(attachments
|
|
|
|
|
;; we only list parts that look like attachments, ie. that have a
|
2012-09-19 15:56:13 +02:00
|
|
|
|
;; non-nil :attachment property; we record a mapping between
|
|
|
|
|
;; user-visible numbers and the part indices
|
2012-09-06 09:20:51 +02:00
|
|
|
|
(remove-if-not
|
2012-05-13 12:10:56 +02:00
|
|
|
|
(lambda (part)
|
2013-10-23 20:34:10 +02:00
|
|
|
|
(let* ((mtype (or (mu4e-message-part-field part :mime-type)
|
|
|
|
|
"application/octet-stream"))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(partsize (or (mu4e-message-part-field part :size) 0))
|
2012-11-25 17:33:03 +01:00
|
|
|
|
(attachtype (mu4e-message-part-field part :type))
|
2013-10-14 21:42:58 +02:00
|
|
|
|
(isattach
|
|
|
|
|
(or ;; we consider parts marked either
|
|
|
|
|
;; "attachment" or "inline" as attachment.
|
|
|
|
|
(member 'attachment attachtype)
|
|
|
|
|
;; list inline parts as attachment (so they can be
|
|
|
|
|
;; saved), unless they are text/plain, which are
|
|
|
|
|
;; usually just message footers in mailing lists
|
2017-02-12 10:33:01 +01:00
|
|
|
|
;;
|
|
|
|
|
;; however, slow bigger text parts as attachments,
|
|
|
|
|
;; except when they're the only part... it's
|
|
|
|
|
;; complicated.
|
2013-10-14 21:42:58 +02:00
|
|
|
|
(and (member 'inline attachtype)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(or
|
2017-02-12 10:33:01 +01:00
|
|
|
|
(and (> partcount 1) (> partsize 256))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(not (string-match "^text/plain" mtype)))))))
|
2012-09-19 15:56:13 +02:00
|
|
|
|
(or ;; remove if it's not an attach *or* if it's an
|
2013-10-14 21:42:58 +02:00
|
|
|
|
;; image/audio/application type (but not a signature)
|
2012-09-06 09:20:51 +02:00
|
|
|
|
isattach
|
|
|
|
|
(string-match "^\\(image\\|audio\\)" mtype)
|
2012-10-31 19:34:42 +01:00
|
|
|
|
(string= "message/rfc822" mtype)
|
2013-09-17 19:19:00 +02:00
|
|
|
|
(string= "text/calendar" mtype)
|
2012-09-06 09:20:51 +02:00
|
|
|
|
(and (string-match "^application" mtype)
|
|
|
|
|
(not (string-match "signature" mtype))))))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(mu4e-message-field msg :parts)))
|
2012-04-19 20:01:38 +02:00
|
|
|
|
(attstr
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(mapconcat
|
|
|
|
|
(lambda (part)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let ((index (mu4e-message-part-field part :index))
|
|
|
|
|
(name (mu4e-message-part-field part :name))
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(size (mu4e-message-part-field part :size)))
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(incf id)
|
|
|
|
|
(puthash id index mu4e~view-attach-map)
|
2012-09-19 15:56:13 +02:00
|
|
|
|
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(concat
|
|
|
|
|
(propertize (format "[%d]" id)
|
2013-10-19 11:05:05 +02:00
|
|
|
|
'face 'mu4e-attach-number-face)
|
|
|
|
|
(propertize name 'face 'mu4e-link-face
|
2014-02-06 09:01:13 +01:00
|
|
|
|
'keymap mu4e-view-attachments-header-keymap
|
2012-09-19 15:56:13 +02:00
|
|
|
|
'mouse-face 'highlight
|
2014-02-13 15:24:01 +01:00
|
|
|
|
'help-echo (concat
|
|
|
|
|
"[mouse-1] or [M-RET] opens the attachment\n"
|
|
|
|
|
"[mouse-2] or [S-RET] offers to save it")
|
2014-02-06 09:01:13 +01:00
|
|
|
|
'mu4e-msg msg
|
|
|
|
|
'mu4e-attnum id
|
2014-02-13 15:24:01 +01:00
|
|
|
|
)
|
2012-08-01 16:07:11 +02:00
|
|
|
|
(when (and size (> size 0))
|
2013-02-21 20:11:03 +01:00
|
|
|
|
(propertize (format "(%s)" (mu4e-display-size size))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
'face 'mu4e-header-key-face)))))
|
2012-08-01 16:07:11 +02:00
|
|
|
|
attachments ", ")))
|
|
|
|
|
(when attachments
|
|
|
|
|
(mu4e~view-construct-header :attachments attstr t))))
|
|
|
|
|
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(defun mu4e-view-for-each-part (msg func)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Apply FUNC to each part in MSG.
|
|
|
|
|
FUNC should be a function taking two arguments:
|
2012-05-16 19:47:13 +02:00
|
|
|
|
1. the message MSG, and
|
|
|
|
|
2. a plist describing the attachment. The plist looks like:
|
|
|
|
|
(:index 1 :name \"test123.doc\"
|
2017-02-12 10:11:16 +01:00
|
|
|
|
:mime-type \"application/msword\" :attachment t :size 1234)."
|
2012-09-19 15:56:13 +02:00
|
|
|
|
(dolist (part (mu4e-msg-field msg :parts))
|
|
|
|
|
(funcall func msg part)))
|
2012-05-16 19:47:13 +02:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(defvar mu4e-view-mode-map nil
|
|
|
|
|
"Keymap for \"*mu4e-view*\" buffers.")
|
|
|
|
|
(unless mu4e-view-mode-map
|
|
|
|
|
(setq mu4e-view-mode-map
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(let ((map (make-sparse-keymap)))
|
2012-04-11 01:08:32 +02:00
|
|
|
|
|
2012-10-25 10:59:50 +02:00
|
|
|
|
(define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
|
2013-04-21 11:43:00 +02:00
|
|
|
|
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
|
2012-10-27 13:41:20 +02:00
|
|
|
|
|
2012-06-15 10:01:40 +02:00
|
|
|
|
(define-key map "q" 'mu4e~view-quit-buffer)
|
2011-10-18 11:39:49 +02:00
|
|
|
|
|
2012-04-08 19:28:49 +02:00
|
|
|
|
;; note, 'z' is by-default bound to 'bury-buffer'
|
|
|
|
|
;; but that's not very useful in this case
|
2015-12-30 14:35:26 +01:00
|
|
|
|
(define-key map "z" 'ignore)
|
2012-04-11 01:08:32 +02:00
|
|
|
|
|
2012-05-01 21:45:54 +02:00
|
|
|
|
(define-key map "s" 'mu4e-headers-search)
|
2012-06-06 07:02:48 +02:00
|
|
|
|
(define-key map "S" 'mu4e-view-search-edit)
|
|
|
|
|
(define-key map "/" 'mu4e-view-search-narrow)
|
2012-05-11 10:18:00 +02:00
|
|
|
|
|
2012-05-07 15:29:48 +02:00
|
|
|
|
(define-key map (kbd "<M-left>") 'mu4e-headers-query-prev)
|
|
|
|
|
(define-key map (kbd "<M-right>") 'mu4e-headers-query-next)
|
2012-05-11 10:18:00 +02:00
|
|
|
|
|
|
|
|
|
(define-key map "b" 'mu4e-headers-search-bookmark)
|
2012-05-05 10:07:58 +02:00
|
|
|
|
(define-key map "B" 'mu4e-headers-search-bookmark-edit)
|
2012-03-27 20:17:00 +02:00
|
|
|
|
|
2012-05-10 08:55:28 +02:00
|
|
|
|
(define-key map "%" 'mu4e-view-mark-pattern)
|
2012-04-26 21:42:53 +02:00
|
|
|
|
(define-key map "t" 'mu4e-view-mark-subthread)
|
|
|
|
|
(define-key map "T" 'mu4e-view-mark-thread)
|
2012-04-28 12:47:13 +02:00
|
|
|
|
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(define-key map "v" 'mu4e-view-verify-msg-popup)
|
2012-07-24 22:39:47 +02:00
|
|
|
|
|
2012-05-01 21:45:54 +02:00
|
|
|
|
(define-key map "j" 'mu4e~headers-jump-to-maildir)
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "g" 'mu4e-view-go-to-url)
|
2015-03-21 14:22:12 +01:00
|
|
|
|
(define-key map "k" 'mu4e-view-save-url)
|
2016-01-10 12:08:18 +01:00
|
|
|
|
(define-key map "f" 'mu4e-view-fetch-url)
|
2011-12-19 08:06:47 +01:00
|
|
|
|
|
2011-12-15 07:51:39 +01:00
|
|
|
|
(define-key map "F" 'mu4e-compose-forward)
|
|
|
|
|
(define-key map "R" 'mu4e-compose-reply)
|
|
|
|
|
(define-key map "C" 'mu4e-compose-new)
|
2012-03-27 20:17:00 +02:00
|
|
|
|
(define-key map "E" 'mu4e-compose-edit)
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2012-04-18 18:10:48 +02:00
|
|
|
|
(define-key map "." 'mu4e-view-raw-message)
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "|" 'mu4e-view-pipe)
|
2012-04-20 17:23:47 +02:00
|
|
|
|
(define-key map "a" 'mu4e-view-action)
|
2011-11-20 09:31:38 +01:00
|
|
|
|
|
2015-12-13 16:42:27 +01:00
|
|
|
|
(define-key map ";" 'mu4e-context-switch)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
|
2013-01-26 09:57:14 +01:00
|
|
|
|
;; toggle header settings
|
|
|
|
|
(define-key map "O" 'mu4e-headers-change-sorting)
|
|
|
|
|
(define-key map "P" 'mu4e-headers-toggle-threading)
|
|
|
|
|
(define-key map "Q" 'mu4e-headers-toggle-full-search)
|
|
|
|
|
(define-key map "W" 'mu4e-headers-toggle-include-related)
|
2013-02-11 22:49:39 +01:00
|
|
|
|
|
2012-05-14 09:45:39 +02:00
|
|
|
|
;; change the number of headers
|
2012-12-23 13:05:48 +01:00
|
|
|
|
(define-key map (kbd "C-+") 'mu4e-headers-split-view-grow)
|
2012-11-11 14:26:09 +01:00
|
|
|
|
(define-key map (kbd "C--") 'mu4e-headers-split-view-shrink)
|
2012-12-23 13:05:48 +01:00
|
|
|
|
(define-key map (kbd "<C-kp-add>") 'mu4e-headers-split-view-grow)
|
2012-11-11 14:26:09 +01:00
|
|
|
|
(define-key map (kbd "<C-kp-subtract>") 'mu4e-headers-split-view-shrink)
|
2012-05-16 19:47:13 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; intra-message navigation
|
2012-10-17 11:07:57 +02:00
|
|
|
|
(define-key map (kbd "SPC") 'mu4e-view-scroll-up-or-next)
|
2012-11-11 14:27:10 +01:00
|
|
|
|
(define-key map (kbd "<home>") 'beginning-of-buffer)
|
|
|
|
|
(define-key map (kbd "<end>") 'end-of-buffer)
|
|
|
|
|
(define-key map (kbd "RET") 'mu4e-scroll-up)
|
2013-10-10 14:47:43 +02:00
|
|
|
|
(define-key map (kbd "<backspace>") 'mu4e-scroll-down)
|
2011-10-18 11:39:49 +02:00
|
|
|
|
|
|
|
|
|
;; navigation between messages
|
2012-05-06 10:00:56 +02:00
|
|
|
|
(define-key map "p" 'mu4e-view-headers-prev)
|
|
|
|
|
(define-key map "n" 'mu4e-view-headers-next)
|
2012-04-09 10:51:24 +02:00
|
|
|
|
;; the same
|
2012-05-06 10:00:56 +02:00
|
|
|
|
(define-key map (kbd "<M-down>") 'mu4e-view-headers-next)
|
|
|
|
|
(define-key map (kbd "<M-up>") 'mu4e-view-headers-prev)
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2015-11-22 22:32:05 +01:00
|
|
|
|
(define-key map (kbd "[") 'mu4e-view-headers-prev-unread)
|
2014-11-28 07:54:08 +01:00
|
|
|
|
(define-key map (kbd "]") 'mu4e-view-headers-next-unread)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
|
2012-04-09 10:51:24 +02:00
|
|
|
|
;; switching to view mode (if it's visible)
|
|
|
|
|
(define-key map "y" 'mu4e-select-other-view)
|
2012-04-11 01:08:32 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; attachments
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(define-key map "e" 'mu4e-view-save-attachment)
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "o" 'mu4e-view-open-attachment)
|
2012-04-20 17:23:47 +02:00
|
|
|
|
(define-key map "A" 'mu4e-view-attachment-action)
|
2012-04-21 09:35:30 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; marking/unmarking
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "d" 'mu4e-view-mark-for-trash)
|
|
|
|
|
(define-key map (kbd "<delete>") 'mu4e-view-mark-for-delete)
|
2014-09-24 22:47:46 +02:00
|
|
|
|
(define-key map (kbd "<deletechar>") 'mu4e-view-mark-for-delete)
|
2012-06-14 20:54:24 +02:00
|
|
|
|
(define-key map (kbd "D") 'mu4e-view-mark-for-delete)
|
|
|
|
|
(define-key map (kbd "m") 'mu4e-view-mark-for-move)
|
2012-09-27 11:53:16 +02:00
|
|
|
|
(define-key map (kbd "r") 'mu4e-view-mark-for-refile)
|
2012-09-27 20:53:21 +02:00
|
|
|
|
|
2014-11-30 10:49:41 +01:00
|
|
|
|
(define-key map (kbd "?") 'mu4e-view-mark-for-unread)
|
|
|
|
|
(define-key map (kbd "!") 'mu4e-view-mark-for-read)
|
2012-06-26 21:49:34 +02:00
|
|
|
|
|
2012-10-03 20:51:26 +02:00
|
|
|
|
(define-key map (kbd "+") 'mu4e-view-mark-for-flag)
|
|
|
|
|
(define-key map (kbd "-") 'mu4e-view-mark-for-unflag)
|
2013-06-28 21:36:54 +02:00
|
|
|
|
(define-key map (kbd "=") 'mu4e-view-mark-for-untrash)
|
2014-11-30 10:49:41 +01:00
|
|
|
|
(define-key map (kbd "&") 'mu4e-view-mark-custom)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
|
2013-06-13 22:53:11 +02:00
|
|
|
|
(define-key map (kbd "*") 'mu4e-view-mark-for-something)
|
|
|
|
|
(define-key map (kbd "<kp-multiply>") 'mu4e-view-mark-for-something)
|
2012-10-06 19:47:59 +02:00
|
|
|
|
(define-key map (kbd "<insert>") 'mu4e-view-mark-for-something)
|
|
|
|
|
(define-key map (kbd "<insertchar>") 'mu4e-view-mark-for-something)
|
2012-10-22 22:52:50 +02:00
|
|
|
|
|
2012-06-14 18:10:02 +02:00
|
|
|
|
(define-key map (kbd "#") 'mu4e-mark-resolve-deferred-marks)
|
2012-06-26 21:49:34 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; misc
|
2012-12-23 13:18:14 +01:00
|
|
|
|
(define-key map "w" 'visual-line-mode)
|
2015-12-27 09:28:11 +01:00
|
|
|
|
(define-key map "#" 'mu4e-view-toggle-hide-cited)
|
|
|
|
|
(define-key map "h" 'mu4e-view-toggle-html)
|
2014-06-27 07:28:33 +02:00
|
|
|
|
(define-key map (kbd "M-q") 'mu4e-view-fill-long-lines)
|
2014-06-27 07:37:15 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; next 3 only warn user when attempt in the message view
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "u" 'mu4e-view-unmark)
|
2012-04-09 10:51:24 +02:00
|
|
|
|
(define-key map "U" 'mu4e-view-unmark-all)
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key map "x" 'mu4e-view-marked-execute)
|
2011-10-18 11:39:49 +02:00
|
|
|
|
|
2012-04-16 17:31:48 +02:00
|
|
|
|
(define-key map "$" 'mu4e-show-log)
|
2011-12-15 07:51:39 +01:00
|
|
|
|
(define-key map "H" 'mu4e-display-manual)
|
2011-12-19 08:06:47 +01:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
;; menu
|
|
|
|
|
(define-key map [menu-bar] (make-sparse-keymap))
|
|
|
|
|
(let ((menumap (make-sparse-keymap "View")))
|
|
|
|
|
(define-key map [menu-bar headers] (cons "View" menumap))
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2012-04-08 19:28:49 +02:00
|
|
|
|
(define-key menumap [quit-buffer]
|
2012-06-15 10:01:40 +02:00
|
|
|
|
'("Quit view" . mu4e~view-quit-buffer))
|
2011-12-15 07:51:39 +01:00
|
|
|
|
(define-key menumap [display-help] '("Help" . mu4e-display-manual))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
|
|
|
|
|
(define-key menumap [sepa0] '("--"))
|
|
|
|
|
(define-key menumap [wrap-lines]
|
2012-12-23 13:18:14 +01:00
|
|
|
|
'("Toggle wrap lines" . visual-line-mode))
|
2015-12-30 14:35:26 +01:00
|
|
|
|
(define-key menumap [toggle-html]
|
2015-12-27 09:28:11 +01:00
|
|
|
|
'("Toggle view-html" . mu4e-view-toggle-html))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [hide-cited]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Toggle hide cited" . mu4e-view-toggle-hide-cited))
|
2012-01-14 10:05:23 +01:00
|
|
|
|
(define-key menumap [raw-view]
|
2012-04-23 19:35:14 +02:00
|
|
|
|
'("View raw message" . mu4e-view-raw-message))
|
2011-11-27 15:23:14 +01:00
|
|
|
|
(define-key menumap [pipe]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Pipe through shell" . mu4e-view-pipe))
|
2011-12-15 20:47:01 +01:00
|
|
|
|
;; (define-key menumap [inspect]
|
|
|
|
|
;; '("Inspect with guile" . mu4e-inspect-message))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
|
|
|
|
|
(define-key menumap [sepa8] '("--"))
|
|
|
|
|
(define-key menumap [open-att]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Open attachment" . mu4e-view-open-attachment))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [extract-att]
|
2012-04-15 13:22:43 +02:00
|
|
|
|
'("Extract attachment" . mu4e-view-save-attachment))
|
2016-01-10 12:06:10 +01:00
|
|
|
|
|
|
|
|
|
(define-key menumap [save-url]
|
|
|
|
|
'("Save URL to kill-ring" . mu4e-view-save-url))
|
|
|
|
|
(define-key menumap [fetch-url]
|
|
|
|
|
'("Fetch URL" . mu4e-view-fetch-url))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [goto-url]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Visit URL" . mu4e-view-go-to-url))
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [sepa1] '("--"))
|
|
|
|
|
(define-key menumap [mark-delete]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Mark for deletion" . mu4e-view-mark-for-delete))
|
2017-02-14 12:02:56 +01:00
|
|
|
|
(define-key menumap [mark-untrash]
|
|
|
|
|
'("Mark for untrash" . mu4e-view-mark-for-untrash))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [mark-trash]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Mark for trash" . mu4e-view-mark-for-trash))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [mark-move]
|
2011-12-13 08:07:38 +01:00
|
|
|
|
'("Mark for move" . mu4e-view-mark-for-move))
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [sepa2] '("--"))
|
2016-02-16 07:14:00 +01:00
|
|
|
|
(define-key menumap [resend] '("Resend" . mu4e-compose-resend))
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(define-key menumap [forward] '("Forward" . mu4e-compose-forward))
|
|
|
|
|
(define-key menumap [reply] '("Reply" . mu4e-compose-reply))
|
2016-02-16 07:14:00 +01:00
|
|
|
|
(define-key menumap [compose-new] '("Compose new" . mu4e-compose-new))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [sepa3] '("--"))
|
|
|
|
|
|
2012-07-03 20:37:30 +02:00
|
|
|
|
(define-key menumap [query-next]
|
|
|
|
|
'("Next query" . mu4e-headers-query-next))
|
|
|
|
|
(define-key menumap [query-prev]
|
|
|
|
|
'("Previous query" . mu4e-headers-query-prev))
|
|
|
|
|
(define-key menumap [narrow-search]
|
|
|
|
|
'("Narrow search" . mu4e-headers-search-narrow))
|
|
|
|
|
(define-key menumap [bookmark]
|
|
|
|
|
'("Search bookmark" . mu4e-headers-search-bookmark))
|
|
|
|
|
(define-key menumap [jump]
|
|
|
|
|
'("Jump to maildir" . mu4e~headers-jump-to-maildir))
|
|
|
|
|
(define-key menumap [search]
|
|
|
|
|
'("Search" . mu4e-headers-search))
|
2012-05-11 10:18:00 +02:00
|
|
|
|
|
2011-10-18 11:39:49 +02:00
|
|
|
|
(define-key menumap [sepa4] '("--"))
|
2012-05-06 10:00:56 +02:00
|
|
|
|
(define-key menumap [next] '("Next" . mu4e-view-headers-next))
|
|
|
|
|
(define-key menumap [previous] '("Previous" . mu4e-view-headers-prev)))
|
2011-10-18 11:39:49 +02:00
|
|
|
|
map)))
|
2011-11-20 00:18:12 +01:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(fset 'mu4e-view-mode-map mu4e-view-mode-map)
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2012-12-12 20:05:57 +01:00
|
|
|
|
(defcustom mu4e-view-mode-hook nil
|
|
|
|
|
"Hook run when entering Mu4e-View mode."
|
|
|
|
|
:options '(turn-on-visual-line-mode)
|
|
|
|
|
:type 'hook
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2012-08-14 09:20:27 +02:00
|
|
|
|
(defvar mu4e-view-mode-abbrev-table nil)
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(defun mu4e~view-mode-body ()
|
|
|
|
|
"Body of the mode-function."
|
|
|
|
|
(use-local-map mu4e-view-mode-map)
|
2015-12-19 09:14:49 +01:00
|
|
|
|
;; show context in mode-string
|
2016-10-26 08:20:25 +02:00
|
|
|
|
(make-local-variable 'global-mode-string)
|
|
|
|
|
(add-to-list 'global-mode-string '(:eval (mu4e-context-label)))
|
2015-12-19 09:14:49 +01:00
|
|
|
|
(setq buffer-undo-list t);; don't record undo info
|
2012-04-19 07:33:13 +02:00
|
|
|
|
;; autopair mode gives error when pressing RET
|
|
|
|
|
;; turn it off
|
|
|
|
|
(when (boundp 'autopair-dont-activate)
|
2012-10-16 20:43:02 +02:00
|
|
|
|
(setq autopair-dont-activate t)))
|
2012-11-25 17:33:03 +01:00
|
|
|
|
|
2018-04-24 13:21:15 +02:00
|
|
|
|
(if mu4e-view-use-gnus
|
|
|
|
|
(define-derived-mode mu4e-view-mode gnus-article-mode "mu4e:view/g"
|
|
|
|
|
;; remove some gnus stuff that does not apply
|
|
|
|
|
(define-key mu4e-view-mode-map [menu-bar Treatment] nil)
|
|
|
|
|
(define-key mu4e-view-mode-map [menu-bar Article] nil)
|
|
|
|
|
(define-key mu4e-view-mode-map [menu-bar post] nil)
|
|
|
|
|
"Major mode for viewing an e-mail message in mu4e, based on Gnus."
|
|
|
|
|
(mu4e~view-mode-body))
|
|
|
|
|
(define-derived-mode mu4e-view-mode special-mode "mu4e:view"
|
|
|
|
|
"Major mode for viewing an e-mail message in mu4e, using the
|
|
|
|
|
mu4e-specific view."
|
|
|
|
|
(mu4e~view-mode-body)))
|
|
|
|
|
|
2015-12-27 13:30:02 +01:00
|
|
|
|
(defun mu4e~view-mark-as-read-maybe (msg)
|
2013-10-20 15:55:36 +02:00
|
|
|
|
"Clear the message MSG New/Unread status and set it to Seen.
|
|
|
|
|
If the message is not New/Unread, do nothing. Evaluates to t if it
|
|
|
|
|
triggers any changes, nil otherwise. If this function does any
|
|
|
|
|
changes, it triggers a refresh."
|
2015-12-27 13:30:02 +01:00
|
|
|
|
(when (and mu4e-view-auto-mark-as-read msg)
|
2013-10-20 15:55:36 +02:00
|
|
|
|
(let ((flags (mu4e-message-field msg :flags))
|
|
|
|
|
(msgid (mu4e-message-field msg :message-id))
|
|
|
|
|
(docid (mu4e-message-field msg :docid)))
|
2017-04-08 14:21:06 +02:00
|
|
|
|
;; attached (embedded) messages don't have docids; leave them alone if it
|
|
|
|
|
;; is a new message
|
2012-10-22 22:52:50 +02:00
|
|
|
|
(when (and docid (or (member 'unread flags) (member 'new flags)))
|
2013-06-24 21:43:03 +02:00
|
|
|
|
;; mark /all/ messages with this message-id as read, so all copies of
|
|
|
|
|
;; this message will be marked as read.
|
2013-10-20 15:55:36 +02:00
|
|
|
|
(mu4e~proc-move msgid nil "+S-u-N")
|
2018-04-25 09:52:29 +02:00
|
|
|
|
(mu4e~proc-view docid mu4e-view-show-images (mu4e~decrypt-p msg))
|
2013-10-20 15:55:36 +02:00
|
|
|
|
t))))
|
2011-09-20 22:59:20 +02:00
|
|
|
|
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(defun mu4e~view-browse-url-func (url)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Return a function that executes `browse-url' with URL.
|
2017-04-08 14:21:06 +02:00
|
|
|
|
The browser that is called depends on
|
2012-11-10 14:01:17 +01:00
|
|
|
|
`browse-url-browser-function' and `browse-url-mailto-function'."
|
2012-06-23 18:38:06 +02:00
|
|
|
|
(save-match-data
|
|
|
|
|
(if (string-match "^mailto:" url)
|
|
|
|
|
(lexical-let ((url url))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(interactive)
|
2017-04-08 14:21:06 +02:00
|
|
|
|
(browse-url-mail url)))
|
2012-06-23 18:38:06 +02:00
|
|
|
|
(lexical-let ((url url))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(interactive)
|
|
|
|
|
(browse-url url))))))
|
2012-06-26 21:49:34 +02:00
|
|
|
|
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(defun mu4e~view-browse-url-from-binding (&optional url)
|
2014-02-07 03:04:26 +01:00
|
|
|
|
"View in browser the url at point, or click location.
|
|
|
|
|
If the optional argument URL is provided, browse that instead.
|
|
|
|
|
If the url is mailto link, start writing an email to that address."
|
2014-02-06 09:01:13 +01:00
|
|
|
|
(interactive)
|
|
|
|
|
(let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url))))
|
2014-02-07 06:21:59 +01:00
|
|
|
|
(when url
|
|
|
|
|
(if (string-match-p "^mailto:" url)
|
2017-04-08 14:21:06 +02:00
|
|
|
|
(browse-url-mail url)
|
2014-02-07 06:21:59 +01:00
|
|
|
|
(browse-url url)))))
|
2014-02-06 09:01:13 +01:00
|
|
|
|
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(defun mu4e~view-show-images-maybe (msg)
|
2012-08-14 09:20:27 +02:00
|
|
|
|
"Show attached images, if `mu4e-show-images' is non-nil."
|
2013-01-26 09:57:14 +01:00
|
|
|
|
(when (and (display-images-p) mu4e-view-show-images)
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(mu4e-view-for-each-part msg
|
|
|
|
|
(lambda (msg part)
|
2013-10-23 20:34:10 +02:00
|
|
|
|
(when (string-match "^image/"
|
|
|
|
|
(or (mu4e-message-part-field part :mime-type)
|
|
|
|
|
"application/object-stream"))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let ((imgfile (mu4e-message-part-field part :temp)))
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(when (and imgfile (file-exists-p imgfile))
|
2013-10-13 22:24:56 +02:00
|
|
|
|
(save-excursion
|
2012-05-16 19:47:13 +02:00
|
|
|
|
(goto-char (point-max))
|
2013-07-06 10:51:33 +02:00
|
|
|
|
(mu4e-display-image imgfile
|
|
|
|
|
mu4e-view-image-max-width
|
|
|
|
|
mu4e-view-image-max-height)))))))))
|
2012-05-20 18:59:57 +02:00
|
|
|
|
|
2015-03-21 09:28:01 +01:00
|
|
|
|
(defvar mu4e~view-beginning-of-url-regexp
|
|
|
|
|
"https?\\://\\|mailto:"
|
2016-01-10 12:08:18 +01:00
|
|
|
|
"Regexp that matches the beginning of http:/https:/mailto:
|
|
|
|
|
URLs; match-string 1 will contain the matched URL, if any.")
|
2015-03-21 09:28:01 +01:00
|
|
|
|
|
2011-12-26 11:16:51 +01:00
|
|
|
|
;; this is fairly simplistic...
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(defun mu4e~view-make-urls-clickable ()
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Turn things that look like URLs into clickable things.
|
|
|
|
|
Also number them so they can be opened using `mu4e-view-go-to-url'."
|
2011-12-26 11:16:51 +01:00
|
|
|
|
(let ((num 0))
|
|
|
|
|
(save-excursion
|
2012-04-28 12:47:13 +02:00
|
|
|
|
(setq mu4e~view-link-map ;; buffer local
|
2012-06-26 21:49:34 +02:00
|
|
|
|
(make-hash-table :size 32 :weakness nil))
|
2011-12-26 11:16:51 +01:00
|
|
|
|
(goto-char (point-min))
|
2015-03-21 09:28:01 +01:00
|
|
|
|
(while (re-search-forward mu4e~view-beginning-of-url-regexp nil t)
|
|
|
|
|
(let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
|
|
|
|
(when bounds
|
|
|
|
|
(let* ((url (thing-at-point-url-at-point))
|
|
|
|
|
(ov (make-overlay (car bounds) (cdr bounds))))
|
|
|
|
|
(puthash (incf num) url mu4e~view-link-map)
|
|
|
|
|
(add-text-properties
|
|
|
|
|
(car bounds)
|
|
|
|
|
(cdr bounds)
|
|
|
|
|
`(face mu4e-link-face
|
|
|
|
|
mouse-face highlight
|
|
|
|
|
mu4e-url ,url
|
|
|
|
|
keymap ,mu4e-view-clickable-urls-keymap
|
|
|
|
|
help-echo
|
|
|
|
|
"[mouse-1] or [M-RET] to open the link"))
|
|
|
|
|
(overlay-put ov 'after-string
|
|
|
|
|
(propertize (format "[%d]" num)
|
|
|
|
|
'face 'mu4e-url-number-face)))))))))
|
2012-01-06 11:31:28 +01:00
|
|
|
|
|
2011-12-01 20:21:29 +01:00
|
|
|
|
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(defun mu4e~view-hide-cited ()
|
2012-04-01 11:31:23 +02:00
|
|
|
|
"Toggle hiding of cited lines in the message body."
|
|
|
|
|
(save-excursion
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(goto-char (point-min))
|
2014-02-05 21:24:05 +01:00
|
|
|
|
(flush-lines mu4e-cited-regexp)
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(setq mu4e~view-cited-hidden t))))
|
|
|
|
|
|
2012-05-06 15:57:11 +02:00
|
|
|
|
(defmacro mu4e~view-in-headers-context (&rest body)
|
2013-06-08 16:36:29 +02:00
|
|
|
|
"Evaluate BODY in the context of the headers buffer connected to
|
|
|
|
|
this view."
|
2012-05-06 15:57:11 +02:00
|
|
|
|
`(progn
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(unless (buffer-live-p (mu4e-get-headers-buffer))
|
|
|
|
|
(mu4e-error "no headers buffer connected"))
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((msg (mu4e-message-at-point))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(docid (mu4e-message-field msg :docid)))
|
2012-09-21 11:35:53 +02:00
|
|
|
|
(unless docid
|
|
|
|
|
(mu4e-error "message without docid: action is not possible."))
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(with-current-buffer (mu4e-get-headers-buffer)
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(unless (eq mu4e-split-view 'single-window)
|
|
|
|
|
(when (get-buffer-window)
|
|
|
|
|
(select-window (get-buffer-window))))
|
2012-09-21 11:35:53 +02:00
|
|
|
|
(if (mu4e~headers-goto-docid docid)
|
2012-06-12 19:58:32 +02:00
|
|
|
|
,@body
|
2012-07-10 21:53:36 +02:00
|
|
|
|
(mu4e-error "cannot find message in headers buffer."))))))
|
2012-04-23 19:35:14 +02:00
|
|
|
|
|
2014-11-28 07:54:08 +01:00
|
|
|
|
(defun mu4e-view-headers-next (&optional n)
|
2012-05-06 10:00:56 +02:00
|
|
|
|
"Move point to the next message header in the headers buffer
|
|
|
|
|
connected with this message view. If this succeeds, return the new
|
|
|
|
|
docid. Otherwise, return nil. Optionally, takes an integer
|
|
|
|
|
N (prefix argument), to the Nth next header."
|
|
|
|
|
(interactive "P")
|
2012-05-06 15:57:11 +02:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(mu4e~headers-move (or n 1))))
|
2012-05-06 10:00:56 +02:00
|
|
|
|
|
2014-11-28 07:54:08 +01:00
|
|
|
|
(defun mu4e-view-headers-prev (&optional n)
|
2012-05-06 10:00:56 +02:00
|
|
|
|
"Move point to the previous message header in the headers buffer
|
|
|
|
|
connected with this message view. If this succeeds, return the new
|
|
|
|
|
docid. Otherwise, return nil. Optionally, takes an integer
|
|
|
|
|
N (prefix argument), to the Nth previous header."
|
|
|
|
|
(interactive "P")
|
2012-05-06 15:57:11 +02:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(mu4e~headers-move (- (or n 1)))))
|
2012-04-20 17:23:47 +02:00
|
|
|
|
|
2015-11-22 22:32:05 +01:00
|
|
|
|
(defun mu4e~view-prev-or-next-unread (backwards)
|
2014-11-28 07:54:08 +01:00
|
|
|
|
"Move point to the next or previous (when BACKWARDS is non-`nil')
|
|
|
|
|
unread message header in the headers buffer connected with this
|
|
|
|
|
message view. If this succeeds, return the new docid. Otherwise,
|
|
|
|
|
return nil."
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(mu4e~view-in-headers-context
|
2015-11-22 22:32:05 +01:00
|
|
|
|
(mu4e~headers-prev-or-next-unread backwards))
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(if (eq mu4e-split-view 'single-window)
|
|
|
|
|
(when (eq (window-buffer) (mu4e-get-view-buffer))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(with-current-buffer (mu4e-get-headers-buffer)
|
|
|
|
|
(mu4e-headers-view-message)))
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(mu4e-select-other-view)
|
|
|
|
|
(mu4e-headers-view-message)))
|
2014-11-28 07:54:08 +01:00
|
|
|
|
|
2015-11-22 22:32:05 +01:00
|
|
|
|
(defun mu4e-view-headers-prev-unread ()
|
|
|
|
|
"Move point to the previous unread message header in the headers
|
|
|
|
|
buffer connected with this message view. If this succeeds, return
|
|
|
|
|
the new docid. Otherwise, return nil."
|
|
|
|
|
(interactive)
|
|
|
|
|
(mu4e~view-prev-or-next-unread t))
|
|
|
|
|
|
|
|
|
|
(defun mu4e-view-headers-next-unread ()
|
|
|
|
|
"Move point to the next unread message header in the headers
|
|
|
|
|
buffer connected with this message view. If this succeeds, return
|
|
|
|
|
the new docid. Otherwise, return nil."
|
|
|
|
|
(interactive)
|
|
|
|
|
(mu4e~view-prev-or-next-unread nil))
|
2014-11-28 07:54:08 +01:00
|
|
|
|
|
2012-04-01 11:31:23 +02:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2012-09-19 15:56:13 +02:00
|
|
|
|
|
|
|
|
|
;; Interactive functions
|
2012-09-19 10:36:02 +02:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(defun mu4e-view-toggle-hide-cited ()
|
2011-09-30 07:37:47 +02:00
|
|
|
|
"Toggle hiding of cited lines in the message body."
|
|
|
|
|
(interactive)
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(if mu4e~view-cited-hidden
|
2012-04-01 11:31:23 +02:00
|
|
|
|
(mu4e-view-refresh)
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(mu4e~view-hide-cited)))
|
2011-09-30 07:37:47 +02:00
|
|
|
|
|
2016-11-27 13:34:50 +01:00
|
|
|
|
(defvar mu4e~view-html-text nil
|
|
|
|
|
"Should we prefer html or text just this once? A symbol `text'
|
|
|
|
|
or `html' or nil.")
|
|
|
|
|
|
2015-12-27 09:28:11 +01:00
|
|
|
|
(defun mu4e-view-toggle-html ()
|
|
|
|
|
"Toggle html-display of the message body (if any)."
|
|
|
|
|
(interactive)
|
2016-11-27 13:34:50 +01:00
|
|
|
|
(setq mu4e~view-html-text
|
|
|
|
|
(if mu4e~message-body-html 'text 'html))
|
|
|
|
|
(mu4e-view-refresh))
|
2015-12-27 09:28:11 +01:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(defun mu4e-view-refresh ()
|
2012-09-16 21:34:11 +02:00
|
|
|
|
"Redisplay the current message."
|
2011-09-30 07:37:47 +02:00
|
|
|
|
(interactive)
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(mu4e-view mu4e~view-msg)
|
2012-09-16 21:34:11 +02:00
|
|
|
|
(setq mu4e~view-cited-hidden nil))
|
2012-06-12 19:58:32 +02:00
|
|
|
|
|
2012-04-20 17:23:47 +02:00
|
|
|
|
(defun mu4e-view-action (&optional msg)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Ask user for some action to apply on MSG, then do it.
|
|
|
|
|
If MSG is nil apply action to message returned
|
|
|
|
|
bymessage-at-point. The actions are specified in
|
2012-04-20 17:23:47 +02:00
|
|
|
|
`mu4e-view-actions'."
|
|
|
|
|
(interactive)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
2012-04-26 21:42:53 +02:00
|
|
|
|
(actionfunc (mu4e-read-option "Action: " mu4e-view-actions)))
|
2012-04-20 17:23:47 +02:00
|
|
|
|
(funcall actionfunc msg)))
|
|
|
|
|
|
2012-05-10 08:55:28 +02:00
|
|
|
|
(defun mu4e-view-mark-pattern ()
|
2012-04-24 21:37:42 +02:00
|
|
|
|
"Ask user for a kind of mark (move, delete etc.), a field to
|
|
|
|
|
match and a regular expression to match with. Then, mark all
|
|
|
|
|
matching messages with that mark."
|
|
|
|
|
(interactive)
|
2012-09-30 19:12:24 +02:00
|
|
|
|
(mu4e~view-in-headers-context (mu4e-headers-mark-pattern)))
|
2012-04-26 21:42:53 +02:00
|
|
|
|
|
2015-11-23 12:33:50 +01:00
|
|
|
|
(defun mu4e-view-mark-thread (&optional markpair)
|
|
|
|
|
"Ask user for a kind of mark (move, delete etc.), and apply it
|
|
|
|
|
to all messages in the thread at point in the headers view. The
|
|
|
|
|
optional MARKPAIR can also be used to provide the mark
|
|
|
|
|
selection."
|
2012-04-26 21:42:53 +02:00
|
|
|
|
(interactive)
|
2015-11-23 12:33:50 +01:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(if markpair (mu4e-headers-mark-thread nil markpair)
|
|
|
|
|
(call-interactively 'mu4e-headers-mark-thread))))
|
|
|
|
|
|
|
|
|
|
(defun mu4e-view-mark-subthread (&optional markpair)
|
|
|
|
|
"Ask user for a kind of mark (move, delete etc.), and apply it
|
|
|
|
|
to all messages in the subthread at point in the headers view.
|
|
|
|
|
The optional MARKPAIR can also be used to provide the mark
|
|
|
|
|
selection."
|
2012-04-26 21:42:53 +02:00
|
|
|
|
(interactive)
|
2015-11-23 12:33:50 +01:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(if markpair (mu4e-headers-mark-subthread markpair)
|
|
|
|
|
(mu4e-headers-mark-subthread))))
|
2012-04-26 21:42:53 +02:00
|
|
|
|
|
2012-06-10 12:24:41 +02:00
|
|
|
|
(defun mu4e-view-search-narrow ()
|
2012-05-06 15:57:11 +02:00
|
|
|
|
"Run `mu4e-headers-search-narrow' in the headers buffer."
|
2012-06-10 12:24:41 +02:00
|
|
|
|
(interactive)
|
2016-01-10 12:08:18 +01:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(call-interactively 'mu4e-headers-search-narrow)))
|
2012-05-06 15:57:11 +02:00
|
|
|
|
|
2012-06-10 12:24:41 +02:00
|
|
|
|
(defun mu4e-view-search-edit ()
|
2012-05-06 15:57:11 +02:00
|
|
|
|
"Run `mu4e-headers-search-edit' in the headers buffer."
|
2012-06-10 12:24:41 +02:00
|
|
|
|
(interactive)
|
2012-09-30 19:12:24 +02:00
|
|
|
|
(mu4e~view-in-headers-context (mu4e-headers-search-edit)))
|
2012-04-26 21:42:53 +02:00
|
|
|
|
|
2014-03-24 09:17:25 +01:00
|
|
|
|
(defun mu4e-mark-region-code ()
|
2014-03-24 09:22:39 +01:00
|
|
|
|
"Highlight region marked with `message-mark-inserted-region'.
|
|
|
|
|
Add this function to `mu4e-view-mode-hook' to enable this feature."
|
2014-03-24 09:17:25 +01:00
|
|
|
|
(require 'message)
|
|
|
|
|
(let (beg end ov-beg ov-end ov-inv)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (re-search-forward
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(concat "^" message-mark-insert-begin) nil t)
|
|
|
|
|
(setq ov-beg (match-beginning 0)
|
|
|
|
|
ov-end (match-end 0)
|
|
|
|
|
ov-inv (make-overlay ov-beg ov-end)
|
|
|
|
|
beg ov-end)
|
|
|
|
|
(overlay-put ov-inv 'invisible t)
|
|
|
|
|
(when (re-search-forward
|
|
|
|
|
(concat "^" message-mark-insert-end) nil t)
|
|
|
|
|
(setq ov-beg (match-beginning 0)
|
|
|
|
|
ov-end (match-end 0)
|
|
|
|
|
ov-inv (make-overlay ov-beg ov-end)
|
|
|
|
|
end ov-beg)
|
|
|
|
|
(overlay-put ov-inv 'invisible t))
|
|
|
|
|
(when (and beg end)
|
|
|
|
|
(let ((ov (make-overlay beg end)))
|
|
|
|
|
(overlay-put ov 'face 'mu4e-region-code))
|
|
|
|
|
(setq beg nil end nil))))))
|
2014-03-24 09:17:25 +01:00
|
|
|
|
|
2014-06-27 07:28:33 +02:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
;; Wash functions
|
|
|
|
|
(defun mu4e-view-fill-long-lines ()
|
|
|
|
|
"Fill lines that are wider than the window width or `fill-column'."
|
|
|
|
|
(interactive)
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(with-current-buffer (mu4e-get-view-buffer)
|
2014-06-27 07:28:33 +02:00
|
|
|
|
(save-excursion
|
|
|
|
|
(let ((inhibit-read-only t)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(width (window-width (get-buffer-window (current-buffer)))))
|
|
|
|
|
(save-restriction
|
|
|
|
|
(message-goto-body)
|
|
|
|
|
(while (not (eobp))
|
|
|
|
|
(end-of-line)
|
|
|
|
|
(when (>= (current-column) (min fill-column width))
|
|
|
|
|
(narrow-to-region (min (1+ (point)) (point-max))
|
|
|
|
|
(point-at-bol))
|
|
|
|
|
(let ((goback (point-marker)))
|
|
|
|
|
(fill-paragraph nil)
|
|
|
|
|
(goto-char (marker-position goback)))
|
|
|
|
|
(widen))
|
|
|
|
|
(forward-line 1)))))))
|
2014-06-27 07:28:33 +02:00
|
|
|
|
|
2012-04-12 08:02:25 +02:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
;; attachment handling
|
2012-06-07 15:24:58 +02:00
|
|
|
|
(defun mu4e~view-get-attach-num (prompt msg &optional multi)
|
2012-04-20 16:53:41 +02:00
|
|
|
|
"Ask the user with PROMPT for an attachment number for MSG, and
|
2012-11-10 14:01:17 +01:00
|
|
|
|
ensure it is valid. The number is [1..n] for attachments
|
|
|
|
|
\[0..(n-1)] in the message. If MULTI is nil, return the number for
|
|
|
|
|
the attachment; otherwise (MULTI is non-nil), accept ranges of
|
|
|
|
|
attachment numbers, as per `mu4e-split-ranges-to-numbers', and
|
|
|
|
|
return the corresponding string."
|
2012-06-07 15:24:58 +02:00
|
|
|
|
(let* ((count (hash-table-count mu4e~view-attach-map)) (def))
|
2012-07-10 11:11:06 +02:00
|
|
|
|
(when (zerop count) (mu4e-error "No attachments for this message"))
|
2012-06-07 15:24:58 +02:00
|
|
|
|
(if (not multi)
|
|
|
|
|
(if (= count 1)
|
|
|
|
|
(read-number (mu4e-format "%s: " prompt) 1)
|
|
|
|
|
(read-number (mu4e-format "%s (1-%d): " prompt count)))
|
|
|
|
|
(progn
|
|
|
|
|
(setq def (if (= count 1) "1" (format "1-%d" count)))
|
2012-09-20 20:25:55 +02:00
|
|
|
|
(read-string (mu4e-format "%s (default %s): " prompt def)
|
|
|
|
|
nil nil def)))))
|
2012-06-10 10:33:31 +02:00
|
|
|
|
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(defun mu4e~view-get-attach (msg attnum)
|
2012-04-20 16:53:41 +02:00
|
|
|
|
"Return the attachment plist in MSG corresponding to attachment
|
|
|
|
|
number ATTNUM."
|
2012-09-02 23:35:39 +02:00
|
|
|
|
(let* ((partid (gethash attnum mu4e~view-attach-map))
|
|
|
|
|
(attach
|
|
|
|
|
(find-if
|
|
|
|
|
(lambda (part)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(eq (mu4e-message-part-field part :index) partid))
|
|
|
|
|
(mu4e-message-field msg :parts))))
|
2012-09-02 23:35:39 +02:00
|
|
|
|
(or attach (mu4e-error "Not a valid attachment"))))
|
2012-05-13 12:10:56 +02:00
|
|
|
|
|
2012-04-20 16:53:41 +02:00
|
|
|
|
|
2013-12-19 08:51:12 +01:00
|
|
|
|
(defun mu4e~view-request-attachment-path (fname path)
|
|
|
|
|
"Ask the user where to save FNAME (default is PATH/FNAME)."
|
|
|
|
|
(let ((fpath (expand-file-name
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(read-file-name
|
|
|
|
|
(mu4e-format "Save as ")
|
|
|
|
|
path nil nil fname) path)))
|
2013-12-19 08:51:12 +01:00
|
|
|
|
(if (file-directory-p fpath)
|
|
|
|
|
(expand-file-name fname fpath)
|
|
|
|
|
fpath)))
|
|
|
|
|
|
2015-02-11 15:46:01 +01:00
|
|
|
|
(defun mu4e~view-request-attachments-dir (path)
|
|
|
|
|
"Ask the user where to save multiple attachments (default is PATH)."
|
|
|
|
|
(let ((fpath (expand-file-name
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(read-directory-name
|
|
|
|
|
(mu4e-format "Save in directory ")
|
|
|
|
|
path nil nil nil) path)))
|
2015-02-11 15:46:01 +01:00
|
|
|
|
(if (file-directory-p fpath)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
fpath)))
|
2015-02-11 15:46:01 +01:00
|
|
|
|
|
2012-06-07 15:24:58 +02:00
|
|
|
|
(defun mu4e-view-save-attachment-single (&optional msg attnum)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Save attachment number ATTNUM from MSG.
|
|
|
|
|
If MSG is nil use the message returned by `message-at-point'.
|
|
|
|
|
If ATTNUM is nil ask for the attachment number."
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(interactive)
|
2012-04-19 20:01:38 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
2012-04-20 16:53:41 +02:00
|
|
|
|
(attnum (or attnum
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(mu4e~view-get-attach-num "Attachment to save" msg)))
|
|
|
|
|
(att (mu4e~view-get-attach msg attnum))
|
2012-09-19 10:36:02 +02:00
|
|
|
|
(fname (plist-get att :name))
|
2012-10-03 11:53:16 +02:00
|
|
|
|
(mtype (plist-get att :mime-type))
|
|
|
|
|
(path (concat
|
|
|
|
|
(mu4e~get-attachment-dir fname mtype) "/"))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(index (plist-get att :index))
|
2013-12-01 19:39:21 +01:00
|
|
|
|
(retry t) (fpath))
|
2011-09-19 23:20:59 +02:00
|
|
|
|
(while retry
|
2013-12-19 08:51:12 +01:00
|
|
|
|
(setq fpath (mu4e~view-request-attachment-path fname path))
|
2011-09-19 23:20:59 +02:00
|
|
|
|
(setq retry
|
2013-10-12 13:12:13 +02:00
|
|
|
|
(and (file-exists-p fpath)
|
|
|
|
|
(not (y-or-n-p (mu4e-format "Overwrite '%s'?" fpath))))))
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(mu4e~proc-extract
|
2016-01-10 12:08:18 +01:00
|
|
|
|
'save (mu4e-message-field msg :docid)
|
|
|
|
|
index mu4e-decryption-policy fpath)))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
|
2013-12-10 14:54:42 +01:00
|
|
|
|
|
2012-06-07 15:24:58 +02:00
|
|
|
|
(defun mu4e-view-save-attachment-multi (&optional msg)
|
|
|
|
|
"Offer to save multiple email attachments from the current message.
|
|
|
|
|
Default is to save all messages, [1..n], where n is the number of
|
|
|
|
|
attachments. You can type multiple values separated by space, e.g.
|
|
|
|
|
1 3-6 8
|
|
|
|
|
will save attachments 1,3,4,5,6 and 8.
|
|
|
|
|
|
|
|
|
|
Furthermore, there is a shortcut \"a\" which so means all
|
|
|
|
|
attachments, but as this is the default, you may not need it."
|
|
|
|
|
(interactive)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(attachstr (mu4e~view-get-attach-num
|
|
|
|
|
"Attachment number range (or 'a' for 'all')" msg t))
|
|
|
|
|
(count (hash-table-count mu4e~view-attach-map))
|
|
|
|
|
(attachnums (mu4e-split-ranges-to-numbers attachstr count)))
|
2015-02-11 15:46:01 +01:00
|
|
|
|
(if mu4e-save-multiple-attachments-without-asking
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(let* ((path (concat (mu4e~get-attachment-dir) "/"))
|
|
|
|
|
(attachdir (mu4e~view-request-attachments-dir path)))
|
|
|
|
|
(dolist (num attachnums)
|
|
|
|
|
(let* ((att (mu4e~view-get-attach msg num))
|
|
|
|
|
(fname (plist-get att :name))
|
|
|
|
|
(index (plist-get att :index))
|
|
|
|
|
(retry t)
|
2015-10-09 01:02:58 +02:00
|
|
|
|
fpath)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(while retry
|
|
|
|
|
(setq fpath (expand-file-name (concat attachdir fname) path))
|
|
|
|
|
(setq retry
|
|
|
|
|
(and (file-exists-p fpath)
|
2016-01-10 12:08:18 +01:00
|
|
|
|
(not (y-or-n-p
|
|
|
|
|
(mu4e-format "Overwrite '%s'?" fpath))))))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(mu4e~proc-extract
|
2016-01-10 12:08:18 +01:00
|
|
|
|
'save (mu4e-message-field msg :docid)
|
|
|
|
|
index mu4e-decryption-policy fpath))))
|
2015-02-11 15:46:01 +01:00
|
|
|
|
(dolist (num attachnums)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(mu4e-view-save-attachment-single msg num)))))
|
2012-06-07 15:24:58 +02:00
|
|
|
|
|
2017-10-09 21:11:10 +02:00
|
|
|
|
(defalias #'mu4e-view-save-attachment #'mu4e-view-save-attachment-multi)
|
2012-04-20 16:53:41 +02:00
|
|
|
|
|
|
|
|
|
(defun mu4e-view-open-attachment (&optional msg attnum)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Open attachment number ATTNUM from MSG.
|
2016-11-12 13:00:11 +01:00
|
|
|
|
If MSG is nil use the message returned by `message-at-point'. If
|
|
|
|
|
ATTNUM is nil ask for the attachment number."
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(interactive)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
2012-04-20 16:53:41 +02:00
|
|
|
|
(attnum (or attnum
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(mu4e~view-get-attach-num "Attachment to open" msg)))
|
2012-09-02 23:35:39 +02:00
|
|
|
|
(att (or (mu4e~view-get-attach msg attnum)))
|
2012-09-21 11:35:53 +02:00
|
|
|
|
(index (plist-get att :index))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(docid (mu4e-message-field msg :docid))
|
2012-09-21 11:35:53 +02:00
|
|
|
|
(mimetype (plist-get att :mime-type)))
|
|
|
|
|
(if (and mimetype (string= mimetype "message/rfc822"))
|
2012-09-23 10:42:00 +02:00
|
|
|
|
;; special handling for message-attachments; we open them in mu4e. we also
|
|
|
|
|
;; send the docid as parameter (4th arg); we'll get this back from the
|
|
|
|
|
;; server, and use it to determine the parent message (ie., the current
|
|
|
|
|
;; message) when showing the embedded message/rfc822, and return to the
|
2015-12-09 22:40:52 +01:00
|
|
|
|
;; current message when quitting that one.
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(mu4e~view-temp-action docid index "mu4e" docid)
|
2012-09-21 11:35:53 +02:00
|
|
|
|
;; otherwise, open with the default program (handled in mu-server
|
2014-10-10 17:34:57 +02:00
|
|
|
|
(mu4e~proc-extract 'open docid index mu4e-decryption-policy))))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
|
2012-04-20 16:53:41 +02:00
|
|
|
|
|
2012-06-06 07:16:16 +02:00
|
|
|
|
(defun mu4e~view-temp-action (docid index what &optional param)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Open attachment INDEX for message with DOCID, and invoke ACTION."
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(interactive)
|
2014-10-10 17:34:57 +02:00
|
|
|
|
(mu4e~proc-extract 'temp docid index mu4e-decryption-policy nil what param ))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
|
2012-09-30 19:12:24 +02:00
|
|
|
|
(defvar mu4e~view-open-with-hist nil "History list for the open-with argument.")
|
2012-05-02 16:26:57 +02:00
|
|
|
|
|
2012-04-20 16:53:41 +02:00
|
|
|
|
(defun mu4e-view-open-attachment-with (msg attachnum &optional cmd)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Open MSG's attachment ATTACHNUM with CMD.
|
|
|
|
|
If CMD is nil, ask user for it."
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(let* ((att (mu4e~view-get-attach msg attachnum))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(ext (file-name-extension (plist-get att :name)))
|
|
|
|
|
(cmd (or cmd
|
|
|
|
|
(read-string
|
2012-05-02 16:26:57 +02:00
|
|
|
|
(mu4e-format "Shell command to open it with: ")
|
2016-12-19 08:51:26 +01:00
|
|
|
|
(assoc-default ext mu4e-view-attachment-assoc)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
'mu4e~view-open-with-hist)))
|
|
|
|
|
(index (plist-get att :index)))
|
2012-10-03 20:51:26 +02:00
|
|
|
|
(mu4e~view-temp-action
|
|
|
|
|
(mu4e-message-field msg :docid) index "open-with" cmd)))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
|
2012-05-02 16:26:57 +02:00
|
|
|
|
(defvar mu4e~view-pipe-hist nil
|
|
|
|
|
"History list for the pipe argument.")
|
|
|
|
|
|
2012-04-20 16:53:41 +02:00
|
|
|
|
(defun mu4e-view-pipe-attachment (msg attachnum &optional pipecmd)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Feed MSG's attachment ATTACHNUM through pipe PIPECMD.
|
|
|
|
|
If PIPECMD is nil, ask user for it."
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(let* ((att (mu4e~view-get-attach msg attachnum))
|
2012-05-02 16:26:57 +02:00
|
|
|
|
(pipecmd (or pipecmd
|
|
|
|
|
(read-string
|
|
|
|
|
(mu4e-format "Pipe: ")
|
|
|
|
|
nil
|
|
|
|
|
'mu4e~view-pipe-hist)))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(index (plist-get att :index)))
|
2012-10-03 20:51:26 +02:00
|
|
|
|
(mu4e~view-temp-action
|
|
|
|
|
(mu4e-message-field msg :docid) index "pipe" pipecmd)))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
|
2012-04-24 17:48:07 +02:00
|
|
|
|
|
2012-04-20 16:53:41 +02:00
|
|
|
|
(defun mu4e-view-open-attachment-emacs (msg attachnum)
|
|
|
|
|
"Open MSG's attachment ATTACHNUM in the current emacs instance."
|
2012-04-23 19:35:14 +02:00
|
|
|
|
(let* ((att (mu4e~view-get-attach msg attachnum))
|
2012-04-15 13:22:43 +02:00
|
|
|
|
(index (plist-get att :index)))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(mu4e~view-temp-action (mu4e-message-field msg :docid) index "emacs")))
|
2011-09-19 23:20:59 +02:00
|
|
|
|
|
2015-02-03 18:14:58 +01:00
|
|
|
|
(defun mu4e-view-import-attachment-diary (msg attachnum)
|
|
|
|
|
"Open MSG's attachment ATTACHNUM in the current emacs instance."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let* ((att (mu4e~view-get-attach msg attachnum))
|
|
|
|
|
(index (plist-get att :index)))
|
|
|
|
|
(mu4e~view-temp-action (mu4e-message-field msg :docid) index "diary")))
|
2012-04-23 18:07:20 +02:00
|
|
|
|
|
2012-04-20 17:23:47 +02:00
|
|
|
|
(defun mu4e-view-attachment-action (&optional msg)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Ask user what to do with attachments in MSG
|
|
|
|
|
If MSG is nil use the message returned by `message-at-point'.
|
|
|
|
|
The actions are specified in `mu4e-view-attachment-actions'."
|
2012-04-12 08:02:25 +02:00
|
|
|
|
(interactive)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(actionfunc (mu4e-read-option
|
|
|
|
|
"Action on attachment: "
|
|
|
|
|
mu4e-view-attachment-actions))
|
|
|
|
|
(multi (eq actionfunc 'mu4e-view-save-attachment-multi))
|
|
|
|
|
(attnum (unless multi
|
|
|
|
|
(mu4e~view-get-attach-num "Which attachment" msg multi))))
|
2016-12-19 08:51:26 +01:00
|
|
|
|
(cond ((and actionfunc attnum)
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(funcall actionfunc msg attnum))
|
|
|
|
|
((and actionfunc multi)
|
|
|
|
|
(funcall actionfunc msg)))))
|
2012-04-18 18:10:48 +02:00
|
|
|
|
|
2012-04-15 13:22:43 +02:00
|
|
|
|
;; handler-function to handle the response we get from the server when we
|
|
|
|
|
;; want to do something with one of the attachments.
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(defun mu4e~view-temp-handler (path what docid param)
|
2012-04-15 13:22:43 +02:00
|
|
|
|
"Handler function for doing things with temp files (ie.,
|
2012-04-23 19:35:14 +02:00
|
|
|
|
attachments) in response to a (mu4e~proc-extract 'temp ... )."
|
2012-05-03 06:53:41 +02:00
|
|
|
|
(cond
|
|
|
|
|
((string= what "open-with")
|
|
|
|
|
;; 'param' will be the program to open-with
|
|
|
|
|
(start-process "*mu4e-open-with-proc*" "*mu4e-open-with*" param path))
|
|
|
|
|
((string= what "pipe")
|
|
|
|
|
;; 'param' will be the pipe command, path the infile for this
|
2012-06-06 07:16:16 +02:00
|
|
|
|
(mu4e-process-file-through-pipe path param))
|
2016-01-10 12:08:18 +01:00
|
|
|
|
;; if it's mu4e, it's some embedded message; 'param' may contain the docid
|
|
|
|
|
;; of the parent message.
|
2012-09-21 11:35:53 +02:00
|
|
|
|
((string= what "mu4e")
|
2012-09-23 10:42:00 +02:00
|
|
|
|
;; remember the mapping path->docid, which maps the path of the embedded
|
|
|
|
|
;; message to the docid of its parent
|
|
|
|
|
(puthash path docid mu4e~path-parent-docid-map)
|
2013-01-26 09:57:14 +01:00
|
|
|
|
(mu4e~proc-view-path path mu4e-view-show-images mu4e-decryption-policy))
|
2012-05-03 06:53:41 +02:00
|
|
|
|
((string= what "emacs")
|
|
|
|
|
(find-file path)
|
|
|
|
|
;; make the buffer read-only since it usually does not make
|
2012-09-23 10:42:00 +02:00
|
|
|
|
;; sense to edit the temp buffer; use C-x C-q if you insist...
|
2012-05-03 06:53:41 +02:00
|
|
|
|
(setq buffer-read-only t))
|
2015-02-03 18:14:58 +01:00
|
|
|
|
((string= what "diary")
|
|
|
|
|
(icalendar-import-file path diary-file))
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(t (mu4e-error "Unsupported action %S" what))))
|
2012-04-12 08:02:25 +02:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2012-10-01 20:17:05 +02:00
|
|
|
|
|
|
|
|
|
(defun mu4e-view-mark-custom ()
|
2012-06-14 20:54:24 +02:00
|
|
|
|
"Run some custom mark function."
|
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(mu4e-headers-mark-custom)))
|
2012-04-23 18:07:20 +02:00
|
|
|
|
|
2012-10-01 20:17:05 +02:00
|
|
|
|
(defun mu4e~view-split-view-p ()
|
2012-04-09 10:51:24 +02:00
|
|
|
|
"Return t if we're in split-view, nil otherwise."
|
|
|
|
|
(member mu4e-split-view '(horizontal vertical)))
|
2012-10-03 11:53:16 +02:00
|
|
|
|
|
2012-10-17 11:07:57 +02:00
|
|
|
|
(defun mu4e-view-scroll-up-or-next ()
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Scroll-up the current message.
|
|
|
|
|
If `mu4e-view-scroll-to-next' is non-nil, and we can't scroll-up
|
|
|
|
|
anymore, go the next message."
|
2012-10-17 11:07:57 +02:00
|
|
|
|
(interactive)
|
2012-10-17 11:26:11 +02:00
|
|
|
|
(condition-case nil
|
|
|
|
|
(scroll-up)
|
|
|
|
|
(error
|
|
|
|
|
(when mu4e-view-scroll-to-next
|
|
|
|
|
(mu4e-view-headers-next)))))
|
2012-10-22 22:52:50 +02:00
|
|
|
|
|
2012-11-11 14:27:10 +01:00
|
|
|
|
(defun mu4e-scroll-up ()
|
|
|
|
|
"Scroll text of selected window up one line."
|
|
|
|
|
(interactive)
|
|
|
|
|
(scroll-up 1))
|
|
|
|
|
|
|
|
|
|
(defun mu4e-scroll-down ()
|
|
|
|
|
"Scroll text of selected window down one line."
|
|
|
|
|
(interactive)
|
|
|
|
|
(scroll-down 1))
|
|
|
|
|
|
2012-04-09 10:51:24 +02:00
|
|
|
|
(defun mu4e-view-unmark-all ()
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"If we're in split-view, unmark all messages.
|
|
|
|
|
Otherwise, warn user that unmarking only works in the header
|
|
|
|
|
list."
|
2011-09-18 13:39:36 +02:00
|
|
|
|
(interactive)
|
2012-10-01 20:17:05 +02:00
|
|
|
|
(if (mu4e~view-split-view-p)
|
2012-05-20 18:59:57 +02:00
|
|
|
|
(mu4e~view-in-headers-context (mu4e-mark-unmark-all))
|
2012-05-02 16:26:57 +02:00
|
|
|
|
(mu4e-message "Unmarking needs to be done in the header list view")))
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2012-04-09 10:51:24 +02:00
|
|
|
|
(defun mu4e-view-unmark ()
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"If we're in split-view, unmark message at point.
|
|
|
|
|
Otherwise, warn user that unmarking only works in the header
|
|
|
|
|
list."
|
2012-04-09 10:51:24 +02:00
|
|
|
|
(interactive)
|
2012-10-01 20:17:05 +02:00
|
|
|
|
(if (mu4e~view-split-view-p)
|
2012-09-27 20:53:21 +02:00
|
|
|
|
(mu4e-view-mark-for-unmark)
|
2012-05-02 16:26:57 +02:00
|
|
|
|
(mu4e-message "Unmarking needs to be done in the header list view")))
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2013-06-24 21:43:03 +02:00
|
|
|
|
|
2012-09-27 20:53:21 +02:00
|
|
|
|
(defmacro mu4e~view-defun-mark-for (mark)
|
|
|
|
|
"Define a function mu4e-view-mark-for-MARK."
|
2012-11-11 14:39:02 +01:00
|
|
|
|
(let ((funcname (intern (format "mu4e-view-mark-for-%s" mark)))
|
|
|
|
|
(docstring (format "Mark the current message for %s." mark)))
|
|
|
|
|
`(progn
|
|
|
|
|
(defun ,funcname () ,docstring
|
|
|
|
|
(interactive)
|
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(mu4e-headers-mark-and-next ',mark)))
|
|
|
|
|
(put ',funcname 'definition-name ',mark))))
|
2012-09-27 20:53:21 +02:00
|
|
|
|
|
|
|
|
|
(mu4e~view-defun-mark-for move)
|
|
|
|
|
(mu4e~view-defun-mark-for trash)
|
|
|
|
|
(mu4e~view-defun-mark-for refile)
|
|
|
|
|
(mu4e~view-defun-mark-for delete)
|
|
|
|
|
(mu4e~view-defun-mark-for flag)
|
|
|
|
|
(mu4e~view-defun-mark-for unflag)
|
|
|
|
|
(mu4e~view-defun-mark-for unmark)
|
2013-02-11 22:49:39 +01:00
|
|
|
|
(mu4e~view-defun-mark-for something)
|
2014-11-30 10:49:41 +01:00
|
|
|
|
(mu4e~view-defun-mark-for read)
|
|
|
|
|
(mu4e~view-defun-mark-for unread)
|
2017-06-17 10:59:16 +02:00
|
|
|
|
(mu4e~view-defun-mark-for untrash)
|
2012-06-14 18:10:02 +02:00
|
|
|
|
|
2013-05-04 13:15:35 +02:00
|
|
|
|
(defun mu4e-view-marked-execute ()
|
2012-05-06 15:57:11 +02:00
|
|
|
|
"Execute the marks."
|
2011-09-18 13:39:36 +02:00
|
|
|
|
(interactive)
|
2012-05-06 15:57:11 +02:00
|
|
|
|
(mu4e~view-in-headers-context
|
|
|
|
|
(mu4e-mark-execute-all)))
|
2011-09-18 13:39:36 +02:00
|
|
|
|
|
2013-11-14 03:08:11 +01:00
|
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
;; URL handling
|
|
|
|
|
(defun mu4e~view-get-urls-num (prompt &optional multi)
|
|
|
|
|
"Ask the user with PROMPT for an URL number for MSG, and ensure
|
|
|
|
|
it is valid. The number is [1..n] for URLs \[0..(n-1)] in the
|
|
|
|
|
message. If MULTI is nil, return the number for the URL;
|
|
|
|
|
otherwise (MULTI is non-nil), accept ranges of URL numbers, as
|
|
|
|
|
per `mu4e-split-ranges-to-numbers', and return the corresponding
|
|
|
|
|
string."
|
|
|
|
|
(let* ((count (hash-table-count mu4e~view-link-map)) (def))
|
|
|
|
|
(when (zerop count) (mu4e-error "No links for this message"))
|
|
|
|
|
(if (not multi)
|
|
|
|
|
(if (= count 1)
|
|
|
|
|
(read-number (mu4e-format "%s: " prompt) 1)
|
|
|
|
|
(read-number (mu4e-format "%s (1-%d): " prompt count)))
|
|
|
|
|
(progn
|
|
|
|
|
(setq def (if (= count 1) "1" (format "1-%d" count)))
|
|
|
|
|
(read-string (mu4e-format "%s (default %s): " prompt def)
|
|
|
|
|
nil nil def)))))
|
|
|
|
|
|
|
|
|
|
(defun mu4e-view-go-to-url (&optional multi)
|
2015-03-21 14:22:12 +01:00
|
|
|
|
"Offer to go to url(s). If MULTI (prefix-argument) is nil, go to
|
|
|
|
|
a single one, otherwise, offer to go to a range of urls."
|
|
|
|
|
(interactive "P")
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(mu4e~view-handle-urls "URL to visit"
|
2017-04-08 14:21:06 +02:00
|
|
|
|
multi
|
|
|
|
|
(lambda (url) (mu4e~view-browse-url-from-binding url))))
|
2015-03-21 14:22:12 +01:00
|
|
|
|
|
|
|
|
|
(defun mu4e-view-save-url (&optional multi)
|
|
|
|
|
"Offer to save urls(s) to the kill-ring. If
|
|
|
|
|
MULTI (prefix-argument) is nil, save a single one, otherwise, offer
|
|
|
|
|
to save a range of URLs."
|
|
|
|
|
(interactive "P")
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(mu4e~view-handle-urls "URL to save" multi
|
2015-03-21 14:22:12 +01:00
|
|
|
|
(lambda (url)
|
|
|
|
|
(kill-new url)
|
|
|
|
|
(mu4e-message "Saved %s to the kill-ring" url))))
|
|
|
|
|
|
2016-01-10 12:06:10 +01:00
|
|
|
|
(defun mu4e-view-fetch-url (&optional multi)
|
|
|
|
|
"Offer to fetch (download) urls(s). If MULTI (prefix-argument) is nil,
|
|
|
|
|
download a single one, otherwise, offer to fetch a range of
|
|
|
|
|
URLs. The urls are fetched to `mu4e-attachment-dir'."
|
|
|
|
|
(interactive "P")
|
|
|
|
|
(mu4e~view-handle-urls "URL to fetch" multi
|
|
|
|
|
(lambda (url)
|
|
|
|
|
(let ((target (concat (mu4e~get-attachment-dir url) "/"
|
|
|
|
|
(file-name-nondirectory url))))
|
|
|
|
|
(url-copy-file url target)
|
|
|
|
|
(mu4e-message "Fetched %s -> %s" url target)))))
|
|
|
|
|
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(defun mu4e~view-handle-urls (prompt multi urlfunc)
|
2015-03-21 14:22:12 +01:00
|
|
|
|
"If MULTI is nil, apply URLFUNC to a single uri, otherwise, apply
|
2015-03-22 10:49:28 +01:00
|
|
|
|
it to a range of uris. PROMPT is the query to present to the user."
|
2013-11-14 03:08:11 +01:00
|
|
|
|
(interactive "P")
|
|
|
|
|
(if multi
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(mu4e~view-handle-multi-urls prompt urlfunc)
|
|
|
|
|
(mu4e~view-handle-single-url prompt urlfunc)))
|
2013-11-14 03:08:11 +01:00
|
|
|
|
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(defun mu4e~view-handle-single-url (prompt urlfunc &optional num)
|
|
|
|
|
"Apply URLFUNC to url NUM in the current message, prompting the
|
|
|
|
|
user with PROMPT."
|
2013-11-14 03:08:11 +01:00
|
|
|
|
(interactive)
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(let* ((num (or num (mu4e~view-get-urls-num prompt)))
|
2017-02-12 10:11:16 +01:00
|
|
|
|
(url (gethash num mu4e~view-link-map)))
|
2012-09-06 09:33:53 +02:00
|
|
|
|
(unless url (mu4e-warn "Invalid number for URL"))
|
2015-03-21 14:22:12 +01:00
|
|
|
|
(funcall urlfunc url)))
|
2012-06-12 19:58:32 +02:00
|
|
|
|
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(defun mu4e~view-handle-multi-urls (prompt urlfunc)
|
|
|
|
|
"Apply URLFUNC to a a range of urls in the current message,
|
|
|
|
|
prompting the user with PROMPT.
|
|
|
|
|
|
2015-03-21 14:22:12 +01:00
|
|
|
|
Default is to aplly it to all URLs, [1..n], where n is the number
|
|
|
|
|
of urls. You can type multiple values separated by space, e.g. 1
|
|
|
|
|
3-6 8 will visit urls 1,3,4,5,6 and 8.
|
2013-11-14 03:08:11 +01:00
|
|
|
|
|
2015-03-21 14:22:12 +01:00
|
|
|
|
Furthermore, there is a shortcut \"a\" which means all urls, but as
|
|
|
|
|
this is the default, you may not need it."
|
2013-11-14 03:08:11 +01:00
|
|
|
|
(interactive)
|
|
|
|
|
(let* ((linkstr (mu4e~view-get-urls-num
|
|
|
|
|
"URL number range (or 'a' for 'all')" t))
|
|
|
|
|
(count (hash-table-count mu4e~view-link-map))
|
|
|
|
|
(linknums (mu4e-split-ranges-to-numbers linkstr count)))
|
|
|
|
|
(dolist (num linknums)
|
2015-03-22 10:49:28 +01:00
|
|
|
|
(mu4e~view-handle-single-url prompt urlfunc num))))
|
2015-03-21 14:22:12 +01:00
|
|
|
|
|
|
|
|
|
(defun mu4e-view-for-each-uri (func)
|
|
|
|
|
"Execute FUNC (which receives a uri) for each uri in the current
|
|
|
|
|
message."
|
|
|
|
|
(maphash (lambda (num uri) (funcall func uri)) mu4e~view-link-map))
|
|
|
|
|
|
2013-11-14 03:08:11 +01:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
2012-09-23 10:42:00 +02:00
|
|
|
|
(defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*"
|
2012-04-23 18:07:20 +02:00
|
|
|
|
"*internal* Name for the raw message view buffer")
|
|
|
|
|
|
2012-04-18 18:10:48 +02:00
|
|
|
|
(defun mu4e-view-raw-message ()
|
|
|
|
|
"Display the raw contents of message at point in a new buffer."
|
2011-11-20 09:31:38 +01:00
|
|
|
|
(interactive)
|
2012-10-28 18:30:27 +01:00
|
|
|
|
(let ((path (mu4e-message-field-at-point :path))
|
|
|
|
|
(buf (get-buffer-create mu4e~view-raw-buffer-name)))
|
2012-04-18 18:10:48 +02:00
|
|
|
|
(unless (and path (file-readable-p path))
|
2012-07-10 11:11:06 +02:00
|
|
|
|
(mu4e-error "Not a readable file: %S" path))
|
2012-04-18 18:10:48 +02:00
|
|
|
|
(with-current-buffer buf
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert-file-contents path)
|
|
|
|
|
(view-mode)
|
|
|
|
|
(goto-char (point-min))))
|
|
|
|
|
(switch-to-buffer buf)))
|
2012-04-19 18:00:09 +02:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(defun mu4e-view-pipe (cmd)
|
2012-04-23 19:35:14 +02:00
|
|
|
|
"Pipe the message at point through shell command CMD, and display
|
|
|
|
|
the results."
|
2011-11-27 15:23:14 +01:00
|
|
|
|
(interactive "sShell command: ")
|
2012-09-26 15:47:42 +02:00
|
|
|
|
(let ((path (mu4e-message-field (mu4e-message-at-point) :path)))
|
2012-04-18 18:10:48 +02:00
|
|
|
|
(mu4e-process-file-through-pipe path cmd)))
|
|
|
|
|
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(defconst mu4e~verify-buffer-name " *mu4e-verify*")
|
|
|
|
|
|
2012-07-24 22:39:47 +02:00
|
|
|
|
(defun mu4e-view-verify-msg-popup (&optional msg)
|
2012-07-19 10:42:38 +02:00
|
|
|
|
"Pop-up a little signature verification window for (optional) MSG
|
|
|
|
|
or message-at-point."
|
|
|
|
|
(interactive)
|
2012-09-26 15:47:42 +02:00
|
|
|
|
(let* ((msg (or msg (mu4e-message-at-point)))
|
|
|
|
|
(path (mu4e-message-field msg :path))
|
2014-10-19 02:53:28 +02:00
|
|
|
|
(cmd (format "%s verify --verbose %s %s"
|
2012-07-19 10:42:38 +02:00
|
|
|
|
mu4e-mu-binary
|
2014-10-19 02:53:28 +02:00
|
|
|
|
(shell-quote-argument path)
|
|
|
|
|
(if mu4e-decryption-policy
|
|
|
|
|
"--decrypt --use-agent"
|
|
|
|
|
"")))
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(output (shell-command-to-string cmd))
|
|
|
|
|
;; create a new one
|
|
|
|
|
(buf (get-buffer-create mu4e~verify-buffer-name))
|
|
|
|
|
(win (or (get-buffer-window buf)
|
|
|
|
|
(split-window-vertically (- (window-height) 6)))))
|
|
|
|
|
(with-selected-window win
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
;; (set-window-dedicated-p win t)
|
|
|
|
|
(switch-to-buffer buf)
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert output)
|
|
|
|
|
(goto-char (point-min))
|
2012-09-19 10:36:02 +02:00
|
|
|
|
(local-set-key "q" 'kill-buffer-and-window))
|
|
|
|
|
(setq buffer-read-only t))
|
2012-07-19 10:42:38 +02:00
|
|
|
|
(select-window win)))
|
|
|
|
|
|
|
|
|
|
|
2012-06-15 10:01:40 +02:00
|
|
|
|
(defun mu4e~view-quit-buffer ()
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Quit the mu4e-view buffer.
|
|
|
|
|
This is a rather complex function, to ensure we don't disturb
|
|
|
|
|
other windows."
|
2012-06-15 10:01:40 +02:00
|
|
|
|
(interactive)
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(if (eq mu4e-split-view 'single-window)
|
|
|
|
|
(when (buffer-live-p (mu4e-get-view-buffer))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(kill-buffer (mu4e-get-view-buffer)))
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(unless (eq major-mode 'mu4e-view-mode)
|
|
|
|
|
(mu4e-error "Must be in mu4e-view-mode (%S)" major-mode))
|
|
|
|
|
(let ((curbuf (current-buffer))
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(curwin (selected-window))
|
|
|
|
|
(headers-win))
|
2017-05-18 03:42:52 +02:00
|
|
|
|
(walk-windows
|
2017-08-27 16:33:37 +02:00
|
|
|
|
(lambda (win)
|
|
|
|
|
;; check whether the headers buffer window is visible
|
|
|
|
|
(when (eq (mu4e-get-headers-buffer) (window-buffer win))
|
|
|
|
|
(setq headers-win win))
|
|
|
|
|
;; and kill any _other_ (non-selected) window that shows the current
|
|
|
|
|
;; buffer
|
|
|
|
|
(when
|
|
|
|
|
(and
|
|
|
|
|
(eq curbuf (window-buffer win)) ;; does win show curbuf?
|
|
|
|
|
(not (eq curwin win)) ;; but it's not the curwin?
|
|
|
|
|
(not (one-window-p))) ;; and not the last one on the frame?
|
|
|
|
|
(delete-window win)))) ;; delete it!
|
2017-05-18 03:42:52 +02:00
|
|
|
|
;; now, all *other* windows should be gone.
|
|
|
|
|
;; if the headers view is also visible, kill ourselves + window; otherwise
|
|
|
|
|
;; switch to the headers view
|
|
|
|
|
(if (window-live-p headers-win)
|
2017-08-27 16:33:37 +02:00
|
|
|
|
;; headers are visible
|
|
|
|
|
(progn
|
|
|
|
|
(kill-buffer-and-window) ;; kill the view win
|
|
|
|
|
(setq mu4e~headers-view-win nil)
|
|
|
|
|
(select-window headers-win)) ;; and switch to the headers win...
|
|
|
|
|
;; headers are not visible...
|
|
|
|
|
(progn
|
|
|
|
|
(kill-buffer)
|
|
|
|
|
(setq mu4e~headers-view-win nil)
|
|
|
|
|
(when (buffer-live-p (mu4e-get-headers-buffer))
|
|
|
|
|
(switch-to-buffer (mu4e-get-headers-buffer))))))))
|
2012-09-30 19:12:24 +02:00
|
|
|
|
|
2011-12-13 08:07:38 +01:00
|
|
|
|
(provide 'mu4e-view)
|
2012-06-15 10:01:40 +02:00
|
|
|
|
;; end of mu4e-view
|