Over the years, I’ve noticed common issues in the private configs of Doom’s users. Here, I document those that stand out most (and are caused by misunderstanding rather than bugs), with hopeful solutions. Many of these cause subtle issues that can lead to misbehavior down the road.
add-hook!
with an explicit lambda
The problem: you may be surprised that this hook never runs:
(add-hook! 'python-mode-hook
(lambda ()
(setq python-indent-offset 2)))
Be careful with the differences between add-hook
and Doom’s add-hook!
convenience macro. add-hook!
will implicitly wrap list arguments in (lambda (&rest) ...)
. What the above actually ends up doing is:
(add-hook 'python-mode-hook
(lambda (&rest _)
(lambda ()
(setq python-indent-offset 2))))
The solution: Either use add-hook
(which accept the lambda the way you were expecting), or remove the lambda and use add-hook!
:
(add-hook 'python-mode-hook
(lambda ()
(setq python-indent-offset 2)))
;; or
(add-hook! 'python-mode-hook
(setq python-indent-offset 2))
Passing after!
a major or minor mode
The problem: the first argument of the after!
macro is not a major or minor mode, but the name of a package. For example:
;; Do
(after! python ...)
(after! flycheck ...)
;; Don't
(after! python-mode ...)
(after! flycheck-mode ...)
The latter is a common mistake; it is never evaluated, and users will wonder why their code isn’t having an effect.
The solution: Use the package’s name, instead. What package to use can be determined using Doom’s documentation facilities:
-
SPChf
python-mode
-
C-hf
python-mode
(for users with evil disabled)
These display the documentation for python-mode
in a popup. The first line of the documentation will indicate the package that defines it:
python-mode is an autoloaded and interactive function defined in python.el.gz.
python.el.gz
is the package’s file name. Strip away the extensions and you have the package’s name (i.e. python
).
To add to the confusion: some packages are named after a mode they define. e.g. The
js2-mode
package definesjs2-mode
. In this case,(after! js2-mode ...)
would be correct.
Loading packages too early
The problem: Doom lazy loads (almost) all of its packages, i.e. they are not loaded immediately, but when you need them. A common mistake is to misuse the use-package
(or use-package!
) macros, causing packages to be eagerly loaded.
(use-package! X
:config
..)
The expectation is that ...
is evaluated later, when X
is loaded, but what actually happens is X
is immediately loaded at startup (then ...
is evaluated).
The solution:
(use-package! X
:defer t
:config
...)
;; or
(after! X
...)
use-package
has other properties that imply:defer t
. e.g.:hook
,:mode
,:commands
, and:after
.
Using exec-path-from-shell
The problem: Your PATH
and other environment variables are sometimes unavailable in GUI Emacs (this is especially true for macOS users). The exec-path-from-shell package was written to correct this, by launching your shell and scraping its environment when you start up Emacs.
So what’s the problem? Doom comes with its own, better version of this. If you weren’t aware, you’ll likely benefit from switching to it. I go into further detail elsewhere.
The solution: Run doom env
in the command line instead. This scrapes your current shell environment into an envvar file which Doom loads at startup.
You only need to do this once. Your envvar file is regenerated each time you doom sync
thereafter.
Installing packages with package.el
The problem: Doom has its own package manager (powered by straight.el). It is far more powerful than the package manager built into Emacs (called package.el
).
However, most package documentation will tell you to install packages with M-x package-install
or with an :ensure t
in a use-package
block. Doom won’t stop you from doing so if you know what you’re doing, but in most cases, the user simply isn’t aware of Doom’s package manager.
The fix: Use Doom’s package manager instead. This section in the manual about package management goes into the details, but the highlights are:
- To install a package: add
(package! package-name)
to~/.doom.d/packages.el
, then execute$ doom sync
on the command line. - Do not use
:ensure t
in your(use-package ...)
declarations, as it uses package.el under the hood - Run
$ doom sync -u
if you’ve changed the recipe of an existing package and want to update your packages.
Loading Org babel plugins yourself
The problem: You might have needed something like this in your previous config:
(org-babel-do-load-languages
'org-babel-load-languages
'((R . t)
(python . t)))
This practice is recommended in the documentation of by many Org babel plugins, but this is unnecessary in Doom Emacs. Babel has been modified to lazy-load your babel plugins. No additional configuration is needed on your part unless the package has special load requirements (such as jupyter
– which Doom will set up for you with :lang org
’s +jupyter
flag).
The solution: remove calls to org-babel-do-load-languages
in your config.
Calling server-start
yourself
The problem: Users may be calling (server-start)
themselves, in their config. This isn’t necessary, because Doom starts the server for you, after a brief delay, after your first input, or when you first unfocus Emacs.
The solution: Don’t start it yourself.