A guide to installing and configuring my DOOM EMACS setup.
You need to ensure the following dependencies are installed to follow this guide successfully.
- Ubuntu LTS (currently 22.04+)
Note: It’s recommended that you follow my guide to installing and configuring Ubuntu.
- Git 2.23+
- ripgrep (11.01)
- (Optional) fd
The first step is to install the base vanilla Gnu-EMACS application compatible with DOOM. For the purposes of this guide, we’ll be installing Gnu-EMACS version 28.1. At this point in time, the minimum required EMACS version is 27.1.
Note: You’ll need to review the DOOM prerequisites for EMACS to identify the EMACS versions compatible with DOOM.
The recommended installation instructions for installing EMACS (and the GUI components) can be found on github,
To get up and running quickly, you can install emacs
using the apt
package manager.
The general repositories used in Ubuntu 20.04 and 22.04 (focal, jammy, etc) run older versions of Emacs (26). These aren’t compatible with DOOM.
You will need to use a Personal Package Archive (PPA) to install a newer version of Emacs in Ubuntu. A recommended PPA is ‘kelleyk/emacs’ and can be added with the following:
sudo add-apt-repository ppa:kelleyk/emacs
Now we should be able to install a newer version of Emacs through the apt
package manager.
sudo apt install emacs28
To uninstall emacs
you’ll need to also remove all dependencies.
sudo apt-get remove emacs emacs28 emacs-bin-common emacs-common emacs-el && sudo apt autoremove
You’ll need the following packages installed to build the emacs
source.
libncurses-dev libgnutls28-dev gnutls-bin pkg-config
Then, obtain the source code tarball for the specific version you want. In this case, we’re installing version 28.1.
wget https://ftp.gnu.org/gnu/emacs/emacs-28.1.tar.xz
Extract the source code from the tarball.
tar xvf emacs-28.1.tar.xz
Finally, configure, build, and install emacs
using the scipts supplied with the source files.
cd emacs-28.1
./configure
sudo make
sudo make install
You should now be able to start emacs
successfully.
Note: To uninstall
emacs
you can follow the step above but instead ofsudo make install
you can runsudo make uninstall
Run the following command to clone the DOOM configuration to your local computer:
git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
~/.config/emacs/bin/doom install
Add the Emacs binary files to the PATH
environment variable, if you haven’t already. You’ll need to add the following line to the bottom of your ~/.bashrc
configuration file:
export PATH="~/.config/emacs/bin:$PATH"
If you have any private emacs
configuration to sync with DOOM, or if you’ve made changes to init.el
or packages.el
, you’ll need to run doom sync
.
Finally, to run the new setup, use doom run
in the terminal. You should be met with the DOOM landing screen.
Note: Visit the official DOOM GitHub repository for more information if you have any installation issues.
You can find information on the DOOM configuration framework for emacs
in the DOOMEMACS github repository.
It’s recommended that you ensure your system is up-to-date.
sudo apt update && sudo apt upgrade
Now, we clone the DOOM configuration from git into the $HOME/emacs.d
directory, and use the doom install
function from the binary.
git clone https://github.com/hlissner/doom-emacs ~/.emacs.d
~/.emacs.d/bin/doom install
Note: Ensure that you have an SSH authentication key-pair loaded into git and the local ssh-agent. You may need to add this code to your
bashrc
configuration.
You should be able to run doom doctor
to diagnose any problems associated with an installation or with the system configuration.
The next time you open emacs
, your DOOM configuration should be loaded.
I’ve added customizations and modules to EMACS that are helpful in my workflow. Consult the sections below for updating your Emacs world.
My configuration for DOOM can be copied into the ~/.config/doom
folder to retain all config below. Alternatively, assuming this repository resides within ~/documents
, you can execute the following code to use symlinks
instead:
ln -s ~/documents/emacs-setup/.config/doom/config.el ~/.config/doom/config.el
ln -s ~/documents/emacs-setup/.config/doom/init.el ~/.config/doom/init.el
ln -s ~/documents/emacs-setup/.config/doom/packages.el ~/.config/doom/packages.el
cp ~/documents/emacs-setup/.config/doom/custom.el ~/.config/doom/custom.el
**Note:** You will notice that
custom.el
is not created as a symlink but is instead copied directly to the.config
folder. This is so that any custom changes per each device made in this repository do not create a conflict with each other, as agit pull
will override settings in symoblically linked files.
For some custom packages/EmacsLisp files that I want EMACS to load, I’ve created the directory ~/packages
to hold these files.
Then, to ensure that the packages will be recognized by EMACS when it initializes, we add the following line to ~/.config/doom/config.el
:
(let ((default-directory (expand-file-name "~/packages/")))
(normal-top-level-add-subdirs-to-load-path))
This allows us to add the
~/packages
directory and all sub-directories to the Emacs load-path
I replace the doom-theme
variable in config.el
with the following:
(setq doom-theme 'doom-horizon)
I also create a custom.el
file with the following code:
(custom-set-faces
'(font-lock-comment-face ((t (:foreground "color-173" :slant italic)))))
This will update some of the font faces that are harder to see with my setup.
I’ve made an update to ~/.config/doom/init.el
to change the directory that org-agenda
uses to find TODO items (~/documents/*
).
At the bottom of the init.el
file, I’ve inserted the following code:
(setq org-agenda-files (directory-files-recursively "~/documents/" "\\.org$"))
Restart DOOMEMACS to apply changes. When prompted, select n
to avoid importing variables from other org files outside of this directory.
I use org-roam as a mind-mapping tool and as a local knowledge-base. I.E - my second-brain.
First, we must create a folder to hold our .org
roam files. For this, I clone/pull second-brain from my private git
repo. mkdir
somewhere will work just as well.
git clone git@github.com:shawngerrard/second-brain.git ~/documents/second-brain
Installing org-roam
in DOOM as is simple as opening init.el
and updating the :lang org
package to pass in the roam flags. We do this by replacing org
in init.el
to (org +roam2)
.
Note: Using the DOOM
org-roam
package will “pin” the package at a specific commit - this is so that DOOM can maintain stability. If you useorg-roam-ui
(as I do - see below!), you may need to bump theorg-roam
package to keep up with theorg-roam-ui
updates. You can do this by usingM-x doom/bump-package-at-point
(with your cursor over the package! statement inpackages.el
). This will generate a:pin
with the the latest commit for the package, which should save you the trouble of guessing where every package is installed.
We must also update our config.el
file with the following:
(setq org-roam-directory (file-truename "~/documents/second-brain/"))
(setq find-file-visit-truename t)
(org-roam-db-autosync-mode)
Note: The above code will set the scanning roam directory for
.org
files to~/documents/second-brain/
. You can set this to any other directory as you wish. The next line will instruct Emacs to resolve symlinks. The final line ensures thatorg-roam
syncs file changes with its cache (sqlite3).
Now, run doom sync -u
to reload your configuration. You should be able to view the org-roam
command menu in Doom by pressing SPC n r
.
Please review SystemCrafter’s useful video to learn how to use org-roam
.
In the second-brain repository, I make use of org-roam-capture
templates. These allow me to create similar org-roam
nodes with a consistent structure, as well as speed-up the process of creating nodes.
To do this, we need to update our init.el
with the following:
(setq org-roam-capture-templates
'(("d" "default" plain
"%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+Title: ${title}\n")
:unnarrowed t)
("p" "exo-planets" plain (file "~/documents/second-brain/templates/exoplanet.org")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+Title: ${title}\n")
:unnarrowed t)
)
)
Note: We keep the “default” setting to ensure that we can continue to create blank nodes.
Note: Please ensure that the filepath in the form above is pointing to the correct template according to your system.
Org-roam-ui
is a fantastic front-end UI for exploring my org-roam
second-brain.
For information relating to this org-roam
UI overlay, visit the org-roam-ui github repository.
To install, because we use Doom we must first unpin the org-roam
package. This is because org-roam-ui
tries to keep up with the latest features of org-roam
. We also must enable the org-roam-ui
package. We can do all of this in our packages.el
with the following:
(unpin! org-roam)
(package! org-roam-ui)
We must then update our config.el
to use the websocket
package and the org-roam-ui
package. Doing this will install a local server where we can interrogate our second-brain in a UI rendered in our default browser. Update config.el
with the following:
(use-package! websocket
:after org-roam)
(use-package! org-roam-ui
:after org-roam ;; or :after org
;; normally we'd recommend hooking orui after org-roam, but since org-roam does not have
;; a hookable mode anymore, you're advised to pick something yourself
;; if you don't care about startup time, use
;; :hook (after-init . org-roam-ui-mode)
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
Finally, run doom sync -u
to install the packages.
Finally, once the sync is complete, we must do the following to access our swish org-roam-ui
:
- Start our local webserver with the following command:
M-x org-roam-ui-mode RET
- Access http://127.0.0.1:35901/ to view our UI served from our local webserver.
NOTE: While the
org-roam-ui-mode
is enabled, the websocket should interpret changes to ourorg-roam
nodes and update the UI in real-time.
I use Fountain mode as my main screenwriting program in EMACS. For more information about Fountain mode, visit https://fountain.io.
Make sure to refer to the documentation regarding syntax formatting for .fountain files.
To install:
- Clone the Fountain mode files into the
~/packages
directory.git clone git@github.com:rnkn/fountain-mode.git ~/packages/fountain-mode
- Add the following line to
~/.config/doom/config.el
:(require 'fountain-mode)
- Restart EMACS.
- Test the file by opening a
.fountain
file.You can download a sample
.fountain
file with the following command:wget https://fountain.io/_downloads/Big-Fish.fountain
I use vterm
as a terminal emulation application within DOOM.
This can be installed pretty easily by uncommenting the vterm
line in init.el
and running doom sync
, however if you attempt to use vterm
(M-x vterm
) you’ll be asked to compile the application first.
Note: If you encounter an error advising you to install libtools, you can run the following command in the terminal.
# Install libtools-bin and cmake sudo apt-get install libtool-bin cmake
I’ve added a hook into init.el
to append the application to the window-setup-hook
variable so it starts when DOOM launches.
;; Hook to start vterm automatically during DOOM startup
(add-hook 'window-setup-hook #'vterm 'append)
I use treemacs
to add a drawer-style UI to DOOM, similar to VS Code.
This can be installed by uncommenting the treemacs
line in init.el
and running doom sync
.
You can open the treemacs
drawer by using M-X treemacs
.
By default, I prefer treemacs
to open my documents
folder in the workspace.
To do this, press M-x treemacs-edit-workspaces
. We can add multiple projects/folders to the workspace by putting the definition ~/documents
under the perspectives heading.
By default, typically the icons in treemacs
cannot be found, resulting in malformed icons.
To allow the correct icons to show, we must do the following:
- Download and extract a
nerd-font
from the official nerd-font website. I use theJetBrainsMono
font.# Download the JetBrainsMono font wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.1.1/JetBrainsMono.zip # Unzip the font files into a subdirectory called JetBrainsMono unzip JetBrainsMono.zip -d ./JetBrainsMono
- Install the fonts.
If using Windows or WSL, right-click and select install. You will also need to use the
windows terminal
settings to update the fonts. PressingCTRL+COMMA
in Windows 11 will open the settings menu, otherwise click on the icon in the top-left of the terminal window and select preferences. Update thefont-face
setting found in the left-hand menuProfiles > Defaults
then clickappearance
.If using Ubuntu, fonts are typically installed in the user font directory under
~/.local/share
. To do this, we must perform the following:# Create the directory under /.local/share mkdir ~/.local/share/fonts # Move the fonts into the new directory above mv /path/to/extracted/fonts/* ~/.local/share/fonts/
- Install the fonts into DOOM using
M-x install-package nerd-icons
- Update
config.el
with the following:;; Use the nerd-icons package (require 'nerd-icons) ;; Install the package into treemacs (use-package treemacs-nerd-icons :config (treemacs-load-theme "nerd-icons")) ;; Configure treemacs to use a specific nerd-font (setq nerd-icons-font-family "JetBrainsMono Nerd Font Mono")
- If the icons are still not showing, the icons have not been installed correctly in step 2. You may need to run
M-x nerd-icons-install-fonts RET
to ensure that these are installed into the correct directory, particularly if using thewslg
GUI version ofemacs
.
Documentation for treemacs can be found in the offcial github page.
You can open a hydra containing option keys you can use from within the treemacs
drawer. You need to change to the treemacs
window and use the ?
key.
I’ve added a hook into init.el
to append the application to the window-setup-hook
variable so it starts when DOOM launches.
;; Hook to start treemacs automatically during DOOM startup
(add-hook 'window-setup-hook #'treemacs 'append)
The next sections under this heading will step through the applications I use to set up an IDE within DOOM.
lsp
is a standardized protocol developed by Microsoft that acts as an interpreter in an IDE and communicates with language servers to provide “language intelligence tools”, such as intelli-sense.
You need to install LSP into DOOM as well as a language server for the particular language you want to provide support for.
It comes out-of-the-box with DOOM and can be installed by uncommenting :tools lsp
in your init.el
.
You also need to indicate which languages should use lsp
. You can do this by updating the specific language in init.el
under :lang
, as per the following examples:
(go +lsp)
(python +lsp)
If your language isn’t supported (I.E doesn’t contain a +lsp
entry in init.el
), then you can add a hook in either config.el
or init.el
.
(add-hook 'MAJOR-MODE-local-vars-hook #'lsp!)
Note: Change
MAJOR-MODE
to the major-mode you’re targeting, E.G -typescript-mode
.
Make sure to run doom sync
after these changes.
You’ll need to install the server package globally on your system. I use typescript
, so I install the typescript-language-server
through npm
.
# Install typescript-language-server
npm install -g typescript-language-server typescript
Then, I can install the appropriate language server in DOOM using M-x lsp-install-server
.
Using typescript
as an example, use ts-ls
(Typescript Language Server) for supporting ts
code - M-x lsp-install-server RET ts-ls RET
.
DOOM will then ask you to select the project directory that the language server should import. Usually the project directory for typescript
projects is the directory that contains tsconfig.json
.
You can install the YAML language server by using npm
:
# Install yaml-language-server
npm install -g yaml-language-server
Then run the Emacs installer with: M-x lsp-install-server RET yamlls RET
.
Install `terraform-mode` from MELPA:
M-x package-install terraform-mode
Install `terraform-ls` language server from the HashiCorp repository:
# Download the HashiCorp signing key to the apt keyring
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
# Verify the key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
# Add the HashiCorp apt repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
# Update the repository and install terraform-ls from apt
sudo apt update && sudo apt install terraform-ls
Add the package hook for `terraform-mode` to start when `.tf` files are opened into `config.el`:
(use-package terraform-mode
:mode ("\\.tf\\'" "\\.tfvars\\'")
:hook (terraform-mode . lsp-deferred)
:config
(setq terraform-indent-level 2))
Similar to lsp
, dap
is a standardised protocol developed by Microsoft that integrates our IDE client (DOOM) with debug servers to provide debugging capabilities to IDE’s.
We need to install dap
into DOOM and also configure it to integrate with our specific language debug servers (I.E typescript
, C#
, etc).
To use DAP, we must install dap-mode
.
Note: It’s possible to use MELPA
M-x package-install
to installdap-mode
, however I had issues with configuring this so have opted to use DOOM’s package management system.
Insert the following code into your packages.el
file.
(package! dap-mode)
Insert the following code into your config.el
file.
(use-package! dap-mode)
Run doom sync
to ensure installation.
Below is the basic configuration to use in config.el
to configure dap-mode
for use with typescript
files by:
- Enabling
typescript-mode
major mode when a.ts
buffer is created. - Starting
lsp
when enteringtypsescript-mode
. - Starting and configuring
dap-node
fortypescript
debugging when enteringtypescript-mode
.
(use-package! typescript-mode
:mode "\\.ts\\'"
:hook (typescript-mode lsp-deferred)
:config
(setq typescript-indent-level 2)
(require 'dap-node)
(dap-node-setup))
By default, I run DOOMEMACS in evil
mode, which means that keybinds in EMACS emulate vi/vim
keybinds.
M-
is short for “Meta key” and is bound toALT
C-
is short for “Control key” and is bound toCTRL
S-
is short for “Super key” and is bound toSHIFT
SPC-
is known as a “Leader” key:- In evil-mode this is bound to
SPC
.
Note: You must change to evil-mode to use the leader key in DOOMEMACS.
Note:
SPC-u
is bound touniversal-argument
which is used to input into many commands, such as shrinking/enlarging windows/buffers. The traditional equivalent in EMACS isC-u
.- In evil-mode this is bound to
I’ve created custom keybindings for certain actions in DOOM that make life a little easier.
This lsp
function describes a keyword/symbol/function/etc in DOOM by pulling up the documentation of it from the official SDK.
I’ve mapped this keybinding as follows:
(global-set-key "\C-c d" lsp-describe-thing-at-point)
Note: This only works when
lsp-mode
is enabled, I.E when we’re in an IDE file (E.G - .ts, .py, etc).
Rationale: Some of the descriptions in documentation (such as SPC-h
or M-x
) for DOOMEMACS are intended for SPACEMACS users rather than vi/vim
users, which can be confusing as simple keybinds for killing/yanking etc are different from what is stated.
Easiest way to disable evil-mode is to toggle is with: M-x evil-mode
Note: This will only toggle evil-mode for the current EMACS session. To disable evil-mode permanently, refer to the StackExchange forum post: https://emacs.stackexchange.com/questions/53319/how-to-disable-evil-mode-everywhere
Insert a URL with C-c C-l
and paste in the URL.
I prefer using the mark
functionality to select text. You can set a mark by using C-SPC
, then use your left/right keys to highlight the text you want to manipulate.
Then, press any of the operations as per below to operate over the text accordingly.
- Copy:
y
- Cut:
d
- Paste:
P
orS-p
- Copy:
M-w
- Cut:
C-w
- Paste:
C-y
- Kill rest of line:
C-k
Rationale: When inserting a file path into the start of a code block, such as ~/documents/
, this can create formatting issues when the org file is rendered, requiring us to place an escape character beside the initial tilde.
Insert a zero width space
beside the first tilde with C-x 8 <RET> zero width space <RET>
.
- Move to the parent heading:
C-c C-u
- Move to the prev heading:
C-c C-p
- Move to the next heading:
C-c C-n
- Move to the prev heading on the same level:
C-c C-b
- Move to the next heading on the same level:
C-c C-f
- Shrink a window to fit its content:
C-x -
- Shrink or enlarge a window by an argument value (negative values shrink):
SPC-u <value> C-x ^
You can shortcut some useful EMACS commands in org-mode
by inserting certain characters and pressing keys.
You can create a code snippet in an org file by prefixing with <s
and pressing tab
.
You can create a quote block in an org file by prefixing with <q
and pressing tab
.