+mail.el 21 KB

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