2019-05-05 14:11:59 -04:00
|
|
|
;;; email/mu4e/autoload/email.el -*- lexical-binding: t; -*-
|
2017-04-19 13:18:03 -04:00
|
|
|
|
2018-06-15 17:37:58 +02:00
|
|
|
;;;###autodef
|
|
|
|
(defun set-email-account! (label letvars &optional default-p)
|
|
|
|
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
|
|
|
|
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
|
|
|
|
|
|
|
|
+ `user-full-name' (this or the global `user-full-name' is required)
|
2020-04-22 23:38:07 +02:00
|
|
|
+ `user-mail-address' (required in mu4e < 1.4)
|
2018-06-15 17:37:58 +02:00
|
|
|
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
|
|
|
|
|
|
|
|
OPTIONAL:
|
|
|
|
+ `mu4e-sent-folder'
|
|
|
|
+ `mu4e-drafts-folder'
|
|
|
|
+ `mu4e-trash-folder'
|
|
|
|
+ `mu4e-refile-folder'
|
|
|
|
+ `mu4e-compose-signature'
|
2020-10-16 10:48:41 +08:00
|
|
|
+ `+mu4e-personal-addresses'
|
2018-06-15 17:37:58 +02:00
|
|
|
|
|
|
|
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
|
|
|
|
default/fallback account."
|
|
|
|
(after! mu4e
|
2020-04-22 23:38:07 +02:00
|
|
|
(when (version< mu4e-mu-version "1.4")
|
|
|
|
(when-let (address (cdr (assq 'user-mail-address letvars)))
|
|
|
|
(add-to-list 'mu4e-user-mail-address-list address)))
|
2021-07-10 13:50:20 -05:00
|
|
|
;; remove existing context with same label
|
2018-06-15 17:37:58 +02:00
|
|
|
(setq mu4e-contexts
|
2018-06-16 01:12:41 +02:00
|
|
|
(cl-loop for context in mu4e-contexts
|
|
|
|
unless (string= (mu4e-context-name context) label)
|
|
|
|
collect context))
|
2018-06-15 17:37:58 +02:00
|
|
|
(let ((context (make-mu4e-context
|
|
|
|
:name label
|
2021-07-10 13:47:44 -05:00
|
|
|
:enter-func
|
|
|
|
(lambda () (mu4e-message "Switched to %s" label))
|
|
|
|
:leave-func
|
|
|
|
(lambda () (progn (setq +mu4e-personal-addresses nil)
|
|
|
|
(mu4e-clear-caches)))
|
2018-06-15 17:37:58 +02:00
|
|
|
:match-func
|
|
|
|
(lambda (msg)
|
|
|
|
(when msg
|
2018-06-16 01:12:41 +02:00
|
|
|
(string-prefix-p (format "/%s" label)
|
2021-07-10 13:51:31 -05:00
|
|
|
(mu4e-message-field msg :maildir) t)))
|
2018-06-15 17:37:58 +02:00
|
|
|
:vars letvars)))
|
2021-07-10 13:50:20 -05:00
|
|
|
(add-to-list 'mu4e-contexts context (not default-p))
|
2018-06-15 17:37:58 +02:00
|
|
|
context)))
|
|
|
|
|
|
|
|
|
2019-05-05 14:11:59 -04:00
|
|
|
(defvar +mu4e-workspace-name "*mu4e*"
|
2020-10-14 15:37:15 +08:00
|
|
|
"Name of the workspace created by `=mu4e', dedicated to mu4e.")
|
2019-12-01 09:30:33 +08:00
|
|
|
(defvar +mu4e--old-wconf nil)
|
2018-02-18 03:09:39 -05:00
|
|
|
|
2019-07-23 12:30:47 +02:00
|
|
|
(add-hook 'mu4e-main-mode-hook #'+mu4e-init-h)
|
2018-02-18 03:09:39 -05:00
|
|
|
|
2017-04-19 13:18:03 -04:00
|
|
|
;;;###autoload
|
2019-05-05 14:11:59 -04:00
|
|
|
(defun =mu4e ()
|
2017-04-19 13:18:03 -04:00
|
|
|
"Start email client."
|
|
|
|
(interactive)
|
2018-02-24 20:26:35 -05:00
|
|
|
(require 'mu4e)
|
2019-12-08 23:42:46 +01:00
|
|
|
(if (featurep! :ui workspaces)
|
2020-10-04 01:01:00 +08:00
|
|
|
;; delete current workspace if empty
|
|
|
|
;; this is useful when mu4e is in the daemon
|
|
|
|
;; as otherwise you can accumulate empty workspaces
|
2020-10-13 17:26:26 +08:00
|
|
|
(progn
|
|
|
|
(unless (+workspace-buffer-list)
|
|
|
|
(+workspace-delete (+workspace-current-name)))
|
|
|
|
(+workspace-switch +mu4e-workspace-name t))
|
2019-12-02 08:20:57 +08:00
|
|
|
(setq +mu4e--old-wconf (current-window-configuration))
|
|
|
|
(delete-other-windows)
|
|
|
|
(switch-to-buffer (doom-fallback-buffer)))
|
2018-02-18 03:09:39 -05:00
|
|
|
(mu4e~start 'mu4e~main-view)
|
|
|
|
;; (save-selected-window
|
|
|
|
;; (prolusion-mail-show))
|
|
|
|
)
|
2017-04-19 13:18:03 -04:00
|
|
|
|
|
|
|
;;;###autoload
|
2019-05-05 14:11:59 -04:00
|
|
|
(defun +mu4e/compose ()
|
2017-04-19 13:18:03 -04:00
|
|
|
"Compose a new email."
|
|
|
|
(interactive)
|
|
|
|
;; TODO Interactively select email account
|
2017-06-08 11:47:56 +02:00
|
|
|
(call-interactively #'mu4e-compose-new))
|
2017-04-19 13:18:03 -04:00
|
|
|
|
2020-10-14 14:15:24 +08:00
|
|
|
(defun +mu4e--get-string-width (str)
|
2020-09-23 13:00:33 +08:00
|
|
|
"Return the width in pixels of a string in the current
|
|
|
|
window's default font. If the font is mono-spaced, this
|
|
|
|
will also be the width of all other printable characters."
|
|
|
|
(let ((window (selected-window))
|
|
|
|
(remapping face-remapping-alist))
|
|
|
|
(with-temp-buffer
|
|
|
|
(make-local-variable 'face-remapping-alist)
|
|
|
|
(setq face-remapping-alist remapping)
|
|
|
|
(set-window-buffer window (current-buffer))
|
|
|
|
(insert str)
|
|
|
|
(car (window-text-pixel-size)))))
|
|
|
|
|
2020-10-14 17:21:20 +08:00
|
|
|
(cl-defun +mu4e-normalised-icon (name &key set color height v-adjust)
|
2020-09-23 13:00:33 +08:00
|
|
|
"Convert :icon declaration to icon"
|
|
|
|
(let* ((icon-set (intern (concat "all-the-icons-" (or set "faicon"))))
|
|
|
|
(v-adjust (or v-adjust 0.02))
|
|
|
|
(height (or height 0.8))
|
2020-10-14 17:21:20 +08:00
|
|
|
(icon (if color
|
|
|
|
(apply icon-set `(,name :face ,(intern (concat "all-the-icons-" color)) :height ,height :v-adjust ,v-adjust))
|
2020-09-23 13:00:33 +08:00
|
|
|
(apply icon-set `(,name :height ,height :v-adjust ,v-adjust))))
|
2020-10-14 14:15:24 +08:00
|
|
|
(icon-width (+mu4e--get-string-width icon))
|
|
|
|
(space-width (+mu4e--get-string-width " "))
|
2020-09-23 13:00:33 +08:00
|
|
|
(space-factor (- 2 (/ (float icon-width) space-width))))
|
|
|
|
(concat (propertize " " 'display `(space . (:width ,space-factor))) icon)))
|
|
|
|
|
|
|
|
;; Set up all the fancy icons
|
|
|
|
;;;###autoload
|
2020-10-13 16:41:33 +08:00
|
|
|
(defun +mu4e-initialise-icons ()
|
2020-09-23 13:00:33 +08:00
|
|
|
(setq mu4e-use-fancy-chars t
|
2020-10-13 16:41:33 +08:00
|
|
|
mu4e-headers-draft-mark (cons "D" (+mu4e-normalised-icon "pencil"))
|
|
|
|
mu4e-headers-flagged-mark (cons "F" (+mu4e-normalised-icon "flag"))
|
|
|
|
mu4e-headers-new-mark (cons "N" (+mu4e-normalised-icon "sync" :set "material" :height 0.8 :v-adjust -0.10))
|
|
|
|
mu4e-headers-passed-mark (cons "P" (+mu4e-normalised-icon "arrow-right"))
|
|
|
|
mu4e-headers-replied-mark (cons "R" (+mu4e-normalised-icon "arrow-right"))
|
2020-10-14 17:21:20 +08:00
|
|
|
mu4e-headers-seen-mark (cons "S" "") ;(+mu4e-normalised-icon "eye" :height 0.6 :v-adjust 0.07 :color "dsilver"))
|
2020-10-13 16:41:33 +08:00
|
|
|
mu4e-headers-trashed-mark (cons "T" (+mu4e-normalised-icon "trash"))
|
2020-10-14 17:21:20 +08:00
|
|
|
mu4e-headers-attach-mark (cons "a" (+mu4e-normalised-icon "file-text-o" :color "silver"))
|
2020-10-13 16:41:33 +08:00
|
|
|
mu4e-headers-encrypted-mark (cons "x" (+mu4e-normalised-icon "lock"))
|
2020-10-14 17:21:20 +08:00
|
|
|
mu4e-headers-signed-mark (cons "s" (+mu4e-normalised-icon "certificate" :height 0.7 :color "dpurple"))
|
2020-10-13 16:41:33 +08:00
|
|
|
mu4e-headers-unread-mark (cons "u" (+mu4e-normalised-icon "eye-slash" :v-adjust 0.05))))
|
2020-09-23 13:00:33 +08:00
|
|
|
|
2020-10-14 17:28:10 +08:00
|
|
|
(defun +mu4e-colorize-str (str &optional unique herring)
|
|
|
|
"Apply a face from `+mu4e-header-colorized-faces' to STR.
|
|
|
|
If HERRING is set, it will be used to determine the face instead of STR.
|
|
|
|
Will try to make unique when non-nil UNIQUE,
|
|
|
|
a quoted symbol for a alist of current strings and faces provided."
|
|
|
|
(unless herring
|
|
|
|
(setq herring str))
|
|
|
|
(put-text-property
|
|
|
|
0 (length str)
|
|
|
|
'face
|
|
|
|
(if (not unique)
|
|
|
|
(+mu4e--str-color-face herring str)
|
|
|
|
(let ((unique-alist (eval unique)))
|
|
|
|
(unless (assoc herring unique-alist)
|
|
|
|
(if (> (length unique-alist) (length +mu4e-header-colorized-faces))
|
|
|
|
(push (cons herring (+mu4e--str-color-face herring)) unique-alist)
|
|
|
|
(let ((offset 0) color color?)
|
|
|
|
(while (not color)
|
|
|
|
(setq color? (+mu4e--str-color-face herring offset))
|
|
|
|
(if (not (rassoc color? unique-alist))
|
|
|
|
(setq color color?)
|
|
|
|
(setq offset (1+ offset))
|
|
|
|
(when (> offset (length +mu4e-header-colorized-faces))
|
|
|
|
(message "Warning: +mu4e-colorize-str was called with non-unique-alist UNIQUE-alist alist.")
|
|
|
|
(setq color (+mu4e--str-color-face herring)))))
|
|
|
|
(push (cons herring color) unique-alist)))
|
|
|
|
(set unique unique-alist))
|
|
|
|
(cdr (assoc herring unique-alist))))
|
|
|
|
str)
|
|
|
|
str)
|
|
|
|
|
|
|
|
(defun +mu4e--str-color-face (str &optional offset)
|
|
|
|
"Select a face from `+mu4e-header-colorized-faces' based on
|
|
|
|
STR and any integer OFFSET."
|
|
|
|
(let* ((str-sum (apply #'+ (mapcar (lambda (c) (% c 3)) str)))
|
|
|
|
(color (nth (% (+ str-sum (if offset offset 0))
|
|
|
|
(length +mu4e-header-colorized-faces))
|
|
|
|
+mu4e-header-colorized-faces)))
|
|
|
|
color))
|
|
|
|
|
2020-10-31 19:04:29 +08:00
|
|
|
(defvar +org-capture-emails-file "todo.org"
|
|
|
|
"Default target for storing mu4e emails captured from within mu4e.
|
|
|
|
Requires a \"* Email\" heading be present in the file.")
|
|
|
|
|
2020-09-23 13:00:33 +08:00
|
|
|
;; Adding emails to the agenda
|
|
|
|
;; Perfect for when you see an email you want to reply to
|
|
|
|
;; later, but don't want to forget about
|
|
|
|
;;;###autoload
|
2020-10-31 19:04:29 +08:00
|
|
|
(defun +mu4e/capture-msg-to-agenda (arg)
|
|
|
|
"Refile a message and add a entry in `+org-capture-emails-file' with a
|
2020-09-23 13:00:33 +08:00
|
|
|
deadline. Default deadline is today. With one prefix, deadline
|
|
|
|
is tomorrow. With two prefixes, select the deadline."
|
|
|
|
(interactive "p")
|
2020-10-31 19:04:29 +08:00
|
|
|
(let ((sec "^* Email")
|
|
|
|
(msg (mu4e-message-at-point)))
|
2020-09-23 13:00:33 +08:00
|
|
|
(when msg
|
|
|
|
;; put the message in the agenda
|
2020-10-31 19:04:29 +08:00
|
|
|
(with-current-buffer (find-file-noselect
|
|
|
|
(expand-file-name +org-capture-emails-file org-directory))
|
2020-09-23 13:00:33 +08:00
|
|
|
(save-excursion
|
|
|
|
;; find header section
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when (re-search-forward sec nil t)
|
|
|
|
(let (org-M-RET-may-split-line
|
|
|
|
(lev (org-outline-level))
|
2021-02-04 12:23:39 +08:00
|
|
|
(folded-p (invisible-p (point-at-eol)))
|
|
|
|
(from (plist-get msg :from)))
|
2020-09-23 13:00:33 +08:00
|
|
|
;; place the subheader
|
|
|
|
(when folded-p (show-branches)) ; unfold if necessary
|
|
|
|
(org-end-of-meta-data) ; skip property drawer
|
|
|
|
(org-insert-todo-heading 1) ; insert a todo heading
|
|
|
|
(when (= (org-outline-level) lev) ; demote if necessary
|
|
|
|
(org-do-demote))
|
|
|
|
;; insert message and add deadline
|
|
|
|
(insert (concat " Respond to "
|
|
|
|
"[[mu4e:msgid:"
|
|
|
|
(plist-get msg :message-id) "]["
|
|
|
|
(truncate-string-to-width
|
2021-02-04 12:23:39 +08:00
|
|
|
(or (caar from) (cadr from)) 25 nil nil t)
|
2020-09-23 13:00:33 +08:00
|
|
|
" - "
|
|
|
|
(truncate-string-to-width
|
|
|
|
(plist-get msg :subject) 40 nil nil t)
|
|
|
|
"]] "))
|
|
|
|
(org-deadline nil
|
|
|
|
(cond ((= arg 1) (format-time-string "%Y-%m-%d"))
|
|
|
|
((= arg 4) "+1d")))
|
|
|
|
|
|
|
|
(org-update-parent-todo-statistics)
|
|
|
|
|
|
|
|
;; refold as necessary
|
|
|
|
(if folded-p
|
|
|
|
(progn
|
|
|
|
(org-up-heading-safe)
|
|
|
|
(hide-subtree))
|
|
|
|
(hide-entry))))))
|
|
|
|
;; refile the message and update
|
|
|
|
;; (cond ((eq major-mode 'mu4e-view-mode)
|
|
|
|
;; (mu4e-view-mark-for-refile))
|
|
|
|
;; ((eq major-mode 'mu4e-headers-mode)
|
|
|
|
;; (mu4e-headers-mark-for-refile)))
|
|
|
|
(message "Refiled \"%s\" and added to the agenda for %s"
|
|
|
|
(truncate-string-to-width
|
|
|
|
(plist-get msg :subject) 40 nil nil t)
|
|
|
|
(cond ((= arg 1) "today")
|
|
|
|
((= arg 4) "tomorrow")
|
|
|
|
(t "later"))))))
|
|
|
|
|
2020-10-31 19:34:42 +08:00
|
|
|
;;;###autoload
|
|
|
|
(defun +mu4e/attach-files (&optional files-to-attach)
|
|
|
|
"When called in a dired buffer, ask for a message to attach the marked files to.
|
2021-04-26 12:10:53 +08:00
|
|
|
When called in a mu4e:compose or org-msg buffer, `read-file-name'to either
|
|
|
|
attach a file, or select a folder to open dired in and select file attachments
|
|
|
|
(using `dired-mu4e-attach-ctrl-c-ctrl-c').
|
2020-10-31 19:34:42 +08:00
|
|
|
|
2021-04-26 12:10:53 +08:00
|
|
|
When otherwise called, open a dired buffer and enable `dired-mu4e-attach-ctrl-c-ctrl-c'."
|
2020-10-31 19:34:42 +08:00
|
|
|
;; TODO add ability to attach files (+dirs) as a single (named) archive
|
|
|
|
(interactive "p")
|
2021-07-31 01:10:54 +08:00
|
|
|
(+mu4e-compose-org-msg-handle-toggle (/= 1 files-to-attach))
|
2020-10-31 19:34:42 +08:00
|
|
|
(pcase major-mode
|
|
|
|
((or 'mu4e-compose-mode 'org-msg-edit-mode)
|
|
|
|
(let ((mail-buffer (current-buffer))
|
|
|
|
(location (read-file-name "Attach: ")))
|
|
|
|
(if (not (file-directory-p location))
|
|
|
|
(pcase major-mode
|
2020-12-16 04:23:23 +08:00
|
|
|
('mu4e-compose-mode
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-max))
|
|
|
|
(unless (eq (current-column) 0)
|
|
|
|
(insert "\n\n")
|
|
|
|
(forward-line 2))
|
|
|
|
(mail-add-attachment location)))
|
2020-10-31 19:34:42 +08:00
|
|
|
('org-msg-edit-mode (org-msg-attach-attach location)))
|
|
|
|
(split-window-sensibly)
|
|
|
|
(with-current-buffer (dired location)
|
|
|
|
(setq-local dired-mail-buffer mail-buffer)
|
|
|
|
(dired-mu4e-attach-ctrl-c-ctrl-c 1)))))
|
|
|
|
('dired-mode
|
|
|
|
(unless (and files-to-attach (/= 1 files-to-attach))
|
|
|
|
(setq files-to-attach
|
|
|
|
(delq nil
|
|
|
|
(mapcar
|
|
|
|
;; don't attach directories
|
|
|
|
(lambda (f) (if (file-directory-p f) nil f))
|
|
|
|
(nreverse (dired-map-over-marks (dired-get-filename) nil))))))
|
|
|
|
(if (not files-to-attach)
|
|
|
|
(progn
|
|
|
|
(message "No files marked, aborting.")
|
|
|
|
(kill-buffer-and-window))
|
|
|
|
(if-let ((mail-target-buffer (bound-and-true-p dired-mail-buffer)))
|
|
|
|
(progn (kill-buffer-and-window)
|
|
|
|
(switch-to-buffer mail-target-buffer))
|
|
|
|
(if (and (+mu4e-current-buffers)
|
|
|
|
(y-or-n-p "Attach files to existing mail composition buffer? "))
|
|
|
|
(progn (setf mail-target-buffer
|
|
|
|
(completing-read "Message: " (+mu4e-current-buffers)))
|
|
|
|
(kill-buffer-and-window)
|
|
|
|
(switch-to-buffer mail-target-buffer))
|
|
|
|
(kill-buffer-and-window)
|
|
|
|
(mu4e-compose 'new)))
|
|
|
|
(mapcar
|
|
|
|
(pcase major-mode
|
|
|
|
('mu4e-compose-mode #'mail-add-attachment)
|
|
|
|
('org-msg-edit-mode #'org-msg-attach-attach))
|
|
|
|
files-to-attach)))
|
|
|
|
(_
|
|
|
|
(split-window-sensibly)
|
|
|
|
(with-current-buffer (call-interactively #'find-file)
|
|
|
|
(dired-mu4e-attach-ctrl-c-ctrl-c 1)))))
|
|
|
|
|
|
|
|
(define-minor-mode dired-mu4e-attach-ctrl-c-ctrl-c
|
2021-07-30 01:55:53 +08:00
|
|
|
"Adds C-c C-c as a keybinding to attach files to a message."
|
2020-10-31 19:34:42 +08:00
|
|
|
:lighter "attach"
|
|
|
|
:keymap (let ((map (make-sparse-keymap)))
|
|
|
|
(define-key map (kbd "C-c C-c") '+mu4e/attach-files)
|
2021-04-26 12:11:28 +08:00
|
|
|
map)
|
|
|
|
(setq header-line-format
|
|
|
|
(when dired-mu4e-attach-ctrl-c-ctrl-c
|
|
|
|
(substitute-command-keys
|
|
|
|
"Mu4e attach active. `\\[+mu4e/attach-files]' to attach the marked files."))))
|
2020-10-31 19:34:42 +08:00
|
|
|
|
|
|
|
(defun +mu4e-current-buffers ()
|
|
|
|
"Return a list of active message buffers."
|
|
|
|
(let (buffers)
|
|
|
|
(save-current-buffer
|
|
|
|
(dolist (buffer (buffer-list t))
|
|
|
|
(set-buffer buffer)
|
|
|
|
(when (or (and (derived-mode-p 'message-mode)
|
|
|
|
(null message-sent-message-via))
|
|
|
|
(eq major-mode 'org-msg-edit-mode))
|
|
|
|
(push (buffer-name buffer) buffers))))
|
|
|
|
(nreverse buffers)))
|
|
|
|
|
2021-07-10 13:59:03 -05:00
|
|
|
;;; Hooks
|
2020-10-14 15:37:15 +08:00
|
|
|
|
|
|
|
(defun +mu4e-init-h ()
|
|
|
|
(add-hook 'kill-buffer-hook #'+mu4e-kill-mu4e-h nil t))
|
|
|
|
|
|
|
|
(defun +mu4e-kill-mu4e-h ()
|
|
|
|
;; (prolusion-mail-hide)
|
|
|
|
(cond
|
|
|
|
((and (featurep! :ui workspaces) (+workspace-exists-p +mu4e-workspace-name))
|
|
|
|
(+workspace/delete +mu4e-workspace-name))
|
|
|
|
|
|
|
|
(+mu4e--old-wconf
|
|
|
|
(set-window-configuration +mu4e--old-wconf)
|
|
|
|
(setq +mu4e--old-wconf nil))))
|
|
|
|
|
2020-09-23 13:00:33 +08:00
|
|
|
;;;###autoload
|
2020-10-14 15:37:15 +08:00
|
|
|
(defun +mu4e-set-from-address-h ()
|
2021-07-10 13:47:44 -05:00
|
|
|
"If the user defines multiple `+mu4e-personal-addresses' for email aliases
|
|
|
|
within a context, set `user-mail-address' to an alias found in the 'To' or
|
|
|
|
'From' headers of the parent message if present, or prompt the user for a
|
|
|
|
preferred alias"
|
|
|
|
(when-let ((addresses (if (or mu4e-contexts+mu4e-personal-addresses)
|
|
|
|
(and (> (length +mu4e-personal-addresses) 1)
|
|
|
|
+mu4e-personal-addresses)
|
|
|
|
(mu4e-personal-addresses))))
|
2020-10-14 14:13:04 +08:00
|
|
|
(setq user-mail-address
|
2021-07-10 13:47:44 -05:00
|
|
|
(if mu4e-compose-parent-message
|
|
|
|
(let ((to (cdr (car (mu4e-message-field mu4e-compose-parent-message :to))))
|
|
|
|
(from (cdr (car (mu4e-message-field mu4e-compose-parent-message :from)))))
|
|
|
|
(cond
|
|
|
|
((member to addresses) to)
|
|
|
|
((member from addresses) from)
|
|
|
|
(t (completing-read "From: " addresses))))
|
|
|
|
(completing-read "From: " addresses)))))
|