Reloading Emacs Lisp Code
While working on erlang-ts-mode recently, someone asked me how I reload the mode’s code while developing it. I realized that while the answer is obvious to me after years of Emacs Lisp hacking, it’s not obvious at all to people who are just getting started with Emacs Lisp development. So here’s a short practical guide.
The Problem
Most Emacs users learn early on that you can evaluate Emacs Lisp with commands
like eval-buffer (M-x eval-buffer), eval-defun (C-M-x), and
eval-expression (M-:). These work great for most code – but they have a
blind spot: defvar and defcustom.
By design, defvar only sets a variable if it’s not already bound. This means
that if you change the default value of a defvar in your source and then run
eval-buffer, the old value sticks around. The same applies to defcustom.
Here’s a quick example:
(defvar my-mode-default-indent 2) ;; eval-buffer sets this to 2
;; Now change it to 4 in source:
(defvar my-mode-default-indent 4) ;; eval-buffer does NOTHING -- still 2
This is intentional – loading a library shouldn’t clobber user customizations. But when you’re developing a package, it’s a real pain. You change a default, re-evaluate, and wonder why nothing happened.
The same issue applies to faces defined with defface – re-evaluating the
definition won’t update an already-defined face.
The Approaches
Here are all the approaches I know of, roughly ordered from lightest to heaviest.
1. eval-defun on Individual Forms
Here’s something that surprises many people: eval-defun (C-M-x) does
handle defvar, defcustom, and defface specially. When you place point
inside a defvar form and hit C-M-x, it unconditionally sets the variable to
the new value, ignoring the “only if unbound” semantics.
This is different from eval-buffer and eval-region, which respect the
normal defvar behavior.
So if you’ve only changed a few forms, C-M-x on each one is the fastest
approach. It’s what I use most of the time during development.
2. setq via eval-expression
If you just need to reset one variable quickly, hit M-: and type:
(setq my-mode-default-indent 4)
Quick and dirty, but it works. This won’t re-evaluate any other code, so it’s only useful for tweaking individual values.
3. load-file
M-x load-file lets you load an .el file from disk. The difference from
require is that require skips loading entirely if the feature is already in
the features list, while load-file always reads and evaluates the file. That
said, it still respects defvar semantics, so already-bound variables won’t be
updated – you’d need eval-defun or unload-feature for that.
Where load-file really helps is when you want to reload a file that isn’t the
one you’re currently editing – e.g., a dependency within your package, or a
file that doesn’t have a provide form.
4. unload-feature + require
This is the “clean reload” approach:
(unload-feature 'my-mode t) ;; the t means "force, even if other things depend on it"
(require 'my-mode)
unload-feature removes everything the feature defined – variables, functions,
hooks, etc. Then require loads it fresh. This is the closest thing to a
clean slate without restarting Emacs.
A few caveats:
unload-featurecan be disruptive. If the feature added hooks or advice, unloading should clean those up, but edge cases exist.- It only works for features that were loaded via
provide/require. If you loaded a file withload-file, there’s no feature to unload. - Some complex packages don’t survive
unload-featurecleanly. For most packages (like a typical major mode), it works well.
You can bind this to a key for quick access during development:
(defun my-reload-feature (feature)
"Unload and reload FEATURE."
(interactive
(list (intern (completing-read "Reload feature: "
features nil t))))
(unload-feature feature t)
(require feature))
5. Restart Emacs
The nuclear option. When nothing else works or when you’ve made lots of changes
and don’t trust the runtime state, just restart. With desktop-save-mode or a
session manager, this is less painful than it sounds.
If you use Emacs in daemon mode, M-x restart-emacs (from the
restart-emacs package) or
the built-in restart-emacs (Emacs 29+) makes this quick.
Don’t Forget to Re-activate the Mode
One thing that trips people up: after reloading the code (via any of the above
methods), you also need to re-activate the mode in existing buffers. Just run
M-x my-mode – this re-runs the mode function and re-applies keymaps, hooks,
font-lock settings, etc. Without this step, existing buffers will still be
running the old code.
For minor modes, toggle off and on: M-x my-minor-mode twice.
My Workflow
For what it’s worth, here’s what my typical mode development workflow looks like:
- Edit the source file.
C-M-xon the specific forms I changed.- Switch to a test buffer and re-activate the mode with
M-x my-mode. - If things are weird,
unload-feature+requirefor a clean reload. - Restart Emacs only when I’ve changed something fundamental (like autoloads or package metadata).
Most of the time, steps 1-3 are all I need.
Wrapping Up
The thing to remember is that defvar/defcustom/defface are intentionally
designed not to override existing values, and most evaluation commands respect
this. Once you know that eval-defun is the exception (it does force the
update) and that unload-feature gives you a clean slate, reloading code during
development is pretty simple.
That’s all I have for you today. Keep hacking!