From 287fb2fbad6a75cc88ccd875ddcb38c18f75338f Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Wed, 22 Oct 2025 17:12:10 -0400 Subject: [PATCH] (custom-initialize-after-file): New function Some global minor modes require initialization. Those that are preloaded currently abuse `custom-initialize-delay` for that, but it's suboptimal and doesn't help those that aren't preloaded. So introduce a new function to fill that need. While at it, make `define-globalized-minor-mode` use it automatically when useful. * lisp/custom.el (custom-initialize-after-file-load): New function. * lisp/tooltip.el (tooltip-mode): * lisp/paren.el (show-paren-mode): * lisp/rfn-eshadow.el (file-name-shadow-mode): * lisp/epa-hook.el (auto-encryption-mode): * lisp/minibuffer.el (minibuffer-regexp-mode, minibuffer-nonselected-mode): * lisp/electric.el (electric-indent-mode): Use it instead of `custom-initialize-delay` since the value does not depend on the runtime context. (electric-quote-mode): Don't use `custom-initialize-delay` since the default value is nil anyway. * lisp/emacs-lisp/easy-mmode.el (define-globalized-minor-mode): Automatically add `:initialize` if needed. * lisp/emacs-lisp/eldoc.el (global-eldoc-mode): Remove `:initialize`, now provided automatically. * doc/lispref/customize.texi (Variable Definitions): * doc/lispref/modes.texi (Defining Minor Modes): Document and Suggest `custom-initialize-after-file-load` instead of `custom-initialize-delay`. --- doc/lispref/customize.texi | 12 +++++------- doc/lispref/modes.texi | 15 ++++++++------- etc/NEWS | 7 +++++++ lisp/custom.el | 22 ++++++++++++++++++++++ lisp/electric.el | 4 ++-- lisp/emacs-lisp/easy-mmode.el | 8 +++++++- lisp/emacs-lisp/eldoc.el | 1 - lisp/epa-hook.el | 7 +++---- lisp/font-core.el | 4 +--- lisp/minibuffer.el | 6 +++--- lisp/paren.el | 2 +- lisp/rfn-eshadow.el | 5 ++--- lisp/tooltip.el | 16 ++++++++-------- 13 files changed, 69 insertions(+), 40 deletions(-) diff --git a/doc/lispref/customize.texi b/doc/lispref/customize.texi index 2c6f02a088c..170bdc5e42c 100644 --- a/doc/lispref/customize.texi +++ b/doc/lispref/customize.texi @@ -434,14 +434,12 @@ Use the @code{:set} function to initialize the variable, if it is already set or has been customized; otherwise, just use @code{set-default-toplevel-value}. -@item custom-initialize-delay +@item custom-initialize-after-file-load This function behaves like @code{custom-initialize-set}, but it -delays the actual initialization to the next Emacs start. This should -be used in files that are preloaded (or for autoloaded variables), so -that the initialization is done in the run-time context rather than -the build-time context. This also has the side-effect that the -(delayed) initialization is performed with the @code{:set} function. -@xref{Building Emacs}. +delays the actual initialization until after the containing file is loaded. +This can be useful to break the common dependency where the setter +is (or uses) a function which needs to be defined after the variable, +such as when a global minor mode has a non-@code{nil} @code{:init-value}. @end table @item :local @var{value} diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index 8c8cbf32b61..6d7c586e23c 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -1868,18 +1868,19 @@ marking the @code{define-minor-mode} form as autoloaded. @item :init-value @var{init-value} This is the value to which the @var{mode} variable is initialized. Except in unusual circumstances (see below), this value must be -@code{nil}. If the mode is global (see below) and preloaded, and the -initial value is @code{t}, i.e., the mode is turned on by default, you -should consider forcing Emacs to run the mode function at startup, like -this: +@code{nil}. Note that @code{define-minor-mode} does not automatically +run the body of the minor mode to ensure the mode is really enabled +according to this value, so if the mode is global (see above) and the +initial value is non-@code{nil}, you should consider forcing Emacs to +run the mode function when loading the mode, like this: @lisp - :initialize #'custom-initialize-delay + :initialize #'custom-initialize-after-file-load @end lisp @noindent -otherwise, the minor mode might not appear in the @file{*Help*} buffer -generated by @kbd{C-h m} (@pxref{Mode Help}). +otherwise, the minor mode might say it's enabled even though it has not +been properly set up. @item :lighter @var{lighter} The string @var{lighter} says what to display in the mode line diff --git a/etc/NEWS b/etc/NEWS index de959aed9f3..f61825f531b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2674,6 +2674,13 @@ New faces have been added to 'icomplete-vertical-mode': ** Customize ++++ +*** New function 'custom-initialize-after-file-load'. +Useful to delay initialization to the end of the file, so it can use +functions defined later than the variable, as is common for minor modes. +'define-globalized-minor-mode' now automatically uses it if the +init-value is non-nil. + --- *** New major mode 'Customize-dirlocals-mode'. This is intended for customizing directory-local variables in the diff --git a/lisp/custom.el b/lisp/custom.el index ceb598b6787..69d09d9e293 100644 --- a/lisp/custom.el +++ b/lisp/custom.el @@ -140,6 +140,7 @@ For the standard setting, use `set-default-toplevel-value'." Once this list has been processed, this var is set to a non-list value.") (defun custom-initialize-delay (symbol value) + ;; FIXME: Rename to `custom-initialize-after-dump'? "Delay initialization of SYMBOL to the next Emacs start. This is used in files that are preloaded (or for autoloaded variables), so that the initialization is done in the run-time @@ -159,6 +160,27 @@ the :set function." ;; delay it, so initialize it "normally" (bug#47072). (custom-initialize-reset symbol value))) +(defun custom-initialize-after-file-load (symbol value) + "Delay initialization to after the current file is loaded. +This is handy when the initialization needs functions defined after the variable, +such as for global minor modes." + ;; Defvar it so as to mark it special, etc (bug#25770). + (internal--define-uninitialized-variable symbol) + + ;; Until the var is actually initialized, it is kept unbound. + ;; This seemed to be at least as good as setting it to an arbitrary + ;; value like nil (evaluating `value' is not an option because it + ;; may have undesirable side-effects). + (if (not load-file-name) + ;; There's no "after file" to speak of. + (custom-initialize-set symbol value) + (let ((thisfile load-file-name)) + (letrec ((f (lambda (file) + (when (equal file thisfile) + (remove-hook 'after-load-functions f) + (custom-initialize-set symbol value))))) + (add-hook 'after-load-functions f))))) + (defun custom-declare-variable (symbol default doc &rest args) "Like `defcustom', but SYMBOL and DEFAULT are evaluated as normal arguments. DEFAULT should be an expression to evaluate to compute the default value, diff --git a/lisp/electric.el b/lisp/electric.el index a1131499be6..d235a384620 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -383,7 +383,7 @@ indent the line according to context and rules of the major mode. This is a global minor mode. To toggle the mode in a single buffer, use `electric-indent-local-mode'." :global t :group 'electricity - :initialize 'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :init-value t (if (not electric-indent-mode) (unless (catch 'found @@ -767,7 +767,7 @@ ones listed here. Also see `electric-quote-replace-consecutive'. This is a global minor mode. To toggle the mode in a single buffer, use `electric-quote-local-mode'." :global t :group 'electricity - :initialize 'custom-initialize-delay + ;; :initialize #'custom-initialize-after-file-load :init-value nil (if (not electric-quote-mode) (unless (catch 'found diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el index c5c95a1be20..8e51825854a 100644 --- a/lisp/emacs-lisp/easy-mmode.el +++ b/lisp/emacs-lisp/easy-mmode.el @@ -522,6 +522,12 @@ on if the hook has explicitly disabled it. (when (easy-mmode--globalized-predicate-p ,MODE-predicate) (funcall ,turn-on-function))))) (_ (push keyw extra-keywords) (push (pop body) extra-keywords)))) + (setq extra-keywords (nreverse extra-keywords)) + + (when (and (plist-get extra-keywords :init-value) + (null (plist-get extra-keywords :initialize))) + (setq extra-keywords `(:initialize #'custom-initialize-after-file-load + . ,extra-keywords))) `(progn (progn @@ -553,7 +559,7 @@ Disable the mode if ARG is a negative number.\n\n" "`%s' is used to control which modes this minor mode is used in." MODE-predicate)) "")) - :global t ,@group ,@(nreverse extra-keywords) + :global t ,@group ,@extra-keywords ;; Setup hook to handle future mode changes and new buffers. (if ,global-mode diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index eb5f112f4a9..b82ee981eb7 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -238,7 +238,6 @@ expression point is on." :lighter eldoc-minor-mode-string ;;;###autoload (define-globalized-minor-mode global-eldoc-mode eldoc-mode turn-on-eldoc-mode - :initialize 'custom-initialize-delay :init-value t ;; For `read--expression', the usual global mode mechanism of ;; `change-major-mode-hook' runs in the minibuffer before diff --git a/lisp/epa-hook.el b/lisp/epa-hook.el index 0f83528bae5..61dba3f244d 100644 --- a/lisp/epa-hook.el +++ b/lisp/epa-hook.el @@ -99,11 +99,10 @@ interface, update `file-name-handler-alist'." (define-minor-mode auto-encryption-mode "Toggle automatic file encryption/decryption (Auto Encryption mode)." :global t :init-value t :group 'epa-file :version "23.1" - ;; We'd like to use custom-initialize-set here so the setup is done - ;; before dumping, but at the point where the defcustom is evaluated, + ;; At the point where the defcustom is evaluated, ;; the corresponding function isn't defined yet, so - ;; custom-initialize-set signals an error. - :initialize 'custom-initialize-delay + ;; custom-initialize-set would signal an error. + :initialize #'custom-initialize-after-file-load (setq file-name-handler-alist (delq epa-file-handler file-name-handler-alist)) (remove-hook 'find-file-hook 'epa-file-find-file-hook) diff --git a/lisp/font-core.el b/lisp/font-core.el index 7c8230f54e7..41bdb94085c 100644 --- a/lisp/font-core.el +++ b/lisp/font-core.el @@ -270,9 +270,7 @@ means that Font Lock mode is turned on for buffers in C and C++ modes only." (define-globalized-minor-mode global-font-lock-mode font-lock-mode turn-on-font-lock-if-desired - ;; What was this :extra-args thingy for? --Stef - ;; :extra-args (dummy) - :initialize 'custom-initialize-delay + :initialize #'custom-initialize-delay :init-value (not (or noninteractive emacs-basic-display)) :group 'font-lock :version "22.1") diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 29628fcb831..bd20d757340 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2349,7 +2349,7 @@ Runs of equal candidate strings are eliminated. GROUP-FUN is a (goto-char (point-max)) (recenter -1))))) (remove-hook 'window-scroll-functions - 'completion--lazy-insert-strings-on-scroll t)) + #'completion--lazy-insert-strings-on-scroll t)) (defun completion--lazy-insert-strings (&optional button) (setq button (or button completions--lazy-insert-button)) @@ -5617,7 +5617,7 @@ and make sexp navigation more intuitive. The list of prompts activating this mode in specific minibuffer interactions is customizable via `minibuffer-regexp-prompts'." :global t - :initialize #'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :init-value t (if minibuffer-regexp-mode (progn @@ -5711,7 +5711,7 @@ Use the face `minibuffer-nonselected' to highlight the contents of the minibuffer window when the minibuffer remains active but its window is no longer selected." :global t - :initialize #'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :init-value t :version "31.1" (if minibuffer-nonselected-mode diff --git a/lisp/paren.el b/lisp/paren.el index 5a70e2771b3..a286811b74b 100644 --- a/lisp/paren.el +++ b/lisp/paren.el @@ -155,7 +155,7 @@ this mode is enabled in. This is a global minor mode. To toggle the mode in a single buffer, use `show-paren-local-mode'." :global t :group 'paren-showing - :initialize 'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :init-value t ;; Enable or disable the mechanism. ;; First get rid of the old idle timer. diff --git a/lisp/rfn-eshadow.el b/lisp/rfn-eshadow.el index 0cbdf57b4c8..6db466b72c6 100644 --- a/lisp/rfn-eshadow.el +++ b/lisp/rfn-eshadow.el @@ -214,11 +214,10 @@ ignored (because the result is passed through `file-name-shadow-properties', which can be used to make that portion dim, invisible, or otherwise less visually noticeable." :global t - ;; We'd like to use custom-initialize-set here so the setup is done - ;; before dumping, but at the point where the defcustom is evaluated, + ;; At the point where the defcustom is evaluated, ;; the corresponding function isn't defined yet, so ;; custom-initialize-set signals an error. - :initialize 'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :init-value t :group 'minibuffer :version "22.1" diff --git a/lisp/tooltip.el b/lisp/tooltip.el index 51a9841b49f..cbea89906fd 100644 --- a/lisp/tooltip.el +++ b/lisp/tooltip.el @@ -53,19 +53,19 @@ echo area, instead of making a pop-up window." ;; Even if we start on a text-only terminal, make this non-nil by ;; default because we can open a graphical frame later (multi-tty). :init-value t - :initialize 'custom-initialize-delay + :initialize #'custom-initialize-after-file-load :group 'tooltip (if (and tooltip-mode (fboundp 'x-show-tip)) (progn - (add-hook 'pre-command-hook 'tooltip-hide) - (add-hook 'tooltip-functions 'tooltip-help-tips) - (add-hook 'x-pre-popup-menu-hook 'tooltip-hide)) + (add-hook 'pre-command-hook #'tooltip-hide) + (add-hook 'tooltip-functions #'tooltip-help-tips) + (add-hook 'x-pre-popup-menu-hook #'tooltip-hide)) (unless (and (boundp 'gud-tooltip-mode) gud-tooltip-mode) - (remove-hook 'pre-command-hook 'tooltip-hide) - (remove-hook 'x-pre-popup-menu-hook 'tooltip-hide)) - (remove-hook 'tooltip-functions 'tooltip-help-tips)) + (remove-hook 'pre-command-hook #'tooltip-hide) + (remove-hook 'x-pre-popup-menu-hook #'tooltip-hide)) + (remove-hook 'tooltip-functions #'tooltip-help-tips)) (setq show-help-function - (if tooltip-mode 'tooltip-show-help 'tooltip-show-help-non-mode))) + (if tooltip-mode #'tooltip-show-help #'tooltip-show-help-non-mode))) ;;; Customizable settings