+mail.el 20 KB

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