mu4e-view: re-enable the gnus/old split

re-enable the gnus/old split (the key was re-implementing
mu4e-view-message-text for gnus)

split helpers into mu4e-view-common

as a bonus 'go to url' now also works with gnus
This commit is contained in:
Dirk-Jan C. Binnema 2021-03-10 20:36:47 +02:00
parent 316633e1b2
commit e2655ba34b
7 changed files with 619 additions and 2073 deletions

View File

@ -40,11 +40,12 @@ dist_lisp_LISP= \
mu4e-utils.el \
mu4e-vars.el \
mu4e-view.el \
mu4e-view-common.el \
mu4e-view-gnus.el \
mu4e-view-old.el \
mu4e.el \
obsolete/org-mu4e.el
# mu4e-view-gnus.el
# mu4e-view-old.el
EXTRA_DIST= \
mu4e-about.org

View File

@ -47,6 +47,7 @@ mu4e_srcs=[
'mu4e-utils.el',
'mu4e-vars.el',
'mu4e-view.el',
'mu4e-view-common.el',
'mu4e-view-gnus.el',
'mu4e-view-old.el',
'obsolete/org-mu4e.el',
@ -57,10 +58,10 @@ foreach src : mu4e_srcs
target_path = join_paths(meson.current_build_dir(), target_name)
target_func = '(setq byte-compile-dest-file-function(lambda(_) "' + target_path + '"))'
custom_target(src.underscorify() + '_el',
build_by_default: true,
input: src,
output: target_name,
command: [emacs,
build_by_default: true,
input: src,
output: target_name,
command: [emacs,
'--no-init-file',
'--batch',
'--eval', target_func,

View File

@ -65,6 +65,17 @@ Setting this to t increases the amount of information in the log."
:type 'boolean
:group 'mu4e)
(defgroup mu4e-view nil
"Settings for the message view."
:group 'mu4e)
(defcustom mu4e-view-use-gnus t
"If non-nil, use the new Gnus-based viewer.
Otherwise, use the old viewer."
:type 'boolean
:group 'mu4e-view)
(defcustom mu4e-speedbar-support nil
"Support having a speedbar to navigate folders/bookmarks."
:type 'boolean
@ -1137,6 +1148,18 @@ sexp received from the server process.")
(defvar mu4e-temp-func 'mu4e~view-temp-handler
"A function called for each (:temp <file> <cookie>) sexp.")
;;; Internals
(defvar mu4e~headers-view-win nil
"The view window connected to this headers view.")
;; It's useful to have the current view message available to
;; `mu4e-view-mode-hooks' functions, and we set up this variable
;; before calling `mu4e-view-mode'. However, changing the major mode
;; clobbers any local variables. Work around that by declaring the
;; variable permanent-local.
(defvar mu4e~view-message nil "The message being viewed in view mode.")
(put 'mu4e~view-message 'permanent-local t)
;;; _
(provide 'mu4e-vars)
;;; mu4e-vars.el ends here

535
mu4e/mu4e-view-common.el Normal file
View File

@ -0,0 +1,535 @@
;;; mu4e-view-utils.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;; Copyright (C) 2021 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 common utils for 'old' and 'gnus' view mode.
;;; Code:
(require 'cl-lib)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-vars)
(require 'mu4e-headers)
(require 'mu4e-mark)
(require 'mu4e-proc)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
(require 'comint)
(require 'browse-url)
(require 'button)
(require 'epa)
(require 'epg)
(require 'thingatpt)
;;; Options
(defcustom mu4e-view-scroll-to-next t
"Move to the next message when calling
`mu4e-view-scroll-up-or-next' (typically bound to SPC) when at
the end of a message. Otherwise, don't move to the next message."
:type 'boolean
:group 'mu4e-view)
(defcustom mu4e-view-fields
'(:from :to :cc :subject :flags :date :maildir :mailing-list :tags
: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)
:group 'mu4e-view)
(defcustom mu4e-view-actions
'( ("capture message" . mu4e-action-capture-message)
("view as pdf" . mu4e-action-view-as-pdf)
("show this thread" . mu4e-action-show-thread))
"List of actions to perform on messages in view mode.
The actions are cons-cells of the form:
(NAME . FUNC)
where:
* NAME is the name of the action (e.g. \"Count lines\")
* FUNC is a function which receives a message plist as an argument.
The first letter of NAME is used as a shortcut character."
:group 'mu4e-view
:type '(alist :key-type string :value-type function))
;; Helpers
(defun mu4e~view-quit-buffer ()
"Quit the mu4e-view buffer.
This is a rather complex function, to ensure we don't disturb
other windows."
(interactive)
(if (eq mu4e-split-view 'single-window)
(when (buffer-live-p (mu4e-get-view-buffer))
(kill-buffer (mu4e-get-view-buffer)))
(unless (eq major-mode 'mu4e-view-mode)
(mu4e-error "Must be in mu4e-view-mode (%S)" major-mode))
(let ((curbuf (current-buffer))
(curwin (selected-window))
(headers-win))
(walk-windows
(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!
;; 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)
;; 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))))))))
(defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*"
"Name for the raw message view buffer.")
(defun mu4e-view-raw-message ()
"Display the raw contents of message at point in a new buffer."
(interactive)
(let ((path (mu4e-message-field-at-point :path))
(buf (get-buffer-create mu4e~view-raw-buffer-name)))
(unless (and path (file-readable-p path))
(mu4e-error "Not a readable file: %S" path))
(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)))
(defun mu4e-view-pipe (cmd)
"Pipe the message at point through shell command CMD.
Then, display the results."
(interactive "sShell command: ")
(let ((path (mu4e-message-field (mu4e-message-at-point) :path)))
(mu4e-process-file-through-pipe path cmd)))
(defmacro mu4e~view-in-headers-context (&rest body)
"Evaluate BODY in the context of the headers buffer connected to
this view."
`(progn
(unless (buffer-live-p (mu4e-get-headers-buffer))
(mu4e-error "no headers buffer connected"))
(let* ((msg (mu4e-message-at-point))
(docid (mu4e-message-field msg :docid)))
(unless docid
(mu4e-error "message without docid: action is not possible."))
(with-current-buffer (mu4e-get-headers-buffer)
(unless (eq mu4e-split-view 'single-window)
(when (get-buffer-window)
(select-window (get-buffer-window))))
(if (mu4e~headers-goto-docid docid)
,@body
(mu4e-error "cannot find message in headers buffer."))))))
(defun mu4e-view-headers-next (&optional n)
"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")
(mu4e~view-in-headers-context
(mu4e~headers-move (or n 1))))
(defun mu4e-view-headers-prev (&optional n)
"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")
(mu4e~view-in-headers-context
(mu4e~headers-move (- (or n 1)))))
(defun mu4e~view-prev-or-next-unread (backwards)
"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."
(mu4e~view-in-headers-context
(mu4e~headers-prev-or-next-unread backwards))
(if (eq mu4e-split-view 'single-window)
(when (eq (window-buffer) (mu4e-get-view-buffer))
(with-current-buffer (mu4e-get-headers-buffer)
(mu4e-headers-view-message)))
(mu4e-select-other-view)
(mu4e-headers-view-message)))
(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))
;;; Interactive functions
(defun mu4e-view-action (&optional msg)
"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
`mu4e-view-actions'."
(interactive)
(let* ((msg (or msg (mu4e-message-at-point)))
(actionfunc (mu4e-read-option "Action: " mu4e-view-actions)))
(funcall actionfunc msg)))
(defun mu4e-view-mark-pattern ()
"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)
(mu4e~view-in-headers-context (mu4e-headers-mark-pattern)))
(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."
(interactive)
(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."
(interactive)
(mu4e~view-in-headers-context
(if markpair (mu4e-headers-mark-subthread markpair)
(mu4e-headers-mark-subthread))))
(defun mu4e-view-search-narrow ()
"Run `mu4e-headers-search-narrow' in the headers buffer."
(interactive)
(mu4e~view-in-headers-context
(call-interactively 'mu4e-headers-search-narrow)))
(defun mu4e-view-search-edit ()
"Run `mu4e-headers-search-edit' in the headers buffer."
(interactive)
(mu4e~view-in-headers-context (mu4e-headers-search-edit)))
(defun mu4e-mark-region-code ()
"Highlight region marked with `message-mark-inserted-region'.
Add this function to `mu4e-view-mode-hook' to enable this feature."
(require 'message)
(let (beg end ov-beg ov-end ov-inv)
(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))))))
;;; View Utilities
(defun mu4e-view-mark-custom ()
"Run some custom mark function."
(mu4e~view-in-headers-context
(mu4e-headers-mark-custom)))
(defun mu4e~view-split-view-p ()
"Return t if we're in split-view, nil otherwise."
(member mu4e-split-view '(horizontal vertical)))
;;; Scroll commands
(defun mu4e-view-scroll-up-or-next ()
"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."
(interactive)
(condition-case nil
(scroll-up)
(error
(when mu4e-view-scroll-to-next
(mu4e-view-headers-next)))))
(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))
;;; Mark commands
(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)
(if (mu4e~view-split-view-p)
(mu4e~view-in-headers-context (mu4e-mark-unmark-all))
(mu4e-message "Unmarking needs to be done in the header list view")))
(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~view-split-view-p)
(mu4e-view-mark-for-unmark)
(mu4e-message "Unmarking needs to be done in the header list view")))
(defmacro mu4e~view-defun-mark-for (mark)
"Define a function mu4e-view-mark-for-MARK."
(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))))
(mu4e~view-defun-mark-for move)
(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)
(mu4e~view-defun-mark-for something)
(mu4e~view-defun-mark-for read)
(mu4e~view-defun-mark-for unread)
(mu4e~view-defun-mark-for trash)
(mu4e~view-defun-mark-for untrash)
(defun mu4e-view-marked-execute ()
"Execute the marked actions."
(interactive)
(mu4e~view-in-headers-context
(mu4e-mark-execute-all)))
;;; URL handling
(defvar mu4e~view-link-map nil
"A map of some number->url so we can jump to url by number.")
(put 'mu4e~view-link-map 'permanent-local t)
(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)
map)
"Keymap used for the urls inside the body.")
(defvar mu4e~view-beginning-of-url-regexp
"https?\\://\\|mailto:"
"Regexp that matches the beginning of http:/https:/mailto:
URLs; match-string 1 will contain the matched URL, if any.")
(defun mu4e~view-browse-url-from-binding (&optional url)
"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."
(interactive)
(let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url))))
(when url
(if (string-match-p "^mailto:" url)
(browse-url-mail url)
(browse-url url)))))
(defun mu4e~view-get-property-from-event (prop)
"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."
(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)))
))
(get-text-property (point) prop)))
;; this is fairly simplistic...
(defun mu4e~view-make-urls-clickable ()
"Turn things that look like URLs into clickable things.
Also number them so they can be opened using `mu4e-view-go-to-url'."
(let ((num 0))
(save-excursion
(setq mu4e~view-link-map ;; buffer local
(make-hash-table :size 32 :weakness nil))
(goto-char (point-min))
(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 (cl-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 "\u200B[%d]" num)
'face 'mu4e-url-number-face)))))))))
(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)
"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")
(mu4e~view-handle-urls "URL to visit"
multi
(lambda (url) (mu4e~view-browse-url-from-binding url))))
(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")
(mu4e~view-handle-urls "URL to save" multi
(lambda (url)
(kill-new url)
(mu4e-message "Saved %s to the kill-ring" url))))
(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)))))
(defun mu4e~view-handle-urls (prompt multi urlfunc)
"If MULTI is nil, apply URLFUNC to a single uri, otherwise, apply
it to a range of uris. PROMPT is the query to present to the user."
(if multi
(mu4e~view-handle-multi-urls prompt urlfunc)
(mu4e~view-handle-single-url prompt urlfunc)))
(defun mu4e~view-handle-single-url (prompt urlfunc &optional num)
"Apply URLFUNC to url NUM in the current message, prompting the
user with PROMPT."
(let* ((num (or num (mu4e~view-get-urls-num prompt)))
(url (gethash num mu4e~view-link-map)))
(unless url (mu4e-warn "Invalid number for URL"))
(funcall urlfunc url)))
(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.
Default is to apply 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.
Furthermore, there is a shortcut \"a\" which means all urls, but as
this is the default, you may not need it."
(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)
(mu4e~view-handle-single-url prompt urlfunc num))))
(defun mu4e-view-for-each-uri (func)
"Evaluate FUNC(uri) for each uri in the current message."
(maphash (lambda (_num uri) (funcall func uri)) mu4e~view-link-map))
(provide 'mu4e-view-common)

View File

@ -27,80 +27,18 @@
;;; Code:
(require 'cl-lib)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-vars)
(require 'mu4e-mark)
(require 'mu4e-proc)
(require 'mu4e-actions)
(require 'mu4e-compose)
(require 'mu4e-message)
(eval-when-compile (require 'gnus-art))
(require 'comint)
(require 'button)
(require 'epa)
(require 'epg)
(require 'thingatpt)
(require 'mu4e-view-common)
(require 'calendar)
(declare-function mu4e-view-mode "mu4e-view")
(defvar gnus-icalendar-additional-identities)
(defvar mu4e~headers-view-win)
(defvar helm-comp-read-use-marked)
;;; Options
(require 'gnus-art)
;;; Variables
;; It's useful to have the current view message available to
;; `mu4e-view-mode-hooks' functions, and we set up this variable
;; before calling `mu4e-view-mode'. However, changing the major mode
;; clobbers any local variables. Work around that by declaring the
;; variable permanent-local.
(defvar-local mu4e~view-message nil
"The message being viewed in view mode.")
(put 'mu4e~view-message 'permanent-local t)
(defvar gnus-icalendar-additional-identities)
(defvar helm-comp-read-use-marked)
(defvar mu4e~view-rendering nil)
;;; Main
(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")
(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)))
(defun mu4e~view-embedded-winbuf ()
"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)))
(defun mu4e~delete-all-overlays ()
"`delete-all-overlays' with compatibility fallback."
(if (functionp 'delete-all-overlays)
(delete-all-overlays)
(remove-overlays)))
;; remember the mime-handles, so we can clean them up when
;; we quit this buffer.
(defvar-local mu4e~gnus-article-mime-handles nil)
@ -108,7 +46,6 @@ found."
(defun mu4e~view-gnus (msg)
"View MSG using Gnus' article mode. Experimental."
(require 'gnus-art)
(let ((path (mu4e-message-field msg :path))
(inhibit-read-only t)
(mm-decrypt-option 'known)
@ -138,11 +75,12 @@ found."
(gnus-display-mime-function (mu4e~view-gnus-display-mime msg))
(gnus-icalendar-additional-identities
(mu4e-personal-addresses 'no-regexp)))
(mu4e-view-mode)
(gnus-article-prepare-display))
(setq mu4e~gnus-article-mime-handles gnus-article-mime-handles)
(setq mu4e~view-message msg)
;; `mu4e-view-mode' derive from `gnus-article-mode'.
(mu4e-view-mode)
(mu4e~view-make-urls-clickable)
(setq gnus-article-decoded-p gnus-article-decode-hook)
(set-buffer-modified-p nil)
(add-hook 'kill-buffer-hook #'mu4e~view-kill-buffer-hook-fn)))
@ -170,8 +108,8 @@ found."
((:flags :tags)
(let ((flags (mapconcat (lambda (flag)
(if (symbolp flag)
(symbol-name flag)
flag)) fieldval ", ")))
(symbol-name flag)
flag)) fieldval ", ")))
(mu4e~view-gnus-insert-header field flags)))
(:size (mu4e~view-gnus-insert-header
field (mu4e-display-size fieldval)))
@ -188,7 +126,7 @@ found."
(defun mu4e~view-gnus-insert-header (field val)
"Insert a header FIELD with value VAL in Gnus article view."
(let* ((info (cdr (assoc field mu4e-header-info)))
(key (plist-get info :name))
(key (plist-get info :name))
(help (plist-get info :help)))
(if (and val (> (length val) 0))
(insert (propertize (concat key ":") 'help-echo help)
@ -198,10 +136,10 @@ found."
"Insert the custom FIELD in Gnus article view."
(let* ((info (cdr-safe (or (assoc field mu4e-header-info-custom)
(mu4e-error "custom field %S not found" field))))
(key (plist-get info :name))
(key (plist-get info :name))
(func (or (plist-get info :function)
(mu4e-error "no :function defined for custom field %S %S"
field info)))
field info)))
(val (funcall func msg))
(help (plist-get info :help)))
(when (and val (> (length val) 0))
@ -251,14 +189,11 @@ with no charset."
(define-key map "%" 'mu4e-view-mark-pattern)
(define-key map "t" 'mu4e-view-mark-subthread)
(define-key map "T" 'mu4e-view-mark-thread)
(define-key map "v" 'mu4e-view-verify-msg-popup)
(define-key map "j" 'mu4e~headers-jump-to-maildir)
(define-key map "g" 'ignore)
(define-key map "k" 'ignore)
(define-key map "f" 'ignore)
(define-key map "g" 'mu4e-view-go-to-url)
(define-key map "k" 'mu4e-view-save-url)
(define-key map "f" 'mu4e-view-fetch-url)
(define-key map "F" 'mu4e-compose-forward)
(define-key map "R" 'mu4e-compose-reply)
@ -448,6 +383,19 @@ Gnus' article-mode."
(setq mu4e~view-buffer-name gnus-article-buffer)
(mu4e~view-mode-body))
(defun mu4e-view-message-text (msg)
"Return the message to display (as a string)."
(with-temp-buffer
(let ((path (mu4e-message-field msg :path))
(inhibit-read-only t)
(mm-decrypt-option 'known)
(gnus-article-emulate-mime t))
(buffer-disable-undo)
(insert-file-contents-literally path nil nil nil t)
(mm-enable-multibyte)
(gnus-article-prepare-display)
(buffer-string))))
(defun mu4e-view-save-attachment (&optional arg)
"Save mime parts from current mu4e gnus view buffer.
@ -461,7 +409,7 @@ attachments is done with `completing-read-multiple', in this case use
(let ((handles '())
(files '())
(helm-comp-read-use-marked t)
(compfn (if helm-mode
(compfn (if (and (boundp 'helm-mode) helm-mode)
#'completing-read
;; Fallback to `completing-read-multiple' with poor
;; completion systems.

View File

@ -1,4 +1,4 @@
;;; mu4e-view.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;;; mu4e-view-old.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
@ -28,23 +28,9 @@
;;; Code:
(require 'cl-lib)
(require 'mu4e-utils) ;; utility functions
(require 'mu4e-vars)
(require 'mu4e-mark)
(require 'mu4e-proc)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
(require 'mu4e-view-common)
(require 'comint)
(require 'browse-url)
(require 'button)
(require 'epa)
(require 'epg)
(require 'thingatpt)
(require 'calendar)
(defvar mu4e~headers-view-win)
(declare-function mu4e-view "mu4e-view")
;;; Options
(defcustom mu4e-view-show-addresses nil
@ -126,12 +112,6 @@ The first letter of NAME is used as a shortcut character."
map)
"Keymap used for the contacts in the header fields.")
(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)
map)
"Keymap used for the urls inside the body.")
(defvar mu4e-view-attachments-header-keymap
(let ((map (make-sparse-keymap)))
@ -159,10 +139,6 @@ The first letter of NAME is used as a shortcut character."
(defvar mu4e~view-cited-hidden nil "Whether cited lines are hidden.")
(put 'mu4e~view-cited-hidden 'permanent-local t)
(defvar mu4e~view-link-map nil
"A map of some number->url so we can jump to url by number.")
(put 'mu4e~view-link-map 'permanent-local t)
(defvar mu4e~path-parent-docid-map (make-hash-table :test 'equal)
"A map of msg paths --> parent-docids.
This is to determine what is the parent docid for embedded
@ -181,15 +157,6 @@ or `html' or nil.")
;;; Main
(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")
(defun mu4e~view-custom-field (msg field)
"Show some custom header field, or raise an error if it is not
found."
@ -304,20 +271,6 @@ found."
(when embedded (local-set-key "q" 'kill-buffer-and-window)))
(switch-to-buffer buf))))
(defun mu4e~view-get-property-from-event (prop)
"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."
(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)))
))
(get-text-property (point) prop)))
(defun mu4e~view-construct-header (field val &optional dont-propertize-val)
"Return header field FIELD (as in `mu4e-header-info') with value
@ -805,16 +758,6 @@ The browser that is called depends on
(interactive)
(browse-url url)))))
(defun mu4e~view-browse-url-from-binding (&optional url)
"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."
(interactive)
(let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url))))
(when url
(if (string-match-p "^mailto:" url)
(browse-url-mail url)
(browse-url url)))))
(defun mu4e~view-show-images-maybe (msg)
"Show attached images, if `mu4e-show-images' is non-nil."
@ -832,39 +775,6 @@ If the url is mailto link, start writing an email to that address."
mu4e-view-image-max-width
mu4e-view-image-max-height)))))))))
(defvar mu4e~view-beginning-of-url-regexp
"https?\\://\\|mailto:"
"Regexp that matches the beginning of http:/https:/mailto:
URLs; match-string 1 will contain the matched URL, if any.")
;; this is fairly simplistic...
(defun mu4e~view-make-urls-clickable ()
"Turn things that look like URLs into clickable things.
Also number them so they can be opened using `mu4e-view-go-to-url'."
(let ((num 0))
(save-excursion
(setq mu4e~view-link-map ;; buffer local
(make-hash-table :size 32 :weakness nil))
(goto-char (point-min))
(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 (cl-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 "\u200B[%d]" num)
'face 'mu4e-url-number-face)))))))))
(defun mu4e~view-hide-cited ()
"Toggle hiding of cited lines in the message body."
@ -1167,92 +1077,6 @@ attachments) in response to a (mu4e~proc-extract 'temp ... )."
(t (mu4e-error "Unsupported action %S" what))))
;;; 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)
"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")
(mu4e~view-handle-urls "URL to visit"
multi
(lambda (url) (mu4e~view-browse-url-from-binding url))))
(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")
(mu4e~view-handle-urls "URL to save" multi
(lambda (url)
(kill-new url)
(mu4e-message "Saved %s to the kill-ring" url))))
(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)))))
(defun mu4e~view-handle-urls (prompt multi urlfunc)
"If MULTI is nil, apply URLFUNC to a single uri, otherwise, apply
it to a range of uris. PROMPT is the query to present to the user."
(if multi
(mu4e~view-handle-multi-urls prompt urlfunc)
(mu4e~view-handle-single-url prompt urlfunc)))
(defun mu4e~view-handle-single-url (prompt urlfunc &optional num)
"Apply URLFUNC to url NUM in the current message, prompting the
user with PROMPT."
(let* ((num (or num (mu4e~view-get-urls-num prompt)))
(url (gethash num mu4e~view-link-map)))
(unless url (mu4e-warn "Invalid number for URL"))
(funcall urlfunc url)))
(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.
Default is to apply 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.
Furthermore, there is a shortcut \"a\" which means all urls, but as
this is the default, you may not need it."
(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)
(mu4e~view-handle-single-url prompt urlfunc num))))
(defun mu4e-view-for-each-uri (func)
"Evaluate FUNC(uri) for each uri in the current message."
(maphash (lambda (_num uri) (funcall func uri)) mu4e~view-link-map))
;;; Various commands
(defconst mu4e~verify-buffer-name " *mu4e-verify*")

File diff suppressed because it is too large Load Diff