+mail.el 20 KB

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