mu/emacs/mu4e-view.el

763 lines
26 KiB
EmacsLisp
Raw Normal View History

;;; mu4e-view.el -- part of mu4e, the mu mail user agent
;;
;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema
;; 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:
;; In this file we define mu4e-view-mode (+ helper functions), which is used for
;; viewing e-mail messages
;;; Code:
2012-04-09 10:51:24 +02:00
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-vars)
(require 'mu4e-raw-view)
;; we prefer the improved fill-region
(require 'filladapt nil 'noerror)
(require 'comint)
;; some buffer-local variables
2011-12-13 08:07:38 +01:00
(defvar mu4e-hdrs-buffer nil
"*internal* Headers buffer connected to this view.")
(defun mu4e-view-message-with-msgid (msgid)
"View message with MSGID. This is meant for external programs
wanting to show specific messages - for example, `mu4e-org'."
(mu4e-proc-view msgid))
(defun mu4e-view-message-text (msg)
"Return the message to display (as a string), based on the MSG
plist."
(concat
(mapconcat
(lambda (field)
(let ((fieldname (cdr (assoc field mu4e-header-names)))
(fieldval (plist-get msg field)))
(case field
(:subject (mu4e-view-header fieldname fieldval))
(:path (mu4e-view-header fieldname fieldval))
(:maildir (mu4e-view-header fieldname fieldval))
(:flags (mu4e-view-header fieldname
(if fieldval (format "%S" fieldval) "")))
;; contact fields
(:to (mu4e-view-contacts msg field))
(:from (mu4e-view-contacts msg field))
(:cc (mu4e-view-contacts msg field))
(:bcc (mu4e-view-contacts msg field))
;; if we (`user-mail-address' are the From, show To, otherwise,
;; show From
(:from-or-to
(let* ((from (plist-get msg :from))
(from (and from (cdar from))))
(if (and from (string-match mu4e-user-mail-address-regexp from))
(mu4e-view-contacts msg :to)
(mu4e-view-contacts msg :from))))
;; date
(:date
(let ((datestr
(when fieldval (format-time-string mu4e-view-date-format
fieldval))))
(if datestr (mu4e-view-header fieldname datestr) "")))
;; size
(:size
(let* (size (mu4e-view-size msg)
(sizestr (when size (format "%d bytes" size))))
(if sizestr (mu4e-view-header fieldname sizestr))))
;; attachments
(:attachments (mu4e-view-attachments msg))
(t (error "Unsupported field: %S" field)))))
mu4e-view-fields "")
"\n"
(mu4e-body-text msg)))
2011-12-13 08:07:38 +01:00
(defun mu4e-view (msg hdrsbuf &optional update)
"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
2011-09-22 20:01:35 +02:00
the the message view affects HDRSBUF, as does marking etc. If
UPDATE is nil, the current message may be (visually) 'massaged',
based on the settings of `mu4e-view-wrap-lines' and
`mu4e-view-hide-cited'.
As a side-effect, a message that is being viewed loses its 'unread'
marking if it still had that."
2011-12-13 08:07:38 +01:00
(let ((buf (get-buffer-create mu4e-view-buffer-name))
2011-09-30 07:37:47 +02:00
(inhibit-read-only t))
(with-current-buffer buf
(setq mu4e-view-buffer buf)
(setq mu4e-attach-map nil) ;; clear any old attachment stuff
(erase-buffer)
(insert (mu4e-view-message-text msg))
2011-09-20 22:59:20 +02:00
;; initialize view-mode
2011-12-13 08:07:38 +01:00
(mu4e-view-mode)
2011-09-20 22:59:20 +02:00
(setq ;; these are buffer-local
buffer-read-only t
2012-04-09 10:51:24 +02:00
mu4e-current-msg msg
2011-12-13 08:07:38 +01:00
mu4e-hdrs-buffer hdrsbuf
mu4e-link-map (make-hash-table :size 32 :rehash-size 2 :weakness nil))
(switch-to-buffer buf)
2011-09-20 22:59:20 +02:00
(goto-char (point-min))
2011-09-22 20:01:35 +02:00
(mu4e-color-cited)
(mu4e-mark-footer)
(mu4e-make-urls-clickable)
(unless update
;; if we're showing the message for the first time, use the values of
;; user-settable variables `mu4e-view-wrap-lines' and
;; `mu4e-view-hide-cited' to determine whether we should wrap/hide
(progn
(when mu4e-view-wrap-lines (mu4e-view-wrap-lines))
(when mu4e-view-hide-cited (mu4e-view-hide-cited))))
;; no use in trying to set flags again
2011-09-22 20:01:35 +02:00
(unless update
2011-12-13 08:07:38 +01:00
(mu4e-view-mark-as-read-maybe)))))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-header (key val &optional dont-propertize-val)
2011-11-05 09:26:24 +01:00
"Show header FIELD for MSG with KEY. ie. <KEY>: value-of-FIELD."
(if val
(concat
2011-12-13 08:07:38 +01:00
(propertize key 'face 'mu4e-view-header-key-face) ": "
(if dont-propertize-val
val
2011-12-13 08:07:38 +01:00
(propertize val 'face 'mu4e-view-header-value-face))
"\n")
""))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-contacts (msg field)
2011-11-05 09:26:24 +01:00
"Add a header for a contact field (ie., :to, :from, :cc, :bcc)."
(let* ((lst (plist-get msg field))
2011-12-13 08:07:38 +01:00
(fieldname (cdr (assoc field mu4e-header-names)))
(contacts
2011-11-05 09:26:24 +01:00
(and lst
(mapconcat
(lambda(c)
(let ((name (car c)) (email (cdr c)))
(if name
(format "%s <%s>" name email)
(format "%s" email)))) lst ", "))))
(if contacts
2011-12-13 08:07:38 +01:00
(mu4e-view-header fieldname contacts)
"")))
2011-12-13 08:07:38 +01:00
(defvar mu4e-attach-map nil
2011-09-19 23:20:59 +02:00
"*internal* Hash which maps a number to a (part-id name mime-type).")
(defun mu4e-open-save-attach-func (num is-open)
"Return a function that offers to save attachment NUM. If IS-OPEN
is nil, and otherwise open it."
(lexical-let ((num num) (is-open is-open))
(lambda ()
(interactive)
(if is-open
(mu4e-view-open-attachment num)
(mu4e-view-save-attachment num)))))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-attachments (msg)
2011-09-19 23:20:59 +02:00
"Display attachment information; the field looks like something like:
:attachments ((:index 4 :name \"test123.doc\"
:mime-type \"application/msword\" :size 1234))."
2011-09-19 23:20:59 +02:00
(let ((atts (plist-get msg :attachments)))
(when atts
2011-12-13 08:07:38 +01:00
(setq mu4e-attach-map
2011-09-19 23:20:59 +02:00
(make-hash-table :size 32 :rehash-size 2 :weakness nil))
(let* ((id 0)
(vals
(mapconcat
(lambda (att)
(let ( (index (plist-get att :index))
(name (plist-get att :name))
(mime-type (plist-get att :mime-type))
(size (plist-get att :size))
(map (make-sparse-keymap)))
(incf id)
2011-12-13 08:07:38 +01:00
(puthash id att mu4e-attach-map)
;; mouse-2, RET offers to save the attachment,
;; S-mouse-2, S-Ret opens it.
(define-key map [mouse-2] (mu4e-open-save-attach-func id nil))
(define-key map [?\r] (mu4e-open-save-attach-func id nil))
(define-key map [S-mouse-2](mu4e-open-save-attach-func id t))
(define-key map (kbd "<S-return>")
(mu4e-open-save-attach-func id t))
(concat
(propertize (format "[%d]" id)
'face 'mu4e-view-attach-number-face)
(propertize name
'face 'mu4e-view-link-face
'keymap map
'mouse-face 'highlight)
(when (and size (> size 0))
(concat (format "(%s)"
(propertize (mu4e-display-size size)
'face 'mu4e-view-header-key-face)))))))
atts ", ")))
2011-12-13 08:07:38 +01:00
(mu4e-view-header (format "Attachments(%d)" id) vals t)))))
2011-09-19 23:20:59 +02:00
2011-11-20 09:31:38 +01: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
(let ((map (make-sparse-keymap)))
(define-key map "q" 'mu4e-view-kill-buffer-and-window)
;; note, 'z' is by-default bound to 'bury-buffer'
;; but that's not very useful in this case
(define-key map "z" 'mu4e-view-kill-buffer-and-window)
2011-12-13 08:07:38 +01:00
(define-key map "s" 'mu4e-search)
2011-12-13 08:07:38 +01:00
(define-key map "b" 'mu4e-search-bookmark)
(define-key map "B" 'mu4e-search-bookmark-edit-first)
2012-03-27 20:17:00 +02:00
2011-12-13 08:07:38 +01:00
(define-key map "j" 'mu4e-jump-to-maildir)
2011-12-13 08:07:38 +01:00
(define-key map "g" 'mu4e-view-go-to-url)
2011-12-19 08:06:47 +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)
(define-key map "." 'mu4e-raw-view)
2011-12-13 08:07:38 +01:00
(define-key map "|" 'mu4e-view-pipe)
;; (define-key map "I" 'mu4e-inspect-message)
2011-11-20 09:31:38 +01:00
;; intra-message navigation
(define-key map (kbd "SPC") 'scroll-up)
(define-key map (kbd "<home>")
'(lambda () (interactive) (goto-char (point-min))))
(define-key map (kbd "<end>")
'(lambda () (interactive) (goto-char (point-max))))
(define-key map (kbd "RET")
'(lambda () (interactive) (scroll-up 1)))
(define-key map (kbd "<backspace>")
'(lambda () (interactive) (scroll-up -1)))
;; navigation between messages
2011-12-13 08:07:38 +01:00
(define-key map "p" 'mu4e-view-prev-header)
2012-04-09 10:51:24 +02:00
(define-key map "n" 'mu4e-view-next-header)
;; the same
(define-key map (kbd "<M-down>") 'mu4e-view-next-header)
(define-key map (kbd "<M-up>") 'mu4e-view-prev-header)
2012-04-09 10:51:24 +02:00
;; switching to view mode (if it's visible)
(define-key map "y" 'mu4e-select-other-view)
;; attachments
(define-key map "e" 'mu4e-view-save-attachment)
2011-12-13 08:07:38 +01:00
(define-key map "o" 'mu4e-view-open-attachment)
(define-key map "a" 'mu4e-view-handle-attachment)
;; marking/unmarking
2011-12-13 08:07:38 +01:00
(define-key map (kbd "<backspace>") 'mu4e-mark-for-trash)
(define-key map "d" 'mu4e-view-mark-for-trash)
2011-12-13 08:07:38 +01:00
(define-key map (kbd "<delete>") 'mu4e-view-mark-for-delete)
(define-key map (kbd "<deletechar>") 'mu4e-mark-for-delete)
2011-12-13 08:07:38 +01:00
(define-key map "D" 'mu4e-view-mark-for-delete)
(define-key map "m" 'mu4e-view-mark-for-move)
2011-11-05 11:29:07 +01:00
;; misc
2011-12-13 08:07:38 +01:00
(define-key map "w" 'mu4e-view-toggle-wrap-lines)
(define-key map "h" 'mu4e-view-toggle-hide-cited)
2011-12-15 20:47:01 +01:00
(define-key map "r" 'mu4e-view-refresh)
;; 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)
(define-key map "$" 'mu4e-show-log)
(define-key map "H" 'mu4e-display-manual)
2011-12-19 08:06:47 +01: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))
(define-key menumap [quit-buffer]
'("Quit view" . mu4e-view-kill-buffer-and-window))
(define-key menumap [display-help] '("Help" . mu4e-display-manual))
(define-key menumap [sepa0] '("--"))
(define-key menumap [wrap-lines]
2011-12-13 08:07:38 +01:00
'("Toggle wrap lines" . mu4e-view-toggle-wrap-lines))
(define-key menumap [hide-cited]
2011-12-13 08:07:38 +01:00
'("Toggle hide cited" . mu4e-view-toggle-hide-cited))
(define-key menumap [raw-view]
'("View raw message" . mu4e-raw-view))
(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))
(define-key menumap [sepa8] '("--"))
(define-key menumap [open-att]
2011-12-13 08:07:38 +01:00
'("Open attachment" . mu4e-view-open-attachment))
(define-key menumap [extract-att]
'("Extract attachment" . mu4e-view-save-attachment))
(define-key menumap [goto-url]
2011-12-13 08:07:38 +01:00
'("Visit URL" . mu4e-view-go-to-url))
(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))
(define-key menumap [mark-trash]
2011-12-13 08:07:38 +01:00
'("Mark for trash" . mu4e-view-mark-for-trash))
(define-key menumap [mark-move]
2011-12-13 08:07:38 +01:00
'("Mark for move" . mu4e-view-mark-for-move))
(define-key menumap [sepa2] '("--"))
2011-12-13 08:07:38 +01:00
(define-key menumap [compose-new] '("Compose new" . mu4e-compose-new))
(define-key menumap [forward] '("Forward" . mu4e-compose-forward))
(define-key menumap [reply] '("Reply" . mu4e-compose-reply))
(define-key menumap [sepa3] '("--"))
2011-12-13 08:07:38 +01:00
(define-key menumap [search] '("Search" . mu4e-search))
(define-key menumap [jump] '("Jump to maildir" . mu4e-jump-to-maildir))
(define-key menumap [sepa4] '("--"))
2011-12-13 08:07:38 +01:00
(define-key menumap [next] '("Next" . mu4e-view-next-header))
(define-key menumap [previous] '("Previous" . mu4e-view-prev-header)))
map)))
2011-12-13 08:07:38 +01:00
(fset 'mu4e-view-mode-map mu4e-view-mode-map)
(defvar mu4e-lines-wrapped nil "*internal* Whether lines are wrapped.")
(defvar mu4e-cited-hidden nil "*internal* Whether cited lines are hidden.")
2011-09-30 07:37:47 +02:00
2012-03-27 20:17:00 +02:00
(define-derived-mode mu4e-view-mode special-mode "mu4e:view"
"Major mode for viewing an e-mail message in mu4e.
\\{mu4e-view-mode-map}."
2011-12-13 08:07:38 +01:00
(use-local-map mu4e-view-mode-map)
2011-12-13 08:07:38 +01:00
(make-local-variable 'mu4e-hdrs-buffer)
;;(make-local-variable 'mu4e-current-msg)
2011-12-13 08:07:38 +01:00
(make-local-variable 'mu4e-link-map)
2011-09-30 07:37:47 +02:00
(make-local-variable 'mu4e-lines-wrapped)
(make-local-variable 'mu4e-cited-hidden)
(setq buffer-undo-list t) ;; don't record undo info
;; filladapt is much better than the built-in filling
;; esp. with '>' cited parts
(when (fboundp 'filladapt-mode)
(filladapt-mode))
(setq truncate-lines t))
;; we mark messages are as read when we leave the message; i.e., when skipping
;; to the next/previous one, or leaving the view buffer altogether.
2011-12-13 08:07:38 +01:00
(defun mu4e-view-mark-as-read-maybe ()
"Clear the current message's New/Unread status and set it to
Seen; if the message is not New/Unread, do nothing."
2012-04-09 10:51:24 +02:00
(when mu4e-current-msg
(let ((flags (plist-get mu4e-current-msg :flags))
(docid (plist-get mu4e-current-msg :docid)))
;; is it a new message?
(when (or (member 'unread flags) (member 'new flags))
(mu4e-proc-move docid nil "+S-u-N")))))
2011-09-20 22:59:20 +02:00
(defun mu4e-color-cited ()
"Colorize message content based on the citation level."
(save-excursion
(let ((more-lines t))
(goto-char (point-min))
(while more-lines
;; Get the citation level at point -- i.e., the number of '>'
;; prefixes, starting with 0 for 'no citation'
(beginning-of-line 1)
(let* ((text (re-search-forward "[[:word:]]" (line-end-position 1) t 1))
(level (or (and text
(how-many ">" (line-beginning-position 1) text)) 0))
(face
(cond
((= 0 level) nil) ;; don't do anything
((= 1 level) 'mu4e-cited-1-face)
((= 2 level) 'mu4e-cited-2-face)
((= 3 level) 'mu4e-cited-3-face)
((= 4 level) 'mu4e-cited-4-face)
(t nil))))
(when face
(add-text-properties (line-beginning-position 1)
(line-end-position 1) `(face ,face))))
(setq more-lines
(and (= 0 (forward-line 1))
;; we need to add this weird check below; it seems in some cases
;; `forward-line' continues to return 0, even when at the end, which
;; would lead to an infinite loop
(not (= (point-max) (line-end-position)))))))))
(defun mu4e-mark-footer ()
"Give the message footers a distinctive color."
(let ((inhibit-read-only t))
(save-excursion
;; give the footer a different color...
(goto-char (point-min))
(let ((p (search-forward "\n-- \n" nil t)))
(when p
2012-04-16 22:10:16 +02:00
(add-text-properties p (point-max) '(face mu4e-footer-face)))))))
2011-12-13 08:07:38 +01:00
(defvar mu4e-link-map nil
2011-09-20 22:59:20 +02:00
"*internal* A map of some number->url so we can jump to url by number.")
(defconst mu4e-url-regexp
"\\(https?://[-+a-zA-Z0-9.?_$%/+&#@!~,:;=/()]+\\)"
"*internal* regexp that matches URLs; match-string 1 will contain
the matched URL, if any.")
(defun mu4e-browse-url-func (url)
"Return a function that executes `browse-url' with URL."
(lexical-let ((url url))
(lambda ()
(interactive)
(browse-url url))))
;; this is fairly simplistic...
(defun mu4e-make-urls-clickable ()
"Turn things that look like URLs into clickable things, and
number them so they can be opened using `mu4e-view-go-to-url'."
(let ((num 0))
(save-excursion
(goto-char (point-min))
(while (re-search-forward mu4e-url-regexp nil t)
(let ((url (match-string 0))
(map (make-sparse-keymap)))
(define-key map [mouse-2] (mu4e-browse-url-func url))
(define-key map [?\r] (mu4e-browse-url-func url))
(puthash (incf num) url mu4e-link-map)
(add-text-properties 0 (length url)
`(face mu4e-view-link-face
mouse-face highlight
keymap ,map) url)
(replace-match (concat url
(propertize (format "[%d]" num)
'face 'mu4e-view-url-number-face))))))))
;; functions for org-contacts
(defun mu4e-view-snarf-from (name-or-email)
"Get the From:-data for the current message; NAME-OR-EMAIL should
be a symbol 'name or 'email to get the corresponding field. If the
field is not found, \"\" is returned.
You can use this with e.g. org-contact with a template like:
(\"c\" \"Contacts\" entry (file \"~/Org/contacts.org\")
\"* %(mu4e-view-snarf-from 'name)
:PROPERTIES:
:EMAIL: %(mu4e-view-snarf-from 'email)
:END:\")))
See the `org-contacts' documentation for more details."
;; FIXME: we need to explictly go to some (hopefully the right!) view buffer,
;; since when using this from org-capture, we'll be taken to the capture
;; buffer instead.
(with-current-buffer mu4e-view-buffer-name
(unless (eq major-mode 'mu4e-view-mode)
(error "Not in mu4e-view mode."))
2012-04-09 10:51:24 +02:00
(unless mu4e-current-msg
(error "No current message."))
2012-04-09 10:51:24 +02:00
(let ((from (car-safe (plist-get mu4e-current-msg :from))))
(cond
((not from) "") ;; nothing found
((eq name-or-email 'name)
(or (car-safe from) ""))
((eq name-or-email 'email)
(or (cdr-safe from) ""))
(t (error "Not supported: %S" name-or-email))))))
2011-11-20 09:31:38 +01:00
(defun mu4e-view-wrap-lines ()
"Wrap lines in the message body."
(save-excursion
(let ((inhibit-read-only t))
(goto-char (point-min))
(when (search-forward "\n\n") ;; search for the message body
(fill-region (point) (point-max)))
(setq mu4e-lines-wrapped t))))
(defun mu4e-view-hide-cited ()
"Toggle hiding of cited lines in the message body."
(save-excursion
(let ((inhibit-read-only t))
(goto-char (point-min))
(flush-lines "^[:blank:]*>")
(setq mu4e-cited-hidden t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2011-09-22 20:01:35 +02:00
;; Interactive functions
2011-12-13 08:07:38 +01:00
(defun mu4e-view-toggle-wrap-lines ()
2011-09-22 20:01:35 +02:00
"Toggle line wrap in the message body."
(interactive)
(if mu4e-lines-wrapped
(mu4e-view-refresh)
(mu4e-view-wrap-lines)))
2011-09-22 20:01:35 +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)
(if mu4e-cited-hidden
(mu4e-view-refresh)
(mu4e-view-hide-cited)))
2011-09-30 07:37:47 +02:00
2011-12-13 08:07:38 +01:00
(defun mu4e-view-refresh ()
"Redisplay the current message, without wrapped lines or hidden
citations."
2011-09-30 07:37:47 +02:00
(interactive)
2012-04-09 10:51:24 +02:00
(mu4e-view mu4e-current-msg mu4e-hdrs-buffer t)
(setq
mu4e-lines-wrapped nil
mu4e-cited-hidden nil))
2011-09-22 20:01:35 +02:00
(defun mu4e-view-kill-buffer-and-window ()
"Quit the message view and return to the headers."
(interactive)
(when (buffer-live-p mu4e-view-buffer)
(with-current-buffer mu4e-view-buffer
(if (window-parent)
(kill-buffer-and-window)
(kill-buffer)))))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-next-header ()
2011-09-20 22:59:20 +02:00
"View the next header."
(interactive)
2011-12-13 08:07:38 +01:00
(when (mu4e-next-header)
(mu4e-view-message)))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-prev-header ()
2011-09-20 22:59:20 +02:00
"View the previous header."
(interactive)
2011-12-13 08:07:38 +01:00
(when (mu4e-prev-header)
(mu4e-view-message)))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-mark-for-move ()
2011-09-20 22:59:20 +02:00
"Mark the current message for moving."
(interactive)
2011-12-13 08:07:38 +01:00
(when (mu4e-mark-for-move)
(mu4e-view-message)))
2011-09-20 22:59:20 +02:00
2011-12-13 08:07:38 +01:00
(defun mu4e-view-mark-for-trash ()
2011-09-20 22:59:20 +02:00
"Mark the current message for moving to the trash folder."
(interactive)
2011-12-13 08:07:38 +01:00
(when (mu4e-mark-for-trash)
(mu4e-view-message)))
2011-09-20 22:59:20 +02:00
2011-12-13 08:07:38 +01:00
(defun mu4e-view-mark-for-delete ()
2011-09-20 22:59:20 +02:00
"Mark the current message for deletion."
(interactive)
2011-12-13 08:07:38 +01:00
(when (mu4e-mark-for-delete)
(mu4e-view-message)))
2011-09-19 23:20:59 +02:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; attachment handling
(defun mu4e--get-attach-count ()
"Get the number of attachments for this message."
(if (null mu4e-attach-map)
0
(hash-table-count mu4e-attach-map)))
(defun mu4e--get-valid-attach (prompt &optional attachnum)
"Find an attachment sexp for attachment by asking user with
PROMPT. Return the sexp with this is present in the message, nil
otherwise. If (optional) INDEX is provided, don't ask user but just
return the corresponding attachment sexp."
(let ((count (mu4e--get-attach-count)))
(when (zerop count)
(error "No attachments for this message"))
(let* ((attnum
(or attachnum
(if (= count 1)
(read-number (format "%s (1): " prompt) 1)
(read-number (format "%s (1-%d): " prompt count)))))
(att (gethash attnum mu4e-attach-map)))
(unless att
(error "Not a valid attachment number"))
att)))
(defun mu4e-view-save-attachment (&optional attachnum)
"Save some attachment (ask user which)."
(interactive)
(unless mu4e-attachment-dir
(error "`mu4e-attachment-dir' is not set"))
(let* ((att (mu4e--get-valid-attach "Attachment to save" attachnum))
(path (concat mu4e-attachment-dir
"/" (plist-get att :name)))
(index (plist-get att :index))
2011-09-19 23:20:59 +02:00
(retry t))
(while retry
(setq path (expand-file-name (read-string "Save as " path)))
(setq retry
(and (file-exists-p path)
(not (y-or-n-p (concat "Overwrite " path "?"))))))
(mu4e-proc-extract
'save (plist-get mu4e-current-msg :docid) index path)))
(defun mu4e-view-open-attachment (&optional attachnum)
"Open some attachment (ask user which)."
(interactive)
(let* ((att (mu4e--get-valid-attach "Attachment to open" attachnum))
(index (plist-get att :index)))
(mu4e-proc-extract 'open
(plist-get mu4e-current-msg :docid) index)))
(defun mu4e--temp-action (docid index what &optional param)
"Open attachment INDEX for message with DOCID, and invoke
ACTION."
(interactive)
(mu4e-proc-extract 'temp docid index nil what param))
(defun mu4e-view-open-attachment-with ()
"Open some attachment with some program (ask user which)."
(interactive)
(let* ((att (mu4e--get-valid-attach "Attachment to open"))
(cmd (read-string "Shell command to open it with: "))
(index (plist-get att :index)))
(mu4e--temp-action (plist-get mu4e-current-msg :docid) index
"open-with" cmd)))
(defun mu4e-view-pipe-attachment ()
"Open some attachment with some program (ask user which)."
(interactive)
(let* ((att (mu4e--get-valid-attach "Attachment to process"))
(cmd (read-string "Pipe: "))
(index (plist-get att :index)))
(mu4e--temp-action (plist-get mu4e-current-msg :docid) index
"pipe" cmd)))
(defun mu4e-view-open-attachment-emacs ()
"Open some attachment in the current emacs instance."
(interactive)
(let* ((att (mu4e--get-valid-attach "Attachment to open in current emacs"))
(index (plist-get att :index)))
(mu4e--temp-action (plist-get mu4e-current-msg :docid) index
"emacs")))
2011-09-19 23:20:59 +02:00
(defun mu4e-view-handle-attachment ()
"Ask user what to do with attachments, then do it."
(interactive)
(let ((choice
(mu4e-read-option
"Handle attachments: "
'(("open-with" ?w)
("in emacs" ?e)
("pipe" ?|)))))
(case choice
(?w (call-interactively 'mu4e-view-open-attachment-with))
(?| (call-interactively 'mu4e-view-pipe-attachment))
(?e (call-interactively 'mu4e-view-open-attachment-emacs))
(otherwise (message "Not yet implemented")))))
;; handler-function to handle the response we get from the server when we
;; want to do something with one of the attachments.
(defun mu4e-view-temp-handler (path what param)
"Handler function for doing things with temp files (ie.,
attachments) in response to a (mu4e-proc-extract 'temp ... )."
(cond
((string= what "open-with")
;; 'param' will be the program to open-with
(start-file-process-shell-command "*mu4e-open-with*" nil
(concat param " " path)))
((string= what "pipe")
;; 'param' will be the pipe command, path the infile for this
(let ((inhibit-read-only t))
(switch-to-buffer (get-buffer-create "*mu4e-output*"))
(erase-buffer)
(call-process-shell-command param path t t)
(view-mode)))
((string= what "emacs")
(find-file path)
;; make the buffer read-only since it usually does not make
;; sense to edit the temp buffer; use C-x C-q if you insist...
(setq buffer-read-only t)
)
(t (error "Unsupported action %S" what))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2012-04-09 10:51:24 +02:00
(defun mu4e--in-split-view ()
"Return t if we're in split-view, nil otherwise."
(member mu4e-split-view '(horizontal vertical)))
(defun mu4e-view-unmark-all ()
"If we're in split-view, unmark all messages. Otherwise, warn
user that unmarking only works in the header list."
(interactive)
2012-04-09 10:51:24 +02:00
(if (mu4e--in-split-view)
(mu4e-unmark-all)
(message "Unmarking needs to be done in the header list view")))
2012-04-09 10:51:24 +02:00
(defun mu4e-view-unmark ()
"If we're in split-view, unmark message at point. Otherwise, warn
user that unmarking only works in the header list."
(interactive)
(if (mu4e--in-split-view)
(mu4e-unmark)
(message "Unmarking needs to be done in the header list view")))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-marked-execute ()
2012-04-09 10:51:24 +02:00
"If we're in split-view, execute the marks. Otherwise, warn user
that execution can only take place in n the header list."
(interactive)
2012-04-09 10:51:24 +02:00
(if (mu4e--in-split-view)
(mu4e-execute-marks)
(message "Execution needs to be done in the header list view")))
2011-12-13 08:07:38 +01:00
(defun mu4e-view-go-to-url (num)
2011-09-20 22:59:20 +02:00
"Go to a numbered url."
(interactive "nGo to url with number: ")
2011-12-13 08:07:38 +01:00
(let ((url (gethash num mu4e-link-map)))
2011-09-20 22:59:20 +02:00
(unless url (error "Invalid number for URL"))
(browse-url url)))
(defun mu4e-raw-view ()
2011-11-20 09:31:38 +01:00
"Show the the raw text of the current message."
(interactive)
2012-04-09 10:51:24 +02:00
(unless mu4e-current-msg
2011-11-20 09:31:38 +01:00
(error "No current message"))
2012-04-09 10:51:24 +02:00
(mu4e-raw-view-message mu4e-current-msg (current-buffer)))
2011-11-20 09:31:38 +01:00
2011-12-13 08:07:38 +01:00
(defun mu4e-view-pipe (cmd)
"Pipe the message through shell command CMD, and display the
results."
(interactive "sShell command: ")
2012-04-09 10:51:24 +02:00
(unless mu4e-current-msg
(error "No current message"))
2012-04-09 10:51:24 +02:00
(mu4e-view-shell-command-on-raw-message mu4e-current-msg
2011-12-21 23:41:27 +01:00
(current-buffer) cmd))
2011-12-13 08:07:38 +01:00
(provide 'mu4e-view)