diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-19 06:05:44 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-19 06:05:44 -0400 |
| commit | 2238b3a40473e8cf1e49915a24d56366b72aede5 (patch) | |
| tree | 5e26d8fd4c72234788074f95441e94284ec7678d /scripts/theme-studio/capture-default-faces.py | |
| parent | 71c52505f074580134e39e914287f7b3a92978dc (diff) | |
| download | dotemacs-2238b3a40473e8cf1e49915a24d56366b72aede5.tar.gz dotemacs-2238b3a40473e8cf1e49915a24d56366b72aede5.zip | |
fix(theme-studio): two-pass default-face capture so faces aren't dropped
Re-capturing the default-face snapshot regressed 43 faces to nonexistent, transient and dirvish among them. The capture loaded each package file whole with a bare `load`, and transient 0.12.0 now requires cond-let, which isn't on the batch load-path. The load failed, the error was swallowed, and every face in the file silently dropped. Any package that gains a dependency would hit the same trap.
I split the capture into two passes. Pass 1 keeps the best-effort full load, the only way to register faces a package builds in a macro or loop rather than a literal defface (rainbow-delimiters depth faces, markdown headers). Pass 2 reads each file and evaluates only its defface forms, so a face whose package can't fully load in batch still registers from its self-contained declaration. Pass 2 runs last so the pristine default spec wins over anything a pass-1 load customized.
The refreshed snapshot now records 137 more faces than before (magit, lsp, git, org, and company defaults the old single pass missed), plus the overline field everywhere. The only two dropped faces are gone upstream: consult-separator was removed from consult, and json-mode is no longer installed.
Diffstat (limited to 'scripts/theme-studio/capture-default-faces.py')
| -rw-r--r-- | scripts/theme-studio/capture-default-faces.py | 27 |
1 files changed, 27 insertions, 0 deletions
diff --git a/scripts/theme-studio/capture-default-faces.py b/scripts/theme-studio/capture-default-faces.py index 97a41eacc..acfd4984d 100644 --- a/scripts/theme-studio/capture-default-faces.py +++ b/scripts/theme-studio/capture-default-faces.py @@ -365,9 +365,36 @@ def main() -> None: (add-to-list 'load-path dir)) (dolist (feature (mapcar #'intern ts-probe-builtin-features)) (ignore-errors (require feature))) +(defun ts-probe--eval-deffaces (file) + "Evaluate only the `defface' forms in FILE. + +A defface form is self-contained, so registering a face this way avoids +loading the whole package (and its dependencies / side effects), which in +batch -Q is fragile: a missing dependency or mid-load error would silently +drop every face in the file. Each form is evaluated independently." + (when (file-readable-p file) + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (condition-case nil + (while t + (let ((form (read (current-buffer)))) + (when (and (consp form) (eq (car form) 'defface)) + (ignore-errors (eval form t))))) + (end-of-file nil))))) +;; Pass 1: best-effort full load. Registers faces that are defined by a macro +;; or loop rather than a literal defface (e.g. rainbow-delimiters depth faces, +;; markdown header faces), which pass 2 cannot see. Failures are swallowed. (dolist (file ts-probe-package-files) (with-temp-file {elisp_quote(str(PROGRESS))} (insert file)) (ignore-errors (load file nil t))) +;; Pass 2: evaluate literal defface forms directly. Robustly registers faces +;; whose package failed to fully load in pass 1 (e.g. transient needing +;; cond-let, magit's transient/forge stack) and resets literal faces to their +;; pristine defface default spec. Runs last so the default spec wins over any +;; customization a pass-1 load may have applied. +(dolist (file ts-probe-package-files) + (ts-probe--eval-deffaces file)) (defun ts-probe--proper-list-p (value) (or (null value) (and (consp value) (ts-probe--proper-list-p (cdr value))))) |
