with-emacs · What you Need to Know About Hooks
source link: https://with-emacs.com/posts/tutorials/what-you-need-to-know-about-hooks/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Hooks are an important mechanism for customizing Emacs. They are used to execute code on certain occasions and are part of any non-trivial Emacs setup. In this post you will learn how to use them for your own configurations and about some pitfalls you might encounter in practice.
This post assumes that you already know some basics about Elisp and variables as discussed earlier. All examples also assume you use a fairly recent Emacs version (v.26 or later).
Basic usage
Hooks are regular variables which hold a function or more commonly a list of them. These functions are called when the hook runs. Here is a typical snippet of user configuration:
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
The above adds the function executable-make-buffer-file-executable-if-script-p
to after-save-hook
. Afterwards this function gets called each time you save a
buffer. If the file is a script (as detected by
#!) the file is made executable.
To remove the function again you use remove-hook
:
(remove-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
By convention those variables end with -hook
in their name so you can easily
search for them. Because many packages and modes come with their own set of
hooks it can be hard to track down the more general ones but you can find a list
of the standard Emacs hooks in the
manual.
It’s important to mention that you shouldn’t use anonymous functions for your
hooks because it becomes hard to tell the purpose of a lambda
when you inspect
the hook at some later point. Another benefit of using named functions is that
you can easily remove them by passing their name to remove-hook
.
Major mode hooks
If you want to run setup functions for specific modes you can use major mode hooks. These hooks will run when you enter the mode. Note that hooks don’t need to be defined ahead of time so you can add functions to them before the corresponding mode is loaded by Emacs.
Sometimes you will come across examples where major mode hooks are used to setup keybindings like this:
(defun python-key-setup+ ()
(local-set-key ...) ...)
(add-hook 'python-mode-hook #'python-key-setup+)
While this works it will set the key bindings every time a buffer enters the mode which isn’t necessary. The local keymap is usually the major mode keymap which is shared by all buffers which use that mode. You can achieve the same effect by defining the bindings only once after the keymap is available:
(with-eval-after-load 'python
(define-key python-mode-map ...))
Buffer local hooks
In the first section executable-make-buffer-file-executable-if-script-p
was
added to the global hook but often you want to adjust hooks only for specific
buffers. You might be tempted to check for buffer names or major modes in your
hook functions like this:
(add-hook 'after-save-hook #'after-save-in-some-buffer+)
(defun after-save-in-some-buffer+ ()
(when (string= (buffer-name) ...)
...))
But a better way is to adjust the hook variable buffer locally by using the
local
argument of add-hook
and remove-hook
. For example if you want to run
the executable-make-buffer-file-executable-if-script-p
only in sh-mode
buffers you can use:
(defun sh-mode-setup+ ()
(add-hook 'after-save-hook
#'executable-make-buffer-file-executable-if-script-p
nil 'local))
(add-hook 'sh-mode-hook #'sh-mode-setup+)
When the major mode sh-mode
is entered, sh-mode-setup+
will run and add
executable-make-buffer-file-executable-if-script-p
to the buffer local save
hook.
Usually you shouldn’t use make-local-variable
to make hook variables local
because add-hook
automatically handles some things for you: It arranges for a
final t
at the end of the local hook list. This will tell run-hooks
that the
global hook should run as well. Usually this is what you want because the global
hook should be expected to run regardless of any additional local setup. Further
any subsequent calls to add-hook
won’t automatically affect the local value
which is useful because there might be some other setup functions running down
the chain which don’t expect the hook variable to be local.
Like with other local variables if you want to get completely rid of the local
version you can use kill-local-variable
.
Minor mode hooks
A common pitfall is to think minor mode hooks work like major mode hooks but there is an important difference: Minor modes run their hooks when you enter and when you leave them. This can be used to run additional setup or teardown code. For example if you always want to toggle a mode in tandem with another one you can use something like:
(add-hook 'visual-fill-column-mode-hook #'visual-fill-column-toggle-wrap+)
(defun visual-fill-column-toggle-wrap+ ()
(adaptive-wrap-prefix-mode
(if visual-fill-column-mode 1 -1)))
The mode variable visual-fill-column-mode
will be t
when activating the
mode. The example above uses this to activate and deactivate
adaptive-wrap-prefix-mode
accordingly.
Abnormal hooks
In contrast to the normal hooks this post talked about there are also so called
abnormal hooks which by convention end with -functions
in their name. Those
can receive additional arguments or their return value is used for further
processing. The exact details need to be described by their documentation. They
are not as relevant for user configuration but can be very useful when writing
Elisp so you might want to learn more about how to use them (see the
comments). Generally the same concepts apply for these hooks and conveniently
you can continue to use add-hook
or remove-hook
for them, too.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK