aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-16 07:58:26 -0500
committerCraig Jennings <c@cjennings.net>2026-06-16 07:58:26 -0500
commit56d105f0ec3b7e5e4088d0683ae9cc5797442854 (patch)
tree6798c110e2fdde6797e4a8a12c7dacaae689313c
parentcbc79f9d49de860134e12fc348560c7e43aea8f2 (diff)
downloaddotemacs-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.el3
-rw-r--r--modules/mail-config.el6
-rw-r--r--modules/system-lib.el23
-rw-r--r--tests/test-system-lib-font-lock-global-modes.el46
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