;; -*- lexical-binding: t -*- ;; user-full-name: from /etc/passwd, chfn(1). ;; user-mail-address: from EMAIL variable, ~/.profile & ~/.xsessionrc. ;; In ~/.authinfo.gpg: ;; machine imap.gmail.com login LOGIN password PASSWORD port 993 ;; machine smtp.gmail.com login LOGIN password PASSWORD port 587 ;; machine irc.freenode.net login LOGIN password PASSWORD (setq gnus-select-method '(nnimap "gmail" (nnimap-address "imap.gmail.com") (nnimap-server-port 993) (nnmail-expiry-target "nnimap+gmail:[Gmail]/Trash") (nnmail-expiry-wait immediate)) gnus-secondary-select-methods '((nntp "archive.lwn.net") (nntp "news.gmane.io")) smtpmail-smtp-server "smtp.gmail.com" smtpmail-smtp-service 587 ;; Archival of sent messages. gnus-gcc-mark-as-read t ;; The next setting makes the previous one useless; keeping both ;; for now because I'm not sure which I'll settle for. gnus-message-archive-group nil ;; Groups. gnus-group-uncollapsed-levels 2 ;; Summary. gnus-summary-line-format "%*%U%R %-16,16&user-date; %B%-23,23f %s\n" gnus-summary-dummy-line-format " ╭ %S\n" gnus-summary-make-false-root 'dummy gnus-sum-thread-tree-root "╭ " gnus-sum-thread-tree-false-root "┬ " gnus-sum-thread-tree-single-indent " " gnus-sum-thread-tree-indent " " gnus-sum-thread-tree-single-leaf "╰► " gnus-sum-thread-tree-leaf-with-other "├► " gnus-sum-thread-tree-vertical "│" gnus-thread-sort-functions '(gnus-thread-sort-by-number (not gnus-thread-sort-by-most-recent-date)) gnus-user-date-format-alist '(((gnus-seconds-today) . "%H:%M") ((+ 86400 (gnus-seconds-today)) . "Yesterday %H:%M") ((* 6 86400) . "%a %H:%M") ((gnus-seconds-month) . "%a %d") ((gnus-seconds-year) . "%b %d") (t . "%F")) ;; Articles. gnus-cite-parse-max-size nil gnus-header-face-alist '(("From" nil gnus-header-from) ("Subject" nil gnus-header-subject) ("Date" nil eighters-date) ("Newsgroups:.*," nil gnus-header-newsgroups) ("" gnus-header-name gnus-header-content)) gnus-sorted-header-list (list ;; What, when. "^Subject:" "^Summary:" "^Keywords:" "^Date:" ;; Who. "^From:" "^Organization:" "^Followup-To:" "^To:" "^Cc:" "^Newsgroups:") gnus-treat-display-smileys nil ;; Do not fill anything; let visual-line-mode wrap text. gnus-article-unfold-long-headers t gnus-treat-fill-article nil gnus-treat-fill-long-lines nil gnus-treat-fold-headers nil) ;;; Summary tweaks. (defun my/gnus-toggle-article-wrap () (interactive) (with-current-buffer gnus-article-buffer (visual-line-mode 'toggle))) (add-hook 'gnus-summary-mode-hook (lambda () (setq fill-column 120) (my/centered-mode) (keymap-local-set "C-c d v" 'my/gnus-toggle-article-wrap))) ;; message-subject-re-regexp is used both in Gnus summary buffers to ;; detect and elide similar subjects in a thread, and by message mode ;; when replying, to determine what to strip from the subject. ;; ;; Some MUAs add cruft to the subject, turning "Re: bug#123: foobar" ;; into "RE: [External] : Re: bug#1234: foobar", which Debbugs will ;; then turn into "bug#1234: [External] : Re: bug#1234: foobar". ;; ;; The only way I can find to tell the Gnus summary code to ;; canonicalize all that cruft away is by tweaking this regexp, but ;; setting its global value causes message mode to elide stuff it ;; shouldn't. Therefore, chase down the best Gnus hook for the job, ;; and set the regexp locally. (defun my/gnus-reply-prefixes () (mapcan (lambda (prefix) (list prefix (upcase prefix) (capitalize prefix))) '("re" "aw" "sv" "fw" "fwd"))) (setq my/gnus-summary-normalize-subject (rx-to-string `(seq bol (+ (or (seq word-start (or ,@(my/gnus-reply-prefixes)) word-end) (seq "bug#" (+ digit)) (seq "[" (or "External" "SPAM UNSURE") "]")) (? (* space) ":") (* space))))) (add-hook 'gnus-summary-generate-hook (lambda () (setq-local message-subject-re-regexp my/gnus-summary-normalize-subject))) (let* ((initials (mapconcat (lambda (s) (substring s 0 1)) (split-string user-full-name) nil)) (sent-prefix (format "%s → " initials))) (setq gnus-summary-to-prefix sent-prefix gnus-summary-newsgroup-prefix sent-prefix)) ;;; Article tweaks. (defun my/gnus-article-has-html () (seq-some (lambda (handle) (and (listp handle) (string= (mm-handle-media-type handle) "text/html"))) gnus-article-mime-handles)) (defun my/gnus-article-should-wrap () (save-excursion (message-goto-body) (let ((should-wrap nil) (has-html (my/gnus-article-has-html))) (while-let (((not should-wrap)) ((not (eobp))) (current-line (thing-at-point 'line))) (setq should-wrap (and ;; The line is bigger than the target width. (> (length current-line) fill-column) ;; The line is not boring (citation, diff addition/removal). (not (string-match-p "\\`[>+-]" current-line)) ;; Lines that start with spaces are boring, unless this ;; is an HTML part: those are choked with tags ;; that shr left-pads with spaces. ;; NB: HAS-HTML is a naive heuristic: we are assuming ;; that "any text/html part is present" means "we are ;; looking at this text/html part". (or (not (string-match-p "\\` " current-line)) has-html))) (forward-line)) should-wrap))) ;; Keep article buffer centered; enable visual-line-mode when it helps ;; (long lines that are not part of citations nor patches). ;; ;; Use article-prepare hook instead of article-mode hook to ensure ;; visual-line-mode will be set and effectively clear truncate-lines. ;; ;; The sequence goes like this: gnus-article-prepare first calls ;; gnus-article-setup-buffer, which ;; ;; (1) sets truncate-lines from gnus-article-truncate-lines, ;; (2) calls gnus-article-mode, which runs gnus-article-mode-hook. ;; ;; But then gnus-article-prepare calls gnus-display-mime, which may ;; end up calling mm-shr, which *might* end up calling shr-tag-table, ;; which unconditionally turns truncate-lines back on. ;; ;; Then gnus-article-prepare runs gnus-article-prepare-hook: that's ;; our cue to run visual-line-mode, which will set truncate-lines back ;; *off*. ;; ;; eww does not have this problem because it checks shr-fill-text & ;; turns visual-line-mode "late enough"; though the display of tables ;; remains less than ideal since rows are wrapped. Ideally ;; shr-tag-table should switch to an alternative layout when ;; shr-fill-text is nil. (defun my/gnus-article-setup () (setq fill-column 80 my/centered-set-right-margin t) (my/centered-mode) (visual-line-mode (unless (my/gnus-article-should-wrap) -1))) (add-hook 'gnus-article-prepare-hook 'my/gnus-article-setup) ;;; Key bindings. ;; ;; m compose ;; ;; Group buffer: ;; ;; L list all groups ;; RET view unread mail in group ;; C-u RET view all mail in group ;; g refresh ;; G G search group ;; ;; Summary buffer: ;; ;; B m move message to group ;; / N fetch new ;; M-g refresh (expire, move, fetch new, show unread) ;; C-u M-g refresh (expire, move, fetch new, show all) ;; C-u g show raw, undecoded message source; g to decode ;; T h collapse (hide) thread ;; T s expand (show) thread ;; T k, C-M-k mark thread as read ;; M-1 T k mark thread as unread ;; r reply ;; R reply (quoting) ;; S w reply-all ;; S W reply-all (quoting) ;; C-c C-f forward ;; d mark read ;; M-u clear marks (≡ mark unread) ;; E expire ;; # toggle mark for next action ;; M-#, M P u unmark for next action ;; ;; Draft summary buffer: ;; ;; D e edit draft ;; ;; Article buffer: ;; ;; o save attachment at point ;; K b add button for inlined MIME part ;; ;; Composing: ;; ;; C-c C-c send ;; C-c C-a attach ;; C-c C-f s change the subject (append "was:") ;; ;;; FAQ. ;; ;; - how to see *all mails*, not just unread? ;; - C-u RET ;; ;; - how to do something on a bunch of mail matching a pattern? ;; - M P R ; mark all mails with subjects matching regexp ;; - M-& ; do on all marked mails ;; ;; - how to delete mail? ;; - E to mark as expired ;; - C-u M-g to refresh ;; ;; - how to remove groups deleted on the IMAP server? ;; - b to iterate over "bogus" groups and remove them ;; ;; - how to list most-recent mails on top? ;; - cf. gnus-thread-sort-functions ;; ;; - how to close a mail without going back to the group list? ;; - = to make summary full-screen ;; ;; - how to get contact completion? ;; - install ebdb from GNU ELPA ;; - or just use message-mail-alias-type ;; ;; - how to refresh? ;; - summary buffer: ;; - / N (fetch new) ;; - M-g (expire, move, fetch & redisplay) ;; - group buffer: g ;; ;; - what do all those letters mean? ;; (info "(gnus) Marking Articles") ;; - O old ≡ read during previous session ;; - R just read ;; - r manually marked as read ;; - A answered ;; - E expirable ;; - G cancelled (e.g. moved somewhere else) ;; - . unseen ;; ;; - how to subscribe to mailing lists? ;; - to browse an NNTP server, either ;; - hit B in the group buffer, then nntp *some server* ;; - or add (nntp "*some server*") to gnus-secondary-methods ;; - over the list: u ;; ;;; TODO. ;; ;; - gnus-summary-line-format (📎 for attachments) ;; ;; - how to archive mails and news locally? ;; ;; - describe-key is mostly useless in article mode: ;; > X runs the command gnus-article-read-summary-keys ;; ;; - detect possibly missing attachments from keywords