- Navigating between buffers
- Navigating in a buffer
- Editing
- Snippets
- Magit
- Language servers
- Org mode
- Dired (file navigation)
- Miscellaneous
- Things I am not using (yet)
After many years of manually assembling an Emacs configuration from scratch, I eventually switched to Doom Emacs a couple of years ago, discovering in the process that Emacs could do a lot more things than I thought, in a more user-friendly way.
Default Doom keybindings are organized in logical groups (SPC s for search, SPC b for buffers, SPC w for windows, SPC p for projects, SPC f for files…), and pausing after a partial combination will show an overlay with available options. However, the sheer amount of commands and bindings makes discovering the most useful ones somewhat difficult for newcomers.

Keybindings starting with SPC, as shown by pressing SPC and pausing.
Accordingly, this page contains hints about useful commands, bindings and configuration snippets for Doom, targeted towards “the colleague I just convinced to give Emacs a try”.
Of course, see also the official Doom documentation, which you can read directly in Emacs with SPC h d h.
Note: Parts of the following assumes that you have enabled the required features in your init.el and ran doom sync. Compared to the default configuration, you likely want to enable :tools lsp, (format +onsave), and the :language that you use. Configuration changes go to config.el.
Navigating between buffers
Opening files (SPC f)
SPC SPC: Open a project file (fuzzy search respecting.gitignore). UseSPC p ito invalidate the cache.
SPC f f: Open an arbitrary file.SPC f r: Open a recent file.- Bookmarks: Go to a bookmark with
SPC ENTER; create one withSPC b mfollowed bybookmarks-saveto persist to disk.
Searching inside files (SPC s)
SPC s p: Search inside files from the current project.
SPC s B: Search inside all open buffers.
Switching between buffers (SPC b)
SPC b l: Swap with the last buffer.SPC b b: Switch with another workspace buffer.SPB b B: Switch with an arbitrary other buffer (including system buffers).SPB b d: Kill the current buffer.
Windows
- Split your window vertically with
SPC w v, horizontally withSPC w s. Move between windows withSPC wandhijk. Adding the following bindings as an alternative can also be convenient, to move with a 2-key chord:(define-key evil-normal-state-map (kbd "C-h") 'evil-window-left) (define-key evil-normal-state-map (kbd "C-j") 'evil-window-down) (define-key evil-normal-state-map (kbd "C-k") 'evil-window-up) (define-key evil-normal-state-map (kbd "C-l") 'evil-window-right) SPC w d: Delete a window.- Kill temporary buffers with
qorESC(depending on the source).
Navigating in a buffer
Whole buffer
Use / to search, n/N to navigate the matches forwards/backwards after pressing ENTER.

As a variant to /, SPC s s shows all the matches in an overlay, and highlights them in the main buffer .
Avy allows quickly jumping to a match from a keyboard-based decision tree (centered around your home row).

g s s(avy-goto-char-2): Type two letters, and pick your match by typing the two-letter identifier of your match.g s /(avy-goto-char-timer): Enter a search query, pause, and select the matches to jump to.g s jandg s kto jump to a line.- The
g sprefix allows jumping to many more objects. Type the prefix and see.
Line
In addition to the usual 1-character motions (find f/F and to t/T), evil-snipe provides line motions with 2-character search: press s (or S for backwards) and type the first two characters. Navigate multiple matches with s/S again. In operator mode, this becomes z/Z and x/X.

Typing s i from the start of the line. Typing t will then jump to the middle it.
This only works for the current line. See the previous section for navigating across lines.
Editing
Enable finer undo in
evilwith(setq evil-want-fine-undo t)Otherwise, undo/redo only applies to sequences of operations in insert mode. In any case, use
uto undo andC-Rto redo.Company suggestions might trigger a bit too quickly. The delay can be increased with
(setq company-idle-delay 0.5)Interactively select and paste text previously cut with
SPC i y.hl-todohighlights keywords such asTODO,NOTE,FIXME, etc. More can be added, e.g.(after! hl-todo (add-to-list 'hl-todo-keyword-faces '("WARN" error bold)) )Display column indicator at 100 characters. Formatters usually enforce a column limit, but not on comments.
(setq-default display-fill-column-indicator-column 100) (setq-default fill-column 100)A very handy feature is the ability to batch edit search results by turning the buffer into edit mode:
- Search for an expression with
SPC s p. C-c C-eturns the search result buffer in edit mode.- Edit the buffer.
- Apply the changes with
C-x C-s.
- Search for an expression with
To insert something at the start or end of every line, one can of course use search and replace with a regex (
: s). Themc/edit-linesandmc/edit-end-of-linescommands from themultiple-cursorpackages are faster to use and more powerful.
evil-mode editing tricks
By default, Doom emacs uses vim bindings with evil-mode. Get familiar with keys such as y, d, g, :, the modes (command, insert, visual, visual block), etc. See also the documentation.
Rather than pressing
ESC, one can move from insert to normal mode by quickly pressingjk(which is a rare enough combination of letters in natural language).From insert mode, saving and returning to normal mode is used so often that it deserves a key binding, for example
SPC j:(map! :leader (:desc "Save" :n "j" (lambda () (interactive) (evil-normal-state) (save-buffer))) )In visual mode, text can be rewrapped with
g wto the desired column length.Comment or uncomment blocks with
g c c.Join lines with
J.Text can be indented with
>and<in visual mode, or withindent-rigidly(C-x TAB). Inpython-mode, the following might be more convenient, given that the language is indentation-based:(after! python (define-key python-mode-map (kbd "<tab>") 'python-indent-shift-right) (define-key python-mode-map (kbd "<backtab>") 'python-indent-shift-left) )The following macro (
SPC l) comes relatively handy to edit the contents inside brackets on a new line.(map! :leader :n "l" (kmacro "i <return> C-S-<return>") )evil-surround(available by default) is very handy to wrap text in brackets, e.g.y s w (will bracketize the word,d s wde-bracketize it;S (will surround a visual selection in brackets.
Flycheck
Flycheck provides syntax checking (with a very generic meaning: from code to natural language), supporting plethora of backends.
Enable flycheck in all buffers with
(global-flycheck-mode)Run
M-x flycheck-describe-checkersto list the checkers available and/or configured in a given mode.Map
SPC eandSPC Eto view flycheck (one buffer) and LSP (project) errors. They can be navigated withC-jandC-kwithout leaving the main buffer.(map! :leader (:desc "Errors" :n "e" 'flycheck-list-errors) (:desc "Errors (All)" :n "E" 'lsp-ui-flycheck-list) )Shameless self-promotion: get offline grammar and spellchecking with
ltapiserv-rsandflycheck-languagetool: see here.
Formatters
Doom uses apheleia to efficiently format-on-save code from a wide variety of languages.
Formatters can be added or modified easily:
(after! format
; Edit an existing formatter
(set-formatter! 'black '("blackd-client" "--line-length" "119"))
; Add a new formatter
(set-formatter! 'typstfmt '("typstyle" "-c=100"))
(setq-hook! 'typst-ts-mode-hook +format-with 'typstfmt)
)
Snippets
Yasnippet is a snippet templating engine, which help avoid repeatedly typing boilerplate. In any mode, call M-x yas-describe-tables to see the available snippets; Doom ships with a relatively large library.

Yasnippet tables in rust-mode
Snippets are usually activated with a prefix and TAB. For many snippets, pressing TAB after the snippet activates also allows jumping between the different fields. For example, the function snippet in rust-mode (activated with fn) is defined as:
fn ${1:function_name}($2) ${3:-> ${4:i32} }{
$0
}
Upon typing fn TAB, the user will be allowed to enter the function name, arguments, optional return type, and finally body.
New snippets can be created with M-x yas/new-snippet.
Enable yasnippet in all modes with
(yas-global-mode)
in your config.el.
Magit
A major selling point of Emacs in general is the presence of Magit, the text user interface for Git.
Main screen
The main screen is opened with SPC g g. The ? key lists available comments.

Some of the most useful commands are:
rfor rebasing.r iallows picking a commit from which to interactively rebase.bto checkout branches:b sto create a branch based on the current one (“spinoff”),b bto checkout a branch.lto access thegitlog and reflog. References to commits can be copied withy b.Oto perform resets.
Hunks can be staged or unstaged with s and u, which also works on sub-hunks by selecting regions and using the same keys. This is an extremely powerful feature to split complex changes into smaller commits.
The stashes can be opened with ENTER. They can be applied totally with Z p, or selectively with a on hunks or sub-hunks or interest after opening the stash.
From a commit, it is possible to open the old version of a file by pressing ENTER on one of the old (red) hunks.
Use x to untrack a file or remove an untracked file (magit never deletes files, it puts them in the trash).
Buffers
SPC g L: View git logs.SPC g B: Git blame.SPC g /: Access general commands.
The :ui vc-gutter displays the version control status of sections.
Extensions
- Stacked git support is provided by magit-stgit.
git-linkallows to copy links to code regions, for sharing (e.g. link to file on GitHub at the given commit, with the region highlighted).
Language servers
Language servers have become the standard for providing language support across code editors. The lsp-mode supports all current features and many servers/languages. In many cases, it is able to install the server by itself, if desired.
The most useful commands are:
g d: Go to symbol definition.g D: Go to symbol references.SPC c r: Rename symbol. One might have to doSPC p sto save project buffers.SPC c a: Code actions.lsp-ui-imenu: Display buffer symbols and navigate.
The following options are useful. They set a response timeout, enable inlay hints, but disable inline documentations, which may be found intrusive and not extremely helpful.
(setq lsp-inlay-hints-mode t)
(setq lsp-inlay-hint-enable t)
(setq lsp-ui-doc-enable nil)
(setq lsp-signature-render-documentation nil)
(setq lsp-response-timeout 30)
(setq lsp-modeline-code-action-fallback-icon "")
When something goes wrong, SPC b B will allow accessing the lsp-err message with the stderr of the server.
lsp-booster
Even though Emacs now has native JSON parsing, lsp-booster can speed up processing and avoid UI locks, by offloading decoding to a separate Rust binary. To install it with doom:
- Install
lsp-booster:$ cargo install --git https://github.com/blahgeek/emacs-lsp-booste - Add
(setenv "LSP_USE_PLISTS" "1")at the end of yourinit.el. - Add this configuration to your
config.el. - Remove
.config/emacs/.local/straight/*/lsp-mode. - Run
doom sync.
Rust and rust-analyzer
The de facto standard language server for Rust is rust analyzer. My configuration reads as follows:
(setq lsp-rust-analyzer-cargo-override-command ["/home/cpg/check.nu"])
(setq rustic-cargo-check-arguments "--workspace")
(setq lsp-rust-analyzer-diagnostics-disabled ["unresolved-proc-macro" "unresolved-macro-call"])
; Types can get really long (e.g. with axum)
(setq lsp-rust-analyzer-max-inlay-hint-length 20)
(setq lsp-rust-analyzer-server-display-inlay-hints t)
(setq lsp-rust-analyzer-proc-macro-enable nil)
(setq lsp-rust-analyzer-experimental-proc-attr-macros nil)
(setq lsp-rust-analyzer-cargo-run-build-scripts nil)
(setq lsp-rust-all-features t)
(setq lsp-rust-analyzer-cargo-all-targets nil)
The first line is a hack to avoid the entire workspace being cargo check’ed whenever a workspace member changes. The nushell script simply wraps cargo check and passes the package of interest from a YAM list. This does not seem to be possible with the current options; an alternative is to open only one package in the LSP.
# - package1
- package2
#!/usr/bin/env nu
def main [] {
mut package = "--workspace"
$package = (cat ~/ra-check-mode.yaml | from yaml | first | str trim);
$package = $"-p=($package)"
cargo check --tests --all-features --message-format=json $package
}
Org mode
Next to Magit, Org mode can certainly be considered another killer feature of Emacs. Text-based productivity tools like Obsidian or Logseq have actually seen a rise in popularity.
According to the homepage, Org mode is
[a] major mode for keeping notes, authoring documents, computational notebooks, literate programming, maintaining to-do lists, planning projects, and more — in a fast and effective plain text system.
Starting with Org mode is very simple: write your note in plain text and let the mode take care of the formatting:

- Use
*,**, etc for headers.M-LeftandM-Rightto increase or decrease the level. They get displayed as bullets. Shift+LeftandShift+Rightto cycle the status of an item (TODO/DONE, etc.).SPC m t tto choose the status among a list.
TABto fold or unfold sections.- The code blocks are highlighted. They can actually be executed.
- Checkboxes:
C-cto change the status of a checkbox.- Write
[%]or[/]in the header of a list and pressC-cto automatically set the progress.
- Tables:
TABto auto-format (as inmarkdown-mode).M-and arrows to move columns and rows.S-Mand arrows to add or remove columns and rows.- The tables can actually act as spreadsheets!
- Export in many formats using
SPC m e - Set deadlines and schedules with
SPC m d. Insert dates withC-c .. - To refile tasks (in other sections, files, etc.), use
SPC m r.
See https://orgmode.org/features.html for a more complete overview of features, and then the manual (or its compact version) or the work knowledge base.
Dired (file navigation)
Dired is a file explorer integrated in emacs. Open it by pressing ENTER on the first line of SPC f f, after navigating to the folder of your choice. Alternatively, use C-x d.

A dired buffer. Sorry, my fr_CH time locale shows up.
Some useful commands:
Rto rename a file.Dto delete a file.-to move one level up in the folder hierarchy.ENTERto open a file
Given that the listings are regular Emacs buffers (after all, everything is a buffer), the usual search/navigation commands.
Similarly to the edition of search results, one can actually turn a Dired buffer into write mode to batch rename files, removing the need for tools like rename:
C-c C-e.- Edit files.
C-x C-sto save.
Miscellaneous
- Besides typing bindings prefixes (e.g.
SPC) and waiting for the options to appear, another way to discover commands is to perform a search withM-x. Bindings are conveniently displayed
- For instantaneous opening of new windows, it is recommended to run emacs as a server, to which new instances will connect:
Then start emacs with$ systemctl --user enable --now emacsemacsclient -c. For example, myi3configuration has:
However, one needs to remember to runbindsym $mod+e exec emacsclient -csystemctl --user restart emacsafterdoom sync. - Use
pretty-hydrato define custom keybinding groups, accessible through a graphical menu. - Colours in certain modes are sometimes unfortunate. For example, the Markdown code block delimiters with the Nord theme are almost invisible. This can be fixed with
custom-set-faces:(custom-set-faces! `(markdown-markup-face :foreground ,(doom-color 'white)) ) doom syncwill capture the environment variables visible to your shell (minus exceptions for sensitive variables) and ensure emacs uses these. This is important when emacs is spawning new processes.
Things I am not using (yet)
- Terminals inside Emacs, e.g.
vterm. For stability, usability and performance reasons, I prefer having wezterm open on the side. - Email, with
wanderlust,notmuchormu4e. Unfortunately, modern email (HTML messages, Gmail idiosyncrasies) is very difficult to use outside a client embedding an almost fully-featured web browser. - Actually properly learning
elisp.