summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorjao <jao@gnu.org>2021-05-12 04:32:25 +0100
committerjao <jao@gnu.org>2021-05-12 04:32:25 +0100
commitcf75e9b067e3feacfdf79b4385eefc0bc762a3eb (patch)
treefed5d5977de8bd4da9db6064b4b8e8396adbec32
parentbfa90567cae4e4b106e0a9ecc6c869a689701332 (diff)
downloadelibs-cf75e9b067e3feacfdf79b4385eefc0bc762a3eb.tar.gz
elibs-cf75e9b067e3feacfdf79b4385eefc0bc762a3eb.tar.bz2
notmuch.org
-rw-r--r--email.org459
-rw-r--r--notmuch.org453
-rw-r--r--readme.org5
3 files changed, 460 insertions, 457 deletions
diff --git a/email.org b/email.org
index 93af047..7dd6388 100644
--- a/email.org
+++ b/email.org
@@ -2,7 +2,7 @@
#+property: header-args :tangle no :comments no :results silent :shebang "#!/bin/bash"
#+auto_tangle: t
-* Message mode
+* message mode
*** Customization
#+begin_src emacs-lisp
(defvar jao-mails "")
@@ -266,451 +266,13 @@
(gnus-demon-scan-news)))))
#+end_src
* notmuch
-*** local path
+*** Local path
#+begin_src emacs-lisp
(jao-load-path "notmuch")
- #+end_src
-*** jumping from/to index
- #+begin_src emacs-lisp
- (defvar-local jao-notmuch--tree-buffer nil)
-
- (defun jao-notmuch-goto-message-buffer (&optional and-click)
- (interactive "P")
- (when (window-live-p notmuch-tree-message-window)
- (let ((b (current-buffer)))
- (select-window notmuch-tree-message-window)
- (setq-local jao-notmuch--tree-buffer b)
- (when (and and-click (button-at (point)))
- (push-button))
- t)))
-
- (defun jao-notmuch-goto-message-buffer* ()
- (interactive)
- (save-window-excursion (jao-notmuch-goto-message-buffer t)))
-
- (defun jao-notmuch-goto-index-buffer ()
- (interactive)
- (if (buffer-live-p jao-notmuch--tree-buffer)
- (pop-to-buffer jao-notmuch--tree-buffer)
- (user-error "No index for this buffer")))
-
- (defun jao-notmuch-browse-urls ()
- (interactive)
- (when (or (derived-mode-p 'notmuch-show-mode)
- (jao-notmuch-goto-message-buffer))
- (notmuch-show-browse-urls)))
-
- (defun jao-notmuch-toggle-mime-parts ()
- (interactive)
- (when (jao-notmuch-goto-message-buffer)
- (goto-char (point-min))
- (let ((notmuch-show-text/html-blocked-images nil)
- (shr-inhibit-images nil)
- (shr-blocked-images nil))
- (save-excursion
- (when (re-search-forward "\\[ multipart/alternative \\]" nil t)
- (while (forward-button 1 nil nil t)
- (notmuch-show-toggle-part-invisibility)))))
- (jao-notmuch-goto-index-buffer)))
-
- (defun jao-notmuch-toggle-images ()
- (interactive)
- (save-window-excursion
- (jao-notmuch-goto-message-buffer)
- (when (derived-mode-p 'notmuch-show-mode)
- (let ((notmuch-show-text/html-blocked-images nil)
- (shr-inhibit-images nil)
- (shr-blocked-images nil))
- (notmuch-refresh-this-buffer)))))
-
- #+end_src
-*** shared commands
- #+begin_src emacs-lisp
- (defun jao-notmuch-tag-jump (reverse)
- "Create a jump menu for tagging operations, ensuring minibuffer can grow."
- (interactive "P")
- (let ((resize-mini-windows t)) (notmuch-tag-jump reverse)))
-
- (defun jao-notmuch-tree-by-tag (&optional new)
- "Jump to a tag tree search, using its key, and ensuring minibuffer can grow."
- (interactive "P")
- (when-let (tag (notmuch-select-tag-with-completion "Forest of tag: "))
- (notmuch-tree (concat "tag:" tag) (when new "tag:/unread|new/"))))
-
- (defun jao-notmuch-tree--tag-next (tags reverse &optional thread)
- (if thread
- (notmuch-tree-tag-thread (notmuch-tag-change-list tags reverse))
- (notmuch-tree-tag (notmuch-tag-change-list tags reverse)))
- (if thread
- (notmuch-tree-next-thread)
- (notmuch-tree-next-matching-message))
- (notmuch-tree-show-message nil))
-
- (defun jao-notmuch-tree-flagged-next (reverse)
- (interactive "P")
- (jao-notmuch-tree--tag-next '("+flagged") reverse))
-
- (defun jao-notmuch-follow-link (&optional external)
- (interactive "P")
- (with-current-notmuch-show-message
- (goto-char (point-min))
- (if (not (or (search-forward-regexp "\\bVia: " nil t)
- (search-forward-regexp "\\bURL: " nil t)
- (and (search-forward-regexp "\\bLink$" nil t)
- (not (beginning-of-line)))))
- (user-error "No standard Via/URL/Link")
- (re-search-forward "https?://" nil t)
- (if external
- (jao-browse-with-external-browser)
- (browse-url (jao-url-around-point))))))
-
- (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)
- (if add
- (emms-add-url url)
- (emms-play-url url)
- (sit-for 1)
- (jao-emms-echo)))
- (error "Found an enclosure, but not a link!")))))
-
-
- #+end_src
-*** minibuffer notifications
- #+begin_src emacs-lisp
- (defvar jao-notmuch-minibuffer-string "")
-
-
- (defvar jao-notmuch-minibuffer-queries
- '((:name "" :query "tag:new" :face jao-themes-f00)
- (:name "B" :query "tag:new and tag:bigml and tag:inbox")
- (: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")
- (:name "W" :query "tag:new and tag:bigml" :face jao-themes-dimm)
- (:name "I" :query "tag:new and tag:jao and tag:inbox")
- (:name "J" :query "tag:new and tag:jao" :face jao-themes-dimm)
- (:name "E" :query "tag:new and tag:feeds and tag:emacs"
- :face jao-themes-dimm)
- (:name "F" :query "tag:new and tag:feeds and not tag:emacs"
- :face jao-themes-dimm)))
-
- (defun jao-notmuch-update-minibuffer ()
- (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) 'default)))
- cnts
- " "))
- (jao-minibuffer-refresh)))
-
- (when (eq jao-afio-mail-function 'notmuch)
- (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20))
-
- #+end_src
-*** searches
- #+begin_src emacs-lisp
- (defun jao-notmuch--and (frags)
- (when frags (mapconcat #'identity frags " AND ")))
-
- (defvar jao-notmuch-alt-searches '())
-
- (defun jao-notmuch-saved-search-jump ()
- "Jump to a saved search, using its key, for the alt list."
- (interactive)
- (let ((resize-mini-windows t)
- (notmuch-saved-searches jao-notmuch-alt-searches))
- (notmuch-jump-search)))
-
- (defun jao-notmuch--q-base (d0 d1 k q)
- (list :name (concat d0 (when d1 "/") d1)
- :search-type 'tree :key k :query q
- :sort-order 'oldest-first))
-
- (defun jao-notmuch--q (d0 d1 &optional k qs)
- (when (null qs)
- (add-to-list 'jao-notmuch-alt-searches
- (jao-notmuch--q-base d0 d1 k
- (format "folder:%s/%s" d0 d1))
- t))
- (let ((q (or (jao-notmuch--and qs)
- (format "folder:%s/%s and tag:unread" d0 d1))))
- (jao-notmuch--q-base d0 d1 nil q)))
-
- (defun jao-notmuch--mboxes-search (box &rest excluded)
- (let ((ms (seq-difference (jao-list-mailboxes box) excluded))
- (bp (substring box 0 1)))
- (mapcar `(lambda (m)
- (jao-notmuch--q ,box (car m) (concat ,bp (cdr m))))
- (shorten-strings (sort ms #'string<)))))
-
- (setq notmuch-saved-searches
- `(,(jao-notmuch--q "bigml" "inbox" "bi")
- ,@(jao-notmuch--mboxes-search "bigml" "inbox" "deploys")
- ,(jao-notmuch--q "jao" "inbox" "ji")
- ,@(jao-notmuch--mboxes-search "jao" "inbox")
- ,@(jao-notmuch--mboxes-search "feeds")
- ,(jao-notmuch--q "bml/today" nil "tb" '("tag:bigml" "date:1d.."))
- ,(jao-notmuch--q "jao/today" nil "tj" '("tag:jao" "date:1d.."))
- ,(jao-notmuch--q "flagged" nil "r" '("tag:flagged"))
- ,(jao-notmuch--q "new" nil "n" '("tag:new"))
- ,(jao-notmuch--q "draft" nil "d" '("tag:draft"))))
- #+end_src
-*** hello
- #+begin_src emacs-lisp
- (use-package notmuch-hello
- :init
- (setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches
- notmuch-hello-insert-alltags
- ;; notmuch-hello-insert-recent-searches
- notmuch-hello-insert-header)
- notmuch-hello-thousands-separator ","
- notmuch-hello-recent-searches-max 5
- notmuch-show-all-tags-list t
- notmuch-show-logo nil
- notmuch-show-empty-saved-searches nil)
- (defun jao--refresh-agenda () (save-window-excursion (org-agenda-list)))
-
- (defun jao-notmuch-refresh-hello ()
- (interactive)
- (notmuch-refresh-this-buffer)
- (let ((inhibit-message t))
- (beginning-of-buffer)
- (widget-forward 2)))
-
- :config
-
- (with-eval-after-load "consult-notmuch"
- (define-key notmuch-hello-mode-map "S" #'consult-notmuch))
-
- :hook ((notmuch-hello-refresh . jao-notmuch-update-minibuffer)
- (notmuch-hello-refresh . jao--refresh-agenda))
-
- :bind (:map notmuch-hello-mode-map
- (("g" . jao-notmuch-refresh-hello)
- ("k" . nil)
- ("SPC" . widget-button-press))))
-
- #+end_src
-*** show
- #+begin_src emacs-lisp
- (use-package notmuch-show
- :init
- (setq notmuch-message-headers '("Subject" "To" "Cc" "Date" "List-Id")
- notmuch-show-all-multipart/alternative-parts nil
- notmuch-show-indent-messages-width 0
- notmuch-show-imenu-indent t
- notmuch-show-part-button-default-action 'notmuch-show-view-part
- notmuch-show-only-matching-messages t
- notmuch-wash-signature-lines-max 0
- notmuch-wash-wrap-lines-length 80
- notmuch-wash-citation-lines-prefix 10
- notmuch-wash-citation-lines-suffix 20)
-
- :config
-
- (defun jao-mail-clean-address (fun address)
- (let ((address (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)))
- (buffer-string))
- (replace-regexp-in-string "^ElDiario.es - ElDiario.es"
- "ElDiario.es"
- address))))
- (funcall fun address)))
-
- (advice-add 'notmuch-clean-address :around #'jao-mail-clean-address)
-
- :bind (:map notmuch-show-mode-map
- (("h" . jao-notmuch-goto-index-buffer)
- ("i" . jao-notmuch-toggle-images)
- ("j" . jao-notmuch-saved-search-jump)
- ("k" . jao-notmuch-tag-jump))))
- #+end_src
-*** tree
- #+begin_src emacs-lisp
- (use-package notmuch-tree
- :init
- (setq notmuch-tree-result-format
- '(("date" . "%12s ")
- ("authors" . "%-35s")
- ((("tree" . "%s ")("subject" . "%s")) . " %-100s")
- ("tags" . " (%s)"))
- notmuch-search-result-format
- '(("date" . "%12s ")
- ("count" . "%-7s ")
- ("authors" . "%-35s")
- ("subject" . "%-100s")
- ("tags" . "(%s)"))
- notmuch-unthreaded-result-format notmuch-tree-result-format)
-
- :config
-
- (defun jao-notmuch-tree-next (no-exit)
- "Next message in forest or exit if none."
- (interactive "P")
- (notmuch-tree-next-matching-message (not no-exit)))
-
- (defun jao-notmuch-tree-scroll-or-next ()
- "Scroll or next message in forest or exit if none."
- (interactive)
- (if (notmuch-tree-scroll-message-window)
- (notmuch-tree-next-matching-message t)
- (when (not (window-live-p notmuch-tree-message-window))
- (notmuch-tree-show-message nil))))
-
- (defun jao-notmuch-tree-show-or-scroll ()
- "Show current message, or scroll it if visible."
- (interactive)
- (if (window-live-p notmuch-tree-message-window)
- (scroll-other-window 1)
- (notmuch-tree-show-message nil)))
-
- (defun jao-notmuch-tree-delete (reverse &optional next)
- "Mark as deleted current message. Prefix undeletes."
- (interactive "P")
- (notmuch-tree-tag
- (notmuch-tag-change-list '("+deleted" "-unread" "-new") reverse))
- (when next (notmuch-tree-next-message)))
-
- (defun jao-notmuch-tree-undelete (reverse)
- "Mark as not-deleted current message and move to next."
- (interactive "P")
- (jao-notmuch-tree-delete (not reverse)))
-
- (defun jao-notmuch-tree-read-thread-next (&optional delete)
- "Mark the current thread as read and move to next one."
- (interactive "P")
- (jao-notmuch-tree--tag-next (if delete
- '("+deleted" "-unread" "-new")
- '("-unread" "-new"))
- nil t))
-
- (dolist (f '(notmuch-tree-match-tree-face
- notmuch-tree-no-match-tree-face))
- (set-face-attribute f nil
- :family "Source Code Pro" :foreground "grey60"))
-
- (defun jao-notmuch--format-field (fun field &rest args)
- (let ((rs (apply fun field args)))
- (cond ((and (stringp field) (string= field "tree"))
- (replace-regexp-in-string "►" "" rs))
- ((not (stringp field)) (truncate-string-to-width rs 100))
- (t rs))))
-
- (advice-add 'notmuch-tree-format-field :around #'jao-notmuch--format-field)
-
- :bind (:map notmuch-tree-mode-map
- (("." . jao-notmuch-toggle-mime-parts)
- ("i" . jao-notmuch-toggle-images)
- ("d" . jao-notmuch-tree-delete)
- ("D" . jao-notmuch-tree-undelete)
- ("h" . jao-notmuch-goto-message-buffer)
- ("H" . jao-notmuch-goto-message-buffer*)
- ("K" . jao-notmuch-tag-jump)
- ("k" . jao-notmuch-tree-read-thread-next)
- ("n" . jao-notmuch-tree-next)
- ("RET" . jao-notmuch-tree-show-or-scroll)
- ("SPC" . jao-notmuch-tree-scroll-or-next))))
- #+end_src
-*** message
- #+begin_src emacs-lisp
- (use-package notmuch-message
- :init (setq notmuch-fcc-dirs
- '((".*@bigml.com" . "bigml/sent +bigml +sent -new")
- (".*" . "jao/sent +jao +sent -new"))
- notmuch-maildir-use-notmuch-insert t))
-
(when (eq jao-afio-mail-function 'notmuch)
- (setq message-directory "~/var/mail/"
- message-auto-save-directory nil)
- (eval-after-load "notmuch-message"
- '(define-key message-mode-map (kbd "C-c C-d")
- #'notmuch-draft-postpone)))
- #+end_src
-*** main
- #+begin_src emacs-lisp
- (use-package notmuch
- :init
- (setq notmuch-crypto-process-mime t
- notmuch-show-mark-read-tags '("-new" "-unread")
- notmuch-archive-tags '("+trove" "-new" "-unread" "-flagged")
- notmuch-tagging-keys
- '(("a" notmuch-archive-tags "Archive")
- ("d" notmuch-show-mark-read-tags "Mark read")
- ("u" ("+new" "+unread") "Mark unread")
- ("f" ("+flagged") "Flag")
- ("x" ("+deleted" "-new" "-flagged" "-unread") "Deleted")))
-
- :config
-
- (when (eq 'notmuch jao-afio-mail-function)
- (setq mm-text-html-renderer 'shr))
-
- :bind (:map notmuch-common-keymap
- (("B" . jao-notmuch-browse-urls)
- ("E" . jao-notmuch-open-enclosure)
- ("M-g" . jao-notmuch-follow-link)
- ("j" . jao-notmuch-saved-search-jump)
- ("k" . jao-notmuch-tag-jump)
- ("s" . notmuch-tree)
- ("T" . notmuch-search-by-tag)
- ("t" . jao-notmuch-tree-by-tag)
- ("u" . jao-notmuch-tree-flagged-next)
- ("U" . notmuch-unthreaded)
- ("z" . notmuch-search))))
- #+end_src
-*** org mode integration
- Stolen and adapted from [[https://gist.github.com/fedxa/fac592424473f1b70ea489cc64e08911][Fedor Bezrukov]].
- #+begin_src emacs-lisp
- (with-eval-after-load "org"
- (with-eval-after-load "notmuch"
- (org-link-set-parameters "notmuch"
- :follow 'org-notmuch-open
- :store 'org-notmuch-store-link)
-
- (defun org-notmuch-open (id)
- "Visit the notmuch message or thread with id ID."
- (notmuch-show id))
-
- (defun org-notmuch-store-link ()
- "Store a link to a notmuch mail message."
- (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))
- (description (format "Mail: %s"
- (notmuch-show-get-subject))))
- (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))
- (description (format "Mail: %s"
- (notmuch-search-find-subject))))
- (org-store-link-props
- :type "notmuch"
- :link link
- :description description)))))))
+ (jao-load-org "notmuch"))
#+end_src
-*** tag shell script
+*** Tag shell script
#+begin_src bash :tangle ./bin/notmuch-tags.sh :tangle-mode (identity #o755)
notmuch new > $HOME/var/log/notmuch.log 2>&1
@@ -745,17 +307,4 @@
# XDG_RUNTIME_DIR='/run/user/1000' \
# /usr/local/bin/emacsclient -e '(jao-notmuch-update-minibuffer)' > /dev/null
-
- #+end_src
-*** expire shell script
- #+begin_src bash :tangle ./bin/notmuch-expire.sh :tangle-mode (identity #o755)
- notmuch search --output=files --format=text0 tag:deleted | xargs -r0 rm
- notmuch new > $HOME/var/log/notmuch-expire.log 2>&1
- notmuch compact >> $HOME/var/log/notmuch-expire.log 2>&1
- #+end_src
-*** gpgsm.conf (decrypt smime)
- #+begin_src conf :tangle ~/.gnupg/gpgsm.conf :shebang ""
- # needed to avoid remote checks of smime signatures
- # and notmuch-crypto-process-mime to work with them
- disable-crl-checks
#+end_src
diff --git a/notmuch.org b/notmuch.org
new file mode 100644
index 0000000..4cbc14f
--- /dev/null
+++ b/notmuch.org
@@ -0,0 +1,453 @@
+#+title: notmuch configuration
+#+property: header-args :tangle no :comments no :results silent :shebang "#!/bin/bash"
+#+auto_tangle: t
+
+* notmuch
+*** jumping from/to index
+ #+begin_src emacs-lisp
+ (defvar-local jao-notmuch--tree-buffer nil)
+
+ (defun jao-notmuch-goto-message-buffer (&optional and-click)
+ (interactive "P")
+ (when (window-live-p notmuch-tree-message-window)
+ (let ((b (current-buffer)))
+ (select-window notmuch-tree-message-window)
+ (setq-local jao-notmuch--tree-buffer b)
+ (when (and and-click (button-at (point)))
+ (push-button))
+ t)))
+
+ (defun jao-notmuch-goto-message-buffer* ()
+ (interactive)
+ (save-window-excursion (jao-notmuch-goto-message-buffer t)))
+
+ (defun jao-notmuch-goto-index-buffer ()
+ (interactive)
+ (if (buffer-live-p jao-notmuch--tree-buffer)
+ (pop-to-buffer jao-notmuch--tree-buffer)
+ (user-error "No index for this buffer")))
+
+ (defun jao-notmuch-browse-urls ()
+ (interactive)
+ (when (or (derived-mode-p 'notmuch-show-mode)
+ (jao-notmuch-goto-message-buffer))
+ (notmuch-show-browse-urls)))
+
+ (defun jao-notmuch-toggle-mime-parts ()
+ (interactive)
+ (when (jao-notmuch-goto-message-buffer)
+ (goto-char (point-min))
+ (let ((notmuch-show-text/html-blocked-images nil)
+ (shr-inhibit-images nil)
+ (shr-blocked-images nil))
+ (save-excursion
+ (when (re-search-forward "\\[ multipart/alternative \\]" nil t)
+ (while (forward-button 1 nil nil t)
+ (notmuch-show-toggle-part-invisibility)))))
+ (jao-notmuch-goto-index-buffer)))
+
+ (defun jao-notmuch-toggle-images ()
+ (interactive)
+ (save-window-excursion
+ (jao-notmuch-goto-message-buffer)
+ (when (derived-mode-p 'notmuch-show-mode)
+ (let ((notmuch-show-text/html-blocked-images nil)
+ (shr-inhibit-images nil)
+ (shr-blocked-images nil))
+ (notmuch-refresh-this-buffer)))))
+
+ #+end_src
+*** shared commands
+ #+begin_src emacs-lisp
+ (defun jao-notmuch-tag-jump (reverse)
+ "Create a jump menu for tagging operations, ensuring minibuffer can grow."
+ (interactive "P")
+ (let ((resize-mini-windows t)) (notmuch-tag-jump reverse)))
+
+ (defun jao-notmuch-tree-by-tag (&optional new)
+ "Jump to a tag tree search, using its key, and ensuring minibuffer can grow."
+ (interactive "P")
+ (when-let (tag (notmuch-select-tag-with-completion "Forest of tag: "))
+ (notmuch-tree (concat "tag:" tag) (when new "tag:/unread|new/"))))
+
+ (defun jao-notmuch-tree--tag-next (tags reverse &optional thread)
+ (if thread
+ (notmuch-tree-tag-thread (notmuch-tag-change-list tags reverse))
+ (notmuch-tree-tag (notmuch-tag-change-list tags reverse)))
+ (if thread
+ (notmuch-tree-next-thread)
+ (notmuch-tree-next-matching-message))
+ (notmuch-tree-show-message nil))
+
+ (defun jao-notmuch-tree-flagged-next (reverse)
+ (interactive "P")
+ (jao-notmuch-tree--tag-next '("+flagged") reverse))
+
+ (defun jao-notmuch-follow-link (&optional external)
+ (interactive "P")
+ (with-current-notmuch-show-message
+ (goto-char (point-min))
+ (if (not (or (search-forward-regexp "\\bVia: " nil t)
+ (search-forward-regexp "\\bURL: " nil t)
+ (and (search-forward-regexp "\\bLink$" nil t)
+ (not (beginning-of-line)))))
+ (user-error "No standard Via/URL/Link")
+ (re-search-forward "https?://" nil t)
+ (if external
+ (jao-browse-with-external-browser)
+ (browse-url (jao-url-around-point))))))
+
+ (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)
+ (if add
+ (emms-add-url url)
+ (emms-play-url url)
+ (sit-for 1)
+ (jao-emms-echo)))
+ (error "Found an enclosure, but not a link!")))))
+ #+end_src
+*** minibuffer notifications
+ #+begin_src emacs-lisp
+ (defvar jao-notmuch-minibuffer-string "")
+
+ (defvar jao-notmuch-minibuffer-queries
+ '((:name "" :query "tag:new" :face jao-themes-f00)
+ (:name "B" :query "tag:new and tag:bigml and tag:inbox")
+ (: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")
+ (:name "W" :query "tag:new and tag:bigml" :face jao-themes-dimm)
+ (:name "I" :query "tag:new and tag:jao and tag:inbox")
+ (:name "J" :query "tag:new and tag:jao" :face jao-themes-dimm)
+ (:name "E" :query "tag:new and tag:feeds and tag:emacs"
+ :face jao-themes-dimm)
+ (:name "F" :query "tag:new and tag:feeds and not tag:emacs"
+ :face jao-themes-dimm)))
+
+ (defun jao-notmuch-update-minibuffer ()
+ (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) 'default)))
+ cnts
+ " "))
+ (jao-minibuffer-refresh)))
+
+ (when (eq jao-afio-mail-function 'notmuch)
+ (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20))
+ #+end_src
+*** searches
+ #+begin_src emacs-lisp
+ (defun jao-notmuch--and (frags)
+ (when frags (mapconcat #'identity frags " AND ")))
+
+ (defvar jao-notmuch-alt-searches '())
+
+ (defun jao-notmuch-saved-search-jump ()
+ "Jump to a saved search, using its key, for the alt list."
+ (interactive)
+ (let ((resize-mini-windows t)
+ (notmuch-saved-searches jao-notmuch-alt-searches))
+ (notmuch-jump-search)))
+
+ (defun jao-notmuch--q-base (d0 d1 k q)
+ (list :name (concat d0 (when d1 "/") d1)
+ :search-type 'tree :key k :query q
+ :sort-order 'oldest-first))
+
+ (defun jao-notmuch--q (d0 d1 &optional k qs)
+ (when (null qs)
+ (add-to-list 'jao-notmuch-alt-searches
+ (jao-notmuch--q-base d0 d1 k
+ (format "folder:%s/%s" d0 d1))
+ t))
+ (let ((q (or (jao-notmuch--and qs)
+ (format "folder:%s/%s and tag:unread" d0 d1))))
+ (jao-notmuch--q-base d0 d1 nil q)))
+
+ (defun jao-notmuch--mboxes-search (box &rest excluded)
+ (let ((ms (seq-difference (jao-list-mailboxes box) excluded))
+ (bp (substring box 0 1)))
+ (mapcar `(lambda (m)
+ (jao-notmuch--q ,box (car m) (concat ,bp (cdr m))))
+ (shorten-strings (sort ms #'string<)))))
+
+ (setq notmuch-saved-searches
+ `(,(jao-notmuch--q "bigml" "inbox" "bi")
+ ,@(jao-notmuch--mboxes-search "bigml" "inbox" "deploys")
+ ,(jao-notmuch--q "jao" "inbox" "ji")
+ ,@(jao-notmuch--mboxes-search "jao" "inbox")
+ ,@(jao-notmuch--mboxes-search "feeds")
+ ,(jao-notmuch--q "bml/today" nil "tb" '("tag:bigml" "date:1d.."))
+ ,(jao-notmuch--q "jao/today" nil "tj" '("tag:jao" "date:1d.."))
+ ,(jao-notmuch--q "flagged" nil "r" '("tag:flagged"))
+ ,(jao-notmuch--q "new" nil "n" '("tag:new"))
+ ,(jao-notmuch--q "draft" nil "d" '("tag:draft"))))
+ #+end_src
+*** hello
+ #+begin_src emacs-lisp
+ (use-package notmuch-hello
+ :init
+ (setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches
+ notmuch-hello-insert-alltags
+ ;; notmuch-hello-insert-recent-searches
+ notmuch-hello-insert-header)
+ notmuch-hello-thousands-separator ","
+ notmuch-hello-recent-searches-max 5
+ notmuch-show-all-tags-list t
+ notmuch-show-logo nil
+ notmuch-show-empty-saved-searches nil)
+ (defun jao--refresh-agenda () (save-window-excursion (org-agenda-list)))
+
+ (defun jao-notmuch-refresh-hello ()
+ (interactive)
+ (notmuch-refresh-this-buffer)
+ (let ((inhibit-message t))
+ (beginning-of-buffer)
+ (widget-forward 2)))
+
+ :config
+
+ (with-eval-after-load "consult-notmuch"
+ (define-key notmuch-hello-mode-map "S" #'consult-notmuch))
+
+ :hook ((notmuch-hello-refresh . jao-notmuch-update-minibuffer)
+ (notmuch-hello-refresh . jao--refresh-agenda))
+
+ :bind (:map notmuch-hello-mode-map
+ (("g" . jao-notmuch-refresh-hello)
+ ("k" . nil)
+ ("SPC" . widget-button-press))))
+
+ #+end_src
+*** show
+ #+begin_src emacs-lisp
+ (use-package notmuch-show
+ :init
+ (setq notmuch-message-headers '("Subject" "To" "Cc" "Date" "List-Id")
+ notmuch-show-all-multipart/alternative-parts nil
+ notmuch-show-indent-messages-width 0
+ notmuch-show-imenu-indent t
+ notmuch-show-part-button-default-action 'notmuch-show-view-part
+ notmuch-show-only-matching-messages t
+ notmuch-wash-signature-lines-max 0
+ notmuch-wash-wrap-lines-length 80
+ notmuch-wash-citation-lines-prefix 10
+ notmuch-wash-citation-lines-suffix 20)
+
+ :config
+
+ (defun jao-mail-clean-address (fun address)
+ (let ((address (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)))
+ (buffer-string))
+ (replace-regexp-in-string "^ElDiario.es - ElDiario.es"
+ "ElDiario.es"
+ address))))
+ (funcall fun address)))
+
+ (advice-add 'notmuch-clean-address :around #'jao-mail-clean-address)
+
+ :bind (:map notmuch-show-mode-map
+ (("h" . jao-notmuch-goto-index-buffer)
+ ("i" . jao-notmuch-toggle-images)
+ ("j" . jao-notmuch-saved-search-jump)
+ ("k" . jao-notmuch-tag-jump))))
+ #+end_src
+*** tree
+ #+begin_src emacs-lisp
+ (use-package notmuch-tree
+ :init
+ (setq notmuch-tree-result-format
+ '(("date" . "%12s ")
+ ("authors" . "%-35s")
+ ((("tree" . "%s ")("subject" . "%s")) . " %-100s")
+ ("tags" . " (%s)"))
+ notmuch-search-result-format
+ '(("date" . "%12s ")
+ ("count" . "%-7s ")
+ ("authors" . "%-35s")
+ ("subject" . "%-100s")
+ ("tags" . "(%s)"))
+ notmuch-unthreaded-result-format notmuch-tree-result-format)
+
+ :config
+
+ (defun jao-notmuch-tree-next (no-exit)
+ "Next message in forest or exit if none."
+ (interactive "P")
+ (notmuch-tree-next-matching-message (not no-exit)))
+
+ (defun jao-notmuch-tree-scroll-or-next ()
+ "Scroll or next message in forest or exit if none."
+ (interactive)
+ (if (notmuch-tree-scroll-message-window)
+ (notmuch-tree-next-matching-message t)
+ (when (not (window-live-p notmuch-tree-message-window))
+ (notmuch-tree-show-message nil))))
+
+ (defun jao-notmuch-tree-show-or-scroll ()
+ "Show current message, or scroll it if visible."
+ (interactive)
+ (if (window-live-p notmuch-tree-message-window)
+ (scroll-other-window 1)
+ (notmuch-tree-show-message nil)))
+
+ (defun jao-notmuch-tree-delete (reverse &optional next)
+ "Mark as deleted current message. Prefix undeletes."
+ (interactive "P")
+ (notmuch-tree-tag
+ (notmuch-tag-change-list '("+deleted" "-unread" "-new") reverse))
+ (when next (notmuch-tree-next-message)))
+
+ (defun jao-notmuch-tree-undelete (reverse)
+ "Mark as not-deleted current message and move to next."
+ (interactive "P")
+ (jao-notmuch-tree-delete (not reverse)))
+
+ (defun jao-notmuch-tree-read-thread-next (&optional delete)
+ "Mark the current thread as read and move to next one."
+ (interactive "P")
+ (jao-notmuch-tree--tag-next (if delete
+ '("+deleted" "-unread" "-new")
+ '("-unread" "-new"))
+ nil t))
+
+ (dolist (f '(notmuch-tree-match-tree-face
+ notmuch-tree-no-match-tree-face))
+ (set-face-attribute f nil
+ :family "Source Code Pro" :foreground "grey60"))
+
+ (defun jao-notmuch--format-field (fun field &rest args)
+ (let ((rs (apply fun field args)))
+ (cond ((and (stringp field) (string= field "tree"))
+ (replace-regexp-in-string "►" "" rs))
+ ((not (stringp field)) (truncate-string-to-width rs 100))
+ (t rs))))
+
+ (advice-add 'notmuch-tree-format-field :around #'jao-notmuch--format-field)
+
+ :bind (:map notmuch-tree-mode-map
+ (("." . jao-notmuch-toggle-mime-parts)
+ ("i" . jao-notmuch-toggle-images)
+ ("d" . jao-notmuch-tree-delete)
+ ("D" . jao-notmuch-tree-undelete)
+ ("h" . jao-notmuch-goto-message-buffer)
+ ("H" . jao-notmuch-goto-message-buffer*)
+ ("K" . jao-notmuch-tag-jump)
+ ("k" . jao-notmuch-tree-read-thread-next)
+ ("n" . jao-notmuch-tree-next)
+ ("RET" . jao-notmuch-tree-show-or-scroll)
+ ("SPC" . jao-notmuch-tree-scroll-or-next))))
+ #+end_src
+*** message
+ #+begin_src emacs-lisp
+ (use-package notmuch-message
+ :init (setq notmuch-fcc-dirs
+ '((".*@bigml.com" . "bigml/sent +bigml +sent -new")
+ (".*" . "jao/sent +jao +sent -new"))
+ notmuch-maildir-use-notmuch-insert t))
+
+ (when (eq jao-afio-mail-function 'notmuch)
+ (setq message-directory "~/var/mail/"
+ message-auto-save-directory nil)
+ (eval-after-load "notmuch-message"
+ '(define-key message-mode-map (kbd "C-c C-d")
+ #'notmuch-draft-postpone)))
+ #+end_src
+*** main
+ #+begin_src emacs-lisp
+ (use-package notmuch
+ :init
+ (setq notmuch-crypto-process-mime t
+ notmuch-show-mark-read-tags '("-new" "-unread")
+ notmuch-archive-tags '("+trove" "-new" "-unread" "-flagged")
+ notmuch-tagging-keys
+ '(("a" notmuch-archive-tags "Archive")
+ ("d" notmuch-show-mark-read-tags "Mark read")
+ ("u" ("+new" "+unread") "Mark unread")
+ ("f" ("+flagged") "Flag")
+ ("x" ("+deleted" "-new" "-flagged" "-unread") "Deleted")))
+
+ :config
+
+ (when (eq 'notmuch jao-afio-mail-function)
+ (setq mm-text-html-renderer 'shr))
+
+ :bind (:map notmuch-common-keymap
+ (("B" . jao-notmuch-browse-urls)
+ ("E" . jao-notmuch-open-enclosure)
+ ("M-g" . jao-notmuch-follow-link)
+ ("j" . jao-notmuch-saved-search-jump)
+ ("k" . jao-notmuch-tag-jump)
+ ("s" . notmuch-tree)
+ ("T" . notmuch-search-by-tag)
+ ("t" . jao-notmuch-tree-by-tag)
+ ("u" . jao-notmuch-tree-flagged-next)
+ ("U" . notmuch-unthreaded)
+ ("z" . notmuch-search))))
+ #+end_src
+*** org mode integration
+ Stolen and adapted from [[https://gist.github.com/fedxa/fac592424473f1b70ea489cc64e08911][Fedor Bezrukov]].
+ #+begin_src emacs-lisp
+ (with-eval-after-load "org"
+ (with-eval-after-load "notmuch"
+ (org-link-set-parameters "notmuch"
+ :follow 'org-notmuch-open
+ :store 'org-notmuch-store-link)
+
+ (defun org-notmuch-open (id)
+ "Visit the notmuch message or thread with id ID."
+ (notmuch-show id))
+
+ (defun org-notmuch-store-link ()
+ "Store a link to a notmuch mail message."
+ (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))
+ (description (format "Mail: %s"
+ (notmuch-show-get-subject))))
+ (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))
+ (description (format "Mail: %s"
+ (notmuch-search-find-subject))))
+ (org-store-link-props
+ :type "notmuch"
+ :link link
+ :description description)))))))
+ #+end_src
+*** expire shell script
+ #+begin_src bash :tangle ./bin/notmuch-expire.sh :tangle-mode (identity #o755)
+ notmuch search --output=files --format=text0 tag:deleted | xargs -r0 rm
+ notmuch new > $HOME/var/log/notmuch-expire.log 2>&1
+ notmuch compact >> $HOME/var/log/notmuch-expire.log 2>&1
+ #+end_src
+*** gpgsm.conf (decrypt smime)
+ #+begin_src conf :tangle ~/.gnupg/gpgsm.conf :shebang ""
+ # needed to avoid remote checks of smime signatures
+ # and notmuch-crypto-process-mime to work with them
+ disable-crl-checks
+ #+end_src
diff --git a/readme.org b/readme.org
index 72960cf..1ee2dfc 100644
--- a/readme.org
+++ b/readme.org
@@ -65,11 +65,12 @@
- [[./completion.org][completion.org]]: completion setup using company, consult and friends.
- [[./org.org][org.org]] org mode configuration.
- [[./blog.org][blog.org]]: blogging using org-static-blog.
-- [[./email.org][email.org]]: email handling outside of gnus.
+- [[./email.org][email.org]]: generic email handling in emacs.
- [[./gnus.org][gnus.org]]: tangled to gnus.el automatically by init.org, so that it's
ready for loading by Gnus.
-- [[./w3m.org][w3m.org]]: browsing with emacs-w3m.
+- [[./notmuch.org][notmuch.org]]: email handling in emacs using notmuch.
- [[./eww.org][eww.org]]: browsing with eww.
+- [[./w3m.org][w3m.org]]: browsing with emacs-w3m.
- [[./exwm.org][exwm.org]]: configuration for exwm, loaded when ~jao-exwmn-enable~ is
called.