+mail.el 22 KB

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