mirror of https://github.com/djcb/mu.git
mu4e-view: Implement actions for gnus-view MIME-parts
In the "old" view, we had `mu4e-view-mime-part-actions`, which describes various actions on attachments; in the brave new gnus-based view, those do not work, but we have added something better: mu4e-view-mime-part-actions This needs some more documentation, but is useful already.
This commit is contained in:
parent
62ae9ead6f
commit
afedfc6708
4
NEWS.org
4
NEWS.org
|
@ -43,8 +43,8 @@
|
||||||
faster crypto, support for S/MIME, syntax-highlighting, calendar
|
faster crypto, support for S/MIME, syntax-highlighting, calendar
|
||||||
invitations and more.
|
invitations and more.
|
||||||
|
|
||||||
It does not do everything the old viewer does though (e.g., attachment
|
The new view is superior in most ways, but if you still depend need
|
||||||
actions), so if you depend on those:
|
something from the old one, you can use:
|
||||||
#+begin_example
|
#+begin_example
|
||||||
;; set *before* loading mu4e; and restart emacs if you want to change it
|
;; set *before* loading mu4e; and restart emacs if you want to change it
|
||||||
;; users of use-packag~ should can use the :init section for this.
|
;; users of use-packag~ should can use the :init section for this.
|
||||||
|
|
|
@ -192,7 +192,7 @@ some Gnus-functionality that does not work in mu4e."
|
||||||
|
|
||||||
(define-key map "q" 'mu4e~view-quit-buffer)
|
(define-key map "q" 'mu4e~view-quit-buffer)
|
||||||
|
|
||||||
;; note, 'z' is by-default bound to 'bury-buffer'
|
;; note, 'z' is by-default bound to 'bury-buffer'
|
||||||
;; but that's not very useful in this case
|
;; but that's not very useful in this case
|
||||||
(define-key map "z" 'ignore)
|
(define-key map "z" 'ignore)
|
||||||
|
|
||||||
|
@ -223,6 +223,8 @@ some Gnus-functionality that does not work in mu4e."
|
||||||
(define-key map "." 'mu4e-view-raw-message)
|
(define-key map "." 'mu4e-view-raw-message)
|
||||||
(define-key map "|" 'mu4e-view-pipe)
|
(define-key map "|" 'mu4e-view-pipe)
|
||||||
(define-key map "a" 'mu4e-view-action)
|
(define-key map "a" 'mu4e-view-action)
|
||||||
|
(define-key map "A" 'mu4e-view-mime-part-action)
|
||||||
|
(define-key map "e" 'mu4e-view-save-attachments)
|
||||||
|
|
||||||
(define-key map ";" 'mu4e-context-switch)
|
(define-key map ";" 'mu4e-context-switch)
|
||||||
|
|
||||||
|
@ -258,8 +260,6 @@ some Gnus-functionality that does not work in mu4e."
|
||||||
;; switching to view mode (if it's visible)
|
;; switching to view mode (if it's visible)
|
||||||
(define-key map "y" 'mu4e-select-other-view)
|
(define-key map "y" 'mu4e-select-other-view)
|
||||||
|
|
||||||
;; attachments
|
|
||||||
(define-key map "e" 'mu4e-view-save-attachment)
|
|
||||||
|
|
||||||
;; marking/unmarking
|
;; marking/unmarking
|
||||||
(define-key map "d" 'mu4e-view-mark-for-trash)
|
(define-key map "d" 'mu4e-view-mark-for-trash)
|
||||||
|
@ -352,6 +352,7 @@ some Gnus-functionality that does not work in mu4e."
|
||||||
"Keymap for mu4e-view mode")
|
"Keymap for mu4e-view mode")
|
||||||
|
|
||||||
(set-keymap-parent mu4e-view-mode-map button-buffer-map)
|
(set-keymap-parent mu4e-view-mode-map button-buffer-map)
|
||||||
|
(suppress-keymap mu4e-view-mode-map)
|
||||||
|
|
||||||
(defcustom mu4e-view-mode-hook nil
|
(defcustom mu4e-view-mode-hook nil
|
||||||
"Hook run when entering Mu4e-View mode."
|
"Hook run when entering Mu4e-View mode."
|
||||||
|
@ -404,45 +405,183 @@ Gnus' article-mode."
|
||||||
(buffer-string))))
|
(buffer-string))))
|
||||||
|
|
||||||
|
|
||||||
(defun mu4e-view-save-attachment (&optional arg)
|
|
||||||
|
|
||||||
|
;;; MIME-parts
|
||||||
|
|
||||||
|
|
||||||
|
(defun mu4e~view-gather-mime-parts ()
|
||||||
|
"Gather all MIME parts as an alist that uniquely maps the number
|
||||||
|
to the gnus-part."
|
||||||
|
(let ((parts '()))
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (not (eobp))
|
||||||
|
(let ((part (get-text-property (point) 'gnus-data))
|
||||||
|
(index (get-text-property (point) 'gnus-part)))
|
||||||
|
(when (and part (numberp index) (not (assoc index parts))
|
||||||
|
(push `(,index . ,part) parts)))
|
||||||
|
(goto-char (or (next-single-property-change (point) 'gnus-part)
|
||||||
|
(point-max))))))
|
||||||
|
parts))
|
||||||
|
|
||||||
|
|
||||||
|
(defun mu4e-view-save-attachments (&optional arg)
|
||||||
"Save mime parts from current mu4e gnus view buffer.
|
"Save mime parts from current mu4e gnus view buffer.
|
||||||
|
|
||||||
When helm-mode is enabled provide completion on attachments and
|
When helm-mode is enabled provide completion on attachments and
|
||||||
possibility to mark candidates to save, otherwise completion on
|
possibility to mark candidates to save, otherwise completion on
|
||||||
attachments is done with `completing-read-multiple', in this case use
|
attachments is done with `completing-read-multiple', in this case
|
||||||
\",\" to separate candidate, completion is provided after each \",\"."
|
use \",\" to separate candidate, completion is provided after
|
||||||
|
each \",\".
|
||||||
|
|
||||||
|
Note, currently this does not work well with file names
|
||||||
|
containing commas."
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
(cl-assert (and (eq major-mode 'mu4e-view-mode)
|
(cl-assert (and (eq major-mode 'mu4e-view-mode)
|
||||||
(derived-mode-p 'gnus-article-mode)))
|
(derived-mode-p 'gnus-article-mode)))
|
||||||
(let ((handles '())
|
(let* ((parts (mu4e~view-gather-mime-parts))
|
||||||
(files '())
|
(handles '())
|
||||||
(helm-comp-read-use-marked t)
|
(files '())
|
||||||
(compfn (if (and (boundp 'helm-mode) helm-mode)
|
(helm-comp-read-use-marked t)
|
||||||
#'completing-read
|
(compfn (if (and (boundp 'helm-mode) helm-mode)
|
||||||
;; Fallback to `completing-read-multiple' with poor
|
#'completing-read
|
||||||
;; completion systems.
|
;; Fallback to `completing-read-multiple' with poor
|
||||||
#'completing-read-multiple))
|
;; completion systems.
|
||||||
|
#'completing-read-multiple))
|
||||||
dir)
|
dir)
|
||||||
(save-excursion
|
(dolist (part parts)
|
||||||
(goto-char (point-min))
|
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
|
||||||
(while (not (eobp))
|
(when fname
|
||||||
(let ((handle (get-text-property (point) 'gnus-data)))
|
(push `(,fname . ,(cdr part)) handles)
|
||||||
(when (consp handle)
|
(push fname files))))
|
||||||
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr handle))))))
|
|
||||||
(when fname
|
|
||||||
(push `(,fname . ,handle) handles)
|
|
||||||
(push fname files)))))
|
|
||||||
(forward-line 1)))
|
|
||||||
(if files
|
(if files
|
||||||
(progn
|
(progn
|
||||||
(setq files (funcall compfn "Save part(s): " files)
|
(setq files (funcall compfn "Save part(s): " files)
|
||||||
dir (if arg
|
dir (if arg (read-directory-name "Save to directory: ") mu4e-attachment-dir))
|
||||||
(read-directory-name "Save to directory: ")
|
|
||||||
mu4e-attachment-dir))
|
|
||||||
(cl-loop for (f . h) in handles
|
(cl-loop for (f . h) in handles
|
||||||
when (member f files)
|
when (member f files)
|
||||||
do (mm-save-part-to-file h (expand-file-name f dir))))
|
do (mm-save-part-to-file h (expand-file-name f dir))))
|
||||||
(message "No attached files found"))))
|
(mu4e-message "No attached files found"))))
|
||||||
|
|
||||||
|
|
||||||
|
(defvar mu4e-view-mime-part-actions
|
||||||
|
'(
|
||||||
|
;;
|
||||||
|
;; some basic ones
|
||||||
|
;;
|
||||||
|
|
||||||
|
;; save mime-part to a file
|
||||||
|
(:name "save" :handler gnus-article-save-part :receives index)
|
||||||
|
;; pipe mime part to some arbitrary shell command
|
||||||
|
(:name "|pipe" :handler gnus-article-pipe-part :receives index)
|
||||||
|
;; open with the default handler, if any
|
||||||
|
(:name "open" :handler mu4e~view-open-file :receives temp)
|
||||||
|
;; open with some custom file.
|
||||||
|
(:name "wopen-with" :handler (lambda (file)(mu4e~view-open-file file t))
|
||||||
|
:receives temp)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; some more examples
|
||||||
|
;;
|
||||||
|
|
||||||
|
;; import GPG key
|
||||||
|
(:name "gpg" :handler epa-import-keys :receives temp)
|
||||||
|
;; count the number of lines in a MIME-part
|
||||||
|
(:name "line-count" :handler "wc -l" :receives pipe)
|
||||||
|
;; open in this emacs instance; tries to use the attachment name,
|
||||||
|
;; so emacs can use specific modes etc.
|
||||||
|
(:name "emacs" :handler find-file :receives temp)
|
||||||
|
;; open in this emacs instance, "raw"
|
||||||
|
(:name "raw" :handler (lambda (str)
|
||||||
|
(let ((tmpbuf (get-buffer-create " *mu4e-raw-mime*")))
|
||||||
|
(with-current-buffer tmpbuf
|
||||||
|
(insert str)
|
||||||
|
(view-mode)
|
||||||
|
(goto-char (point-min)))
|
||||||
|
(switch-to-buffer tmpbuf))) :receives pipe))
|
||||||
|
|
||||||
|
"Actions for MIME-parts. Each is a plist with keys
|
||||||
|
`(:name <name> ;; name of the action; shortcut is first letter of name
|
||||||
|
|
||||||
|
:handler ;; one of:
|
||||||
|
;; - a function receiving the index/temp/pipe
|
||||||
|
;; - a string, which is taken as a shell command
|
||||||
|
|
||||||
|
:receives ;; a symbol specifying what the handler receives
|
||||||
|
;; - index: the index number of the mime part (default)
|
||||||
|
;; - temp: the full path to the mime part in a
|
||||||
|
;; temporary file, which is deleted immediately
|
||||||
|
;; after invoking handler
|
||||||
|
;; - pipe: the attachment is piped to some shell command
|
||||||
|
;; or as a string parameter to a function
|
||||||
|
).")
|
||||||
|
|
||||||
|
|
||||||
|
(defun mu4e~view-mime-part-to-temp-file (handle)
|
||||||
|
"Write mime-part N to a temporary file and return the file name.
|
||||||
|
The filename is deduced from the MIME-part's filename, or
|
||||||
|
otherwise random; the result is placed in temporary directory
|
||||||
|
with a unique name. Returns the full path for the file
|
||||||
|
created. The directory and file are self-destructed."
|
||||||
|
(let* ((tmpdir (make-temp-file "mu4e-temp-" t))
|
||||||
|
(fname (cdr-safe (assoc 'filename (assoc "attachment" (cdr handle)))))
|
||||||
|
(fname (if fname
|
||||||
|
(concat tmpdir "/" (replace-regexp-in-string "/" "-" fname))
|
||||||
|
(let ((temporary-file-directory tmpdir))
|
||||||
|
(make-temp-file "mimepart")))))
|
||||||
|
(mm-save-part-to-file handle fname)
|
||||||
|
(run-at-time "30 sec" nil (lambda () (ignore-errors (delete-directory tmpdir t))))
|
||||||
|
fname))
|
||||||
|
|
||||||
|
|
||||||
|
(defun mu4e~view-open-file (file &optional force-ask)
|
||||||
|
"Open FILE with default handler, if any. Otherwise, or if FORCE_ASK is set,
|
||||||
|
ask user for the program to open with."
|
||||||
|
(let* ((opener
|
||||||
|
(pcase system-type
|
||||||
|
(`darwin "open")
|
||||||
|
((or 'gnu 'gnu/linux 'gnu/kfreebsd) "xdg-open")))
|
||||||
|
(prog (if (or force-ask (not opener))
|
||||||
|
(read-shell-command "Open MIME-part with: ")
|
||||||
|
opener)))
|
||||||
|
(call-process prog nil 0 nil file)))
|
||||||
|
|
||||||
|
(defun mu4e-view-mime-part-action (&optional n)
|
||||||
|
"Apply some action on mime-part N in the current messsage. If N
|
||||||
|
is not specified, ask for it."
|
||||||
|
(interactive "NNumber of MIME-part: ")
|
||||||
|
(let* ((parts (mu4e~view-gather-mime-parts))
|
||||||
|
(options (mapcar (lambda (action) `(,(plist-get action :name) . ,action))
|
||||||
|
mu4e-view-mime-part-actions))
|
||||||
|
(handle (or (cdr-safe (cl-find-if (lambda (part) (eq (car part) n)) parts))
|
||||||
|
(mu4e-error "MIME-part %s not found" n)))
|
||||||
|
(action (or (and options (mu4e-read-option "Action on mime-part: " options))
|
||||||
|
(mu4e-error "No such action")))
|
||||||
|
(handler (or (plist-get action :handler)
|
||||||
|
(mu4e-error "No :handler found for action %S" action)))
|
||||||
|
(receives (or (plist-get action :receives)
|
||||||
|
(mu4e-error "No :receives found for action %S" action))))
|
||||||
|
(save-excursion
|
||||||
|
(cond
|
||||||
|
((functionp handler)
|
||||||
|
(cond
|
||||||
|
((eq receives 'index) (funcall handler n))
|
||||||
|
((eq receives 'pipe) (funcall handler (mm-with-unibyte-buffer
|
||||||
|
(mm-insert-part handle)
|
||||||
|
(buffer-string))))
|
||||||
|
((eq receives 'temp)
|
||||||
|
(funcall handler (mu4e~view-mime-part-to-temp-file handle)))
|
||||||
|
(t (mu4e-error "Invalid :receive for %S" action))))
|
||||||
|
((stringp handler)
|
||||||
|
(cond
|
||||||
|
((eq receives 'index) (shell-command (concat handler " " (shell-quote-argument n))))
|
||||||
|
((eq receives 'pipe) (mm-pipe-part handle handler))
|
||||||
|
((eq receives 'temp)
|
||||||
|
(shell-command (shell-command (concat handler " "
|
||||||
|
(shell-quote-argument
|
||||||
|
(mu4e~view-mime-part-to-temp-file handle))))))
|
||||||
|
(t (mu4e-error "Invalid action %S" action))))))))
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
(provide 'mu4e-view-gnus)
|
(provide 'mu4e-view-gnus)
|
||||||
|
|
|
@ -1803,7 +1803,21 @@ attachments. For a general discussion on how to define your own, see
|
||||||
on the current message. You can specify these actions using the variable
|
on the current message. You can specify these actions using the variable
|
||||||
@code{mu4e-view-actions}; @t{mu4e} defines a number of example actions.
|
@code{mu4e-view-actions}; @t{mu4e} defines a number of example actions.
|
||||||
|
|
||||||
|
|
||||||
|
@subsection MIME-part actions
|
||||||
|
|
||||||
|
Note -- these actions are only available for the new, gnus-based
|
||||||
|
message view; see @xref{Message view}.
|
||||||
|
|
||||||
|
MIME-part actions allow you to act upon MIME-parts in a message - such
|
||||||
|
as attachments. For now, these actions are defined and documented in
|
||||||
|
@code{mu4e-view-mime-part-actions}.
|
||||||
|
|
||||||
@subsection Attachment actions
|
@subsection Attachment actions
|
||||||
|
|
||||||
|
Note -- these actions are only available for the old message view; see
|
||||||
|
@xref{Old message view}.
|
||||||
|
|
||||||
Similarly, there is @code{mu4e-view-attachment-action} (@key{A}) for actions
|
Similarly, there is @code{mu4e-view-attachment-action} (@key{A}) for actions
|
||||||
on attachments, which you can specify with
|
on attachments, which you can specify with
|
||||||
@code{mu4e-view-attachment-actions}.
|
@code{mu4e-view-attachment-actions}.
|
||||||
|
|
Loading…
Reference in New Issue