jao-custom-notmuch.el 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. ;; -*- lexical-binding: t; -*-
  2. ;;; minibuffer
  3. (defvar jao-notmuch-minibuffer-string "")
  4. (defvar jao-notmuch-minibuffer-queries
  5. `((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00)
  6. (:name "B" :query "tag:new and tag:bigml and tag:inbox" :face default)
  7. (:name "A" :query "tag:new and tag:alba" :face default)
  8. (:name "b" :query "tag:new and tag:bigml and tag:bugs"
  9. :face jao-themes-error)
  10. (:name "S" :query "tag:new and tag:bigml and tag:support" :face default)
  11. (:name "W"
  12. :query "tag:new and tag:bigml and not tag:\"/support|bugs|inbox/\""
  13. :face default)
  14. (:name "I" :query "tag:new and tag:jao and tag:inbox" :face jao-themes-warning)
  15. (:name "W" :query "tag:new and tag:jao and tag:write" :face jao-themes-warning)
  16. (:name "J"
  17. :query ,(concat "tag:new and tag:jao and not "
  18. "tag:\"/local|hacking|draft|inbox|prog|words|write/\"")
  19. :face default)
  20. (:name "H" :query "tag:new and tag:hacking and not tag:\"/emacs/\"")
  21. (:name "E" :query "tag:new and tag:\"/emacs/\"")
  22. (:name "l" :query "tag:new and tag:local")
  23. (:name "F" :query "tag:new and tag:feeds and not tag:\"/emacs/\"")))
  24. (defun jao-notmuch-notify ()
  25. (let ((cnts (notmuch-hello-query-counts jao-notmuch-minibuffer-queries)))
  26. (setq jao-notmuch-minibuffer-string
  27. (mapconcat (lambda (c)
  28. (propertize (format "%s%s"
  29. (plist-get c :name)
  30. (plist-get c :count))
  31. 'face (or (plist-get c :face)
  32. 'jao-themes-dimm)))
  33. cnts
  34. " "))
  35. (jao-minibuffer-refresh)))
  36. (when jao-notmuch-enabled
  37. (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20))
  38. ;;; saved searches
  39. (defvar jao-notmuch--new "tag:\"/^(unread|new)$/\"")
  40. (defvar jao-notmuch--newa (concat jao-notmuch--new " AND "))
  41. (defun jao-notmuch--q (d0 d1 &optional k qs st)
  42. (let ((q (or (when qs (mapconcat #'identity qs " AND "))
  43. (concat jao-notmuch--newa
  44. (mapconcat (lambda (d) (when d (concat "tag:" d)))
  45. (list d0 d1) " AND ")))))
  46. (list :name (concat d0 (when (and d1 (not (string= "" d1))) "/") d1)
  47. :key k :query q :search-type (or st 'tree)
  48. :sort-order 'oldest-first)))
  49. (defun jao-notmuch--qn (d0 d1 k qs &optional st)
  50. (jao-notmuch--q d0 d1 k (cons jao-notmuch--new qs) st))
  51. (defun jao-notmuch--sq (tag &optional k d0 d1)
  52. (jao-notmuch--qn (or d0 "feeds") (or d1 tag) k (list (concat "tag:" tag))))
  53. (defun jao-notmuch-tree-widen-search ()
  54. (interactive)
  55. (when-let ((query (notmuch-tree-get-query)))
  56. (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto)))
  57. (notmuch-tree-close-message-window)
  58. (notmuch-tree (string-replace jao-notmuch--newa "" query)))))
  59. (defun jao-notmuch-widen-searches (searches &optional extra)
  60. (mapcar (lambda (s)
  61. (let* ((q (plist-get s :query))
  62. (qs (string-replace jao-notmuch--newa "" q)))
  63. (plist-put (copy-sequence s) :query (concat qs extra))))
  64. searches))
  65. (defun jao-notmuch-hello--insert-searches (searches title)
  66. (when-let (searches (notmuch-hello-query-counts searches))
  67. (let* ((cnt (when title
  68. (seq-reduce (lambda (c q)
  69. (+ c (or (plist-get q :count) 0)))
  70. searches
  71. 0)))
  72. (title (if title (format "[ %d %s ]\n\n" cnt title) "\n"))
  73. (notmuch-column-control 1.0))
  74. (widget-insert (propertize title 'face 'jao-themes-f00))
  75. (notmuch-hello-insert-buttons searches))))
  76. (defmacro jao-notmuch-def-searches (name searches)
  77. (declare (indent 1))
  78. (let ((name (and name (format "%s" name)))
  79. (id (intern (format "jao-notmuch-%s-searches" (or name (gensym))))))
  80. `(progn (defvar ,id ,searches)
  81. (defun ,id () (jao-notmuch-hello--insert-searches ,id ,name))
  82. (setq notmuch-saved-searches (append notmuch-saved-searches ,id))
  83. (add-to-list 'notmuch-hello-sections ',id t))))
  84. (setq notmuch-hello-sections nil notmuch-saved-searches nil)
  85. (jao-notmuch-def-searches bigml
  86. `(,(jao-notmuch--q "bigml" "inbox" "bi")
  87. ,(jao-notmuch--q "bigml" "alba" "ba")
  88. ,(jao-notmuch--q "bigml" "support" "bs")
  89. ,(jao-notmuch--q "bigml" "bugs" "bb")
  90. ,(jao-notmuch--q "bigml" "drivel" "bd")
  91. ,(jao-notmuch--q "bigml" "lists" "bl")))
  92. (jao-notmuch-def-searches inbox
  93. `(,(jao-notmuch--q "jao" "inbox" "ji")
  94. ,(jao-notmuch--qn "jao" "bills" "jb" '("tag:bills"))
  95. ,(jao-notmuch--qn "jao" "write" "jw" '("tag:write"))
  96. ,(jao-notmuch--q "jao" "drivel" "jd")
  97. ,(jao-notmuch--q "jao" "mdk" "jm")
  98. ,(jao-notmuch--qn "jao" "hacking" "jh" '("tag:hacking" "not tag:\"/emacs/\""))
  99. ,(jao-notmuch--qn "jao" "local" "jl" '("tag:local"))))
  100. (jao-notmuch-def-searches news
  101. (mapcar #'jao-notmuch--sq '("news" "noticias" "fun" "words" "computers")))
  102. (jao-notmuch-def-searches hacking
  103. (mapcar #'jao-notmuch--sq '("xmobar" "geiser" "mdk" "mailutils" "notmuch")))
  104. (jao-notmuch-def-searches prog
  105. (append (mapcar #'jao-notmuch--sq
  106. '( "lobsters" "clojure" "lisp" "scheme"
  107. "haskell" "idris" "erlang" "pharo"))
  108. `(,(jao-notmuch--qn "feeds" "prog" "fp"
  109. '("tag:prog" "not tag:\"/emacs/\"")))))
  110. (jao-notmuch-def-searches emacs
  111. `(,(jao-notmuch--sq "emacs" "ee" "emacs" "feeds")
  112. ,(jao-notmuch--sq "emacs-help" "eh" "emacs" "help")
  113. ,(jao-notmuch--sq "emacs-github" "eg" "emacs" "github")
  114. ,(jao-notmuch--sq "emacs-devel" "ed" "emacs" "devel")
  115. ,(jao-notmuch--sq "emacs-bugs" "eb" "emacs" "bugs")
  116. ,(jao-notmuch--sq "emacs-diffs" "ec" "emacs" "diffs")))
  117. (jao-notmuch-def-searches sci
  118. (mapcar #'jao-notmuch--sq
  119. '("philosophy" "math" "physics" "sci" "gr-qc" "quant-ph")))
  120. (jao-notmuch-def-searches flags
  121. (jao-notmuch-widen-searches notmuch-saved-searches " AND tag:flagged"))
  122. (jao-notmuch-def-searches nil
  123. `(,(jao-notmuch--q "bml" "flagged" "rb" '("tag:flagged" "tag:bigml"))
  124. ,(jao-notmuch--q "jao" "flagged" "rj" '("tag:flagged" "tag:jao"))
  125. ,(jao-notmuch--q "feeds" "flagged" "rf" '("tag:flagged" "tag:feeds"))))
  126. (jao-notmuch-def-searches today
  127. `(,(jao-notmuch--q "new" nil "nn" '("tag:new" "not tag:draft"))
  128. ,(jao-notmuch--q "jao" "drafts" "d" '("tag:draft"))
  129. ,(jao-notmuch--q "bml" "sent" "ts"
  130. '("tag:bigml" "date:1d.." "tag:sent"))
  131. ,(jao-notmuch--q "jao" "sent" "tS"
  132. '("tag:\"/jao|hacking/\"" "date:1d.." "tag:sent"))
  133. ,(jao-notmuch--q "bml" "today" "tb"
  134. '("not tag:sent" "tag:bigml" "date:24h.."))
  135. ,(jao-notmuch--q "jao" "today" "tj"
  136. '("tag:jao" "date:24h.."
  137. "not tag:\"/(sent|feeds|spam|local)/\""))))
  138. (jao-notmuch-def-searches trove
  139. (mapcar (lambda (m) (list :query (concat "tag:trove and tag:" m)
  140. :name (concat "trove/" m)
  141. :key (concat "t" (substring m 0 1))
  142. :search-type 'tree))
  143. '("jao" "hacking" "feeds" "bills")))
  144. (jao-notmuch-def-searches nil
  145. '((:query "not tag:trove and not tag:bigml" :name "jao/untroved" :search-type tree)
  146. (:query "tag:sent and tag:bigml" :name "bigml/sent" :search-type tree)
  147. (:query "tag:sent and not tag:bigml" :name "jao/sent" :search-type tree)
  148. (:query "*" :name "messages" :search-type tree)))
  149. (defvar jao-notmuch-widened-searches
  150. (jao-notmuch-widen-searches notmuch-saved-searches))
  151. (defun jao-notmuch-jump-search (&optional widen)
  152. (interactive "P")
  153. (let ((notmuch-saved-searches
  154. (if widen jao-notmuch-widened-searches notmuch-saved-searches)))
  155. (notmuch-jump-search)))
  156. ;;; tags
  157. (defvar jao-notmuch--shared-tags
  158. '("new" "unread" "flagged" "signed" "sent" "attachment" "forwarded" "inbox"
  159. "encrypted" "gmane" "gnus" "feeds" "rss" "mce" "trove" "prog" "emacs"))
  160. (defun jao-notmuch--subtags (tag &rest excl)
  161. (let* ((cmd (concat "notmuch search --output=tags tag:" tag))
  162. (ts (split-string (shell-command-to-string cmd))))
  163. (seq-difference ts (append jao-notmuch--shared-tags (cons tag excl)))))
  164. (setq notmuch-archive-tags '("+trove" "-new" "-drivel" "-words" "-inbox")
  165. notmuch-show-mark-read-tags '("-new" "-unread")
  166. notmuch-tag-formats
  167. (let (;; (d `(:foreground ,(face-attribute 'jao-themes-dimm :foreground)))
  168. (e `(:foreground ,(face-attribute 'jao-themes-error :foreground))))
  169. `(("unread")
  170. ("signed")
  171. ("new" "·")
  172. ("replied" "↩" (propertize tag 'face '(:family "Fira Code")))
  173. ("sent" "🛪")
  174. ("attachment" "📎")
  175. ("deleted" "🗙" (propertize tag 'face '(:underline nil ,@e)))
  176. ("flagged" "✓")
  177. ("jao" "j")
  178. ("bigml" "b")
  179. ("feeds" "f")
  180. ("lists" "l")
  181. ("gmane" "g")))
  182. notmuch-tag-deleted-formats
  183. '(("unread")
  184. ("new")
  185. ("flagged")
  186. ("deleted")
  187. (".*" (notmuch-apply-face tag 'notmuch-tag-deleted))))
  188. (with-eval-after-load "notmuch-tag"
  189. (advice-add #'notmuch-read-tag-changes
  190. :filter-return (lambda (x) (mapcar #'string-trim x))))
  191. ;;; package
  192. ;; (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/")
  193. (jao-load-path "notmuch")
  194. (use-package notmuch
  195. :init
  196. (setq notmuch-address-use-company t
  197. notmuch-address-command 'as-is
  198. notmuch-always-prompt-for-sender t
  199. notmuch-draft-folder "drafts"
  200. notmuch-draft-quoted-tags '("part")
  201. notmuch-fcc-dirs
  202. '(("\\(support\\|education\\)@bigml.com" . nil)
  203. ("mail@jao.io" . nil)
  204. (".*@bigml.com" . "bigml -new -unread +sent +bigml")
  205. (".*" . "jao -new -unread +sent +jao"))
  206. notmuch-maildir-use-notmuch-insert t)
  207. :custom ((notmuch-address-internal-completion '(sent nil)))
  208. :config
  209. (add-hook 'message-send-hook #'notmuch-mua-attachment-check)
  210. (when jao-notmuch-enabled
  211. (define-key message-mode-map (kbd "C-c C-d") #'notmuch-draft-postpone)
  212. (setq message-directory "~/var/mail/"
  213. message-auto-save-directory "/tmp"
  214. mail-user-agent 'message-user-agent))
  215. :bind (:map notmuch-common-keymap
  216. (("E" . jao-notmuch-open-enclosure)
  217. ("B" . notmuch-show-resend-message)
  218. ("b" . jao-notmuch-browse-urls))))
  219. (use-package jao-notmuch :demand t)
  220. ;;; hello
  221. (defun jao-notmuch-hello--header () (insert " "))
  222. (when (display-graphic-p)
  223. (add-to-list 'notmuch-hello-sections 'jao-notmuch-hello--header))
  224. (add-to-list 'notmuch-hello-sections 'notmuch-hello-insert-alltags t)
  225. (defun jao-notmuch-refresh-agenda ()
  226. (interactive)
  227. (save-window-excursion (calendar) (jao-org-agenda)))
  228. (defun jao-notmuch-hello-first ()
  229. (interactive)
  230. (let ((inhibit-message t))
  231. (goto-char (point-min))
  232. (widget-forward 1)))
  233. (defun jao-notmuch-refresh-hello (&optional agenda)
  234. (interactive "P")
  235. (ignore-errors
  236. (when (and (string= "mail" (jao-afio-frame-name))
  237. (derived-mode-p 'notmuch-hello-mode))
  238. (when (not (string-blank-p jao-notmuch-minibuffer-string))
  239. (let ((notmuch-hello-auto-refresh nil)) (notmuch-hello)))
  240. (let ((jao-minibuffer-inhibit t))
  241. (when agenda (jao-notmuch-refresh-agenda)))
  242. (unless (widget-at) (jao-notmuch-hello-first))
  243. (jao-minibuffer-refresh))))
  244. (defvar jao-notmuch-hello--sec-rx "^\\(\\[ [0-9]+\\|All tags:.+\\)")
  245. (defun jao-notmuch-hello-next-section ()
  246. (interactive)
  247. (when (re-search-forward jao-notmuch-hello--sec-rx nil t)
  248. (widget-forward 1)))
  249. (defun jao-notmuch-hello-prev-section ()
  250. (interactive)
  251. (beginning-of-line)
  252. (unless (looking-at-p jao-notmuch-hello--sec-rx)
  253. (re-search-backward jao-notmuch-hello--sec-rx nil t))
  254. (when (re-search-backward jao-notmuch-hello--sec-rx nil t)
  255. (end-of-line)
  256. (widget-forward 1)))
  257. (defun jao-notmuch-hello-next ()
  258. (interactive)
  259. (if (widget-at)
  260. (widget-button-press (point))
  261. (jao-notmuch-hello-next-section)))
  262. (use-package notmuch-hello
  263. :init
  264. (setq notmuch-column-control 1.0
  265. notmuch-hello-hide-tags nil
  266. notmuch-hello-thousands-separator ","
  267. notmuch-hello-auto-refresh t
  268. notmuch-show-all-tags-list nil
  269. notmuch-show-logo nil
  270. notmuch-show-empty-saved-searches nil)
  271. (add-to-list 'display-buffer-alist
  272. '("\\*notmuch-hello\\*"
  273. (display-buffer-reuse-window)
  274. (body-function . (lambda (w) (set-window-margins w 1)))))
  275. :hook ((notmuch-hello-refresh . jao-notmuch-notify))
  276. :config
  277. (when jao-notmuch-enabled
  278. (add-hook 'jao-afio-switch-hook #'jao-notmuch-refresh-hello))
  279. :bind (:map notmuch-hello-mode-map
  280. (("a" . jao-notmuch-refresh-agenda)
  281. ("g" . jao-notmuch-refresh-hello)
  282. ("j" . jao-notmuch-jump-search)
  283. ("n" . jao-notmuch-hello-next)
  284. ("p" . widget-backward)
  285. ("SPC" . widget-button-press)
  286. ("/" . consult-notmuch)
  287. ("." . jao-notmuch-hello-first)
  288. ("[" . jao-notmuch-hello-prev-section)
  289. ("]" . jao-notmuch-hello-next-section))))
  290. ;;; show
  291. (defun jao-notmuch-open-enclosure (add)
  292. (interactive "P")
  293. (with-current-notmuch-show-message
  294. (goto-char (point-min))
  295. (if (not (search-forward "Enclosure:" nil t))
  296. (user-error "No enclosure in message body")
  297. (re-search-forward "https?://" nil t)
  298. (if-let (url (thing-at-point-url-at-point))
  299. (progn
  300. (message "%s %s ..." (if add "Adding" "Playing") url)
  301. (unless add (jao-mpc-clear))
  302. (jao-mpc-add-url url)
  303. (unless add (jao-mpc-play)))
  304. (error "Found an enclosure, but not a link!")))))
  305. (defconst jao-mail-clean-rx
  306. (regexp-opt '("ElDiario.es - ElDiario.es: " "The Guardian: "
  307. "The Conversation – Articles (UK): "
  308. "gr-qc updates on arXiv.org: "
  309. "quant-ph updates on arXiv.org: ")))
  310. (defun jao-mail-clean-address (args)
  311. (when-let ((address (car args)))
  312. (list (thread-last (replace-regexp-in-string jao-mail-clean-rx "" address)
  313. (replace-regexp-in-string " " ", ")))))
  314. (use-package notmuch-show
  315. :init
  316. (setq gnus-blocked-images "."
  317. notmuch-message-headers
  318. '("Subject" "To" "Cc" "Date" "Reply-To" "List-Id" "X-RSS-Feed")
  319. notmuch-show-only-matching-messages t
  320. notmuch-show-part-button-default-action 'notmuch-show-view-part
  321. notmuch-wash-signature-lines-max 0
  322. notmuch-wash-wrap-lines-length 120
  323. notmuch-wash-citation-lines-prefix 120
  324. notmuch-wash-citation-lines-suffix 120
  325. notmuch-show-text/html-blocked-images "."
  326. notmuch-show-header-line nil ;; #'jao-notmuch-message-header-line
  327. jao-notmuch-header-line-format "[%N / %M / %T] %n / %m / %t")
  328. :config
  329. (advice-add 'notmuch-clean-address :filter-args #'jao-mail-clean-address)
  330. (add-hook 'notmuch-show-mode-hook (lambda () (setq fill-column 80)))
  331. :bind
  332. (:map notmuch-show-mode-map
  333. (("h" . jao-notmuch-goto-tree-buffer)
  334. ("r" . notmuch-show-reply)
  335. ("R" . notmuch-show-reply-sender)
  336. ("TAB" . jao-notmuch-show-next-button)
  337. ([backtab] . jao-notmuch-show-previous-button)
  338. ("RET" . jao-notmuch-show-ret))))
  339. ;;; search
  340. (use-package notmuch-search
  341. :init (setq notmuch-search-result-format
  342. '(("date" . "%12s ")
  343. ("count" . "%-7s ")
  344. ("authors" . "%-35s")
  345. ("subject" . " %-100s")
  346. (jao-notmuch-format-tags . " (%s)"))
  347. notmuch-search-buffer-name-format "*%s*"
  348. notmuch-saved-search-buffer-name-format "*%s*")
  349. :bind (:map notmuch-search-mode-map
  350. (("RET" . notmuch-tree-from-search-thread)
  351. ("M-RET" . notmuch-search-show-thread))))
  352. ;;; tree
  353. (defun jao-notmuch-tree--forward (&optional prev)
  354. (interactive)
  355. (forward-line (if prev -1 1))
  356. (when prev (forward-char 2))
  357. (jao-notmuch-tree-scroll-or-next))
  358. (defun jao-notmuch-tree--backward ()
  359. (interactive)
  360. (jao-notmuch-tree--forward t))
  361. (defun jao-notmuch--via-url ()
  362. (when (window-live-p notmuch-tree-message-window)
  363. (with-selected-window notmuch-tree-message-window
  364. (goto-char (point-min))
  365. (when (re-search-forward "^Via: http" nil t)
  366. (thing-at-point-url-at-point)))))
  367. (defun jao-notmuch-browse-url (ext)
  368. (interactive "P")
  369. (when-let (url (or (jao-notmuch--via-url)
  370. (car (last (jao-notmuch-message-urls)))))
  371. (funcall (if ext browse-url-secondary-browser-function #'browse-url)
  372. url)))
  373. (defun jao-notmuch-adjust-tree-fonts (&optional family)
  374. (let ((fg (face-attribute 'jao-themes-dimm :foreground)))
  375. (dolist (f '(notmuch-tree-match-tree-face
  376. notmuch-tree-no-match-tree-face))
  377. (if family
  378. (set-face-attribute f nil :family family :foreground fg)
  379. (set-face-attribute f nil :foreground fg)))))
  380. (use-package notmuch-tree
  381. :init
  382. (setq notmuch-tree-result-format
  383. `(("date" . "%12s ")
  384. ("authors" . "%-25s")
  385. ;; (jao-notmuch-format-author . 25)
  386. (jao-notmuch-format-msg-ticks . ,jao-mails-regexp)
  387. (jao-notmuch-format-tree-and-subject . "%>-85s")
  388. (jao-notmuch-format-tags . " (%s)"))
  389. notmuch-unthreaded-result-format notmuch-search-result-format
  390. consult-notmuch-result-format
  391. `((jao-notmuch-format-msg-ticks . ,jao-mails-regexp)
  392. ("date" . "%12s ")
  393. ("authors" . "%-35s")
  394. ("subject" . " %-100s")
  395. (jao-notmuch-format-tags . " (%s)"))
  396. notmuch-tree-thread-symbols
  397. '((prefix . "─") (top . "─") (top-tee . "┬")
  398. (vertical . "│") (vertical-tee . "├") (bottom . "╰")
  399. (arrow . ""))
  400. ;; notmuch-tree-thread-symbols
  401. ;; '((prefix . " ") (top . " ") (top-tee . " ")
  402. ;; (vertical . " ") (vertical-tee . " ") (bottom . " ")
  403. ;; (arrow . ""))
  404. notmuch-tree-outline-enabled t
  405. notmuch-tree-outline-visibility 'hide-others
  406. notmuch-tree-outline-auto-close t
  407. notmuch-tree-outline-open-on-next t)
  408. :config
  409. (when (display-graphic-p)
  410. (jao-notmuch-adjust-tree-fonts
  411. (when (string-prefix-p "Hack" jao-themes-default-face) "Source Code Pro")))
  412. (defun jao-notmuch-before-tree (&rest _args)
  413. (when (string= (buffer-name) "*notmuch-hello*")
  414. (window-configuration-to-register ?G)
  415. (split-window-right 40)
  416. (other-window 1)))
  417. (defvar jao-notmuch--visits 0)
  418. (defun jao-notmuch-after-tree-quit (&optional _both)
  419. (when (not (derived-mode-p 'notmuch-tree-mode 'notmuch-hello-mode))
  420. (jump-to-register ?G))
  421. (jao-notmuch-refresh-hello (= 0 (mod (cl-incf jao-notmuch--visits) 50))))
  422. (defun jao-notmuch-tree--sentinel (proc)
  423. (when (eq (process-status proc) 'exit)
  424. (let ((inhibit-read-only t))
  425. (save-excursion
  426. (goto-char (point-max))
  427. (when (re-search-backward "^End of search results." nil t)
  428. (delete-line))))
  429. (jao-notmuch-thread-info-mode)))
  430. (add-hook 'notmuch-tree-process-exit-functions #'jao-notmuch-tree--sentinel)
  431. (advice-add 'notmuch-tree :before #'jao-notmuch-before-tree)
  432. (advice-add 'notmuch-tree-quit :after #'jao-notmuch-after-tree-quit)
  433. :bind (:map notmuch-tree-mode-map
  434. (("b" . jao-notmuch-browse-urls)
  435. ("d" . jao-notmuch-tree-toggle-delete)
  436. ("D" . jao-notmuch-tree-toggle-delete-thread)
  437. ("h" . jao-notmuch-goto-message-buffer)
  438. ("i" . jao-notmuch-toggle-images)
  439. ("k" . jao-notmuch-tree-read-thread)
  440. ("N" . jao-notmuch-tree--forward)
  441. ("O" . notmuch-tree-toggle-order)
  442. ("o" . jao-notmuch-tree-widen-search)
  443. ("P" . jao-notmuch-tree--backward)
  444. ("r" . notmuch-tree-reply)
  445. ("R" . notmuch-tree-reply-sender)
  446. ("s" . jao-notmuch-tree-toggle-spam)
  447. ("u" . jao-notmuch-tree-toggle-flag)
  448. ("v" . notmuch-tree-scroll-message-window)
  449. ("V" . notmuch-tree-scroll-message-window-back)
  450. ("x" . jao-notmuch-arXiv-capture)
  451. ("<" . jao-notmuch-tree-beginning-of-buffer)
  452. (">" . jao-notmuch-tree-end-of-buffer)
  453. ("\\" . notmuch-tree-view-raw-message)
  454. ("." . jao-notmuch-toggle-mime-parts)
  455. (";" . bbdb-mua-display-sender)
  456. ("=" . jao-notmuch-tree-toggle-message)
  457. ("RET" . jao-notmuch-tree-show-or-scroll)
  458. ("SPC" . jao-notmuch-tree-scroll-or-next)
  459. ("M-g" . jao-notmuch-browse-url)
  460. ("M-u" . jao-notmuch-tree-reset-tags))))
  461. ;;; browse-url
  462. (defvar jao-notmuch-url-rx "^notmuch:\\(/+\\|id:\\)\\(.+\\)")
  463. (defun jao-notmuch-open-url (url &rest _)
  464. (and (string-match jao-notmuch-url-rx url)
  465. (notmuch-show (concat "id:" (match-string 2 url)))))
  466. (add-to-list 'browse-url-handlers
  467. (cons jao-notmuch-url-rx 'jao-notmuch-open-url))
  468. ;;; org mode
  469. (defvar jao-org-notmuch-last-subject nil)
  470. (defun jao-org-notmuch-last-subject () jao-org-notmuch-last-subject)
  471. (defun jao-notmuch--add-tags (tags)
  472. (if (derived-mode-p 'notmuch-show-mode)
  473. (notmuch-show-add-tag tags)
  474. (notmuch-tree-add-tag tags)))
  475. (defun org-notmuch-store-link ()
  476. "Store a link to a notmuch mail message."
  477. (cl-case major-mode
  478. ((notmuch-show-mode notmuch-tree-mode)
  479. ;; Store link to the current message
  480. (let* ((id (notmuch-show-get-message-id))
  481. (link (concat "notmuch:" id))
  482. (subj (notmuch-show-get-subject))
  483. (description (format "Mail: %s" subj)))
  484. (setq jao-org-notmuch-last-subject subj)
  485. (when (y-or-n-p "Archive message? ")
  486. (jao-notmuch--add-tags '("+trove")))
  487. (when (y-or-n-p "Flag message? ")
  488. (jao-notmuch--add-tags '("+flagged")))
  489. (org-link-store-props :type "notmuch"
  490. :link link
  491. :description description)))
  492. (notmuch-search-mode
  493. ;; Store link to the thread on the current line
  494. (let* ((id (notmuch-search-find-thread-id))
  495. (link (concat "notmuch:" id))
  496. (subj (notmuch-search-find-subject))
  497. (description (format "Mail: %s" subj)))
  498. (setq jao-org-notmuch-last-subject subj)
  499. (org-link-store-props
  500. :type "notmuch"
  501. :link link
  502. :description description)))))
  503. (with-eval-after-load "org"
  504. (org-link-set-parameters "notmuch"
  505. :follow 'notmuch-show
  506. :store 'org-notmuch-store-link))
  507. ;;; arXiv
  508. (use-package org-capture
  509. :config
  510. (when jao-notmuch-enabled
  511. (add-to-list 'org-capture-templates
  512. '("X" "arXiv" entry (file "notes/physics/arxiv.org")
  513. "* %(jao-org-notmuch-last-subject)\n %i"
  514. :immediate-finish t)
  515. t)
  516. (org-capture-upgrade-templates org-capture-templates)))
  517. (defun jao-notmuch-arXiv-capture ()
  518. (interactive)
  519. (save-window-excursion
  520. (jao-notmuch-goto-message-buffer)
  521. (save-excursion
  522. (goto-char (point-min))
  523. (re-search-forward "\\[ text/html \\]")
  524. (forward-paragraph)
  525. (setq-local transient-mark-mode 'lambda)
  526. (set-mark (point))
  527. (goto-char (point-max))
  528. (org-capture nil "X"))))
  529. ;;; html renderer
  530. (when jao-notmuch-enabled (setq mm-text-html-renderer 'shr))
  531. ;;; consult
  532. (jao-load-path "consult-notmuch")
  533. (require 'consult-notmuch)
  534. (setq consult-notmuch-newest-first t)
  535. (consult-customize consult-notmuch :preview-key 'any)
  536. (defvar jao-consult-notmuch-history nil)
  537. (defvar jao-mailbox-folders '("bigml" "jao"))
  538. (defun jao-consult-notmuch-folder (&optional tree folder)
  539. (interactive "P")
  540. (let* ((folder (if folder
  541. (file-name-as-directory folder)
  542. (completing-read "Group: "
  543. jao-mailbox-folders
  544. nil nil nil
  545. jao-consult-notmuch-history
  546. ".")))
  547. (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder))
  548. (init (read-string "Initial query: "))
  549. (init (format "folder:/%s/ %s" folder init)))
  550. (if tree (consult-notmuch-tree init) (consult-notmuch init))))
  551. (with-eval-after-load "notmuch-hello"
  552. (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder))
  553. ;;; recoll
  554. (defun jao-notmuch-open-file (fname &optional _page)
  555. (with-temp-buffer
  556. (insert-file-contents-literally fname)
  557. (goto-char (point-min))
  558. (and (re-search-forward "^Message-ID: <\\([^>]+\\)>$" nil t)
  559. (notmuch-show (concat "id:" (match-string 1))))))
  560. (when jao-notmuch-enabled
  561. (with-eval-after-load "org"
  562. (org-link-set-parameters "message" :follow #'jao-notmuch-open-file))
  563. (with-eval-after-load "consult-recoll"
  564. (add-to-list 'consult-recoll-open-fns
  565. '("message/rfc822" . jao-notmuch-open-file))))
  566. ;;; .
  567. (provide 'jao-custom-notmuch)