javascript-today

Getting Started with Doom Emacs (and Why It Beats Neovim for Me)

I’ve already written about migrating from Neovim to Doom Emacs and put together a keybindings cheat sheet for the day-to-day stuff. This post is the one I wish existed when I started: how to actually get Doom installed and running, and the handful of things — keyboard-first design chief among them, plus ediff — that make it worth the setup time even if you’re already happy with Neovim or VS Code.

Installing Doom

The whole install is three commands:

git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
~/.config/emacs/bin/doom install

doom install walks you through an interactive setup — it asks before touching your existing Emacs config, offers to install missing dependencies (ripgrep, fd, a few fonts), and generates your personal config directory at ~/.config/doom (or ~/.doom.d on older installs) with three files:

  • init.el — turns Doom modules on and off. This is a checklist, not code you write by hand.
  • config.el — your actual customizations: keybindings, hooks, theme tweaks.
  • packages.el — extra packages not bundled with a Doom module.

After editing init.el to enable the modules you want (I turned on :lang (javascript +lsp), :lang (web), :tools magit, and a few others), run:

~/.config/emacs/bin/doom sync

doom sync installs/removes packages to match init.el and rebuilds byte-compiled caches. This is the command you run after any config change — it’s Doom’s equivalent of :Lazy sync, except it’s deterministic: the same init.el produces the same package set every time, because Doom pins versions in a lockfile rather than floating on whatever HEAD happens to be.

Add ~/.config/emacs/bin to your PATH and you get the doom CLI everywhere, which matters for the next section.

Why keyboard focus is the actual selling point

Every modal editor claims to be keyboard-first. What sets Doom apart isn’t evil-mode itself (Neovim obviously has that covered) — it’s that everything, not just text editing, lives behind the same leader-key grammar.

In Neovim, once you leave the buffer — file tree, git UI, LSP peek windows, a plugin’s custom UI — you’re at the mercy of whatever bindings that plugin’s author picked, and they rarely agree with each other. In Doom, SPC is the single entry point into everything: files (SPC f), buffers (SPC b), windows (SPC w), git (SPC g), project actions (SPC p), code actions (SPC c), search (SPC s). Every plugin that’s part of Doom is expected to register itself under this namespace, and which-key pops up automatically to show you the next valid keys the moment you pause on a prefix. You’re never more than a half-second hesitation away from discovering a command you didn’t know existed, without leaving the home row and without alt-tabbing to a keybindings page in a browser.

The practical effect: my hands never leave home row to reach for a mouse, and they also never have to context-switch into “which plugin’s convention am I in right now” mode. A file tree, a git status buffer, and a terminal all feel like the same editor, because they use the same keys for the same verbs (stage, open, close, search). That consistency is the thing Neovim’s more decentralized plugin ecosystem structurally can’t guarantee — every plugin is a separate author’s opinion about keybindings, and Doom’s curation is what turns forty separate opinions into one coherent one.

ediff: the feature that sold me

This is the one thing I didn’t expect to become indispensable: ediff, Emacs’s built-in structural diff and merge tool. It isn’t a Doom leader-key binding — it lives inside Magit itself. From a Magit status or diff buffer, e runs magit-ediff-dwim, which guesses whether you want to compare or resolve based on what’s at point; E opens the full Ediff transient if you want to be explicit (E r to compare two revisions/branches, E m/E M to resolve a conflict).

What makes ediff different from a normal side-by-side diff view (in VS Code, Neovim’s diffsplit, or GitHub’s web UI) is that it’s navigable and actionable rather than just visual. It computes a fine-grained, hunk-by-hunk alignment between two buffers — not line-by-line like a naive diff, but a semantic pairing of changed regions — and drops you into a control panel (a tiny dedicated window, not a modal) with single-key commands:

Key Action
n / p Jump to the next/previous diff hunk
a Copy hunk from buffer A into buffer B
b Copy hunk from buffer B into buffer A
! Auto-diff refresh after edits
q Quit ediff

Because it’s a real Emacs buffer and not a special-purpose UI bolted onto the editor, everything else still works while you’re in it — you can search, run macros, jump to a hunk and then just start editing normally, then hit ! to recompute the diff live. That’s the part I haven’t found an equivalent for elsewhere: most diff tools force you to leave “diff mode” to make an edit and re-diff; ediff treats editing as part of diffing.

Where I use it constantly:

  • Merge conflicts — on a conflicted file in Magit’s status buffer, E m (magit-ediff-resolve-rest, keeps any manual resolutions you’ve already made) or E M (magit-ediff-resolve-all, reconstructs the file from both sides plus the merge base) opens a multi-buffer conflict view, which is dramatically clearer than reading <<<<<<</=======/>>>>>>> markers by eye.
  • Comparing branchesE r (magit-ediff-compare) diffs two revisions or branches without needing a full checkout; it shows you the changed files and lets you ediff each one.
  • Reviewing a big refactor before committing — rather than scrolling a unified diff in Magit, ediff’s side-by-side hunk navigation makes it much faster to confirm “yes, this refactor only touched what I think it touched.”

Neither Neovim’s diffsplit/vimdiff nor VS Code’s built-in diff viewer offer that same merge-base-aware conflict view or the “keep editing while diffing” workflow. Plugins get close (diffview.nvim is genuinely good), but ediff is decades-old, extremely stable, and just there the moment you enable :tools magit — no extra plugin, no extra config.

The plugins that actually matter day-to-day

Doom ships a curated set of modules, and you opt into them by uncommenting lines in init.el. A few are worth calling out specifically (I go much deeper on keybindings for these in the cheat sheet post):

  • Magit (:tools magit) — the git interface, and it’s not why I went looking at Emacs in the first place, but it’s the plugin that ended up surprising me the most once I was here. A full interactive status buffer where you stage individual lines (not just hunks), write commit messages with real syntax highlighting, and never leave the keyboard. I didn’t expect a git UI to change how I work, but I genuinely reach for the terminal for git less than I used to. SPC g g opens it from anywhere.
  • Projectile (bundled by default) — project-aware file search, project switching, and “run tests for this project” commands, all scoped automatically to whatever git repo you’re in.
  • Vertico/Consult (bundled by default) — the fuzzy-finder layer behind SPC SPC (find file), SPC s p (project grep), and SPC s b (search open buffers). Feels like Telescope split into a few focused, composable commands instead of one monolithic UI.
  • lsp-mode or eglot (via :lang modules, e.g. :lang (javascript +lsp)) — standard LSP support: SPC c d to jump to definition, SPC c a for code actions, SPC c r to rename. If you’ve used any modern editor’s LSP integration, this will feel immediately familiar.
  • vterm (:term vterm) — a real terminal emulator inside an Emacs buffer, backed by libvterm rather than an Emacs-native reimplementation, so it’s fast enough for full-screen TUI apps.
  • Treemacs (:ui treemacs) — a file-tree sidebar if you want one, toggled with SPC o p, though projectile + fuzzy-find covers most of what I used a sidebar for in Neovim.

Where to go from here

Once doom sync finishes and you’re staring at a mostly-empty frame, the single most useful habit to build is: when in doubt, hit SPC and wait. which-key will show you the next step every time, and within a week or two you stop needing it because the muscle memory under each prefix key is genuinely consistent across the whole editor — which, going back to the keyboard-focus point, is the whole reason this setup was worth switching to in the first place.