Emacs configuration and packages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

27 KiB

notmuch configuration

minibuffer

  (defvar jao-notmuch-minibuffer-string "")

  (defvar jao-notmuch-minibuffer-queries
    '((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00)
      (:name "B" :query "tag:new and tag:bigml and tag:inbox" :face default)
      (:name "b" :query "tag:new and tag:bigml and tag:bugs"
             :face jao-themes-error)
      (:name "S" :query "tag:new and tag:bigml and tag:support" :face default)
      (:name "W"
             :query "tag:new and tag:bigml and not tag:\"/support|bugs|inbox/\""
             :face default)
      (:name "I"
             :query "tag:new and tag:jao and tag:inbox"
             :face jao-themes-warning)
      (:name "J"
             :query "tag:new and tag:jao and not tag:\"/local|hacking|draft|inbox/\""
             :face default)
      (:name "H" :query "tag:new and tag:hacking and not tag:\"/emacs/\"")
      (:name "E" :query "tag:new and tag:\"/emacs/\"")
      (:name "l" :query "tag:new and tag:local")
      (:name "F" :query "tag:new and tag:feeds and not tag:\"/emacs/\"")))

  (defun jao-notmuch-notify ()
    (let ((cnts (notmuch-hello-query-counts jao-notmuch-minibuffer-queries)))
      (setq jao-notmuch-minibuffer-string
            (mapconcat (lambda (c)
                         (propertize (format "%s%s"
                                             (plist-get c :name)
                                             (plist-get c :count))
                                     'face (or (plist-get c :face)
                                               'jao-themes-dimm)))
                       cnts
                       " "))
      (jao-minibuffer-refresh)))

  (when jao-notmuch-enabled
    (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20))

saved searches

  (defvar jao-notmuch--new "tag:\"/^(unread|new)$/\"")
  (defvar jao-notmuch--newa (concat jao-notmuch--new " AND "))

  (defun jao-notmuch--q (d0 d1 &optional k qs st)
    (let ((q (or (when qs (mapconcat #'identity qs " AND "))
                 (concat jao-notmuch--newa
                         (mapconcat (lambda (d) (when d (concat "tag:" d)))
                                    (list d0 d1) " AND ")))))
      (list :name (concat d0 (when (and d1 (not (string= "" d1))) "/") d1)
            :key k :query q :search-type (or st 'tree)
            :sort-order 'oldest-first)))

  (defun jao-notmuch--qn (d0 d1 k qs &optional st)
    (jao-notmuch--q d0 d1 k (cons jao-notmuch--new qs) st))

  (defun jao-notmuch--sq (tag &optional k d0 d1)
    (jao-notmuch--qn (or d0 "feeds") (or d1 tag) k (list (concat "tag:" tag))))

  (defvar jao-notmuch--shared-tags
    '("new" "unread" "flagged" "signed" "sent" "attachment" "forwarded"
      "encrypted" "gmane" "gnus" "feeds" "rss" "mce" "trove" "prog" "emacs"))

  (defun jao-notmuch--subtags (tag &rest excl)
    (let* ((cmd (concat "notmuch search --output=tags tag:" tag))
           (ts (split-string (shell-command-to-string cmd))))
      (seq-difference ts (append jao-notmuch--shared-tags (cons tag excl)))))

  (defvar jao-notmuch-feed-searches
    (append (mapcar #'jao-notmuch--sq '("news"
                                        "fun"
                                        "words"
                                        "computers"
                                        "mailutils"
                                        "notmuch"
                                        "lobsters"
                                        "clojure"
                                        "haskell"
                                        "idris"
                                        "pharo"
                                        "lisp"
                                        "scheme"
                                        "xmobar"
                                        "geiser"))
            `(,(jao-notmuch--qn "feeds" "prog" "fp"
                                '("tag:prog" "not tag:\"/emacs/\"")))
            (mapcar #'jao-notmuch--sq '("philosophy"
                                        "math"
                                        "physics"
                                        "sci"
                                        "gr-qc"
                                        "quant-ph"))))

  (defvar jao-notmuch-bigml-searches
    `(,(jao-notmuch--q "bigml" "inbox" "bi")
      ,(jao-notmuch--q "bigml" "support" "bs")
      ,(jao-notmuch--q "bigml" "bugs" "bb")
      ,(jao-notmuch--q "bigml" "drivel" "bd")
      ,(jao-notmuch--q "bigml" "lists" "bl")))

  (defvar jao-notmuch-inbox-searches
    `(,(jao-notmuch--q "jao" "inbox" "ji")
      ,(jao-notmuch--q "jao" "bills" "jb")
      ,(jao-notmuch--q "jao" "drivel" "jd")
      ,(jao-notmuch--q "jao" "mdk" "jm")
      ,(jao-notmuch--qn "jao" "hacking" "jh"
                        '("tag:hacking" "not tag:\"/emacs/\""))
      ,(jao-notmuch--qn "jao" "local" "jl" '("tag:local"))))

  (defvar jao-notmuch-mark-searches
    `(,(jao-notmuch--q "jao" "drafts" "d" '("tag:draft"))
      ,(jao-notmuch--q "bml" "flagged" "rb" '("tag:flagged" "tag:bigml"))
      ,(jao-notmuch--q "jao" "flagged" "rj" '("tag:flagged" "tag:jao"))
      ,(jao-notmuch--q "feeds" "flagged" "rf" '("tag:flagged" "tag:feeds"))))

  (defvar jao-notmuch-emacs-searches
    `(,(jao-notmuch--sq "emacs" "ee" "emacs" "feeds")
      ,(jao-notmuch--sq "emacs-github" "eg" "emacs" "github")
      ,(jao-notmuch--sq "emacs-devel" "ed" "emacs" "devel")
      ,(jao-notmuch--sq "emacs-bugs" "eb" "emacs" "bugs")
      ,(jao-notmuch--sq "emacs-diffs" "ec" "emacs" "diffs")
      ,(jao-notmuch--sq "emacs-orgmode" "eo" "emacs" "org")))

  (setq notmuch-saved-searches
        (append jao-notmuch-inbox-searches
                jao-notmuch-bigml-searches
                jao-notmuch-mark-searches
                jao-notmuch-feed-searches
                jao-notmuch-emacs-searches))

  (defvar jao-notmuch-dynamic-searches
    `(,(jao-notmuch--q "bml" "today" "tb" '("tag:bigml" "date:24h.."))
      ,(jao-notmuch--q "jao" "today" "tj"
                       '("tag:jao" "date:24h.."
                         "not tag:\"/(feeds|spam|local)/\""))))

  (defvar jao-notmuch-new-searches
    `(,(jao-notmuch--q "new" nil "nn" '("tag:new" "not tag:draft"))
      ,(jao-notmuch--q "unread" nil "nu" '("tag:unread"))
      (:query "*" :name "messages")))

  (defun jao-notmuch-tree-widen-search ()
    (interactive)
    (when-let ((query (notmuch-tree-get-query)))
      (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto)))
        (notmuch-tree-close-message-window)
        (notmuch-tree (string-replace jao-notmuch--newa "" query)))))

  (defun jao-notmuch-widen-searches (searches &optional extra)
    (mapcar (lambda (s)
              (let* ((q (plist-get s :query))
                     (qs (string-replace jao-notmuch--newa "" q)))
                (plist-put (copy-sequence s) :query (concat qs extra))))
            searches))

  (defvar jao-notmuch-widened-searches
    (jao-notmuch-widen-searches notmuch-saved-searches))

  (defvar jao-notmuch-flagged-searches
    (let ((s (seq-difference notmuch-saved-searches
                             jao-notmuch-mark-searches)))
      (jao-notmuch-widen-searches s " AND tag:flagged")))

  (defun jao-notmuch-jump-search (&optional widen)
    (interactive "P")
    (let ((notmuch-saved-searches
           (if widen jao-notmuch-widened-searches notmuch-saved-searches)))
      (notmuch-jump-search)))

tags

  (setq notmuch-archive-tags '("+trove" "-new" "-inbox")
        notmuch-show-mark-read-tags '("-new" "-unread")
        notmuch-tag-formats
        (let ((d `(:foreground ,(face-attribute 'jao-themes-dimm :foreground)))
              (e `(:foreground ,(face-attribute 'jao-themes-error :foreground)
                   :weight bold)))
          `(("unread")
            ("signed")
            ("new" "N")
            ("replied" "↩" (propertize tag 'face '(:family "Fira Code")))
            ("sent" "S")
            ("attachment" "📎")
            ("deleted" "🗙" (propertize tag 'face '(:underline nil ,@e)))
            ("flagged" "!" (propertize tag 'face ',e))
            ("jao" "j")
            ("bigml" "b")
            ("feeds" "f")
            ("gmane" "g")))
        notmuch-tag-deleted-formats
        '(("unread")
          ("new")
          (".*" (notmuch-apply-face tag 'notmuch-tag-deleted))))

  (with-eval-after-load "notmuch-tag"
    (advice-add #'notmuch-read-tag-changes
                :filter-return (lambda (x) (mapcar #'string-trim x))))

package

  (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/")

  (use-package notmuch
    :init
    (setq notmuch-address-use-company nil
          notmuch-address-command (if jao-notmuch-enabled 'internal 'as-is)
          notmuch-always-prompt-for-sender t
          notmuch-draft-folder "local"
          notmuch-draft-quoted-tags '("part")
          notmuch-address-internal-completion '(received nil)
          notmuch-fcc-dirs
          '(("\\(support\\|education\\)@bigml.com" . nil)
            (".*@bigml.com" . "bigml.trove +bigml +sent -new -unread")
            (".*" . "jao.trove +jao +sent +trove -new -unread"))
          notmuch-maildir-use-notmuch-insert t)

    :config

    (add-hook 'message-send-hook #'notmuch-mua-attachment-check)

    (when jao-notmuch-enabled
      (define-key message-mode-map (kbd "C-c C-d") #'notmuch-draft-postpone)
      (setq message-directory "~/var/mail/"
            message-auto-save-directory "/tmp"
            mail-user-agent 'message-user-agent))

    :bind (:map notmuch-common-keymap
           (("E" . jao-notmuch-open-enclosure)
            ("B" . notmuch-show-resend-message)
            ("b" . jao-notmuch-browse-urls))))

  (use-package jao-notmuch :demand t)

hello

  (defun jao-notmuch-hello--insert-searches (searches title)
    (when-let (searches (notmuch-hello-query-counts searches))
      (let* ((cnt (when title
                    (seq-reduce (lambda (c q)
                                  (+ c (or (plist-get q :count) 0)))
                                searches
                                0)))
             (title (if title (format "[ %d %s ]\n\n" cnt title) "\n")))
        (widget-insert (propertize title 'face 'jao-themes-f00))
        (let ((notmuch-column-control 1.0)
              (start (point)))
          (notmuch-hello-insert-buttons searches)
          (indent-rigidly start (point) notmuch-hello-indent)))))

  (defun jao-notmuch-hello-insert-inbox-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-inbox-searches "inbox"))

  (defun jao-notmuch-hello-insert-bigml-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-bigml-searches "bigml"))

  (defun jao-notmuch-hello-insert-mark-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-mark-searches "marks")
    (jao-notmuch-hello--insert-searches jao-notmuch-flagged-searches nil))

  (defun jao-notmuch-hello-insert-feeds-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-feed-searches "feeds"))

  (defun jao-notmuch-hello-insert-emacs-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-emacs-searches "emacs"))

  (defun jao-notmuch-hello-insert-dynamic-searches ()
    (jao-notmuch-hello--insert-searches jao-notmuch-dynamic-searches "dynamic")
    (jao-notmuch-hello--insert-searches jao-notmuch-new-searches nil))

  (defun jao-notmuch-refresh-agenda ()
    (interactive)
    (save-window-excursion (org-agenda-list))
    (let ((b (current-buffer)))
      (pop-to-buffer "*Calendar*")
      (goto-char (point-min))
      (calendar-goto-today)
      (pop-to-buffer b)))

  (defun jao-notmuch-hello-first ()
    (interactive)
    (let ((inhibit-message t))
      (beginning-of-buffer)
      (widget-forward 1)))

  (defun jao-notmuch-refresh-hello (&optional agenda)
    (interactive "P")
    (ignore-errors
      (when (and (string= "Mail" (jao-afio-current-frame))
                 (derived-mode-p 'notmuch-hello-mode))
        (when (not (string-blank-p jao-notmuch-minibuffer-string))
          (let ((notmuch-hello-auto-refresh nil)) (notmuch-hello)))
        (when agenda (jao-notmuch-refresh-agenda))
        (unless (widget-at) (jao-notmuch-hello-first)))))

  (defvar jao-notmuch-hello--sec-rx "^\\(\\[ [0-9]+\\|All tags:.+\\)")

  (defun jao-notmuch-hello-next-section ()
    (interactive)
    (when (re-search-forward jao-notmuch-hello--sec-rx  nil t)
      (widget-forward 1)))

  (defun jao-notmuch-hello-prev-section ()
    (interactive)
    (beginning-of-line)
    (unless (looking-at-p jao-notmuch-hello--sec-rx)
      (re-search-backward jao-notmuch-hello--sec-rx nil t))
    (when (re-search-backward jao-notmuch-hello--sec-rx  nil t)
      (end-of-line)
      (widget-forward 1)))

  (defun jao-notmuch-hello-next ()
    (interactive)
    (if (widget-at)
        (widget-button-press (point))
      (jao-notmuch-hello-next-section)))

  (use-package notmuch-hello
    :init
    (setq notmuch-column-control t
          notmuch-hello-sections '(jao-notmuch-hello-insert-bigml-searches
                                   jao-notmuch-hello-insert-inbox-searches
                                   jao-notmuch-hello-insert-feeds-searches
                                   jao-notmuch-hello-insert-emacs-searches
                                   jao-notmuch-hello-insert-mark-searches
                                   jao-notmuch-hello-insert-dynamic-searches
                                   notmuch-hello-insert-alltags)
          notmuch-hello-hide-tags nil
          notmuch-hello-thousands-separator ","
          notmuch-hello-auto-refresh t
          notmuch-show-all-tags-list nil
          notmuch-show-logo nil
          notmuch-show-empty-saved-searches nil)

    :hook ((notmuch-hello-refresh . jao-notmuch-notify)
           (jao-afio-switch . jao-notmuch-refresh-hello))

    :bind (:map notmuch-hello-mode-map
           (("a" . jao-notmuch-refresh-agenda)
            ("j" . jao-notmuch-jump-search)
            ("n" . jao-notmuch-hello-next)
            ("p" . widget-backward)
            ("S" . consult-notmuch)
            ("g" . jao-notmuch-refresh-hello)
            ("." . jao-notmuch-hello-first)
            ("SPC" . widget-button-press)
            ("[" . jao-notmuch-hello-prev-section)
            ("]" . jao-notmuch-hello-next-section))))

show

  (defun jao-notmuch-open-enclosure (add)
    (interactive "P")
    (with-current-notmuch-show-message
     (goto-char (point-min))
     (if (not (search-forward "Enclosure:" nil t))
         (user-error "No enclosure in message body")
       (re-search-forward "https?://" nil t)
       (if-let (url (thing-at-point-url-at-point))
           (progn
             (message "%s %s ..." (if add "Adding" "Playing") url)
             (unless add (jao-mpc-clear))
             (jao-mpc-add-url url)
             (unless add (jao-mpc-play)))
         (error "Found an enclosure, but not a link!")))))

  (defconst jao-mail-clean-rx
    (regexp-opt '("ElDiario.es - ElDiario.es: " "The Guardian: "
                  "The Conversation – Articles (UK): ")))

  (defun jao-mail-clean-address (args)
    (when-let ((address (car args)))
      (list (if (string-match ".+ updates on arXiv.org: \\(.+\\)" address)
                (with-temp-buffer
                  (insert (match-string 1 address))
                  (let ((shr-width 1000))
                    (shr-render-region (point-min) (point-max)))
                  (replace-regexp-in-string "\"" "" (buffer-string)))
              (replace-regexp-in-string jao-mail-clean-rx "" address)))))

  (use-package notmuch-show
    :init
    (setq gnus-blocked-images "."
          notmuch-message-headers
          '("To" "Cc" "Date" "Reply-To" "List-Id" "X-RSS-Feed")
          notmuch-show-only-matching-messages t
          notmuch-show-part-button-default-action 'notmuch-show-view-part
          notmuch-wash-signature-lines-max 0
          notmuch-wash-wrap-lines-length 80
          notmuch-wash-citation-lines-prefix 10
          notmuch-wash-citation-lines-suffix 20
          notmuch-show-text/html-blocked-images "."
          notmuch-show-header-line #'jao-notmuch-message-header-line)

    :config

    (advice-add 'notmuch-clean-address :filter-args #'jao-mail-clean-address)

    :bind
    (:map notmuch-show-mode-map
     (("h" . jao-notmuch-goto-tree-buffer)
      ("TAB" . jao-notmuch-show-next-button)
      ([backtab] . jao-notmuch-show-previous-button)
      ("RET" . jao-notmuch-show-ret))))

search

  (use-package notmuch-search
    :init (setq notmuch-search-result-format
                '(("date" . "%12s ")
                  ("count" . "%-7s ")
                  ("authors" . "%-35s")
                  ("subject" . " %-100s")
                  (jao-notmuch-format-tags . "  (%s)"))
                notmuch-search-buffer-name-format "*%s*"
                notmuch-saved-search-buffer-name-format "*%s*")
    :bind (:map notmuch-search-mode-map
           (("RET" . notmuch-tree-from-search-thread)
            ("M-RET" . notmuch-search-show-thread))))

tree

  (defun jao-notmuch-tree--forward (&optional prev)
    (interactive)
    (forward-line (if prev -1 1))
    (when prev (forward-char 2))
    (jao-notmuch-tree-scroll-or-next))

  (defun jao-notmuch-tree--backward ()
    (interactive)
    (jao-notmuch-tree--forward t))

  (defun jao-notmuch--via-url ()
    (when (window-live-p notmuch-tree-message-window)
      (with-selected-window notmuch-tree-message-window
        (goto-char (point-min))
        (when (re-search-forward "^Via: http" nil t)
          (thing-at-point-url-at-point)))))

  (defun jao-notmuch-browse-url (ext)
    (interactive "P")
    (when-let (url (or (jao-notmuch--via-url)
                       (car (last (jao-notmuch-message-urls)))))
      (funcall (if ext browse-url-secondary-browser-function #'browse-url)
               url)))

  (defun jao-notmuch-adjust-tree-fonts (&optional family)
    (let ((fg (face-attribute 'jao-themes-dimm :foreground))
          (family (or family "Source Code Pro")))
      (dolist (f '(notmuch-tree-match-tree-face
                   notmuch-tree-no-match-tree-face))
        (set-face-attribute f nil :family family :foreground fg))))

  (use-package notmuch-tree
    :init
    (setq notmuch-tree-result-format
          `(("date" . "%12s  ")
            ("authors" . "%-25s")
            (jao-notmuch-msg-ticks . ,jao-mails-regexp)
            (jao-notmuch-tree-and-subject . "%>-85s")
            (jao-notmuch-format-tags . "  (%s)"))
          notmuch-unthreaded-result-format notmuch-search-result-format
          consult-notmuch-result-format
          `((jao-notmuch-msg-ticks . ,jao-mails-regexp)
            ("date" . "%12s  ")
            ("authors" . "%-35s")
            ("subject" . " %-100s")
            (jao-notmuch-format-tags . "  (%s)"))
          notmuch-tree-thread-symbols
          '((prefix . "─") (top . "─") (top-tee . "┬")
            (vertical . "│") (vertical-tee . "├") (bottom . "╰")
            (arrow . "")))

    :config

    (when (string-prefix-p "Hack" jao-themes-default-face)
      (jao-notmuch-adjust-tree-fonts))

    (jao-notmuch-tree-setup "T")

    (defun jao-notmuch-before-tree (&rest args)
      (when (string= (buffer-name) "*notmuch-hello*")
        (split-window-right 40)
        (other-window 1)))

    (defvar jao-notmuch--visits 0)

    (defun jao-notmuch-after-tree-quit (&optional both)
      (when (and (not (derived-mode-p 'notmuch-tree-mode 'notmuch-hello-mode))
                 (save-window-excursion (other-window -1)
                                        (derived-mode-p 'notmuch-hello-mode)))
        (delete-window)
        (jao-notmuch-refresh-hello (= 0 (mod (cl-incf jao-notmuch--visits) 10)))))

    (advice-add 'notmuch-tree :before #'jao-notmuch-before-tree)
    (advice-add 'notmuch-tree-quit :after #'jao-notmuch-after-tree-quit)

    :bind (:map notmuch-tree-mode-map
                (("b" . jao-notmuch-browse-urls)
                 ("d" . jao-notmuch-tree-toggle-delete)
                 ("D" . jao-notmuch-tree-toggle-delete-thread)
                 ("h" . jao-notmuch-goto-message-buffer)
                 ("H" . jao-notmuch-click-message-buffer)
                 ("i" . jao-notmuch-toggle-images)
                 ("K" . jao-notmuch-tag-jump-and-next)
                 ("k" . jao-notmuch-tree-read-thread)
                 ("n" . jao-notmuch-tree-next)
                 ("N" . jao-notmuch-tree--forward)
                 ("O" . notmuch-tree-toggle-order)
                 ("o" . jao-notmuch-tree-widen-search)
                 ("p" . jao-notmuch-tree-previous)
                 ("P" . jao-notmuch-tree--backward)
                 ("r" . notmuch-tree-reply)
                 ("R" . notmuch-tree-reply-sender)
                 ("s" . jao-notmuch-tree-toggle-spam)
                 ("u" . jao-notmuch-tree-toggle-flag)
                 ("v" . notmuch-tree-scroll-message-window)
                 ("V" . notmuch-tree-scroll-message-window-back)
                 ("x" . jao-notmuch-arXiv-capture)
                 ("<" . jao-notmuch-tree-beginning-of-buffer)
                 (">" . jao-notmuch-tree-end-of-buffer)
                 ("\\" . notmuch-tree-view-raw-message)
                 ("." . jao-notmuch-toggle-mime-parts)
                 ("=" . jao-notmuch-tree-toggle-message)
                 ("RET" . jao-notmuch-tree-show-or-scroll)
                 ("SPC" . jao-notmuch-tree-scroll-or-next)
                 ("M-g" . jao-notmuch-browse-url)
                 ("M-u" . jao-notmuch-tree-reset-tags))))

org mode

Stolen and adapted from Fedor Bezrukov.

  (defvar jao-org-notmuch-last-subject nil)
  (defun jao-org-notmuch-last-subject () jao-org-notmuch-last-subject)

  (defun jao-notmuch--add-tags (tags)
    (if (derived-mode-p 'notmuch-show-mode)
        (notmuch-show-add-tag tags)
      (notmuch-tree-add-tag tags)))

  (defun org-notmuch-store-link ()
    "Store a link to a notmuch mail message."
    (cl-case major-mode
      ((notmuch-show-mode notmuch-tree-mode)
       ;; Store link to the current message
       (let* ((id (notmuch-show-get-message-id))
              (link (concat "notmuch:" id))
              (subj (notmuch-show-get-subject))
              (description (format "Mail: %s" subj)))
         (setq jao-org-notmuch-last-subject subj)
         (when (y-or-n-p "Archive message? ")
           (jao-notmuch--add-tags '("+trove")))
         (when (y-or-n-p "Flag message as todo? ")
           (jao-notmuch--add-tags '("+flagged")))
         (org-store-link-props
   :type "notmuch"
   :link link
   :description description)))
      (notmuch-search-mode
       ;; Store link to the thread on the current line
       (let* ((id (notmuch-search-find-thread-id))
              (link (concat "notmuch:" id))
              (subj (notmuch-search-find-subject))
              (description (format "Mail: %s" subj)))
         (setq jao-org-notmuch-last-subject subj)
         (org-store-link-props
          :type "notmuch"
          :link link
          :description description)))))

  (with-eval-after-load "org"
    (org-link-set-parameters "notmuch"
                             :follow 'notmuch-show
                             :store 'org-notmuch-store-link))

arXiv

  (use-package org-capture
    :config
    (add-to-list 'org-capture-templates
                 '("X" "arXiv" entry (file "notes/physics/arxiv.org")
                   "* %(jao-org-notmuch-last-subject)\n %i"
                   :immediate-finish t)
                 t)
    (org-capture-upgrade-templates org-capture-templates))

  (defun jao-notmuch-arXiv-capture ()
    (interactive)
    (save-window-excursion
      (jao-notmuch-goto-message-buffer)
      (save-excursion
        (goto-char (point-min))
        (re-search-forward "\\[ text/html \\]")
        (forward-paragraph)
        (setq-local transient-mark-mode 'lambda)
        (set-mark (point))
        (goto-char (point-max))
        (org-capture nil "X"))))

html render

  (when jao-notmuch-enabled (setq mm-text-html-renderer 'shr))

consult

  (jao-load-path "consult-notmuch")
  (require 'consult-notmuch)
  (consult-customize consult-notmuch :preview-key 'any)

  (defvar jao-consult-notmuch-history nil)

  (defvar jao-mailbox-folders '("bigml" "jao"))

  (defun jao-consult-notmuch-folder (&optional tree folder)
    (interactive "P")
    (let ((folder (if folder
                      (file-name-as-directory folder)
                    (completing-read "Group: "
                                     jao-mailbox-folders
                                     nil nil nil
                                     jao-consult-notmuch-history
                                     ".")))
           (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder))
           (init (read-string "Initial query: "))
           (init (format "folder:/%s/ %s" folder init)))
      (if tree (consult-notmuch-tree init) (consult-notmuch init))))

  (with-eval-after-load "notmuch-hello"
    (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder))

link hint

  (with-eval-after-load "link-hint"
    (defun jao-link-hint--notmuch-next-part (&optional bound)
      (when-let (p (next-single-property-change (point) :notmuch-part nil bound))
        (and (< p (or bound (point-max))) p)))

    (defun jao-link-hint--notmuch-part-p ()
      (and (get-text-property (point) :notmuch-part)
           (when-let (b (button-at (point))) (button-label b))))

    (link-hint-define-type 'notmuch-part
      :next #'jao-link-hint--notmuch-next-part
      :at-point-p #'jao-link-hint--notmuch-part-p
      :vars '(notmuch-show-mode)
      :open #'push-button
      :open-message "Toggled"
      :open-multiple t)

    (push 'link-hint-notmuch-part link-hint-types))