ambevar-dotfiles/.emacs.d/lisp/init-mu4e.el

330 lines
13 KiB
EmacsLisp
Raw Normal View History

;;; mu4e
2017-10-10 17:09:19 +02:00
;;; REVIEW: Reply to all by default.
;;; https://github.com/djcb/mu/issues/1135
;;; TODO: Is it possible to mbsync without attachments?
;;; REVIEW: Do not cite when replying: https://github.com/djcb/mu/issues/1110.
;;; TODO: Face of `message-cited-text' does not work.
;;; REVIEW: Handle attachments in attached e-mails.
;;; See https://github.com/djcb/mu/issues/454#issuecomment-320616279.
;;; TODO: <tab> should go to next link in text e-mails too.
2018-02-15 15:55:30 +01:00
;; We need 'main' to setup pinentry-emacs for GPG.
(require 'main)
(when (require 'mu4e-maildirs-extension nil t)
(mu4e-maildirs-extension))
2018-02-15 13:28:49 +01:00
(defun ambrevar/mu4e-headers ()
2017-07-31 08:59:03 +02:00
"Like `mu4e' but show the header view.
2018-02-15 13:28:49 +01:00
Default to unread messages if the header buffer does not already exist."
(interactive)
2017-07-31 08:59:03 +02:00
(mu4e~start)
(if (get-buffer "*mu4e-headers*" )
(switch-to-buffer "*mu4e-headers*")
(mu4e-headers-search "flag:unread AND NOT flag:trashed")))
(setq
2017-07-31 08:57:46 +02:00
;; Attachments
mu4e-attachment-dir "~/temp"
2017-07-31 08:57:46 +02:00
mu4e-save-multiple-attachments-without-asking t
;; IMAP sync.
mu4e-maildir "~/.cache/mail"
mu4e-get-mail-command "mbsync -a"
mu4e-update-interval 90
2018-05-17 17:17:27 +02:00
mu4e-headers-auto-update nil ; Don't refresh so that we don't lose the current filter upon, e.g. reading e-mails.
mu4e-change-filenames-when-moving t ; Preferred for mbsync according to the man page.
;; SMTP
message-send-mail-function 'smtpmail-send-it
;; Don't bother me with context on startup.
mu4e-context-policy nil
;; Don't keep sent e-mail buffer.
message-kill-buffer-on-exit t
;; For reporting bugs, "C-x m", etc.
mail-user-agent 'mu4e-user-agent
mu4e-compose-dont-reply-to-self t
;; Display
mu4e-headers-date-format "%F %R"
mu4e-headers-fields '((:human-date . 16)
(:flags . 6)
2017-07-31 08:58:44 +02:00
(:size . 6)
(:mailing-list . 10)
(:from . 22)
(:subject))
mu4e-headers-time-format "%R"
mu4e-view-show-addresses t
mu4e-view-show-images t
mu4e-view-image-max-width 800
mu4e-hide-index-messages t
;; If you're using a dark theme, and the messages are hard to read, it
;; can help to change the luminosity, e.g.:
shr-color-visible-luminance-min 80
;; Gmail-style threading.
mu4e-headers-include-related t
;; Gmail likes format=flowed(?)
;; mu4e-compose-format-flowed
2018-03-01 18:41:32 +01:00
;; Also crypt to self so that we can read sent e-mails.
mml-secure-openpgp-encrypt-to-self t
;; Because default completion can be extended (e.g. Helm, Ivy).
mu4e-completing-read-function 'completing-read)
2017-10-08 20:36:28 +02:00
;;; Press "aV" to view in browser.
(add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)
2017-10-08 20:36:28 +02:00
;;; Unicode chars for decoration might cause issues with some fonts or in terminals.
;;; https://github.com/djcb/mu/issues/733
;;; https://github.com/djcb/mu/issues/1062
;; (setq mu4e-use-fancy-chars t)
2018-07-12 17:27:56 +02:00
;;; REVIEW: Sorting in ascending order is impeded by
;;; `mu4e-search-results-limit': the 500 oldest e-mails will be displayed first.
;;; https://github.com/djcb/mu/issues/809
;; (setq mu4e-headers-sort-direction 'ascending)
;;; Since we sort in ascending direction, we default to the end of buffer.
;; (add-hook 'mu4e-headers-found-hook 'end-of-buffer)
2018-07-12 17:27:56 +02:00
(defvar ambrevar/mu4e-compose-fortune-p nil
"Whether or not to include a fortune in the signature.")
2018-02-15 13:28:49 +01:00
(defun ambrevar/mu4e-add-fortune-signature ()
(require 'functions) ; For `call-process-to-string'.
(setq mu4e-compose-signature
(if (and ambrevar/mu4e-compose-fortune-p
(executable-find "fortune"))
2017-10-15 21:09:30 +02:00
(format "%s\n\n%s"
user-full-name
(ambrevar/call-process-to-string "fortune" "-s"))
2017-10-15 21:09:30 +02:00
user-full-name)))
2018-02-15 13:28:49 +01:00
(add-hook 'mu4e-compose-pre-hook 'ambrevar/mu4e-add-fortune-signature)
2017-10-10 17:09:19 +02:00
;;; Make some e-mails stand out a bit.
(set-face-foreground 'mu4e-unread-face "yellow")
(set-face-attribute 'mu4e-flagged-face nil :inherit 'font-lock-warning-face)
2017-10-10 17:09:19 +02:00
;;; Confirmation on every mark execution is too slow to my taste.
2018-02-15 13:28:49 +01:00
(defun ambrevar/mu4e-mark-execute-all-no-confirm ()
2017-10-10 17:09:19 +02:00
(interactive)
(mu4e-mark-execute-all t))
2018-02-15 13:28:49 +01:00
(define-key mu4e-headers-mode-map "x" 'ambrevar/mu4e-mark-execute-all-no-confirm)
2017-10-10 17:09:19 +02:00
2017-07-31 08:58:15 +02:00
(when (require 'helm-mu nil t)
(dolist (map (list mu4e-headers-mode-map mu4e-main-mode-map mu4e-view-mode-map))
2017-10-28 12:29:24 +02:00
(define-key map "s" 'helm-mu)))
2017-07-31 08:58:15 +02:00
2018-03-19 11:30:41 +01:00
(defvar ambrevar/mu4e-compose-signed-p t)
2018-02-15 13:28:49 +01:00
(defvar ambrevar/mu4e-compose-signed-and-crypted-p nil)
(defun ambrevar/mu4e-compose-maybe-signed-and-crypted ()
"Maybe sign or encrypt+sign message.
Message is signed or encrypted+signed when replying to a signed or encrypted
message, respectively.
Alternatively, message is signed or encrypted+signed if
2018-02-15 13:28:49 +01:00
`ambrevar/mu4e-compose-signed-p' or `ambrevar/mu4e-compose-signed-and-crypted-p' is
non-nil, respectively.
This function is suitable for `mu4e-compose-mode-hook'."
(let ((msg mu4e-compose-parent-message))
(cond
2018-02-15 13:28:49 +01:00
((or ambrevar/mu4e-compose-signed-and-crypted-p
(and msg (member 'encrypted (mu4e-message-field msg :flags))))
(mml-secure-message-sign-encrypt))
2018-02-15 13:28:49 +01:00
((or ambrevar/mu4e-compose-signed-p
(and msg (member 'signed (mu4e-message-field msg :flags))))
(mml-secure-message-sign-pgpmime)))))
2018-02-15 13:28:49 +01:00
(add-hook 'mu4e-compose-mode-hook 'ambrevar/mu4e-compose-maybe-signed-and-crypted)
2017-12-19 09:47:51 +01:00
(defun ambrevar/message-send-maybe-crypted ()
"Crypt message if all recipients have a trusted key.
This will prompt the user if only some recipients have a suitable public key.
Suitable for `message-send-hook'."
(let (recipients valid-addresses untrusted-recipients)
(setq recipients
(save-restriction
(message-narrow-to-headers)
(apply 'append
(mapcar
(lambda (header)
(mapcar 'cadr (mail-extract-address-components
(message-fetch-field header) t)))
(seq-filter 'message-fetch-field
'("To" "Cc" "Bcc"))))))
(dolist (key (epg-list-keys (epg-make-context epa-protocol)))
(dolist (user-id (epg-key-user-id-list key))
(when (memq (epg-user-id-validity user-id) '(marginal full ultimate))
(push (cadr (mail-extract-address-components (epg-user-id-string user-id))) valid-addresses))))
(setq untrusted-recipients
(seq-difference recipients valid-addresses))
(when (/= (length untrusted-recipients)
(length recipients))
;; Some recipients have valid keys.
(mml-secure-message-sign-encrypt)
(when (and untrusted-recipients
(yes-or-no-p
(format "Some recipients don't have a trusted key %S.
Sending unencrypted? "
untrusted-recipients)))
(mml-secure-message-sign)
(mu4e-message "Sending unencrypted"))))
t)
(add-hook 'message-send-hook 'ambrevar/message-send-maybe-crypted)
;; Because it's to tempting to send an e-mail riddled with typos...
(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)
2017-12-19 09:47:51 +01:00
;;; Org capture
(when (require 'org-mu4e nil t)
(dolist (map (list mu4e-view-mode-map mu4e-headers-mode-map))
;; Org mode has "C-c C-t" for 'org-todo.
(define-key map (kbd "C-c C-t") 'org-mu4e-store-and-capture))
2017-12-19 09:47:51 +01:00
(setq org-mu4e-link-query-in-headers-mode nil))
2018-04-11 16:34:26 +02:00
;;; Gmail trash fix.
(defvar ambrevar/mu4e-move-to-trash-patterns nil
"List of regexps to match for moving to trash instead of deleting them.
Matches are done against the :maildir field of the e-mail at
point. See `ambrevar/mu4e-headers-move-to-trash' and
`ambrevar/mu4e-view-move-to-trash'.")
(defun ambrevar/mu4e-headers-move-to-trash ()
(interactive)
(let ((msg-dir (mu4e-message-field (mu4e-message-at-point) :maildir)))
(if (not (seq-filter (lambda (re)
(string-match re msg-dir))
ambrevar/mu4e-move-to-trash-patterns))
(mu4e-headers-mark-for-delete)
(mu4e-mark-set 'move (funcall mu4e-trash-folder (mu4e-message-at-point)))
(mu4e-headers-next))))
(defun ambrevar/mu4e-view-move-to-trash ()
(interactive)
(mu4e~view-in-headers-context
(ambrevar/mu4e-headers-move-to-trash)
(mu4e~headers-move (or n 1))))
;;; Don't display trashed messages in bookmarks. This is useful for Gmail where
;;; the "delete" flag is not used.
(defvar ambrevar/mu4e-trash-folders nil
"List of trash folders to filter out from bookmarks.")
(load "~/personal/mail/mu4e.el" t)
2018-04-11 16:34:26 +02:00
;; Do this after setting `ambrevar/mu4e-trash-folders'.
(dolist (bookmark mu4e-bookmarks)
;; TODO: Why mu4e-bookmark-query does not work here?
(setf (car bookmark) (concat (mapconcat (lambda (s) (format "NOT maildir:\"%s\" and " s))
ambrevar/mu4e-trash-folders "")
(car bookmark))))
(defun ambrevar/message-github ()
"When replying to a github message, clean up all bogus recipients.
2018-06-24 10:23:04 +02:00
This function could be useful in `mu4e-compose-mode-hook'."
(interactive)
(let ((to (message-fetch-field "To")))
(when (and to
(string-match (rx "@reply.github.com" string-end) (cadr (mail-extract-address-components to))))
(dolist (hdr '("To" "Cc" "Bcc"))
2018-06-24 10:23:04 +02:00
(let ((addr (message-fetch-field hdr))
recipients
bogus-recipients
clean-recipients)
(when (stringp addr)
(setq recipients (mail-extract-address-components addr t)
bogus-recipients (message-bogus-recipient-p addr))
(when bogus-recipients
(setq clean-recipients (seq-difference recipients bogus-recipients
(lambda (addrcomp addr)
(string= (cadr addrcomp) addr))))
;; See `message-simplify-recipients'.
(message-replace-header
hdr
(mapconcat
(lambda (addrcomp)
(if (and message-recipients-without-full-name
(string-match
(regexp-opt message-recipients-without-full-name)
(cadr addrcomp)))
(cadr addrcomp)
(if (car addrcomp)
(message-make-from (car addrcomp) (cadr addrcomp))
(cadr addrcomp))))
clean-recipients
", "))))))
2018-06-24 10:23:04 +02:00
(message-sort-headers)
;; Delete signature if any.
(delete-region (save-excursion
(message-goto-signature)
(unless (eobp)
(forward-line -1))
(point))
(point-max))
;; Deleting trailing blank lines.
(save-excursion
(goto-char (point-max))
(delete-blank-lines)
(delete-blank-lines)))))
(add-hook 'mu4e-compose-mode-hook 'ambrevar/message-github)
;;; Org captures
(when (require 'org-mu4e nil t)
(require 'init-org) ; For org-agenda-files
(add-to-list 'org-capture-templates
`("t" "Mark e-mail in agenda" entry (file+headline ,(car org-agenda-files) "E-mails")
"* TODO [#A] %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%a\n"))
;; TODO: Don't duplicate contacts.
(defun ambrevar/mu4e-contact-dwim ()
"Return a list of (NAME . ADDRESS).
If point has an `email' property, move it to the front of the list.
Addresses in `mu4e-user-mail-address-list' are skipped."
(let (result
(message org-store-link-plist))
(setq result (seq-remove
(lambda (contact) (member (cadr contact) mu4e-user-mail-address-list))
(apply 'append
(mapcar (lambda (addr) (mail-extract-address-components addr t))
(delq nil
(mapcar (lambda (field) (mu4e-message-field message field))
'(:from :to :cc :bcc)))))))
;; Move contact at point to front.
(let ((email-at-point (get-text-property (point) 'email))
(contacts result))
(when email-at-point
(while contacts
(if (not (string= (cadr (car contacts)) email-at-point))
(setq contacts (cdr contacts))
(setq result (delete (car contacts) result))
(push (car contacts) result)
(setq contacts nil)))))
result))
(defun ambrevar/org-contacts-template-name (&optional return-value)
"Like `org-contacts-template-name' for mu4e."
(or (car (car (ambrevar/mu4e-contact-dwim)))
return-value
"%^{Name}"))
(defun ambrevar/org-contacts-template-email (&optional return-value)
"Like `org-contacts-template-name' for mu4e."
(or (cadr (car (ambrevar/mu4e-contact-dwim)))
return-value
(concat "%^{" org-contacts-email-property "}p")))
(add-to-list 'org-capture-templates
`("c" "Add e-mail address to contacts" entry (file+headline ,(car org-contacts-files) "Contacts")
"* %(ambrevar/org-contacts-template-name)
:PROPERTIES:
:EMAIL: %(ambrevar/org-contacts-template-email)
:END:")))
(provide 'init-mu4e)