diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-16 07:58:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-16 07:58:26 -0500 |
| commit | 56d105f0ec3b7e5e4088d0683ae9cc5797442854 (patch) | |
| tree | 6798c110e2fdde6797e4a8a12c7dacaae689313c | |
| parent | cbc79f9d49de860134e12fc348560c7e43aea8f2 (diff) | |
| download | dotemacs-56d105f0ec3b7e5e4088d0683ae9cc5797442854.tar.gz dotemacs-56d105f0ec3b7e5e4088d0683ae9cc5797442854.zip | |
fix(mail): theme mu4e buffers via a shared font-lock exclusion
mu4e paints its header lines, main menu, and view headers with manual `face' text properties. Global font-lock stripped them, so the buffers rendered unthemed, the same failure the dashboard hit. I extracted the dashboard's one-off exclusion into a shared, additive cj/exclude-from-global-font-lock helper in system-lib and repointed the dashboard to it. Then I excluded mu4e-headers-mode, mu4e-main-mode, and mu4e-view-mode. The view body renders through gnus's own washing rather than font-lock, so excluding it is safe.
| -rw-r--r-- | modules/dashboard-config.el | 3 | ||||
| -rw-r--r-- | modules/mail-config.el | 6 | ||||
| -rw-r--r-- | modules/system-lib.el | 23 | ||||
| -rw-r--r-- | tests/test-system-lib-font-lock-global-modes.el | 46 |
4 files changed, 77 insertions, 1 deletions
diff --git a/modules/dashboard-config.el b/modules/dashboard-config.el index b92162932..38510e801 100644 --- a/modules/dashboard-config.el +++ b/modules/dashboard-config.el @@ -17,6 +17,7 @@ ;;; Code: +(require 'system-lib) ;; cj/exclude-from-global-font-lock (eval-when-compile (require 'undead-buffers)) (declare-function cj/make-buffer-undead "undead-buffers" (string)) (autoload 'cj/make-buffer-undead "undead-buffers" nil t) @@ -168,7 +169,7 @@ system-defaults) are preserved rather than overwritten." ;; running the banner and headings fall back to the default face. Excluding ;; dashboard-mode lets those text-property faces survive. (Item and navigator ;; colors ride a `dashboard-items-face' overlay, which font-lock leaves alone.) -(setq font-lock-global-modes '(not dashboard-mode)) +(cj/exclude-from-global-font-lock 'dashboard-mode) (use-package dashboard :demand t diff --git a/modules/mail-config.el b/modules/mail-config.el index dfc0c4e0c..1ec41f213 100644 --- a/modules/mail-config.el +++ b/modules/mail-config.el @@ -161,6 +161,12 @@ Prompts user for the action when executing." (display-buffer-reuse-window display-buffer-same-window) (inhibit-same-window . nil))) +;; Keep global font-lock out of the mu4e buffers. mu4e paints header lines, the +;; main menu, and view headers with manual `face' text properties; global +;; font-lock strips them (the same failure the dashboard hit), leaving the +;; buffers unthemed. Excluding these modes keeps mu4e's faces. +(cj/exclude-from-global-font-lock 'mu4e-headers-mode 'mu4e-main-mode 'mu4e-view-mode) + (use-package mu4e :ensure nil ;; mu4e gets installed by installing 'mu' via the system package manager :load-path "/usr/share/emacs/site-lisp/mu4e/" diff --git a/modules/system-lib.el b/modules/system-lib.el index 9e25be5b7..ed98a476e 100644 --- a/modules/system-lib.el +++ b/modules/system-lib.el @@ -141,5 +141,28 @@ long-form answer, keeping a stray RET or space from confirming." (let ((use-short-answers nil)) (yes-or-no-p prompt))) +(defun cj/--font-lock-global-modes-excluding (current mode) + "Return CURRENT `font-lock-global-modes' with MODE added to the exclusion. +CURRENT has one of three shapes: t (font-lock on in all modes), a +\(not M...) exclusion list, or an (M...) inclusion list. Pure: returns +the new value and mutates nothing." + (cond + ((eq current t) (list 'not mode)) + ((and (consp current) (eq (car current) 'not)) + (if (memq mode (cdr current)) current + (cons 'not (cons mode (cdr current))))) + ((consp current) (delq mode (copy-sequence current))) + (t current))) + +(defun cj/exclude-from-global-font-lock (&rest modes) + "Exclude MODES from `global-font-lock-mode'. +Some major modes (dashboard, mu4e) paint their buffers with manual `face' +text properties; global font-lock then strips those, leaving the buffer +unthemed. Excluding the mode keeps its faces. Additive, so each caller +contributes its own modes regardless of load order." + (dolist (mode modes) + (setq font-lock-global-modes + (cj/--font-lock-global-modes-excluding font-lock-global-modes mode)))) + (provide 'system-lib) ;;; system-lib.el ends here diff --git a/tests/test-system-lib-font-lock-global-modes.el b/tests/test-system-lib-font-lock-global-modes.el new file mode 100644 index 000000000..e074bd256 --- /dev/null +++ b/tests/test-system-lib-font-lock-global-modes.el @@ -0,0 +1,46 @@ +;;; test-system-lib-font-lock-global-modes.el --- Tests for the font-lock exclusion helper -*- lexical-binding: t; -*- + +;;; Commentary: +;; ERT tests for `cj/--font-lock-global-modes-excluding', the pure transform +;; behind `cj/exclude-from-global-font-lock'. Some major modes (dashboard, +;; mu4e) paint their buffers with manual `face' text properties; global +;; font-lock then strips those. The helper adds a mode to the +;; `font-lock-global-modes' exclusion, handling its three shapes: t (all +;; modes on), a (not M...) exclusion list, and an (M...) inclusion list. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'system-lib) + +(ert-deftest test-system-lib-flgm-from-t-builds-not-list () + "Normal: t (all modes on) becomes a (not MODE) exclusion." + (let ((r (cj/--font-lock-global-modes-excluding t 'dashboard-mode))) + (should (eq (car r) 'not)) + (should (memq 'dashboard-mode (cdr r))))) + +(ert-deftest test-system-lib-flgm-adds-to-existing-not-list () + "Normal: a second mode is added to an existing (not ...) list." + (let ((r (cj/--font-lock-global-modes-excluding '(not dashboard-mode) 'mu4e-headers-mode))) + (should (eq (car r) 'not)) + (should (memq 'dashboard-mode (cdr r))) + (should (memq 'mu4e-headers-mode (cdr r))))) + +(ert-deftest test-system-lib-flgm-idempotent-on-already-excluded () + "Boundary: excluding an already-excluded mode does not duplicate it." + (let ((r (cj/--font-lock-global-modes-excluding '(not a-mode) 'a-mode))) + (should (eq (car r) 'not)) + (should (= 1 (cl-count 'a-mode (cdr r)))))) + +(ert-deftest test-system-lib-flgm-removes-from-inclusion-list () + "Boundary: in an (M...) inclusion list, excluding a mode removes it." + (should (equal (cj/--font-lock-global-modes-excluding '(foo-mode bar-mode) 'foo-mode) + '(bar-mode)))) + +(ert-deftest test-system-lib-flgm-nil-stays-nil () + "Boundary: nil (no mode gets global font-lock) already excludes everything." + (should (equal (cj/--font-lock-global-modes-excluding nil 'x-mode) nil))) + +(provide 'test-system-lib-font-lock-global-modes) +;;; test-system-lib-font-lock-global-modes.el ends here |
