Skip to main content

What you Need to Know About Hooks

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.