init.el (13436B)
1 ;;; -*- lexical-binding: t -*- 2 3 ;;; Externalities. 4 5 ;; user-full-name from /etc/passwd; set with chfn(1). 6 ;; user-mail-address from EMAIL variable; set with ~/.profile, 7 ;; ~/.xsessionrc, DE's convention-du-jour. 8 9 ;; ~/.authinfo.gpg: 10 ;; machine imap.gmail.com login LOGIN password PASSWORD port 993 11 ;; machine smtp.gmail.com login LOGIN password PASSWORD port 587 12 13 ;;; Þe Olde Setq. 14 (setq gnus-select-method 15 '(nnimap "gmail" 16 ;; TODO: try auth-source-xoauth2-plugin: 17 ;; (nnimap-authenticator xoauth2) 18 (nnimap-address "imap.gmail.com") 19 (nnimap-server-port 993) 20 (nnmail-expiry-target "nnimap+gmail:[Gmail]/Trash") 21 (nnmail-expiry-wait immediate)) 22 gnus-secondary-select-methods 23 '((nntp "archive.lwn.net") 24 (nntp "news.gmane.io")) 25 26 smtpmail-smtp-server "smtp.gmail.com" 27 smtpmail-smtp-service 587 28 29 ;; Archival of sent messages. 30 gnus-gcc-mark-as-read t 31 ;; The next setting makes the previous one useless; keeping both 32 ;; for now because I'm not sure which I'll settle for. 33 gnus-message-archive-group nil 34 35 ;; Groups. 36 gnus-group-uncollapsed-levels 2 37 38 ;; Summary. 39 gnus-auto-center-summary nil 40 gnus-prompt-before-saving t 41 gnus-summary-line-format "%*%U%R %-11,11&user-date; %B%-23,23f %s\n" 42 gnus-summary-dummy-line-format " ╭ %S\n" 43 gnus-summary-make-false-root 'dummy 44 gnus-sum-thread-tree-root "╭ " 45 gnus-sum-thread-tree-false-root "┬ " 46 gnus-sum-thread-tree-single-indent " " 47 gnus-sum-thread-tree-indent " " 48 gnus-sum-thread-tree-single-leaf "╰► " 49 gnus-sum-thread-tree-leaf-with-other "├► " 50 gnus-sum-thread-tree-vertical "│" 51 gnus-thread-sort-functions 52 '(gnus-thread-sort-by-number 53 (not gnus-thread-sort-by-most-recent-date)) 54 gnus-user-date-format-alist '(((gnus-seconds-today) 55 . " %H:%M") 56 ((+ 86400 (gnus-seconds-today)) 57 . "Yestd %H:%M") 58 ((* 6 86400) 59 . " %a %H:%M") 60 ((gnus-seconds-month) 61 . "%a %d") 62 ((gnus-seconds-year) 63 . "%b %d") 64 (t 65 . "%F")) 66 ;; Articles. 67 gnus-cite-parse-max-size nil 68 gnus-header-face-alist 69 '(("From" nil gnus-header-from) 70 ("Subject" nil gnus-header-subject) 71 ("Date" nil eighters-date) 72 ("Newsgroups:.*," nil gnus-header-newsgroups) 73 ("" gnus-header-name gnus-header-content)) 74 gnus-sorted-header-list 75 (list 76 ;; What, when. 77 "^Subject:" "^Summary:" "^Keywords:" "^Date:" 78 ;; Who. 79 "^From:" "^Organization:" "^Followup-To:" "^To:" "^Cc:" "^Newsgroups:") 80 gnus-treat-display-smileys nil 81 ;; Do not fill anything; let visual-line-mode wrap text. 82 ;;; NB: for format=flowed, there is no setting to say "un-fill 83 ;;; flowed lines", so we *enable* filling, setting an absurd 84 ;;; line length limit, in order to un-fill flowed lines. 85 fill-flowed-display-column most-positive-fixnum 86 mm-fill-flowed t 87 ;;; More long-line-folding settings. 88 gnus-article-unfold-long-headers t 89 gnus-treat-fill-article nil 90 gnus-treat-fill-long-lines nil 91 gnus-treat-fold-headers nil) 92 93 ;;; Window configurations. 94 95 (defvar my/gnus-side-by-side-threshold 160) 96 97 (gnus-add-configuration 98 '(article 99 (if (>= (frame-width) my/gnus-side-by-side-threshold) 100 '(horizontal 1.0 101 (summary 1.0 point) 102 (article 80)) 103 '(vertical 1.0 104 (summary 0.25 point) 105 (article 1.0))))) 106 107 (dolist (buf-name '(forward reply reply-yank)) 108 (gnus-add-configuration 109 `(,buf-name 110 (if (>= (frame-width) my/gnus-side-by-side-threshold) 111 '(vertical 1.0 112 (summary 0.25) 113 (horizontal 1.0 114 (article 0.5) 115 (message 1.0 point))) 116 '(vertical 1.0 117 (summary 0.2) 118 (article 0.2) 119 (message 1.0 point)))))) 120 121 ;;; Summary tweaks. 122 123 (defun my/gnus-toggle-article-wrap () 124 (interactive) 125 (with-current-buffer gnus-article-buffer 126 (visual-line-mode 'toggle))) 127 128 (defun my/gnus-summary-tweak-keys () 129 (keymap-local-set "C-c d v" 'my/gnus-toggle-article-wrap)) 130 131 (add-hook 'gnus-summary-mode-hook 'my/gnus-summary-tweak-keys) 132 133 ;; message-subject-re-regexp is used both in Gnus summary buffers to 134 ;; detect and elide similar subjects in a thread, and by message mode 135 ;; when replying, to determine what to strip from the subject. 136 ;; 137 ;; Some MUAs add cruft to the subject, turning "Re: bug#123: foobar" 138 ;; into "RE: [External] : Re: bug#1234: foobar", which Debbugs will 139 ;; then turn into "bug#1234: [External] : Re: bug#1234: foobar". 140 ;; 141 ;; The only way I can find to tell the Gnus summary code to 142 ;; canonicalize all that cruft away is by tweaking this regexp, but 143 ;; setting its global value causes message-mode to elide stuff it 144 ;; shouldn't when crafting subjects. Therefore, chase down the best 145 ;; Gnus hook for the job, and set the regexp locally. 146 (defun my/gnus-reply-prefixes () 147 (mapcan (lambda (prefix) (list prefix (upcase prefix) (capitalize prefix))) 148 '("re" "aw" "sv" "fw" "fwd"))) 149 150 (setq my/gnus-summary-normalize-subject 151 (rx-to-string 152 `(seq bol 153 (+ (or (seq word-start (or ,@(my/gnus-reply-prefixes)) word-end) 154 (seq "bug#" (+ digit)) 155 (seq "[" (or "External" "SPAM UNSURE") "]")) 156 (? (* space) ":") (* space))))) 157 158 (add-hook 'gnus-summary-generate-hook 159 (lambda () 160 (setq-local message-subject-re-regexp 161 my/gnus-summary-normalize-subject))) 162 163 (let* ((initials (mapconcat (lambda (s) (substring s 0 1)) 164 (split-string user-full-name) 165 nil)) 166 (sent-prefix (format "%s → " initials))) 167 (setq gnus-summary-to-prefix sent-prefix 168 gnus-summary-newsgroup-prefix sent-prefix)) 169 170 ;;; Article tweaks. 171 172 (defun my/gnus-article-eschew-tables () 173 ;; I set shr-fill-text to nil because I prefer letting 174 ;; visual-line-mode manage wrapping. Unfortunately, many HTML 175 ;; emails rely on <table>s for layouts, and rendering can get ugly. 176 ;; Work around this by treating <table> & children as any other 177 ;; <div>. 178 (make-local-variable 'shr-external-rendering-functions) 179 (pcase-dolist (`(,tag . ,shr-function) 180 '((table . shr-tag-div) 181 (thead . shr-tag-div) 182 (tbody . shr-tag-div) 183 (tr . shr-tag-ul) 184 (th . shr-tag-li) 185 (td . shr-tag-li))) 186 (setf (alist-get tag shr-external-rendering-functions) shr-function))) 187 188 (defun my/gnus-article-has-html () 189 ;; Hard to tell the difference between 190 ;; * the variable `gnus-article-mime-handles', 191 ;; * the function `gnus-article-mime-handles', 192 ;; * the variable `gnus-article-mime-handle-alist'. 193 ;; 194 ;; Stealing debbugs.el's patch-finding logic. 195 (seq-some 196 (lambda (handle) 197 (string= (mm-handle-media-type (cdr handle)) "text/html")) 198 (gnus-article-mime-handles))) 199 200 (defun my/gnus-article-should-wrap () 201 (save-excursion 202 (message-goto-body) 203 (let ((should-wrap nil) 204 (has-html (my/gnus-article-has-html))) 205 (while-let (((not should-wrap)) 206 ((not (eobp))) 207 (current-line (thing-at-point 'line))) 208 (setq should-wrap 209 (and 210 ;; The line is bigger than the target width. 211 (> (length current-line) 212 (window-width (get-buffer-window gnus-article-buffer))) 213 ;; The line is not boring (citation, diff addition/removal). 214 (not (string-match-p "\\`[>+-]" current-line)) 215 ;; Lines that start with spaces are boring, except in 216 ;; HTML parts: those are choked with <table> tags that 217 ;; shr left-pads with spaces. 218 ;; NB: HAS-HTML is a naive heuristic: we are assuming 219 ;; that "any text/html part is present" means "we are 220 ;; looking at this text/html part". 221 (or (not (string-match-p "\\` " current-line)) has-html))) 222 (forward-line)) 223 should-wrap))) 224 225 (defun my/gnus-article-wrap-maybe () 226 ;; Enable visual-line-mode when it helps, i.e. when the message has 227 ;; long lines that are not part of citations nor patches. 228 (with-current-buffer gnus-article-buffer 229 (visual-line-mode 230 (unless (my/gnus-article-should-wrap) -1)))) 231 232 ;; Article setup is tricky. In order, `gnus-article-prepare' 233 ;; 234 ;; (1) calls `gnus-article-setup-buffer', which 235 ;; (a) calls `gnus-article-mode', which runs 236 ;; gnus-article-mode-hook, 237 ;; (b) sets truncate-lines from gnus-article-truncate-lines, 238 ;; 239 ;; (2) calls `gnus-display-mime', which may end up calling `mm-shr'; 240 ;; this can call `shr-tag-table', which turns truncate-lines on 241 ;; unconditionally. 242 ;; 243 ;; (3) runs gnus-article-prepare-hook. 244 ;; 245 ;; Gnus will only run (1a) once, and skip that step when it re-uses 246 ;; the same *Article* buffer for subsequent articles. So for our 247 ;; purposes, we need to 248 ;; 249 ;; (Ⅰ) hack the shr rendering functions in mode-hook, before `mm-shr' 250 ;; gets to work. 251 ;; (Ⅱ) call `visual-line-mode' (if needed) in prepare-hook, after 252 ;; truncate-lines has been set. 253 254 (add-hook 'gnus-article-mode-hook 'my/gnus-article-eschew-tables) 255 (add-hook 'gnus-article-prepare-hook 'my/gnus-article-wrap-maybe) 256 257 ;;; MIME display. 258 (defun my/mm-display-markdown-inline (handle) 259 (mm-display-inline-fontify handle 'markdown-mode)) 260 261 (with-eval-after-load 'mm-decode 262 ;; bug-gnu-emacs:<jwvzfsnntlq.fsf-monnier+emacs@gnu.org> 263 (setf (alist-get "text/markdown" mm-inline-media-tests nil nil 'equal) 264 '(my/mm-display-markdown-inline))) 265 266 ;;; Key bindings. 267 ;; 268 ;; m compose 269 ;; 270 ;; Group buffer: 271 ;; 272 ;; L list all groups 273 ;; RET view unread mail in group 274 ;; C-u RET view all mail in group 275 ;; g refresh 276 ;; G G search group 277 ;; 278 ;; Summary buffer: 279 ;; 280 ;; B m move message to group 281 ;; / N fetch new 282 ;; M-g refresh (expire, move, fetch new, show unread) 283 ;; C-u M-g refresh (expire, move, fetch new, show all) 284 ;; C-u g show raw, undecoded message source; g to decode 285 ;; T h collapse (hide) thread 286 ;; T s expand (show) thread 287 ;; T k, C-M-k mark thread as read 288 ;; M-1 T k mark thread as unread 289 ;; r reply 290 ;; R reply (quoting) 291 ;; S w reply-all 292 ;; S W reply-all (quoting) 293 ;; C-c C-f forward 294 ;; d mark read 295 ;; M-u clear marks (≡ mark unread) 296 ;; E expire 297 ;; # toggle mark for next action 298 ;; M-#, M P u unmark for next action 299 ;; 300 ;; Draft summary buffer: 301 ;; 302 ;; D e edit draft 303 ;; 304 ;; Article buffer: 305 ;; 306 ;; o save attachment at point 307 ;; K b add button for inlined MIME part 308 ;; 309 ;; Composing: 310 ;; 311 ;; C-c C-c send 312 ;; C-c C-a attach 313 ;; C-c C-f s change the subject (append "was:") 314 ;; 315 ;;; FAQ. 316 ;; 317 ;; - how to see *all mails*, not just unread? 318 ;; - C-u RET 319 ;; 320 ;; - how to do something on a bunch of mail matching a pattern? 321 ;; - M P R ; mark all mails with subjects matching regexp 322 ;; - M-& <x> ; do <x> on all marked mails 323 ;; 324 ;; - how to delete mail? 325 ;; - E to mark as expired 326 ;; - C-u M-g to refresh 327 ;; 328 ;; - how to remove groups deleted on the IMAP server? 329 ;; - b to iterate over "bogus" groups and remove them 330 ;; 331 ;; - how to list most-recent mails on top? 332 ;; - cf. gnus-thread-sort-functions 333 ;; 334 ;; - how to close a mail without going back to the group list? 335 ;; - = to make summary full-screen 336 ;; 337 ;; - how to get contact completion? 338 ;; - install ebdb from GNU ELPA 339 ;; - or just use message-mail-alias-type 340 ;; 341 ;; - how to refresh? 342 ;; - summary buffer: 343 ;; - / N (fetch new) 344 ;; - M-g (expire, move, fetch & redisplay) 345 ;; - group buffer: g 346 ;; 347 ;; - what do all those letters mean? 348 ;; (info "(gnus) Marking Articles") 349 ;; - O old ≡ read during previous session 350 ;; - R just read 351 ;; - r manually marked as read 352 ;; - A answered 353 ;; - E expirable 354 ;; - G cancelled (e.g. moved somewhere else) 355 ;; - . unseen 356 ;; 357 ;; - how to subscribe to mailing lists? 358 ;; - to browse an NNTP server, either 359 ;; - hit B in the group buffer, then nntp *some server* 360 ;; - or add (nntp "*some server*") to gnus-secondary-methods 361 ;; - over the list: u 362 ;; 363 ;;; TODO. 364 ;; 365 ;; - gnus-summary-line-format (📎 for attachments) 366 ;; 367 ;; - how to archive mails and news locally? 368 ;; 369 ;; - describe-key is mostly useless in article mode: 370 ;; > X runs the command gnus-article-read-summary-keys 371 ;; 372 ;; - detect possibly missing attachments from keywords