blob: 12846a09e4ef60aa2f2709a104868d0638055361 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
;;; rul-mail.el --- Email configuration
;; mml-sec.el
;; Use sender to find GPG key.
(setq mml-secure-openpgp-sign-with-sender t)
;; Keep HTML mail readable without sender-defined styling.
(setq shr-use-colors nil
shr-use-fonts nil)
(with-eval-after-load 'shr
(set-face-attribute 'shr-link nil :inherit 'default :underline t))
(use-package notmuch
:ensure t
:config
;; UI
(setq notmuch-show-logo nil
notmuch-column-control 1.0
notmuch-hello-auto-refresh t
notmuch-hello-recent-searches-max 20
notmuch-hello-thousands-separator ""
notmuch-show-all-tags-list t
notmuch-show-text/html-blocked-images nil
)
(setq notmuch-draft-folder "current/Drafts")
(defvar-local rul/notmuch-show-refreshing-for-olivetti nil)
(defun rul/notmuch-show-enable-olivetti ()
"Enable Olivetti before notmuch renders message HTML."
(setq-local olivetti-body-width 100
shr-width nil
shr-max-width olivetti-body-width)
(olivetti-mode 1))
(defun rul/notmuch-show-refresh-after-olivetti ()
"Refresh an already-rendered notmuch buffer after Olivetti changes width."
(when (and (derived-mode-p 'notmuch-show-mode)
olivetti-mode
notmuch-show-thread-id
(not rul/notmuch-show-refreshing-for-olivetti))
(setq rul/notmuch-show-refreshing-for-olivetti t)
(run-at-time
0 nil
(lambda (buffer)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(unwind-protect
(when (and (derived-mode-p 'notmuch-show-mode)
olivetti-mode
notmuch-show-thread-id)
(notmuch-refresh-this-buffer))
(setq rul/notmuch-show-refreshing-for-olivetti nil)))))
(current-buffer))))
(add-hook 'notmuch-show-mode-hook #'rul/notmuch-show-enable-olivetti)
(with-eval-after-load 'olivetti
(add-hook 'olivetti-mode-on-hook
#'rul/notmuch-show-refresh-after-olivetti
t))
(defun rul/notmuch-inline-override-images (types)
"Treat standalone image MIME parts as attachments in notmuch."
(if (member "image/.*" types)
types
(append types '("image/.*"))))
(unless (advice-member-p #'rul/notmuch-inline-override-images
'notmuch--inline-override-types)
(advice-add 'notmuch--inline-override-types :filter-return
#'rul/notmuch-inline-override-images))
;; Keymaps
(defun rul/capture-mail()
"Capture mail to org mode."
(interactive)
(org-store-link nil)
(org-capture nil "m")
)
(bind-key "c" 'rul/capture-mail notmuch-show-mode-map)
;; Keep uppercase refresh cheap after tag operations; use M-g for a full poll.
(define-key notmuch-show-mode-map "G" 'notmuch-refresh-this-buffer)
(define-key notmuch-search-mode-map "G" 'notmuch-refresh-this-buffer)
(define-key notmuch-show-mode-map (kbd "M-g") 'notmuch-poll-and-refresh-this-buffer)
(define-key notmuch-search-mode-map (kbd "M-g") 'notmuch-poll-and-refresh-this-buffer)
(define-key notmuch-show-mode-map "R" 'notmuch-show-reply)
(define-key notmuch-search-mode-map "R" 'notmuch-search-reply-to-thread)
;; Spam
(define-key notmuch-show-mode-map "S"
(lambda ()
"mark message as spam"
(interactive)
(notmuch-show-tag (list "+spam" "-inbox" "-unread"))))
(define-key notmuch-search-mode-map "S"
(lambda (&optional beg end)
"mark thread as spam"
(interactive (notmuch-search-interactive-region))
(notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end)))
;; 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"))
(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"
(lambda (&optional beg end)
"mark thread as read"
(interactive (notmuch-search-interactive-region))
(notmuch-search-tag (list "-unread") beg end)
(notmuch-search-next-thread)))
(define-key notmuch-search-mode-map (kbd "RET")
(lambda ()
"Show the selected thread with notmuch-tree if it has more
than one email. Use notmuch-show otherwise."
(interactive)
(if (= (plist-get (notmuch-search-get-result) :total) 1)
(notmuch-search-show-thread)
(notmuch-tree (notmuch-search-find-thread-id)
notmuch-search-query-string
nil
(notmuch-prettify-subject (notmuch-search-find-subject))))))
(defun color-inbox-if-unread () (interactive)
(save-excursion
(goto-char (point-min))
(let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread"))))
(when (> (string-to-number cnt) 0)
(save-excursion
(when (search-forward "inbox" (point-max) t)
(let* ((overlays (overlays-in (match-beginning 0) (match-end 0)))
(overlay (car overlays)))
(when overlay
(overlay-put overlay 'face '((:inherit bold) (:foreground "green")))))))))))
(defvar notmuch-hello-refresh-count 0)
(defun notmuch-hello-refresh-status-message ()
(let* ((new-count
(string-to-number
(car (process-lines notmuch-command "count"))))
(diff-count (- new-count notmuch-hello-refresh-count)))
(cond
((= notmuch-hello-refresh-count 0)
(message "You have %s messages."
(notmuch-hello-nice-number new-count)))
((> diff-count 0)
(message "You have %s more messages since last refresh."
(notmuch-hello-nice-number diff-count)))
((< diff-count 0)
(message "You have %s fewer messages since last refresh."
(notmuch-hello-nice-number (- diff-count)))))
(setq notmuch-hello-refresh-count new-count)))
(add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread)
(add-hook 'notmuch-hello-refresh-hook 'notmuch-hello-refresh-status-message)
(setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches
notmuch-hello-insert-search
notmuch-hello-insert-recent-searches
notmuch-hello-insert-alltags
))
;; https://git.sr.ht/~tslil/dotfiles/tree/4e51afbb/emacs/notmuch-config.el#L76-82
(defmacro make-binds (mode-map binds argfunc &rest body)
"Create keybindings in `mode-map' using a list of (keystr . arg)
pairs in `binds' of the form ( ... (argfunc arg) body)."
`(progn ,@(mapcar (lambda (pair)
`(define-key ,mode-map (kbd ,(car pair))
(lambda () (interactive) (,argfunc ,(cdr pair)) ,@body)))
(eval binds))))
(defvar notmuch-hello-tree-searches '(("u" . "tag:unread")
("i" . "tag:inbox")
("*" . "*"))
"List of (key . query) pairs to bind in notmuch-hello.")
(make-binds notmuch-hello-mode-map
notmuch-hello-tree-searches
notmuch-search)
) ;; ends use-package notmuch
(use-package notmuch-indicator :ensure t)
(provide 'rul-mail)
|