+mail.el 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. ;;; -*- lexical-binding: t; -*-
  2. ;;; Config is mostly from https://kkatsuyuki.github.io/notmuch-conf/ aadsa
  3. ;;; This is all modified from
  4. ;;; https://github.com/fuxialexander/doom-emacs-private-xfu/
  5. ;;;; Notmuch
  6. (use-package! notmuch
  7. :commands (notmuch
  8. notmuch-tree
  9. notmuch-tree-mode
  10. notmuch-search
  11. notmuch-search-mode
  12. notmuch-hello
  13. notmuch-hello-mode
  14. notmuch-show
  15. notmuch-show-mode
  16. notmuch-message-mode)
  17. :init
  18. (map!
  19. (:leader
  20. (:prefix "e"
  21. :desc "Open (i)nbox" "i" #'=mail
  22. :desc "Open (n)otmuch" "n" #'notmuch
  23. :desc "(C)ompose mail" "c" #'notmuch-mua-new-mail))))
  24. (set-evil-initial-state! '(notmuch-hello-mode
  25. notmuch-show-mode
  26. notmuch-search-mode
  27. notmuch-tree-mode
  28. notmuch-message-mode) 'normal)
  29. ;; (add-hook 'notmuch-tree-mode-hook '+mail/buffer-face-mode-notmuch)
  30. ;; (add-hook 'notmuch-search-mode-hook '+mail/buffer-face-mode-notmuch)
  31. ;; (add-hook 'notmuch-message-mode-hook '+mail/buffer-face-mode-notmuch)
  32. (add-hook 'notmuch-message-mode-hook (lambda () (set (make-local-variable 'company-backends) '(notmuch-company (company-ispell :with company-yasnippet)))))
  33. (add-hook 'notmuch-tree-mode-hook (lambda () (setq-local line-spacing nil)))
  34. (remove-hook 'message-mode-hook #'turn-on-auto-fill)
  35. (remove-hook 'notmuch-message-mode-hook #'turn-on-auto-fill)
  36. ;; (push 'notmuch-tree-mode evil-snipe-disabled-modes)
  37. ;; (push 'notmuch-hello-mode evil-snipe-disabled-modes)
  38. ;; (push 'notmuch-search-mode evil-snipe-disabled-modes)
  39. ;; (push 'notmuch-show-mode evil-snipe-disabled-modes)
  40. (advice-add #'notmuch-start-notmuch-sentinel :override #'+mail/notmuch-start-notmuch-sentinel)
  41. (advice-add #'notmuch-show :override #'+mail/notmuch-show-reuse-buffer)
  42. (advice-add #'notmuch-hello-insert-searches :override #'+mail/notmuch-hello-insert-searches)
  43. (advice-add #'notmuch-hello-insert-saved-searches :override #'+mail/notmuch-hello-insert-saved-searches)
  44. (advice-add #'notmuch-hello-insert-buttons :override #'+mail/notmuch-hello-insert-buttons)
  45. ;; (set! :popup "\\*notmuch-hello\\*" '((size . 20) (side . left)) '((quit . t) (modeline . nil)))
  46. (push (lambda (buf) (string-match-p "^\\*notmuch" (buffer-name buf)))
  47. doom-real-buffer-functions)
  48. (map! (:after notmuch
  49. (:map notmuch-show-mode-map
  50. :nmv "o" #'ace-link-notmuch-show
  51. :nmv "i" #'+mail/open-message-with-mail-app-notmuch-show
  52. :nmv "I" #'notmuch-show-view-all-mime-parts
  53. :nmv "q" #'notmuch-bury-or-kill-this-buffer
  54. (:when (featurep! :completion ivy)
  55. :nmv "s" #'counsel-notmuch)
  56. (:when (featurep! :completion helm)
  57. :nmv "s" #'helm-notmuch)
  58. :nmv "t" #'notmuch-tree-from-show-current-query
  59. :nmv "N" #'notmuch-mua-new-mail
  60. :nmv "n" #'notmuch-show-next-thread-show
  61. :nmv "r" #'notmuch-show-reply
  62. :nmv "<tab>" #'notmuch-show-toggle-visibility-headers
  63. :nmv "R" #'notmuch-show-reply-sender
  64. :nmv "p" #'notmuch-show-previous-thread-show)
  65. (:map notmuch-hello-mode-map
  66. :nmv "o" #'ace-link-notmuch-hello
  67. :nmv "t" #'notmuch-tree
  68. :nmv "k" #'widget-backward
  69. :nmv "n" #'notmuch-mua-new-mail
  70. :nmv "N" #'notmuch-mua-new-mail
  71. :nmv "j" #'widget-forward
  72. (:when (featurep! :completion ivy)
  73. :nmv "s" #'counsel-notmuch)
  74. (:when (featurep! :completion helm)
  75. :nmv "s" #'helm-notmuch)
  76. :nmv "q" #'+mail/quit
  77. :nmv "r" #'notmuch-hello-update)
  78. (:map notmuch-search-mode-map
  79. :nmv "j" #'notmuch-search-next-thread
  80. :nmv "k" #'notmuch-search-previous-thread
  81. :nmv "t" #'notmuch-tree-from-search-thread
  82. ;; :nmv "RET" #'notmuch-tree-from-search-thread
  83. :nmv "RET" #'notmuch-search-show-thread
  84. :nmv "N" #'notmuch-mua-new-mail
  85. :nmv "T" #'notmuch-tree-from-search-current-query
  86. :nmv ";" #'notmuch-search-tag
  87. :nmv "," #'notmuch-jump-search
  88. :nmv "d" #'+mail/notmuch-search-delete
  89. :nmv "a" #'notmuch-search-archive-thread
  90. ;; :nmv "q" #'notmuch
  91. :nmv "q" #'+mail/quit
  92. :nmv "R" #'notmuch-search-reply-to-thread-sender
  93. :nmv "r" #'notmuch-search-reply-to-thread
  94. :nmv "go" #'+notmuch-exec-offlineimap
  95. (:when (featurep! :completion ivy)
  96. :nmv "s" #'counsel-notmuch)
  97. (:when (featurep! :completion helm)
  98. :nmv "s" #'helm-notmuch)
  99. :nmv "x" #'+mail/notmuch-search-spam)
  100. (:map notmuch-tree-mode-map
  101. :nmv "j" #'notmuch-tree-next-message
  102. :nmv "k" #'notmuch-tree-prev-message
  103. :nmv "S" #'notmuch-search-from-tree-current-query
  104. (:when (featurep! :completion ivy)
  105. :nmv "s" #'counsel-notmuch)
  106. (:when (featurep! :completion helm)
  107. :nmv "s" #'helm-notmuch)
  108. :nmv "t" #'notmuch-tree
  109. :nmv ";" #'notmuch-tree-tag
  110. :nmv "RET" #'notmuch-tree-show-message
  111. :nmv "q" #'notmuch-tree-quit
  112. :nmv "s-n" #'notmuch-mua-new-mail
  113. :nmv "r" #'notmuch-search-reply-to-thread-sender
  114. :nmv "a" #'notmuch-tree-archive-message-then-next
  115. :nmv "A" #'notmuch-tree-archive-thread
  116. :nmv "i" #'+mail/open-message-with-mail-app-notmuch-tree
  117. :nmv "d" #'+mail/notmuch-tree-delete
  118. :nmv "x" #'+mail/notmuch-tree-spam)
  119. (:map notmuch-message-mode-map
  120. :localleader
  121. :desc "Send and Exit" doom-localleader-key #'notmuch-mua-send-and-exit
  122. :desc "Kill Message Buffer" "k" #'notmuch-mua-kill-buffer
  123. :desc "Save as Draft" "s" #'message-dont-send
  124. :desc "Attach file" "f" #'mml-attach-file)))
  125. ; Use w3m to parse HTML email
  126. (setq mm-text-html-renderer 'w3m)
  127. (setq w3m-fill-column 72)
  128. ;;;; counsel-notmuch
  129. (when (featurep! :completion ivy)
  130. (use-package! counsel-notmuch
  131. :commands counsel-notmuch
  132. :after notmuch))
  133. ;;;; helm-notmuch
  134. (when (featurep! :completion helm)
  135. (use-package! helm-notmuch
  136. :commands helm-notmuch
  137. :after notmuch))
  138. ;;;; org-mime
  139. (use-package! org-mime
  140. :after (org notmuch))
  141. :config (setq org-mime-library 'mml)
  142. ;;;###autoload
  143. (defun =mail ()
  144. "Activate (or switch to) `notmuch' in its workspace."
  145. (interactive)
  146. (if-let* ((buf (cl-find-if (lambda (it) (string-match-p "^\\*notmuch" (buffer-name (window-buffer it))))
  147. (doom-visible-windows))))
  148. (select-window (get-buffer-window buf))
  149. (notmuch-search "tag:inbox")))
  150. ;; (call-interactively 'notmuch-hello-sidebar))
  151. ;;;###autoload
  152. (defun +mail/quit ()
  153. (interactive)
  154. ;; (+popup/close (get-buffer-window "*notmuch-hello*"))
  155. (doom-kill-matching-buffers "^\\*notmuch"))
  156. ;;;###autoload
  157. (defun +mail/notmuch-search-delete ()
  158. (interactive)
  159. (notmuch-search-add-tag
  160. (list "+deleted" "-inbox" "-unread"))
  161. (notmuch-search-next-thread))
  162. ;;;###autoload
  163. (defun +mail/notmuch-tree-delete ()
  164. (interactive)
  165. (notmuch-tree-add-tag
  166. (list "+trash" "-inbox" "-unread"))
  167. (notmuch-tree-next-message))
  168. ;;;###autoload
  169. (defun +mail/notmuch-search-spam ()
  170. (interactive)
  171. (notmuch-search-add-tag
  172. (list "+spam" "-inbox" "-unread"))
  173. (notmuch-search-next-thread))
  174. ;;;###autoload
  175. (defun +mail/notmuch-tree-spam ()
  176. (interactive)
  177. (notmuch-tree-add-tag
  178. (list "+spam" "-inbox" "-unread"))
  179. (notmuch-tree-next-message))
  180. ;;;###autoload
  181. (defun +mail/open-message-with-mail-app-notmuch-tree ()
  182. (interactive)
  183. (let* ((msg-path (car (plist-get (notmuch-tree-get-message-properties) :filename)))
  184. (temp (make-temp-file "notmuch-message-" nil ".eml")))
  185. (shell-command-to-string (format "cp '%s' '%s'" msg-path temp))
  186. (start-process-shell-command "email" nil (format "thunderbird '%s'" temp))))
  187. ;; Override
  188. ;;;###autoload
  189. (defun +mail/notmuch-start-notmuch-sentinel (proc event)
  190. "Process sentinel function used by `notmuch-start-notmuch'."
  191. (let* ((err-file (process-get proc 'err-file))
  192. (err-buffer (or (process-get proc 'err-buffer)
  193. (find-file-noselect err-file)))
  194. (err (when (not (zerop (buffer-size err-buffer)))
  195. (with-current-buffer err-buffer (buffer-string))))
  196. (sub-sentinel (process-get proc 'sub-sentinel))
  197. (real-command (process-get proc 'real-command)))
  198. (condition-case err
  199. (progn
  200. ;; Invoke the sub-sentinel, if any
  201. (when sub-sentinel
  202. (funcall sub-sentinel proc event))
  203. ;; Check the exit status. This will signal an error if the
  204. ;; exit status is non-zero. Don't do this if the process
  205. ;; buffer is dead since that means Emacs killed the process
  206. ;; and there's no point in telling the user that (but we
  207. ;; still check for and report stderr output below).
  208. (when (buffer-live-p (process-buffer proc))
  209. (notmuch-check-async-exit-status proc event real-command err))
  210. ;; If that didn't signal an error, then any error output was
  211. ;; really warning output. Show warnings, if any.
  212. (let ((warnings
  213. (when err
  214. (with-current-buffer err-buffer
  215. (goto-char (point-min))
  216. (end-of-line)
  217. ;; Show first line; stuff remaining lines in the
  218. ;; errors buffer.
  219. (let ((l1 (buffer-substring (point-min) (point))))
  220. (skip-chars-forward "\n")
  221. (cons l1 (unless (eobp)
  222. (buffer-substring (point) (point-max)))))))))
  223. (when warnings
  224. (notmuch-logged-error (car warnings) (cdr warnings)))))
  225. (error
  226. ;; Emacs behaves strangely if an error escapes from a sentinel,
  227. ;; so turn errors into messages.
  228. (message "%s" (error-message-string err))))
  229. (when err-buffer
  230. (set-process-query-on-exit-flag (get-buffer-process err-buffer) nil)
  231. (kill-buffer err-buffer))
  232. (when err-file (ignore-errors (delete-file err-file)))))
  233. ;;;###autoload
  234. (defun +mail/notmuch-show-reuse-buffer (thread-id &optional elide-toggle parent-buffer query-context buffer-name)
  235. "Run \"notmuch show\" with the given thread ID and display results.
  236. ELIDE-TOGGLE, if non-nil, inverts the default elide behavior.
  237. The optional PARENT-BUFFER is the notmuch-search buffer from
  238. which this notmuch-show command was executed, (so that the
  239. next thread from that buffer can be show when done with this
  240. one).
  241. The optional QUERY-CONTEXT is a notmuch search term. Only
  242. messages from the thread matching this search term are shown if
  243. non-nil.
  244. The optional BUFFER-NAME provides the name of the buffer in
  245. which the message thread is shown. If it is nil (which occurs
  246. when the command is called interactively) the argument to the
  247. function is used.
  248. Returns the buffer containing the messages, or NIL if no messages
  249. matched."
  250. (interactive "sNotmuch show: \nP")
  251. (let ((buffer-name (generate-new-buffer-name
  252. (or (concat "*notmuch-" buffer-name "*")
  253. (concat "*notmuch-" thread-id "*"))))
  254. ;; We override mm-inline-override-types to stop application/*
  255. ;; parts from being displayed unless the user has customized
  256. ;; it themselves.
  257. (mm-inline-override-types
  258. (if (equal mm-inline-override-types
  259. (eval (car (get 'mm-inline-override-types 'standard-value))))
  260. (cons "application/*" mm-inline-override-types)
  261. mm-inline-override-types)))
  262. (switch-to-buffer (get-buffer-create buffer-name))
  263. ;; No need to track undo information for this buffer.
  264. (setq buffer-undo-list t)
  265. (notmuch-show-mode)
  266. ;; Set various buffer local variables to their appropriate initial
  267. ;; state. Do this after enabling `notmuch-show-mode' so that they
  268. ;; aren't wiped out.
  269. (setq notmuch-show-thread-id thread-id
  270. notmuch-show-parent-buffer parent-buffer
  271. notmuch-show-query-context (if (or (string= query-context "")
  272. (string= query-context "*"))
  273. nil query-context)
  274. notmuch-show-process-crypto notmuch-crypto-process-mime
  275. ;; If `elide-toggle', invert the default value.
  276. notmuch-show-elide-non-matching-messages
  277. (if elide-toggle
  278. (not notmuch-show-only-matching-messages)
  279. notmuch-show-only-matching-messages))
  280. (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
  281. (jit-lock-register #'notmuch-show-buttonise-links)
  282. (notmuch-tag-clear-cache)
  283. (let ((inhibit-read-only t))
  284. (if (notmuch-show--build-buffer)
  285. ;; Messages were inserted into the buffer.
  286. (current-buffer)
  287. ;; No messages were inserted - presumably none matched the
  288. ;; query.
  289. (kill-buffer (current-buffer))
  290. (ding)
  291. (message "No messages matched the query!")
  292. nil))))
  293. ;;;###autoload
  294. (defun +mail/notmuch-hello-insert-searches (title query-list &rest options)
  295. "Insert a section with TITLE showing a list of buttons made from QUERY-LIST.
  296. QUERY-LIST should ideally be a plist but for backwards
  297. compatibility other forms are also accepted (see
  298. `notmuch-saved-searches' for details). The plist should
  299. contain keys :name and :query; if :count-query is also present
  300. then it specifies an alternate query to be used to generate the
  301. count for the associated search.
  302. Supports the following entries in OPTIONS as a plist:
  303. :initially-hidden - if non-nil, section will be hidden on startup
  304. :show-empty-searches - show buttons with no matching messages
  305. :hide-if-empty - hide if no buttons would be shown
  306. (only makes sense without :show-empty-searches)
  307. :filter - This can be a function that takes the search query as its argument and
  308. returns a filter to be used in conjuction with the query for that search or nil
  309. to hide the element. This can also be a string that is used as a combined with
  310. each query using \"and\".
  311. :filter-count - Separate filter to generate the count displayed each search. Accepts
  312. the same values as :filter. If :filter and :filter-count are specified, this
  313. will be used instead of :filter, not in conjunction with it."
  314. (widget-insert (propertize title 'face 'org-agenda-structure))
  315. (if (and notmuch-hello-first-run (plist-get options :initially-hidden))
  316. (add-to-list 'notmuch-hello-hidden-sections title))
  317. (let ((is-hidden (member title notmuch-hello-hidden-sections))
  318. (widget-push-button-prefix "")
  319. (widget-push-button-suffix "")
  320. (start (point)))
  321. (if is-hidden
  322. (widget-create 'push-button
  323. :notify `(lambda (widget &rest ignore)
  324. (setq notmuch-hello-hidden-sections
  325. (delete ,title notmuch-hello-hidden-sections))
  326. (notmuch-hello-update))
  327. (propertize " +" 'face 'org-agenda-structure))
  328. (widget-create 'push-button
  329. :notify `(lambda (widget &rest ignore)
  330. (add-to-list 'notmuch-hello-hidden-sections
  331. ,title)
  332. (notmuch-hello-update))
  333. " -"))
  334. (widget-insert "\n")
  335. (when (not is-hidden)
  336. (let ((searches (apply 'notmuch-hello-query-counts query-list options)))
  337. (when (or (not (plist-get options :hide-if-empty))
  338. searches)
  339. (widget-insert "\n")
  340. (notmuch-hello-insert-buttons searches)
  341. (indent-rigidly start (point) notmuch-hello-indent))))))
  342. ;;;###autoload
  343. (defun +mail/notmuch-hello-insert-saved-searches ()
  344. "Insert the saved-searches section."
  345. (let ((searches (notmuch-hello-query-counts
  346. (if notmuch-saved-search-sort-function
  347. (funcall notmuch-saved-search-sort-function
  348. notmuch-saved-searches)
  349. notmuch-saved-searches)
  350. :show-empty-searches notmuch-show-empty-saved-searches)))
  351. (when searches
  352. (widget-insert (propertize "Notmuch" 'face 'org-agenda-date-today))
  353. (widget-insert "\n\n")
  354. (widget-insert (propertize "Saved searches" 'face 'org-agenda-structure))
  355. (widget-insert "\n\n")
  356. (let ((start (point)))
  357. (notmuch-hello-insert-buttons searches)
  358. (indent-rigidly start (point) notmuch-hello-indent)))))
  359. ;;;###autoload
  360. (defun +mail/notmuch-hello-insert-buttons (searches)
  361. "Insert buttons for SEARCHES.
  362. SEARCHES must be a list of plists each of which should contain at
  363. least the properties :name NAME :query QUERY and :count COUNT,
  364. where QUERY is the query to start when the button for the
  365. corresponding entry is activated, and COUNT should be the number
  366. of messages matching the query. Such a plist can be computed
  367. with `notmuch-hello-query-counts'."
  368. (let* ((widest (notmuch-hello-longest-label searches))
  369. (tags-and-width (notmuch-hello-tags-per-line widest))
  370. (tags-per-line (car tags-and-width))
  371. (column-width (cdr tags-and-width))
  372. (column-indent 0)
  373. (count 0)
  374. (reordered-list (notmuch-hello-reflect searches tags-per-line))
  375. ;; Hack the display of the buttons used.
  376. (widget-push-button-prefix "")
  377. (widget-push-button-suffix ""))
  378. ;; dme: It feels as though there should be a better way to
  379. ;; implement this loop than using an incrementing counter.
  380. (mapc (lambda (elem)
  381. ;; (not elem) indicates an empty slot in the matrix.
  382. (when elem
  383. (if (> column-indent 0)
  384. (widget-insert (make-string column-indent ? )))
  385. (let* ((name (plist-get elem :name))
  386. (query (plist-get elem :query))
  387. (oldest-first (case (plist-get elem :sort-order)
  388. (newest-first nil)
  389. (oldest-first t)
  390. (otherwise notmuch-search-oldest-first)))
  391. (search-type (eq (plist-get elem :search-type) 'tree))
  392. (msg-count (plist-get elem :count)))
  393. (widget-insert (format "\n%5s "
  394. (notmuch-hello-nice-number msg-count)))
  395. (widget-create 'push-button
  396. :notify #'notmuch-hello-widget-search
  397. :notmuch-search-terms query
  398. :notmuch-search-oldest-first oldest-first
  399. :notmuch-search-type search-type
  400. name)
  401. (setq column-indent
  402. (1+ (max 0 (- column-width (length name)))))))
  403. (setq count (1+ count))
  404. (when (eq (% count tags-per-line) 0)
  405. (setq column-indent 0)
  406. (widget-insert "\n")))
  407. reordered-list)
  408. ;; If the last line was not full (and hence did not include a
  409. ;; carriage return), insert one now.
  410. (unless (eq (% count tags-per-line) 0)
  411. (widget-insert "\n"))))
  412. ; Kill email message buffers when you close them
  413. (setq message-kill-buffer-on-exit t)
  414. (setq message-default-mail-headers "Cc: \nBcc: \n")
  415. (setq message-auto-save-directory "~/Mail/colin@unbl.ink/Drafts/")
  416. (setq message-directory "~/Mail/colin@unbl.ink/")
  417. ;;; Setup sending email with msmtp
  418. (setq send-mail-function 'sendmail-send-it
  419. sendmail-program "/usr/local/bin/msmtp"
  420. mail-specify-envelope-from t
  421. message-sendmail-f-is-evil t
  422. message-sendmail-envelope-from 'header
  423. message-sendmail-extra-arguments '("--read-envelope-from")
  424. mail-envelope-from 'header)