mirror of https://github.com/djcb/mu.git
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:
parent
316633e1b2
commit
e2655ba34b
|
@ -40,11 +40,12 @@ dist_lisp_LISP= \
|
||||||
mu4e-utils.el \
|
mu4e-utils.el \
|
||||||
mu4e-vars.el \
|
mu4e-vars.el \
|
||||||
mu4e-view.el \
|
mu4e-view.el \
|
||||||
|
mu4e-view-common.el \
|
||||||
|
mu4e-view-gnus.el \
|
||||||
|
mu4e-view-old.el \
|
||||||
mu4e.el \
|
mu4e.el \
|
||||||
obsolete/org-mu4e.el
|
obsolete/org-mu4e.el
|
||||||
|
|
||||||
# mu4e-view-gnus.el
|
|
||||||
# mu4e-view-old.el
|
|
||||||
|
|
||||||
EXTRA_DIST= \
|
EXTRA_DIST= \
|
||||||
mu4e-about.org
|
mu4e-about.org
|
||||||
|
|
|
@ -47,6 +47,7 @@ mu4e_srcs=[
|
||||||
'mu4e-utils.el',
|
'mu4e-utils.el',
|
||||||
'mu4e-vars.el',
|
'mu4e-vars.el',
|
||||||
'mu4e-view.el',
|
'mu4e-view.el',
|
||||||
|
'mu4e-view-common.el',
|
||||||
'mu4e-view-gnus.el',
|
'mu4e-view-gnus.el',
|
||||||
'mu4e-view-old.el',
|
'mu4e-view-old.el',
|
||||||
'obsolete/org-mu4e.el',
|
'obsolete/org-mu4e.el',
|
||||||
|
|
|
@ -65,6 +65,17 @@ Setting this to t increases the amount of information in the log."
|
||||||
:type 'boolean
|
:type 'boolean
|
||||||
:group 'mu4e)
|
: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
|
(defcustom mu4e-speedbar-support nil
|
||||||
"Support having a speedbar to navigate folders/bookmarks."
|
"Support having a speedbar to navigate folders/bookmarks."
|
||||||
:type 'boolean
|
:type 'boolean
|
||||||
|
@ -1137,6 +1148,18 @@ sexp received from the server process.")
|
||||||
(defvar mu4e-temp-func 'mu4e~view-temp-handler
|
(defvar mu4e-temp-func 'mu4e~view-temp-handler
|
||||||
"A function called for each (:temp <file> <cookie>) sexp.")
|
"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)
|
(provide 'mu4e-vars)
|
||||||
;;; mu4e-vars.el ends here
|
;;; mu4e-vars.el ends here
|
||||||
|
|
|
@ -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)
|
|
@ -27,80 +27,18 @@
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'cl-lib)
|
(require 'mu4e-view-common)
|
||||||
(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 'calendar)
|
(require 'calendar)
|
||||||
|
(require 'gnus-art)
|
||||||
(declare-function mu4e-view-mode "mu4e-view")
|
|
||||||
(defvar gnus-icalendar-additional-identities)
|
|
||||||
(defvar mu4e~headers-view-win)
|
|
||||||
(defvar helm-comp-read-use-marked)
|
|
||||||
|
|
||||||
;;; Options
|
|
||||||
|
|
||||||
;;; Variables
|
;;; Variables
|
||||||
|
|
||||||
;; It's useful to have the current view message available to
|
(defvar gnus-icalendar-additional-identities)
|
||||||
;; `mu4e-view-mode-hooks' functions, and we set up this variable
|
(defvar helm-comp-read-use-marked)
|
||||||
;; 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 mu4e~view-rendering nil)
|
(defvar mu4e~view-rendering nil)
|
||||||
|
|
||||||
;;; Main
|
;;; 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
|
;; remember the mime-handles, so we can clean them up when
|
||||||
;; we quit this buffer.
|
;; we quit this buffer.
|
||||||
(defvar-local mu4e~gnus-article-mime-handles nil)
|
(defvar-local mu4e~gnus-article-mime-handles nil)
|
||||||
|
@ -108,7 +46,6 @@ found."
|
||||||
|
|
||||||
(defun mu4e~view-gnus (msg)
|
(defun mu4e~view-gnus (msg)
|
||||||
"View MSG using Gnus' article mode. Experimental."
|
"View MSG using Gnus' article mode. Experimental."
|
||||||
(require 'gnus-art)
|
|
||||||
(let ((path (mu4e-message-field msg :path))
|
(let ((path (mu4e-message-field msg :path))
|
||||||
(inhibit-read-only t)
|
(inhibit-read-only t)
|
||||||
(mm-decrypt-option 'known)
|
(mm-decrypt-option 'known)
|
||||||
|
@ -138,11 +75,12 @@ found."
|
||||||
(gnus-display-mime-function (mu4e~view-gnus-display-mime msg))
|
(gnus-display-mime-function (mu4e~view-gnus-display-mime msg))
|
||||||
(gnus-icalendar-additional-identities
|
(gnus-icalendar-additional-identities
|
||||||
(mu4e-personal-addresses 'no-regexp)))
|
(mu4e-personal-addresses 'no-regexp)))
|
||||||
|
(mu4e-view-mode)
|
||||||
(gnus-article-prepare-display))
|
(gnus-article-prepare-display))
|
||||||
(setq mu4e~gnus-article-mime-handles gnus-article-mime-handles)
|
(setq mu4e~gnus-article-mime-handles gnus-article-mime-handles)
|
||||||
(setq mu4e~view-message msg)
|
(setq mu4e~view-message msg)
|
||||||
;; `mu4e-view-mode' derive from `gnus-article-mode'.
|
;; `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)
|
(setq gnus-article-decoded-p gnus-article-decode-hook)
|
||||||
(set-buffer-modified-p nil)
|
(set-buffer-modified-p nil)
|
||||||
(add-hook 'kill-buffer-hook #'mu4e~view-kill-buffer-hook-fn)))
|
(add-hook 'kill-buffer-hook #'mu4e~view-kill-buffer-hook-fn)))
|
||||||
|
@ -251,14 +189,11 @@ with no charset."
|
||||||
(define-key map "%" 'mu4e-view-mark-pattern)
|
(define-key map "%" 'mu4e-view-mark-pattern)
|
||||||
(define-key map "t" 'mu4e-view-mark-subthread)
|
(define-key map "t" 'mu4e-view-mark-subthread)
|
||||||
(define-key map "T" 'mu4e-view-mark-thread)
|
(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 "j" 'mu4e~headers-jump-to-maildir)
|
||||||
|
|
||||||
(define-key map "g" 'ignore)
|
(define-key map "g" 'mu4e-view-go-to-url)
|
||||||
(define-key map "k" 'ignore)
|
(define-key map "k" 'mu4e-view-save-url)
|
||||||
(define-key map "f" 'ignore)
|
(define-key map "f" 'mu4e-view-fetch-url)
|
||||||
|
|
||||||
(define-key map "F" 'mu4e-compose-forward)
|
(define-key map "F" 'mu4e-compose-forward)
|
||||||
(define-key map "R" 'mu4e-compose-reply)
|
(define-key map "R" 'mu4e-compose-reply)
|
||||||
|
@ -448,6 +383,19 @@ Gnus' article-mode."
|
||||||
(setq mu4e~view-buffer-name gnus-article-buffer)
|
(setq mu4e~view-buffer-name gnus-article-buffer)
|
||||||
(mu4e~view-mode-body))
|
(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)
|
(defun mu4e-view-save-attachment (&optional arg)
|
||||||
"Save mime parts from current mu4e gnus view buffer.
|
"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 '())
|
(let ((handles '())
|
||||||
(files '())
|
(files '())
|
||||||
(helm-comp-read-use-marked t)
|
(helm-comp-read-use-marked t)
|
||||||
(compfn (if helm-mode
|
(compfn (if (and (boundp 'helm-mode) helm-mode)
|
||||||
#'completing-read
|
#'completing-read
|
||||||
;; Fallback to `completing-read-multiple' with poor
|
;; Fallback to `completing-read-multiple' with poor
|
||||||
;; completion systems.
|
;; completion systems.
|
||||||
|
|
|
@ -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
|
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
|
||||||
|
|
||||||
|
@ -28,23 +28,9 @@
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'mu4e-utils) ;; utility functions
|
(require 'mu4e-view-common)
|
||||||
(require 'mu4e-vars)
|
|
||||||
(require 'mu4e-mark)
|
|
||||||
(require 'mu4e-proc)
|
|
||||||
(require 'mu4e-compose)
|
|
||||||
(require 'mu4e-actions)
|
|
||||||
(require 'mu4e-message)
|
|
||||||
|
|
||||||
(require 'comint)
|
(declare-function mu4e-view "mu4e-view")
|
||||||
(require 'browse-url)
|
|
||||||
(require 'button)
|
|
||||||
(require 'epa)
|
|
||||||
(require 'epg)
|
|
||||||
(require 'thingatpt)
|
|
||||||
(require 'calendar)
|
|
||||||
|
|
||||||
(defvar mu4e~headers-view-win)
|
|
||||||
|
|
||||||
;;; Options
|
;;; Options
|
||||||
(defcustom mu4e-view-show-addresses nil
|
(defcustom mu4e-view-show-addresses nil
|
||||||
|
@ -126,12 +112,6 @@ The first letter of NAME is used as a shortcut character."
|
||||||
map)
|
map)
|
||||||
"Keymap used for the contacts in the header fields.")
|
"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
|
(defvar mu4e-view-attachments-header-keymap
|
||||||
(let ((map (make-sparse-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.")
|
(defvar mu4e~view-cited-hidden nil "Whether cited lines are hidden.")
|
||||||
(put 'mu4e~view-cited-hidden 'permanent-local t)
|
(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)
|
(defvar mu4e~path-parent-docid-map (make-hash-table :test 'equal)
|
||||||
"A map of msg paths --> parent-docids.
|
"A map of msg paths --> parent-docids.
|
||||||
This is to determine what is the parent docid for embedded
|
This is to determine what is the parent docid for embedded
|
||||||
|
@ -181,15 +157,6 @@ or `html' or nil.")
|
||||||
|
|
||||||
;;; Main
|
;;; 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)
|
(defun mu4e~view-custom-field (msg field)
|
||||||
"Show some custom header field, or raise an error if it is not
|
"Show some custom header field, or raise an error if it is not
|
||||||
found."
|
found."
|
||||||
|
@ -304,20 +271,6 @@ found."
|
||||||
(when embedded (local-set-key "q" 'kill-buffer-and-window)))
|
(when embedded (local-set-key "q" 'kill-buffer-and-window)))
|
||||||
(switch-to-buffer buf))))
|
(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)
|
(defun mu4e~view-construct-header (field val &optional dont-propertize-val)
|
||||||
"Return header field FIELD (as in `mu4e-header-info') with value
|
"Return header field FIELD (as in `mu4e-header-info') with value
|
||||||
|
@ -805,16 +758,6 @@ The browser that is called depends on
|
||||||
(interactive)
|
(interactive)
|
||||||
(browse-url url)))))
|
(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)
|
(defun mu4e~view-show-images-maybe (msg)
|
||||||
"Show attached images, if `mu4e-show-images' is non-nil."
|
"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-width
|
||||||
mu4e-view-image-max-height)))))))))
|
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 ()
|
(defun mu4e~view-hide-cited ()
|
||||||
"Toggle hiding of cited lines in the message body."
|
"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))))
|
(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
|
;;; Various commands
|
||||||
|
|
||||||
(defconst mu4e~verify-buffer-name " *mu4e-verify*")
|
(defconst mu4e~verify-buffer-name " *mu4e-verify*")
|
||||||
|
|
1828
mu4e/mu4e-view.el
1828
mu4e/mu4e-view.el
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue