Subject: bug#77215: 31.0.50; enable-theme-functions fires twice after a load-theme

From: Mauro Aranda <[email-masked]>
Date: Mon, 24 Mar 2025 06:18:43 -0300

Filippo Argiolas <[email-masked]> writes:

 > Hi,
 >
 > I noticed that my `enable-theme-functions' hook always fires twice
 > after either a `load-theme' or a `enable-theme'.
 >
 > Problem is `enable-theme' in custom.el always also enables the user
 > theme to push it on top of the list.
 >
 > Is this desired?
 >
 > Technically I'd say it's correct, two enables spark two hooks, buyt
 > from the user point of view seems a counterintuitive side effect
 > without digging into the code.
 >
 > Filippo

I think it would be good to avoid it.  The attached patch does that.
From 89a9b3e8e708d5bd097f7265d8be6b450903076c Mon Sep 17 00:00:00 2001
From: Mauro Aranda <[email-masked]>
Date: Mon, 24 Mar 2025 06:13:24 -0300
Subject: [PATCH] Avoid running enable-theme-functions twice

* lisp/custom.el (custom--apply-theme-settings): New helper
function, extracted from enable-theme.
(enable-theme): Do not call itself to enable the user theme,
just call custom--apply-theme-settings.  (Bug#77215)
---
 lisp/custom.el | 54 +++++++++++++++++++++++++++-----------------------
 1 file changed, 29 insertions(+), 25 deletions(-)

diff --git a/lisp/custom.el b/lisp/custom.el
index d309754ab9b..db105b7618a 100644
--- a/lisp/custom.el
+++ b/lisp/custom.el
@@ -1535,23 +1535,8 @@ disable-theme-functions
   :group 'customize
   :version "29.1")
 
-(defun enable-theme (theme)
-  "Reenable all variable and face settings defined by THEME.
-THEME should be either `user', or a theme loaded via `load-theme'.
-
-After this function completes, THEME will have the highest
-precedence (after `user') among enabled themes.
-
-Note that any already-enabled themes remain enabled after this
-function runs.  To disable other themes, use `disable-theme'.
-
-After THEME has been enabled, runs `enable-theme-functions'."
-  (interactive (list (intern
-		      (completing-read
-		       "Enable custom theme: "
-		       obarray (lambda (sym) (get sym 'theme-settings)) t))))
-  (unless (custom-theme-p theme)
-    (error "Undefined Custom theme %s" theme))
+(defun custom--apply-theme-settings (theme)
+  "Recalculate and apply the THEME settings."
   (let ((settings (get theme 'theme-settings)) ; '(prop symbol theme value)
         ;; We are enabling the theme, so don't inhibit enabling it.  (Bug#34027)
         (custom--inhibit-theme-enable nil))
@@ -1582,18 +1567,37 @@ enable-theme
                                  (equal (custom-quote val) (nth 3 s))))))
           (setq spec-list `((changed ,(custom-quote val)))))
         (put symbol prop (cons (cddr s) (assq-delete-all theme spec-list)))
-	(cond
-	 ((eq prop 'theme-face)
-	  (custom-theme-recalc-face symbol))
-	 ((eq prop 'theme-value)
-	  ;; Ignore `custom-enabled-themes' and `custom-safe-themes'.
-	  (unless (memq symbol '(custom-enabled-themes custom-safe-themes))
-	    (custom-theme-recalc-variable symbol)))))))
+        (cond
+         ((eq prop 'theme-face)
+          (custom-theme-recalc-face symbol))
+         ((eq prop 'theme-value)
+          ;; Ignore `custom-enabled-themes' and `custom-safe-themes'.
+          (unless (memq symbol '(custom-enabled-themes custom-safe-themes))
+            (custom-theme-recalc-variable symbol))))))))
+
+(defun enable-theme (theme)
+  "Reenable all variable and face settings defined by THEME.
+THEME should be either `user', or a theme loaded via `load-theme'.
+
+After this function completes, THEME will have the highest
+precedence (after `user') among enabled themes.
+
+Note that any already-enabled themes remain enabled after this
+function runs.  To disable other themes, use `disable-theme'.
+
+After THEME has been enabled, runs `enable-theme-functions'."
+  (interactive (list (intern
+		      (completing-read
+		       "Enable custom theme: "
+		       obarray (lambda (sym) (get sym 'theme-settings)) t))))
+  (unless (custom-theme-p theme)
+    (error "Undefined Custom theme %s" theme))
+  (custom--apply-theme-settings theme)
   (unless (eq theme 'user)
     (setq custom-enabled-themes
 	  (cons theme (remq theme custom-enabled-themes)))
     ;; Give the `user' theme the highest priority.
-    (enable-theme 'user))
+    (custom--apply-theme-settings 'user))
   ;; Allow callers to react to the enabling.
   (run-hook-with-args 'enable-theme-functions theme))
 
-- 
2.34.1