mu4e: show inline text/plain as attachment

Show inline text parts as attachments too, so we can save them; however,
filter outer really small ones (ie. footers)
This commit is contained in:
djcb 2017-02-12 11:11:16 +02:00
parent 235dc75a1a
commit 1c4dbe580c
1 changed files with 115 additions and 112 deletions

View File

@ -1,6 +1,6 @@
;;; mu4e-view.el -- part of mu4e, the mu mail user agent
;;
;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
;; Copyright (C) 2011-2017 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
@ -53,7 +53,7 @@
(defcustom mu4e-view-fields
'(:from :to :cc :subject :flags :date :maildir :mailing-list :tags
:attachments :signature :decryption)
:attachments :signature :decryption)
"Header fields to display in the message view buffer.
For the complete list of available headers, see `mu4e-header-info'."
:type (list 'symbol)
@ -273,7 +273,7 @@ found."
(body (mu4e-message-body-text msg prefer-html)))
(setq mu4e~view-html-text nil)
(when (fboundp 'add-face-text-property)
(add-face-text-property 0 (length body) 'mu4e-view-body-face t body))
(add-face-text-property 0 (length body) 'mu4e-view-body-face t body))
body)))
(defun mu4e~view-embedded-winbuf ()
@ -332,12 +332,12 @@ Meant to be evoked from interactive commands."
(if (and (eventp last-command-event)
(mouse-event-p last-command-event))
(let ((posn (event-end last-command-event)))
(when (numberp (posn-point posn))
(get-text-property
(posn-point posn)
prop
(window-buffer (posn-window posn)))
))
(when (numberp (posn-point posn))
(get-text-property
(posn-point posn)
prop
(window-buffer (posn-window posn)))
))
(get-text-property (point) prop)))
(defun mu4e~view-construct-header (field val &optional dont-propertize-val)
@ -348,7 +348,7 @@ add text-properties to VAL."
(append mu4e-header-info mu4e-header-info-custom))))
(key (plist-get info :name))
(val (if val (propertize val 'field 'mu4e-header-field-value
'front-sticky '(field))))
'front-sticky '(field))))
(help (plist-get info :help)))
(if (and val (> (length val) 0))
(with-temp-buffer
@ -377,24 +377,24 @@ add text-properties to VAL."
"Fold/unfold headers' value if there are more than one line."
(interactive)
(let ((name-pos (field-beginning))
(value-pos (1+ (field-end))))
(value-pos (1+ (field-end))))
(if (and name-pos value-pos
(eq (get-text-property name-pos 'field) 'mu4e-header-field-key))
(eq (get-text-property name-pos 'field) 'mu4e-header-field-key))
(save-excursion
(let* ((folded))
(mapc (lambda (o)
(when (overlay-get o 'mu4e~view-header-field-folded)
(delete-overlay o)
(setq folded t)))
(overlays-at value-pos))
(unless folded
(let* ((o (make-overlay value-pos (field-end value-pos)))
(vals (split-string (field-string value-pos) "\n" t))
(val (if (= (length vals) 1)
(car vals)
(concat (substring (car vals) 0 -3) "..."))))
(overlay-put o 'mu4e~view-header-field-folded t)
(overlay-put o 'display val))))))))
(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))))))))
(defun mu4e~view-compose-contact (&optional point)
"Compose a message for the address at point."
@ -501,7 +501,7 @@ add text-properties to VAL."
"Open the attachement at point, or click location."
(interactive)
(let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
(when (and msg attnum)
(mu4e-view-open-attachment msg attnum))))
@ -509,17 +509,17 @@ add text-properties to VAL."
"Save the attachement at point, or click location."
(interactive)
(let* (( msg (mu4e~view-get-property-from-event 'mu4e-msg))
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
( attnum (mu4e~view-get-property-from-event 'mu4e-attnum)))
(when (and msg attnum)
(mu4e-view-save-attachment-single msg attnum))))
(defun mu4e~view-construct-attachments-header (msg)
"Display attachment information; the field looks like something like:
:parts ((:index 1 :name \"1.part\" :mime-type \"text/plain\"
:type (leaf) :attachment nil :size 228)
(:index 2 :name \"analysis.doc\"
:mime-type \"application/msword\"
:type (leaf attachment) :attachment nil :size 605196))"
:type (leaf) :attachment nil :size 228)
(:index 2 :name \"analysis.doc\"
:mime-type \"application/msword\"
:type (leaf attachment) :attachment nil :size 605196))"
(setq mu4e~view-attach-map ;; buffer local
(make-hash-table :size 64 :weakness nil))
(let* ((id 0)
@ -531,6 +531,7 @@ add text-properties to VAL."
(lambda (part)
(let* ((mtype (or (mu4e-message-part-field part :mime-type)
"application/octet-stream"))
(partsize (or (mu4e-message-part-field part :size) 0))
(attachtype (mu4e-message-part-field part :type))
(isattach
(or ;; we consider parts marked either
@ -540,7 +541,9 @@ add text-properties to VAL."
;; saved), unless they are text/plain, which are
;; usually just message footers in mailing lists
(and (member 'inline attachtype)
(not (string-match "^text/plain" mtype))))))
(or
(> partsize 256) ;; filter out footers
(not (string-match "^text/plain" mtype)))))))
(or ;; remove if it's not an attach *or* if it's an
;; image/audio/application type (but not a signature)
isattach
@ -573,7 +576,7 @@ add text-properties to VAL."
)
(when (and size (> size 0))
(propertize (format "(%s)" (mu4e-display-size size))
'face 'mu4e-header-key-face)))))
'face 'mu4e-header-key-face)))))
attachments ", ")))
(when attachments
(mu4e~view-construct-header :attachments attstr t))))
@ -584,7 +587,7 @@ FUNC should be a function taking two arguments:
1. the message MSG, and
2. a plist describing the attachment. The plist looks like:
(:index 1 :name \"test123.doc\"
:mime-type \"application/msword\" :attachment t :size 1234)."
:mime-type \"application/msword\" :attachment t :size 1234)."
(dolist (part (mu4e-msg-field msg :parts))
(funcall func msg part)))
@ -635,7 +638,7 @@ FUNC should be a function taking two arguments:
(define-key map "a" 'mu4e-view-action)
(define-key map ";" 'mu4e-context-switch)
;; toggle header settings
(define-key map "O" 'mu4e-headers-change-sorting)
(define-key map "P" 'mu4e-headers-toggle-threading)
@ -664,7 +667,7 @@ FUNC should be a function taking two arguments:
(define-key map (kbd "[") 'mu4e-view-headers-prev-unread)
(define-key map (kbd "]") 'mu4e-view-headers-next-unread)
;; switching to view mode (if it's visible)
(define-key map "y" 'mu4e-select-other-view)
@ -688,7 +691,7 @@ FUNC should be a function taking two arguments:
(define-key map (kbd "-") 'mu4e-view-mark-for-unflag)
(define-key map (kbd "=") 'mu4e-view-mark-for-untrash)
(define-key map (kbd "&") 'mu4e-view-mark-custom)
(define-key map (kbd "*") 'mu4e-view-mark-for-something)
(define-key map (kbd "<kp-multiply>") 'mu4e-view-mark-for-something)
(define-key map (kbd "<insert>") 'mu4e-view-mark-for-something)
@ -806,9 +809,9 @@ FUNC should be a function taking two arguments:
;; show context in mode-string
(make-local-variable 'global-mode-string)
(add-to-list 'global-mode-string '(:eval (mu4e-context-label)))
(setq buffer-undo-list t);; don't record undo info
;; autopair mode gives error when pressing RET
;; turn it off
(when (boundp 'autopair-dont-activate)
@ -956,7 +959,7 @@ N (prefix argument), to the Nth previous header."
unread message header in the headers buffer connected with this
message view. If this succeeds, return the new docid. Otherwise,
return nil."
(mu4e~view-in-headers-context
(mu4e~view-in-headers-context
(mu4e~headers-prev-or-next-unread backwards))
(mu4e-select-other-view)
(mu4e-headers-view-message))
@ -1059,23 +1062,23 @@ Add this function to `mu4e-view-mode-hook' to enable this feature."
(save-excursion
(goto-char (point-min))
(while (re-search-forward
(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))))))
(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))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Wash functions
@ -1085,19 +1088,19 @@ Add this function to `mu4e-view-mode-hook' to enable this feature."
(with-current-buffer mu4e~view-buffer
(save-excursion
(let ((inhibit-read-only t)
(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)))))))
(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)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; attachment handling
@ -1134,9 +1137,9 @@ number ATTNUM."
(defun mu4e~view-request-attachment-path (fname path)
"Ask the user where to save FNAME (default is PATH/FNAME)."
(let ((fpath (expand-file-name
(read-file-name
(mu4e-format "Save as ")
path nil nil fname) path)))
(read-file-name
(mu4e-format "Save as ")
path nil nil fname) path)))
(if (file-directory-p fpath)
(expand-file-name fname fpath)
fpath)))
@ -1144,11 +1147,11 @@ number ATTNUM."
(defun mu4e~view-request-attachments-dir (path)
"Ask the user where to save multiple attachments (default is PATH)."
(let ((fpath (expand-file-name
(read-directory-name
(mu4e-format "Save in directory ")
path nil nil nil) path)))
(read-directory-name
(mu4e-format "Save in directory ")
path nil nil nil) path)))
(if (file-directory-p fpath)
fpath)))
fpath)))
(defun mu4e-view-save-attachment-single (&optional msg attnum)
"Save attachment number ATTNUM from MSG.
@ -1186,30 +1189,30 @@ Furthermore, there is a shortcut \"a\" which so means all
attachments, but as this is the default, you may not need it."
(interactive)
(let* ((msg (or msg (mu4e-message-at-point)))
(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)))
(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)))
(if mu4e-save-multiple-attachments-without-asking
(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)
(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)
fpath)
(while retry
(setq fpath (expand-file-name (concat attachdir fname) path))
(setq retry
(and (file-exists-p fpath)
(while retry
(setq fpath (expand-file-name (concat attachdir fname) path))
(setq retry
(and (file-exists-p fpath)
(not (y-or-n-p
(mu4e-format "Overwrite '%s'?" fpath))))))
(mu4e~proc-extract
(mu4e~proc-extract
'save (mu4e-message-field msg :docid)
index mu4e-decryption-policy fpath))))
(dolist (num attachnums)
(mu4e-view-save-attachment-single msg num)))))
(mu4e-view-save-attachment-single msg num)))))
(defun mu4e-view-save-attachment (&optional multi)
"Offer to save attachment(s).
@ -1254,13 +1257,13 @@ ATTNUM is nil ask for the attachment number."
"Open MSG's attachment ATTACHNUM with CMD.
If CMD is nil, ask user for it."
(let* ((att (mu4e~view-get-attach msg attachnum))
(ext (file-name-extension (plist-get att :name)))
(cmd (or cmd
(read-string
(ext (file-name-extension (plist-get att :name)))
(cmd (or cmd
(read-string
(mu4e-format "Shell command to open it with: ")
(assoc-default ext mu4e-view-attachment-assoc)
'mu4e~view-open-with-hist)))
(index (plist-get att :index)))
'mu4e~view-open-with-hist)))
(index (plist-get att :index)))
(mu4e~view-temp-action
(mu4e-message-field msg :docid) index "open-with" cmd)))
@ -1300,16 +1303,16 @@ If MSG is nil use the message returned by `message-at-point'.
The actions are specified in `mu4e-view-attachment-actions'."
(interactive)
(let* ((msg (or msg (mu4e-message-at-point)))
(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))))
(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))))
(cond ((and actionfunc attnum)
(funcall actionfunc msg attnum))
((and actionfunc multi)
(funcall actionfunc msg)))))
(funcall actionfunc msg attnum))
((and actionfunc multi)
(funcall actionfunc msg)))))
;; handler-function to handle the response we get from the server when we
;; want to do something with one of the attachments.
@ -1480,7 +1483,7 @@ it to a range of uris. PROMPT is the query to present to the user."
user with PROMPT."
(interactive)
(let* ((num (or num (mu4e~view-get-urls-num prompt)))
(url (gethash num mu4e~view-link-map)))
(url (gethash num mu4e~view-link-map)))
(unless url (mu4e-warn "Invalid number for URL"))
(funcall urlfunc url)))
@ -1594,12 +1597,12 @@ other windows."
;; headers are visible
(progn
(kill-buffer-and-window) ;; kill the view win
(setq mu4e~headers-view-win nil)
(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)
(setq mu4e~headers-view-win nil)
(when (buffer-live-p mu4e~view-headers-buffer)
(switch-to-buffer mu4e~view-headers-buffer))))))