DOOM Emacs

Configuration is at the same time the most fun and biggest challenge of any Emacs installation. I think it’s a good time to recall Anthony Bourdain’s thoughts about mise en place1.

Mise-en-place is the religion of all good line cooks. Do not mess with a line cook’s ‘meez’ — meaning his setup, his carefully arranged supplies of sea salt, rough-cracked pepper, softened butter, cooking oil, wine, backups, and so on. As a cook, your station, and its condition, its state of readiness, is an extension of your nervous system… The universe is in order when your station is set up the way you like it: you know where to find everything with your eyes closed, everything you need during the course of the shift is at the ready at arm’s reach, your defenses are deployed. If you let your mise-en-place run down, get dirty and disorganized, you’ll quickly find yourself spinning in place and calling for backup. I worked with a chef who used to step behind the line to a dirty cook’s station in the middle of a rush to explain why the offending cook was falling behind. He’d press his palm down on the cutting board, which was littered with peppercorns, spattered sauce, bits of parsley, bread crumbs and the usual flotsam and jetsam that accumulates quickly on a station if not constantly wiped away with a moist side towel. “You see this?” he’d inquire, raising his palm so that the cook could see the bits of dirt and scraps sticking to his chef’s palm. “That’s what the inside of your head looks like now.”

— Anthony Bourdain, from Kitchen Confidential.

This an annotated version of my DOOM Emacs configuration. The source files can be found on Github or Sourcehut.


The most basic installation of Doom Emacs consists of:

$ git clone --depth 1 ~/.emacs.d
$ ~/.emacs.d/bin/doom install

After any configuration changes you should run the sync command with

$ ~/.emacs.d/bin/doom sync

Any upgrade to the doom Emacs packages can be done using the upgrade command.

$ ~/.emacs.d/bin/doom upgrade




In order to use Python’s virtualenv, the virtualenvwrapper.el2 module is used. This is done by adding to packages.el

(package! virtualenvwrapper)

The only configuration I use for this library is setting my virtualenv root location in config.el

(use-package! virtualenvwrapper)
(after! virtualenvwrapper
  (setq venv-location "~/.virtualenvs/")

From a Python project the virtual environment can be selected by using M-x venv-workon.

Black formatter

To allow formatting of Python blocks in org-mode and elsewhere, add python-black 3 to packages.el.

(package! python-black)

Then we configure it with

(use-package! python-black
  :after python
  :hook (python-mode . python-black-on-save-mode-enable-dwim))

We should install black-machiatto 4 to allow formatting of partial regions.


The following modules must be enabled in init.el:

  • (go +lsp) in the lang section
  • lsp in the tools section
  • snippets in the editor section

gopls should be installed.


To enable support of Go templates, install the lang/web packages by adding to init.el.

(web +html)

This will install the web-mode package5 To specify the sepcific template engine as Go M-x web-mode-set-engine to go.

Running doom sync will finish the setup.


Support for Pikchr6 is added via the pikchr-mode package. By adding

(package! pikchr-mode)

It is necessary to install the pikchr binary according to the instructions in Pikchr. That page also contains examples.


I also use a basic mode7 to support xonsh8 scripts. The package can be installed with

;; packages.el
(package! xonsh-mode)

and invoked with

;; config.el
(use-package! xonsh-mode)



To disable completed items to show up in agenda views, the following is set in config.el

(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-skip-deadline-if-done t)

Key bindings

For Doom Emacs we add some additional key-bindings to the org-mode specifically. These bindings go under the local org leader key with the u prefix (which in my case is ) SPC m u. They are

  • SPC m u
    • a, archive an org-agenda item using #org-archive-subtree-default
    • r, get a random note using #org-randomnote

For more information on how to set doom Emacs keybings, check the elevant section.

Pretty bullets

The default Doom Emacs org-mode bullets are *. To get “pretty” bullets you need to add the +pretty flag to init.el:

(org +pretty ) ; organize your plain life in plain text

Optionally, you can specify the bullet’s hierarchy glyphs with

    org-superstar-headline-bullets-list '("⁖" "◉" "○" "✸" "✿")


Resolution dependent fonts

I have to work with a variety of monitor resolutions, from a 720p pocket book to a 2160p iMac. The fonts usually look tiny in my larger screen so I’ve added support for resolution dependent font sizes in my config.el.

For instance

(when (window-system) ;; only apply to GUI frames

;; Adjust font size dependent of the screen resolution

	(when (> (x-display-pixel-width) 3000)

		(setq doom-font (font-spec :family "Ubuntu Mono" :size 26 :weight 'regular)))

	(when (< (x-display-pixel-width) 3000)

		(setq doom-font (font-spec :family "Ubuntu Mono" :size 14 :weight 'regular)))

Would set a larger font size for monitors with a horizontal resolution higher than 3000 pixels.

Prettify symbols

Since Emacs 24.49 there is a builtin prettify-symbols-mode. It can be customized by changing prettify-symbols-alist. These strings will be replace by our selection, typically an unicode symbol. In this configuration we use the following:

(defun my/pretty-symbols ()
  (setq prettify-symbols-alist
          '(("#+begin_src python" . "🐍")
            ("#+begin_src elisp" . "λ")
            ("#+begin_src jupyter-python" . "🐍")
            ("#+end_src" . "―")
            ("#+results:" . "🔨")
            ("#+RESULTS:" . "🔨"))))

This will, for instance, replace the beginning of Python org-babel~ blocks with the single symbol. To register the prettify list with each mode we use

(add-hook 'org-mode-hook 'my/pretty-symbols)

Or we can register the prettify-symbols-mode as a global mode

(global-prettify-symbols-mode +1)


company is great, but it can get in the way when using on org-mode buffers. To disable add the following:

(after! org
  ;; disable auto-complete in org-mode buffers
  (remove-hook 'org-mode-hook #'auto-fill-mode)
  ;; disable company too
  (setq company-global-modes '(not org-mode))
  ;; ...


Beacon highlights the current cursor line after major movements. Especially useful for HDPi screens. Add it on packages.el:

(package! beacon)

And enable the global minor-mode on config.el with:

;; global beacon minor-mode
 (use-package! beacon)
 (after! beacon (beacon-mode 1))


This mode relies on the Focus package that dims regions not on … focus. Since the package is on MELPA, it can be installed by adding to packages.el:

  (package! focus)

Next, require it from config.el

  (use-package! focus)

And call it by setting the mode with M-x focus-mode.


treemacs10 is a great file navigation explorer for Emacs. However, since it has its own iternal concept of project, it needs an external helper to be able to synchronise with other project management tools, such as projectile.

To be able to synchronise treemacs and projectile the treemacs-projectile module must be used. It can be activated using

(use-package treemacs-projectile
  :after (treemacs projectile))

(after! (treemacs projectile)
  (treemacs-project-follow-mode 1))

This guarantees that when moving to a buffer of a different projectile project, the treemacs tree will reflect that.

Personally, I’m not a fan of doom Emacs’ and treemacs’ default behaviour of use variable pitch fonts. If you want, for consistency, to set treemacs to use a monospaced font, set the following on your config.el:

(setq doom-themes-treemacs-enable-variable-pitch nil)

Opening on project

To open Treemacs automatically in Doom Emacs when you open a project, regardless of whether it’s recognized by Projectile or another project management system Doom integrates with, you can leverage Doom’s project detection mechanisms and hooks. Since SPC p p uses Doom’s project management system which is built on top of Projectile but also accommodates other forms of project recognition, you can write a function that checks if you’re in a recognized project directory at startup and opens Treemacs accordingly.

Doom Emacs doesn’t have a direct hook that fires after opening a project with SPC p p (or +projectile/find-file), since this action is more interactive and manual. However, you can achieve automatic Treemacs opening on startup if you’re in a project directory by using the doom-first-file-hook. This hook runs after the first buffer is opened, which can be used to check if the opened buffer is part of a recognized project.

Here’s how you can do it. Add a Custom Function**: Insert the following Elisp code into your config.el:

(defun my/open-treemacs-in-project ()
  "Automatically open Treemacs in a project."
  (when (doom-project-root) ; Checks if the current buffer is in a Doom-recognized project
    (treemacs))) ; Opens Treemacs

(add-hook 'doom-first-file-hook 'my/open-treemacs-in-project)

This function my/open-treemacs-in-project checks if the current buffer belongs to a project (Doom’s doom-project-root will return non-nil if so) and opens Treemacs if it does. By adding this function to doom-first-file-hook, it ensures Treemacs opens automatically when you start Doom Emacs in a project directory or open a file that is part of a project.


Dirvish offers a suitable replacement/enhancement for dired with features such as improved UI and image preview. The only requirement for dirvish is the ls alternative exa It is available from MELPA which means you can add it to packages.el with

(package! dirvish)

and then enable it on config.el with

(use-package! dirvish)



An interesting talk on the advantages of using vterm as the default Emacs terminal emulator can be found can found at EmacsConf 2021A Tour of vterm”. To use vterm as the Emacs shell, the respective section in init.el should be selected:

 ;;eshell            ; the elisp shell that works everywhere
 ;;shell             ; simple shell REPL for Emacs
 ;;term              ; basic terminal emulator for Emacs
 vterm             ; the best terminal emulation in Emacs

We also need to install libvterm, with in macOS can be done with

brew install libvterm

and in Linux with

$ sudo apt-get install -y libvterm-dev # Ubuntu
$ sudo dnf -y install libvterm # Fedora


For full-text search, deadgrep11 is used, which leverages ripgrep. To install ripgrep on Linux, run

$ sudo apt install ripgrep # Ubuntu
$ sudo dnf install ripgrep # Fedora

Defining key bindings

(map! :leader

	(:prefix-map ("f" . "foo")
		(:prefix ("b" . "bar")
		:desc "Baz" "b" #'foo-bar-baz
		:desc "Qux" "q" #'foo-bar-qux)))

This will define SPC-f-b-b to invoke foo-bar-baz and SPC-f-b-q to invoke foo-bar-qux.

Dynamic modules

The best place to put dynamic modules in Doom Emacs is inside the actual .doom.d folder. This allows to load modules from Doom’s “private dir”. For instance, for a module called my_module and sub-directory named modules we would create

mkdir ~/.doom.d/modules

and then add to config.el

(add-to-list 'load-path (expand-file-name "modules/my_module" doom-private-dir))

(def-package! my_module
  :commands (mymodule-foo mymodule-bar))