init-notmuch.el 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. ;; Notmuch
  2. (require 'init-message)
  3. (require 'patch-notmuch)
  4. (require 'init-notmuch-sync)
  5. ;; To find files matching email:
  6. ;; notmuch search --output=files FOO
  7. ;; The following is good enough for multiple-account support if they use the
  8. ;; same SMTP server.
  9. (setq notmuch-fcc-dirs
  10. '(("mail@ambrevar.xyz" . "mail/Sent +sent -inbox -unread")
  11. ("pierre@atlas.engineer" . "atlas/Sent +sent -inbox -unread")))
  12. (setq notmuch-saved-searches
  13. `((:name "inbox" :query "tag:inbox and date:1w.." :key ,(kbd "i"))
  14. (:name "unread" :query "tag:unread" :key ,(kbd "u"))
  15. (:name "flagged" :query "tag:flagged" :key ,(kbd "f"))
  16. (:name "sent" :query "tag:sent and date:1w.." :key ,(kbd "t"))
  17. (:name "drafts" :query "tag:draft" :key ,(kbd "d"))
  18. (:name "all mail" :query "date:2w.." :key ,(kbd "a"))))
  19. (defun ambrevar/notmuch-change-sender (&optional sender)
  20. (interactive)
  21. (unless (derived-mode-p 'message-mode)
  22. (error "Must be in message mode"))
  23. (unless sender
  24. (setq sender (completing-read "Sender: " (mapcar 'car notmuch-fcc-dirs))))
  25. (message-replace-header "From" sender)
  26. (message-remove-header "Fcc")
  27. (notmuch-fcc-header-setup))
  28. (when (require 'helm-notmuch nil t)
  29. (setq helm-notmuch-match-incomplete-words t)
  30. (dolist (map (list notmuch-search-mode-map
  31. notmuch-hello-mode-map
  32. notmuch-show-mode-map
  33. notmuch-tree-mode-map))
  34. (define-key map "s" 'helm-notmuch))
  35. (define-key notmuch-show-mode-map (kbd "M-s f") #'helm-imenu))
  36. (when (require 'ol-notmuch nil 'noerror)
  37. (dolist (map (list notmuch-show-mode-map notmuch-tree-mode-map))
  38. (define-key map (kbd "C-c C-t") 'org-capture))
  39. (add-to-list 'org-capture-templates
  40. `("t" "Mark e-mail in agenda" entry (file+headline ,(car org-agenda-files) "E-mails")
  41. "* %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"++7d\" nil (notmuch-show-get-date)))\n%a\n")))
  42. (defun notmuch-show-bounce (&optional address)
  43. "Bounce the current message."
  44. (interactive "sBounce To: ")
  45. (notmuch-show-view-raw-message)
  46. (message-resend address))
  47. (define-key notmuch-show-mode-map "b" #'notmuch-show-bounce)
  48. ;; Improve address completion with Helm.
  49. (setq notmuch-address-use-company nil)
  50. (setq notmuch-address-selection-function
  51. (lambda (prompt collection initial-input)
  52. (completing-read prompt (cons initial-input collection) nil t nil 'notmuch-address-history)))
  53. ;; The following can be used to use notmuch with debbugs, but it won't retrieve
  54. ;; the emails so this has to be done separately.
  55. (defun debbugs-notmuch-select-report (&rest _)
  56. (let* ((status (debbugs-gnu-current-status))
  57. (id (cdr (assq 'id status)))
  58. (merged (cdr (assq 'mergedwith status))))
  59. (setq merged (if (listp merged) merged (list merged)))
  60. (unless id
  61. (user-error "No bug report on the current line"))
  62. (let ((address (format "%s@debbugs.gnu.org" id))
  63. (merged-addresses (string-join (mapcar (lambda (id)
  64. (format "%s@debbugs.gnu.org %s" id))
  65. merged)
  66. " ")))
  67. (notmuch-search (format "%s %s" address merged-addresses)))))
  68. (defun ambrevar/notmuch-poll-async ()
  69. "Like `notmuch-poll' but asynchronous."
  70. (notmuch-start-notmuch
  71. "notmuch-new"
  72. nil
  73. (lambda (_proc change)
  74. (with-current-buffer (cl-find-if (lambda (b)
  75. (with-current-buffer b
  76. (eq major-mode 'notmuch-search-mode)))
  77. (buffer-list))
  78. (notmuch-refresh-this-buffer))
  79. (message "notmuch-new: %s" change))
  80. "new"))
  81. ;; TODO: This is a bit brittle since it only works if the given gpg file exists.
  82. ;; Is there a way to unlock gpg manually without a file?
  83. (defun ambrevar/notmuch-poll-after-gpg-unlock ()
  84. "Unlock GPG and get Notmuch mail."
  85. ;; The gpg unlock needs to be asynchronous for EXWM, or else pinentry-emacs
  86. ;; will be blocked.
  87. (let ((sentinel (lambda (_process _args)
  88. (ambrevar/notmuch-poll-async))))
  89. (make-process :name "gpg" :buffer nil
  90. :command (list "gpg" "--decrypt"
  91. (expand-file-name
  92. (or (cl-find-if (lambda (agenda) (string-suffix-p ".gpg" agenda))
  93. org-agenda-files)
  94. (error "No .gpg file in `org-agenda-files'."))))
  95. :sentinel sentinel)))
  96. (advice-add 'notmuch-poll-and-refresh-this-buffer
  97. :override #'ambrevar/notmuch-poll-after-gpg-unlock)
  98. ;; (advice-add 'debbugs-gnu-select-report :override #'debbugs-notmuch-select-report)
  99. ;; Extend `notmuch-show-stash-mlarchive-link':
  100. (defvar ambrevar/known-mailing-list-archives
  101. '(("help-guix@gnu.org" . "guix-user")
  102. ("guix-devel@gnu.org" . "guix-devel")
  103. ("debbugs.gnu.org" . "guix-bugs"))
  104. "Alist of mail adresses and their Yhetil name.
  105. Alternatively the key may just be a host name against which a
  106. recipient will be matched.")
  107. (defun ambrevar/guess-yhetil-link (message-id)
  108. (let* ((all-addresses
  109. (mapcar #'second
  110. (mail-extract-address-components
  111. (mapconcat #'identity
  112. (list
  113. (notmuch-show-get-header :To)
  114. (notmuch-show-get-header :Cc))
  115. ", ")
  116. 'all)))
  117. (mailing-list
  118. (cdr (seq-find
  119. (lambda (pair)
  120. (let ((address-or-host (car pair)))
  121. (if (string-match "@" address-or-host)
  122. (member address-or-host all-addresses)
  123. (seq-find (lambda (address)
  124. (string-match address-or-host address))
  125. all-addresses))))
  126. ambrevar/known-mailing-list-archives))))
  127. (when mailing-list
  128. (concat "https://yhetil.org/"
  129. mailing-list "/" message-id))))
  130. (defun ambrevar/guess-yhetil-link-prefer-patches (message-id)
  131. (let ((ambrevar/known-mailing-list-archives
  132. (copy-alist
  133. ambrevar/known-mailing-list-archives)))
  134. (push '("debbugs.gnu.org" . "guix-patches")
  135. ambrevar/known-mailing-list-archives)
  136. (ambrevar/guess-yhetil-link message-id)))
  137. (add-to-list 'notmuch-show-stash-mlarchive-link-alist
  138. (cons "Yhetil" #'ambrevar/guess-yhetil-link))
  139. (add-to-list 'notmuch-show-stash-mlarchive-link-alist
  140. (cons "Yhetil/patches" #'ambrevar/guess-yhetil-link-prefer-patches))
  141. (setq notmuch-show-stash-mlarchive-link-default "Yhetil")
  142. (provide 'init-notmuch)