diff options
| author | Raul Benencia <id@rbenencia.name> | 2026-04-19 09:29:49 -0700 |
|---|---|---|
| committer | Raul Benencia <id@rbenencia.name> | 2026-04-19 09:29:49 -0700 |
| commit | fab6a5fbf53cb22a027cfeaa65279c160c96820f (patch) | |
| tree | fa0ec90c260e1e6f36ddf20b7baab18967c28eb2 /.emacs.d/rul-lisp/packages | |
| parent | deb15b61ec69ca2bf1d6e3922eae7bc168a8019f (diff) | |
unspam
Diffstat (limited to '.emacs.d/rul-lisp/packages')
| -rw-r--r-- | .emacs.d/rul-lisp/packages/rul-mail.el | 82 |
1 files changed, 70 insertions, 12 deletions
diff --git a/.emacs.d/rul-lisp/packages/rul-mail.el b/.emacs.d/rul-lisp/packages/rul-mail.el index 4af5cc7..a16614e 100644 --- a/.emacs.d/rul-lisp/packages/rul-mail.el +++ b/.emacs.d/rul-lisp/packages/rul-mail.el @@ -51,21 +51,79 @@ (interactive (notmuch-search-interactive-region)) (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end))) + ;; Unspam: restore false-positives back to INBOX. Unlike archive, the file + ;; move has to happen inline — the hook's path:current/Spam/** rule would + ;; otherwise re-tag +spam on the next post-new, reverting the user's action. + ;; Move the file BEFORE changing tags so an interleaved post-new from mbsync + ;; can't re-apply +spam between our tag change and our rename. + (defun rul/notmuch-move-to-inbox (query) + "Move files matching QUERY that live under spam/trash maildirs into +~/mail/INBOX/cur/ with fresh uuid names. Reindex if any moved." + (let ((inbox-cur (expand-file-name "~/mail/INBOX/cur/")) + (spam-paths "(path:current/Spam/** or path:current/spam/** or path:current/Trash/** or path:current/trash/**)") + (count 0)) + ;; Spam/trash are in search.exclude_tags, so include excluded files here. + (dolist (f (process-lines "notmuch" "search" "--exclude=false" "--output=files" + (concat "(" query ") and " spam-paths))) + (when (file-exists-p f) + (let* ((base (file-name-nondirectory f)) + (colon (cl-position ?: base :from-end t)) + (suffix (if colon (substring base colon) "")) + (uuid (string-trim (shell-command-to-string "uuidgen")))) + (rename-file f (concat inbox-cur uuid suffix)) + (cl-incf count)))) + (when (> count 0) + (call-process "notmuch" nil nil nil "new" "--no-hooks")) + count)) + + (defun rul/notmuch-search-unspam (&optional beg end) + "Restore threads from spam/trash: move files back to INBOX first (so a +racing post-new can't re-tag +spam based on path), then tag -spam -trash +-killed +inbox +unread, then refresh." + (interactive (notmuch-search-interactive-region)) + (let ((tids (save-excursion + (goto-char beg) + (cl-loop while (< (point) end) + collect (notmuch-search-find-thread-id) + do (forward-line 1))))) + (dolist (tid tids) + (rul/notmuch-move-to-inbox (concat "thread:" tid))) + (notmuch-search-tag (list "-spam" "-trash" "-killed" "+inbox" "+unread") beg end)) + (notmuch-refresh-this-buffer)) + + (defun rul/notmuch-show-unspam () + "Restore current message from spam/trash back to INBOX." + (interactive) + (let ((mid (notmuch-show-get-message-id))) + (rul/notmuch-move-to-inbox mid) + (notmuch-show-tag (list "-spam" "-trash" "-killed" "+inbox" "+unread"))) + (notmuch-refresh-this-buffer)) + + (define-key notmuch-search-mode-map "U" 'rul/notmuch-search-unspam) + (define-key notmuch-show-mode-map "U" 'rul/notmuch-show-unspam) + ;; Archive + ;; Stock `a` applies notmuch-archive-tags but doesn't re-run the search, so + ;; the thread stays visible in the tag:inbox buffer with stale results. Wrap + ;; both `a` and `A` to refresh the buffer so the thread drops out of view. (setq notmuch-archive-tags (list "-inbox" "+archive")) - (define-key notmuch-show-mode-map "A" - (lambda () - "archive" - (interactive) - (notmuch-show-tag (list "+archive" "-inbox" "-unread")) - (notmuch-refresh-this-buffer))) - (define-key notmuch-search-mode-map "A" - (lambda (&optional beg end) - "archive thread" - (interactive (notmuch-search-interactive-region)) - (notmuch-search-tag (list "+archive" "-inbox" "-unread") beg end) - (notmuch-refresh-this-buffer))) + (defun rul/notmuch-search-archive (&optional unarchive beg end) + "Archive threads and refresh so they drop out of the inbox view." + (interactive (cons current-prefix-arg (notmuch-search-interactive-region))) + (notmuch-search-archive-thread unarchive beg end) + (notmuch-refresh-this-buffer)) + + (defun rul/notmuch-show-archive () + "Archive the current message with +archive -inbox -unread and refresh." + (interactive) + (notmuch-show-tag (list "+archive" "-inbox" "-unread")) + (notmuch-refresh-this-buffer)) + + (define-key notmuch-search-mode-map "a" 'rul/notmuch-search-archive) + (define-key notmuch-search-mode-map "A" 'rul/notmuch-search-archive) + (define-key notmuch-show-mode-map "a" 'rul/notmuch-show-archive) + (define-key notmuch-show-mode-map "A" 'rul/notmuch-show-archive) ;; Mark as read (define-key notmuch-search-mode-map "r" |
