master 7c8c769a4d4 7/9: Set major-mode before updating modules in erc-open

"F. Jason Park" <[email protected]> Tue, 30 Jun 2026 23:37:22 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 7c8c769a4d487b6b0cdb909444a10c7cc484a528
Author: F. Jason Park <[email protected]>
Commit: F. Jason Park <[email protected]>

    Set major-mode before updating modules in erc-open
    
    * etc/ERC-NEWS: Mention change.
    * lisp/erc/erc-button.el (erc-button-mode, erc-button-enable)
    (erc-button-disable): Use `erc-with-initialized-session' instead of
    `erc-mode-hook'.
    * lisp/erc/erc-common.el (erc-with-initialized-session): New macro.
    * lisp/erc/erc-goodies.el (erc-scrolltobottom-mode)
    (erc-scrolltobottom-enable, erc-scrolltobottom-disable)
    (erc-move-to-prompt-mode, erc-move-to-prompt-enable)
    (erc-move-to-prompt-disable): Use `erc-with-initialized-session' instead
    of `erc-mode-hook'.
    * lisp/erc/erc-imenu.el (erc-imenu-mode, erc-imenu-enable)
    (erc-imenu-disable): Use macro instead of `erc-mode-hook'.
    * lisp/erc/erc-stamp.el (erc-stamp-mode, erc-stamp-enable)
    (erc-stamp-disable): Use macro instead of `erc-mode-hook'.
    * lisp/erc/erc-track.el (erc-track-mode, erc-track-enable)
    (erc-track-disable): Use macro instead of `erc-mode-hook'.
    * lisp/erc/erc-truncate.el (erc-truncate-mode, erc-truncate-enable)
    (erc-truncate-disable): Use macro instead of `erc-mode-hook'.
    * lisp/erc/erc.el (erc--set-modules-functions): New variable, an
    internal hook to modify erc-modules membership.
    (erc-open): Set major mode before updating modules and run
    `erc--set-modules-functions'.  (Bug#76019)
---
 etc/ERC-NEWS             | 10 ++++++++++
 lisp/erc/erc-button.el   |  4 +---
 lisp/erc/erc-common.el   | 16 ++++++++++++++++
 lisp/erc/erc-goodies.el  | 14 +++++---------
 lisp/erc/erc-imenu.el    |  8 +++-----
 lisp/erc/erc-stamp.el    |  8 +++-----
 lisp/erc/erc-track.el    |  4 +---
 lisp/erc/erc-truncate.el |  4 +---
 lisp/erc/erc.el          | 10 ++++++++--
 9 files changed, 48 insertions(+), 30 deletions(-)

diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 2088345f97e..68655ecd4bd 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -51,6 +51,16 @@ were simply reversed as given.  Now, just like with global modules, ERC
 preserves the preferred order when activating local modules for new
 sessions.
 
+*** Major-mode setup runs before global-module setup in 'erc-open'.
+The "enable" body of a global module's minor-mode command will now see
+'erc-mode' as the 'major-mode'.  Although ERC still delays its
+major-mode hook until after it has initialized its main session
+variables, a module can also now set any of its own variables that don't
+depend on such initialization immediately and without fear of their
+being killed.  For modules still needing to run delayed, session-aware
+code, a new convenience macro called 'erc-with-initialized-session' is
+now available to automate any 'erc-mode-hook' wrangling for you.
+
 
 * Changes in ERC 5.6.2
 
diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el
index a7b5855e9eb..0fbd43ad22f 100644
--- a/lisp/erc/erc-button.el
+++ b/lisp/erc/erc-button.el
@@ -54,13 +54,11 @@
   "This mode buttonizes all messages according to `erc-button-alist'."
   ((add-hook 'erc-insert-modify-hook #'erc-button-add-buttons 30)
    (add-hook 'erc-send-modify-hook #'erc-button-add-buttons 30)
-   (add-hook 'erc-mode-hook #'erc-button-setup 91)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-button-setup))
+   (erc-with-initialized-session (erc-button-setup))
    (add-hook 'erc--tab-functions #'erc-button-next)
    (erc--modify-local-map t "<backtab>" #'erc-button-previous))
   ((remove-hook 'erc-insert-modify-hook #'erc-button-add-buttons)
    (remove-hook 'erc-send-modify-hook #'erc-button-add-buttons)
-   (remove-hook 'erc-mode-hook #'erc-button-setup)
    (remove-hook 'erc--tab-functions #'erc-button-next)
    (erc--modify-local-map nil "<backtab>" #'erc-button-previous)))
 
diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index aa176a14b46..481b6084a6a 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -434,6 +434,22 @@ if ARG is omitted or nil.
        (put ',enable  'definition-name ',name)
        (put ',disable 'definition-name ',name))))
 
+(defmacro erc-with-initialized-session (&rest body)
+  "Run BODY in all ERC buffers if outside `erc-open' and soon otherwise.
+When inside `erc-open', run BODY after session variables have been
+initialzied and after all `erc-mode-hook' members but before any
+`after-change-major-mode-hook' members.  Expect caller to know this is
+only useful in global-module setup and that they're still responsible
+for teardown, which is often done with `erc-buffer-do' or similar."
+  (let ((fn (make-symbol "fn"))
+        (hook-var (make-symbol "hook-var")))
+    `(let ((,fn (lambda () ,@body)))
+       (if erc--updating-modules-p
+           (let ((,hook-var (gensym "erc--oneoff-major-mode-hook-")))
+             (set ,hook-var ,fn)
+             (push ,hook-var delayed-mode-hooks))
+         (erc-buffer-do ,fn)))))
+
 (defmacro erc-with-buffer (spec &rest body)
   "Execute BODY in the buffer associated with SPEC.
 
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index ec110b65b8c..ddb5a24bd0d 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -79,12 +79,11 @@ be experimental.  It currently only works with Emacs 28+."
 ;;;###autoload(autoload 'erc-scrolltobottom-mode "erc-goodies" nil t)
 (define-erc-module scrolltobottom nil
   "This mode causes the prompt to stay at the end of the window."
-  ((add-hook 'erc-mode-hook #'erc--scrolltobottom-setup)
-   (when (and erc-scrolltobottom-all (< emacs-major-version 28))
+  ((when (and erc-scrolltobottom-all (< emacs-major-version 28))
      (erc-button--display-error-notice-with-keys
       "Option `erc-scrolltobottom-all' requires Emacs 28+. Disabling.")
      (setq erc-scrolltobottom-all nil))
-   (unless erc--updating-modules-p (erc-buffer-do #'erc--scrolltobottom-setup))
+   (erc-with-initialized-session (erc--scrolltobottom-setup))
    (if erc-scrolltobottom-all
        (progn
          (remove-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom)
@@ -97,8 +96,7 @@ be experimental.  It currently only works with Emacs 28+."
      (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
      (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-all)
      (add-hook 'erc-insert-done-hook #'erc-possibly-scroll-to-bottom)))
-  ((remove-hook 'erc-mode-hook #'erc--scrolltobottom-setup)
-   (erc-buffer-do #'erc--scrolltobottom-setup)
+  ((erc-buffer-do #'erc--scrolltobottom-setup)
    (remove-hook 'erc-insert-pre-hook #'erc--scrolltobottom-on-pre-insert)
    (remove-hook 'erc-send-completed-hook #'erc--scrolltobottom-all)
    (remove-hook 'erc-insert-done-hook #'erc--scrolltobottom-all)
@@ -258,10 +256,8 @@ Put this function on `erc-insert-post-hook' and/or `erc-send-post-hook'."
 ;;;###autoload(autoload 'erc-move-to-prompt-mode "erc-goodies" nil t)
 (define-erc-module move-to-prompt nil
   "This mode causes the point to be moved to the prompt when typing text."
-  ((add-hook 'erc-mode-hook #'erc-move-to-prompt-setup)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-move-to-prompt-setup)))
-  ((remove-hook 'erc-mode-hook #'erc-move-to-prompt-setup)
-   (dolist (buffer (erc-buffer-list))
+  ((erc-with-initialized-session (erc-move-to-prompt-setup)))
+  ((dolist (buffer (erc-buffer-list))
      (with-current-buffer buffer
        (remove-hook 'pre-command-hook #'erc-move-to-prompt t)))))
 
diff --git a/lisp/erc/erc-imenu.el b/lisp/erc/erc-imenu.el
index 965e8ead14c..669d8bf42b1 100644
--- a/lisp/erc/erc-imenu.el
+++ b/lisp/erc/erc-imenu.el
@@ -135,11 +135,9 @@ Don't rely on this function, read it first!"
 ;;;###autoload(autoload 'erc-imenu-mode "erc-imenu" nil t)
 (define-erc-module imenu nil
   "Simple Imenu integration for ERC."
-  ((add-hook 'erc-mode-hook #'erc-imenu-setup)
-   (add-hook 'which-function-mode-hook #'erc-imenu--disable-which-func)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-imenu-setup)))
-  ((remove-hook 'erc-mode-hook #'erc-imenu-setup)
-   (remove-hook 'which-function-mode-hook #'erc-imenu--disable-which-func)
+  ((add-hook 'which-function-mode-hook #'erc-imenu--disable-which-func)
+   (erc-with-initialized-session (erc-imenu-setup)))
+  ((remove-hook 'which-function-mode-hook #'erc-imenu--disable-which-func)
    (erc-buffer-do #'erc-imenu-setup)))
 
 (defun erc-imenu-setup ()
diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el
index fa4b829b4cf..f80d49ca343 100644
--- a/lisp/erc/erc-stamp.el
+++ b/lisp/erc/erc-stamp.el
@@ -178,13 +178,11 @@ from entering them and instead jump over them."
 ;;;###autoload(autoload 'erc-timestamp-mode "erc-stamp" nil t)
 (define-erc-module stamp timestamp
   "This mode timestamps messages in the channel buffers."
-  ((add-hook 'erc-mode-hook #'erc-stamp--setup)
-   (add-hook 'erc-insert-modify-hook #'erc-add-timestamp 70)
+  ((add-hook 'erc-insert-modify-hook #'erc-add-timestamp 70)
    (add-hook 'erc-send-modify-hook #'erc-add-timestamp 70)
    (add-hook 'erc-mode-hook #'erc-stamp--recover-on-reconnect)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-stamp--setup)))
-  ((remove-hook 'erc-mode-hook #'erc-stamp--setup)
-   (remove-hook 'erc-insert-modify-hook #'erc-add-timestamp)
+   (erc-with-initialized-session (erc-stamp--setup)))
+  ((remove-hook 'erc-insert-modify-hook #'erc-add-timestamp)
    (remove-hook 'erc-send-modify-hook #'erc-add-timestamp)
    (remove-hook 'erc-mode-hook #'erc-stamp--recover-on-reconnect)
    (erc-buffer-do #'erc-stamp--setup)))
diff --git a/lisp/erc/erc-track.el b/lisp/erc/erc-track.el
index 91466b9d5e3..53213217f7c 100644
--- a/lisp/erc/erc-track.el
+++ b/lisp/erc/erc-track.el
@@ -607,8 +607,7 @@ keybindings will not do anything useful."
      ;; enable the tracking keybindings
      (add-hook 'erc-connect-pre-hook #'erc-track-minor-mode-maybe)
      (erc-track-minor-mode-maybe))
-   (add-hook 'erc-mode-hook #'erc-track--setup)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-track--setup))
+   (erc-with-initialized-session (erc-track--setup))
    (add-hook 'erc-networks--copy-server-buffer-functions
              #'erc-track--replace-killed-buffer))
   ;; Disable:
@@ -630,7 +629,6 @@ keybindings will not do anything useful."
      (remove-hook 'erc-connect-pre-hook #'erc-track-minor-mode-maybe)
      (when erc-track-minor-mode
        (erc-track-minor-mode -1)))
-   (remove-hook 'erc-mode-hook #'erc-track--setup)
    (erc-buffer-do #'erc-track--setup)
    (remove-hook 'erc-networks--copy-server-buffer-functions
                 #'erc-track--replace-killed-buffer)))
diff --git a/lisp/erc/erc-truncate.el b/lisp/erc/erc-truncate.el
index 340584e26db..d651d87d412 100644
--- a/lisp/erc/erc-truncate.el
+++ b/lisp/erc/erc-truncate.el
@@ -67,12 +67,10 @@ for other purposes should customize either `erc-enable-logging' or
   ;;enable
   ((add-hook 'erc-insert-done-hook #'erc-truncate-buffer)
    (add-hook 'erc-connect-pre-hook #'erc-truncate--warn-about-logging)
-   (add-hook 'erc-mode-hook #'erc-truncate--setup)
-   (unless erc--updating-modules-p (erc-buffer-do #'erc-truncate--setup)))
+   (erc-with-initialized-session (erc-truncate--setup)))
   ;; disable
   ((remove-hook 'erc-insert-done-hook #'erc-truncate-buffer)
    (remove-hook 'erc-connect-pre-hook #'erc-truncate--warn-about-logging)
-   (remove-hook 'erc-mode-hook #'erc-truncate--setup)
    (erc-buffer-do #'erc-truncate--setup)))
 
 (defvar-local erc-truncate--buffer-size nil
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 22f2a90de63..b661453fdce 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -2497,6 +2497,11 @@ realizes it's missing some required module \"foo\", it can
 confidently call (erc-foo-mode 1) without having to learn
 anything about the dependency's implementation.")
 
+(defvar erc--set-modules-functions nil
+  "Abnormal hook run before updating modules on major-mode init.
+Calls members with ID and TARGET parameters of `erc-open', both possibly
+nil, along with a non-nil TARGET's server buffer when applicable.")
+
 (defvar erc--setup-buffer-hook '(erc--warn-about-aberrant-modules)
   "Internal hook for module setup involving windows and frames.")
 
@@ -2652,6 +2657,9 @@ side effect of setting the current buffer to the one it returns.  Use
     (when connect (run-hook-with-args 'erc-before-connect server port nick))
     (set-buffer buffer)
     (setq old-point (point))
+    (delay-mode-hooks (erc-mode))
+    (run-hook-with-args 'erc--set-modules-functions id channel
+                        (and channel old-buffer))
     (setq delayed-modules
           (erc--merge-local-modes (let ((erc--updating-modules-p t))
                                     (erc--update-modules
@@ -2659,8 +2667,6 @@ side effect of setting the current buffer to the one it returns.  Use
                                   (or erc--server-reconnecting
                                       erc--target-priors)))
 
-    (delay-mode-hooks (erc-mode))
-
     (setq erc-server-reconnect-count old-recon-count)
 
     (when (setq erc-server-connected (not connect))