- 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 i
to 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 m
followed bybookmarks-save
to 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 w
andhijk
. 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
q
orESC
(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 j
andg s k
to jump to a line.- The
g s
prefix 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
evil
with(setq evil-want-fine-undo t)
Otherwise, undo/redo only applies to sequences of operations in insert mode. In any case, use
u
to undo andC-R
to 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-todo
highlights 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-e
turns 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-lines
andmc/edit-end-of-lines
commands from themultiple-cursor
packages 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 w
to 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 w
de-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-checkers
to list the checkers available and/or configured in a given mode.Map
SPC e
andSPC E
to view flycheck (one buffer) and LSP (project) errors. They can be navigated withC-j
andC-k
without 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-rs
andflycheck-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:
r
for rebasing.r i
allows picking a commit from which to interactively rebase.b
to checkout branches:b s
to create a branch based on the current one (“spinoff”),b b
to checkout a branch.l
to access thegit
log and reflog. References to commits can be copied withy b
.O
to 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-link
allows 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 s
to 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-Left
andM-Right
to increase or decrease the level. They get displayed as bullets. Shift+Left
andShift+Right
to cycle the status of an item (TODO
/DONE
, etc.).SPC m t t
to choose the status among a list.TAB
to fold or unfold sections.- The code blocks are highlighted. They can actually be executed.
- Checkboxes:
C-c
to change the status of a checkbox.- Write
[%]
or[/]
in the header of a list and pressC-c
to automatically set the progress.
- Tables:
TAB
to auto-format (as inmarkdown-mode
).M-
and arrows to move columns and rows.S-M
and 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:
R
to rename a file.D
to delete a file.-
to move one level up in the folder hierarchy.ENTER
to 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-s
to 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 emacs
emacsclient -c
. For example, myi3
configuration has:
However, one needs to remember to runbindsym $mod+e exec emacsclient -c
systemctl --user restart emacs
afterdoom sync
. - Use
pretty-hydra
to 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 sync
will 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
,notmuch
ormu4e
. 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
.