javascript-today

Doom Emacs common default keybindings

A categorized cheat sheet of commonly used Doom Emacs keybindings, written as a companion to my Neovim-to-Doom migration post. All sequences use the default SPC leader in evil mode unless noted otherwise.

Doom splits what Telescope used to do as one tool across a few focused commands, mostly under the SPC s (“search”) prefix, backed by consult and vertico.

Keybinding Action
SPC s s Search (fuzzy) inside the current buffer
SPC s S Search current buffer, pre-filled with symbol at point
SPC s b Search across all open buffers
SPC s p Search (grep) across the whole project
SPC s P Search across a different (non-current) project
SPC * or SPC s * Search project for the symbol/word at point
SPC s d Search a specific directory
SPC s i Jump to a symbol (function, variable) in the current buffer (imenu)
SPC s I Jump to a symbol across the whole project
SPC s r Resume the last search session
SPC / Shorthand alias for project-wide search
SPC SPC Fuzzy find a file by name in the current project
SPC . Fuzzy find a file relative to the current directory

A few notes on regex vs. literal search: project search (SPC s p) is backed by ripgrep, which treats your query as a regex-capable pattern by default — no special mode-switch needed the way Telescope sometimes required. Inside the search minibuffer, you can keep typing to narrow results live, and C-n / C-p (or j / k if you’re in evil-collection’s minibuffer bindings) move between matches before you hit RET to jump.

Search and replace

Because evil mode emulates real vim, the classic :s substitute command works exactly like it did in Neovim — no new syntax to learn here, just a different set of tools for the project-wide case.

Single line (cursor’s current line only):

Command Action
:s/old/new/ Replace the first match on the current line
:s/old/new/g Replace all matches on the current line
/old then cgn Jump to the next match of old and change it; repeat with . to step through remaining matches one at a time, reviewing each

Whole buffer:

Command Action
:%s/old/new/g Replace every match in the buffer
:%s/old/new/gc Replace every match in the buffer, prompting y/n/a/q/l to confirm each one
Visual-select lines, then : Auto-fills :'<,'>s/old/new/g, scoping the replace to just the selected region

Evil’s :s uses vim-flavored regex syntax (\(...\) for groups, \d, \w, etc.), not native Emacs regex — so muscle memory from Neovim’s :%s transfers directly, which was one of the smoother parts of this whole switch.

Project-wide, where Doom departs from a single Telescope-style command and gives you two real options:

  1. Quick and bluntM-x projectile-replace for a literal string replace across the whole project, or M-x projectile-replace-regexp for a regex-based one. Projectile walks you through matches file by file with a confirmation prompt. No leader-key binding for this by default, so it’s worth binding one if you use it often.
  2. Visual and reviewable — run SPC s p to ripgrep the project, export the results into a real, editable buffer (via embark-export, typically on C-c C-e from the search results — exact key can vary by Doom version, check with SPC h k if it doesn’t fire), then flip that results buffer into wgrep-mode (C-c C-p). At that point it behaves like any other buffer: run :%s/old/new/g across it, eyeball the diff-style view, then C-c C-e to write the changes back out to every affected file, or C-c C-k to bail out without touching anything.

I lean on option 2 for anything risky — being able to see every change across every file before committing to it is a step up from a blind project-wide replace.

Windows

Window management borrows from vim’s C-w prefix but also has a full SPC w leader-based equivalent, which is what I default to.

Keybinding Action
SPC w v Split window vertically (side by side)
SPC w s Split window horizontally (stacked)
SPC w c or SPC w d Close the current window
SPC w o Close all other windows (keep only the current one)
SPC w m Toggle maximize on the current window (zoom in/out)
SPC w w Cycle focus to the next window
SPC w h/j/k/l Move focus left / down / up / right
SPC w H/J/K/L Move the window itself (swap position) left / down / up / right
SPC w = Balance all windows to equal size
SPC w r Rotate windows forward
SPC w R Rotate windows backward
SPC w u Undo the last window layout change
SPC w U Redo a window layout change
SPC 1SPC 9 Jump directly to window number N

The vim-native C-w versions (C-w v, C-w s, C-w c, C-w =, C-w o) still work if that’s what your fingers already know — Doom doesn’t take those away.

Git

Doom’s git interface is Magit, which is one of the bigger quality-of-life upgrades coming from a terminal-git-plus-fugitive workflow. Rather than a thin keybinding wrapper, Magit gives you a full interactive status buffer: you stage and unstage individual hunks or even individual lines, write commit messages in a dedicated buffer with syntax highlighting, and see diffs inline without leaving the window. It’s discoverable — pressing ? almost anywhere in Magit shows you every available action in that context, which made it easy to pick up without memorizing everything up front.

The general workflow I use daily: open the status buffer, stage the hunks I want with s, commit with c c, and push with P p. Everything happens without touching a separate terminal.

Keybinding Action
SPC g g Open Magit status (the main git dashboard)
SPC g b Show git blame for the current line/region
SPC g B Open the current line/file on the remote (GitHub, etc.) in your browser
SPC g L Open the Magit log for the current buffer/project
SPC g f Find/open a file as it exists in a git revision
SPC g t Open git-timemachine to step through a file’s history
SPC g C Clone a repository
SPC g F Fetch from remote

Inside the Magit status buffer itself (not leader-key bindings, just single keys):

Key Action
s Stage the hunk/file at point
u Unstage the hunk/file at point
c c Start a commit
P p Push to the current branch’s upstream
F p Pull from upstream
b b Checkout a branch
RET Visit the thing at point (file, commit, hunk)
g r Refresh the status buffer
q Quit back out of Magit

Terminal

Keybinding Action
SPC o t Toggle a popup terminal (vterm) at the project root
SPC o T Open a dedicated (non-popup) terminal buffer

vterm behaves a bit differently than a typical Emacs buffer: while you’re in insert state, most keystrokes pass straight through to the underlying shell, since it’s a real terminal emulation rather than a text buffer. Drop into evil’s normal state (ESC) to scroll back, visually select, or yank terminal output the way you would in a normal buffer.

Buffers & Files

Keybinding Action
SPC b b Switch to another open buffer
SPC b d Kill (close) the current buffer
SPC b n Go to the next buffer
SPC b p Go to the previous buffer
] b Next buffer (Doom’s “bracket” navigation convention)
[ b Previous buffer
SPC f f Find/open a file
SPC f s Save the current file
SPC f r Open a recently-opened file
SPC f y Copy the current file’s path to the clipboard

Doom’s bracket convention ([ x / ] x) shows up in a few other places too — ] e / [ e for next/previous error, ] t / [ t for tabs, and so on. Once you notice the pattern it applies more broadly than it first appears.

Code Navigation & LSP

Keybinding Action
SPC c d Jump to definition
SPC c D Jump to references
SPC c r Rename symbol
SPC c a Show code actions
SPC c f Format the buffer/region
K Show documentation/hover info for symbol at point
SPC h k Describe a keybinding (what does this key actually do?)

Optional but commonly used plugins

Doom ships with a lot enabled out of the box (projectile, which-key, company, doom-modeline), but these are worth knowing about since they come up often in the Doom ecosystem and are easy to enable via module flags in ~/.doom.d/init.el or as extra packages in ~/.doom.d/packages.el:

  • treemacs — a file-tree sidebar, similar to nvim-tree or neo-tree. Toggle with SPC o p.
  • org-roam — networked note-taking/zettelkasten built on top of org-mode, popular for personal knowledge management.
  • magit-todos — surfaces TODO/FIXME comments from your codebase inside Magit’s status buffer.
  • corfu — a lighter-weight alternative to the default company completion frontend, if you want something closer to a minimal popup.
  • flycheck (or flymake on newer configs) — inline linting/diagnostics, usually pulled in automatically by language modules.
  • apheleia or format-all — auto-formatting on save, handy if you’re used to conform.nvim or similar.
  • centaur-tabs — adds a visible tab bar across the top of the frame, if you miss bufferline-style tabs from Neovim.

After enabling any module or package, run doom sync and restart Emacs for it to take effect — that deliberate step is exactly the thing I appreciated after fighting silent breakage in Neovim.