Add :after-hook facility to define-derived-mode.

This allow a form to be evaluated _after_ a major mode's hooks have been run.
It is needed to solve some problems in CC Mode, including bug #16759 and
bug #23476.

* lisp/emacs-lisp/derived.el (define-derived-mode): introduce the new argument
`:after-hook', and generate the requisite code for it.
(derived-mode-make-docstring): Take account of the possibility of :after-hook.

* lisp/subr.el (delayed-after-hook-forms): New variable.
(run-mode-hooks): As the last thing evaluate the forms in
delayed-after-hook-forms.

* doc/lispref/modes.texi (Derived Modes): Document :after-hook.
(Mode Hooks): Document the new feature in run-mode-hooks.

* etc/NEWS: Note the new feature.
This commit is contained in:
Alan Mackenzie 2016-05-08 13:24:20 +00:00
parent 344eb61ab3
commit 2eb6817ba9
4 changed files with 53 additions and 13 deletions

View file

@ -752,7 +752,8 @@ The new mode has its own abbrev table, kept in the variable
@item
The new mode has its own mode hook, @code{@var{variant}-hook}. It
runs this hook, after running the hooks of its ancestor modes, with
@code{run-mode-hooks}, as the last thing it does. @xref{Mode Hooks}.
@code{run-mode-hooks}, as the last thing it does, apart from running
any @code{:after-hook} form it may have. @xref{Mode Hooks}.
@end itemize
In addition, you can specify how to override other aspects of
@ -776,8 +777,9 @@ about the mode's hook, followed by the mode's keymap, at the end of this
documentation string. If you omit @var{docstring},
@code{define-derived-mode} generates a documentation string.
The @var{keyword-args} are pairs of keywords and values. The values
are evaluated. The following keywords are currently supported:
The @var{keyword-args} are pairs of keywords and values. The values,
except for @code{:after-hook}'s, are evaluated. The following
keywords are currently supported:
@table @code
@item :syntax-table
@ -801,6 +803,15 @@ this mode. (Not all major modes have one.) Only the (still
experimental and unadvertised) command @code{customize-mode} currently
uses this. @code{define-derived-mode} does @emph{not} automatically
define the specified customization group.
@item :after-hook
This optional keyword specifies a single Lisp form to evaluate as the
final act of the mode function, after the mode hooks have been run.
It should not be quoted. Since the form might be evaluated after the
mode function has terminated, it should not access any element of the
mode function's local state. An @code{:after-hook} form is useful for
setting up aspects of the mode which depend on the user's settings,
which in turn may have been changed in a mode hook.
@end table
Here is a hypothetical example:
@ -912,12 +923,15 @@ Major modes should run their mode hook using this function. It is
similar to @code{run-hooks} (@pxref{Hooks}), but it also runs
@code{change-major-mode-after-body-hook}, @code{hack-local-variables}
(when the buffer is visiting a file) (@pxref{File Local Variables}),
and @code{after-change-major-mode-hook}.
and @code{after-change-major-mode-hook}. The last thing it does is to
evaluate any @code{:after-hook} forms declared by parent modes
(@pxref{Derived Modes}).
When this function is called during the execution of a
@code{delay-mode-hooks} form, it does not run the hooks or
@code{hack-local-variables} immediately. Instead, it arranges for the
next call to @code{run-mode-hooks} to run them.
@code{hack-local-variables} or evaluate the forms immediately.
Instead, it arranges for the next call to @code{run-mode-hooks} to run
them.
@end defun
@defmac delay-mode-hooks body@dots{}

View file

@ -369,6 +369,12 @@ variable.
** New var syntax-ppss-table to control the syntax-table used in syntax-ppss.
+++
** `define-derived-mode' can now specify an :after-hook form, which
gets evaluated after the new mode's hook has run. This can be used to
incorporate configuration changes made in the mode hook into the
mode's setup.
** Autoload files can be generated without timestamps,
by setting 'autoload-timestamps' to nil.
FIXME As an experiment, nil is the current default.

View file

@ -137,6 +137,9 @@ BODY can start with a bunch of keyword arguments. The following keyword
:abbrev-table TABLE
Use TABLE instead of the default (CHILD-abbrev-table).
A nil value means to simply use the same abbrev-table as the parent.
:after-hook FORM
A single lisp form which is evaluated after the mode hooks have been
run. It should not be quoted.
Here is how you could define LaTeX-Thesis mode as a variant of LaTeX mode:
@ -184,7 +187,8 @@ See Info node `(elisp)Derived Modes' for more details."
(declare-abbrev t)
(declare-syntax t)
(hook (derived-mode-hook-name child))
(group nil))
(group nil)
(after-hook nil))
;; Process the keyword args.
(while (keywordp (car body))
@ -192,6 +196,7 @@ See Info node `(elisp)Derived Modes' for more details."
(`:group (setq group (pop body)))
(`:abbrev-table (setq abbrev (pop body)) (setq declare-abbrev nil))
(`:syntax-table (setq syntax (pop body)) (setq declare-syntax nil))
(`:after-hook (setq after-hook (pop body)))
(_ (pop body))))
(setq docstring (derived-mode-make-docstring
@ -272,7 +277,11 @@ No problems result if this variable is not bound.
,@body
)
;; Run the hooks, if any.
(run-mode-hooks ',hook)))))
(run-mode-hooks ',hook)
,@(when after-hook
`((if delay-mode-hooks
(push ',after-hook delayed-after-hook-forms)
,after-hook)))))))
;; PUBLIC: find the ultimate class of a derived mode.
@ -344,7 +353,7 @@ which more-or-less shadow%s %s's corresponding table%s."
(format "`%s' " parent))
"might have run,\nthis mode "))
(format "runs the hook `%s'" hook)
", as the final step\nduring initialization.")))
", as the final or penultimate step\nduring initialization.")))
(unless (string-match "\\\\[{[]" docstring)
;; And don't forget to put the mode's keymap.

View file

@ -1736,6 +1736,11 @@ if it is empty or a duplicate."
(make-variable-buffer-local 'delayed-mode-hooks)
(put 'delay-mode-hooks 'permanent-local t)
(defvar delayed-after-hook-forms nil
"List of delayed :after-hook forms waiting to be run.
These forms come from `define-derived-mode'.")
(make-variable-buffer-local 'delayed-after-hook-forms)
(defvar change-major-mode-after-body-hook nil
"Normal hook run in major mode functions, before the mode hooks.")
@ -1751,9 +1756,12 @@ If the variable `delay-mode-hooks' is non-nil, does not do anything,
just adds the HOOKS to the list `delayed-mode-hooks'.
Otherwise, runs hooks in the sequence: `change-major-mode-after-body-hook',
`delayed-mode-hooks' (in reverse order), HOOKS, then runs
`hack-local-variables' and finally runs the hook
`after-change-major-mode-hook'. Major mode functions should use
this instead of `run-hooks' when running their FOO-mode-hook."
`hack-local-variables', runs the hook `after-change-major-mode-hook', and
finally evaluates the forms in `delayed-after-hook-forms' (see
`define-derived-mode').
Major mode functions should use this instead of `run-hooks' when
running their FOO-mode-hook."
(if delay-mode-hooks
;; Delaying case.
(dolist (hook hooks)
@ -1765,7 +1773,10 @@ this instead of `run-hooks' when running their FOO-mode-hook."
(if (buffer-file-name)
(with-demoted-errors "File local-variables error: %s"
(hack-local-variables 'no-mode)))
(run-hooks 'after-change-major-mode-hook)))
(run-hooks 'after-change-major-mode-hook)
(dolist (form (nreverse delayed-after-hook-forms))
(eval form))
(setq delayed-after-hook-forms nil)))
(defmacro delay-mode-hooks (&rest body)
"Execute BODY, but delay any `run-mode-hooks'.