Emacs vs. Neovim + LazyVim: Running Both, and Making Them Feel Like One Editor
October 19, 2026
I use two editors often: I have mostly switched to Doom Emacs but still like Neovim with LazyVim sometimes too. That sounds like a recipe for constant muscle-memory errors, but it works better than you'd expect. Both are "distros": a framework someone else maintains, with a thin personal layer on top. Both use Vim keys and a Space leader with a which-key popup. Also both allow a personal config file on-top of the already maintained config.
This post covers how the two differ, what each does well and badly, where the keybindings line up and where they don't, how I've made them feel closer, and what I plan to add to each next.
A note on scope: when I say "Emacs" I mostly mean Doom Emacs, because that's the closest Emacs equivalent to LazyVim. Comparing a bare Emacs with a fully set-up LazyVim wouldn't be fair to either.
What's actually different
On the surface, both are modal editors with a space-bar menu. Underneath, they're built on different ideas.
1. What the program is
- Emacs is a Lisp runtime that happens to include a text editor. Nearly everything is written in Emacs Lisp and can be inspected and redefined while it runs. Mail clients, file managers, terminals and Git UIs all run inside it as buffers.
- Neovim is a text editor that you can script. The core is C, plugins are written in Lua, and the design goal is to edit text fast and leave everything else to the terminal around it.
2. Config language
- Doom: Emacs Lisp.
init.elturns modules on,packages.eladds packages,config.elholds your own settings. - LazyVim: Lua.
lazyvim.jsonlists the enabled "extras",lua/plugins/*.luaadds or overrides plugins, andlua/config/*.luaholds options, keymaps and autocmds.
3. How you turn features on
- Doom uses module flags:
(python +lsp +pyright)and thendoom sync. - LazyVim uses extras: open
:LazyExtras, togglelang.python, restart.
Both approaches feel similar. The real difference is that Doom needs a separate sync step and LazyVim does not.
4. Modal editing
- Neovim is Vim, so modal editing is built in.
- Doom gets it from evil-mode (
evil +everywhere), which is a very good emulation of Vim, but still an emulation. Some packages still need extra glue code before Vim keys work in them.
5. The surrounding tooling
| Job | Neovim (LazyVim) | Doom Emacs |
|---|---|---|
| Completion | blink.cmp | corfu + orderless |
| Pickers | Telescope (+ fzf-native) | vertico + consult + marginalia |
| File tree | neo-tree | dired |
| LSP | nvim-lspconfig + Mason (installs servers for you) | lsp-mode (you install servers yourself) |
| Formatting | conform.nvim | format module (off in my config) |
| Syntax | Treesitter | Regex font-lock (tree-sitter module off) |
| Git | Neogit, diffview, lazygit, gitsigns | Magit, vc-gutter |
| Terminal | snacks terminal | vterm |
| Notes | render-markdown, markdown-preview | Org + org-roam + journal |
| Remote files | — | TRAMP |
| Motion | flash.nvim | evil-snipe + avy |
| Plugin updates | lazy.nvim checks on its own | doom upgrade / doom sync |
6. GUI vs. terminal
I run Doom in a GUI with Lilex Nerd Font, so the editor controls the font, images and variable-pitch text. Neovim lives in the terminal, so the terminal decides the font. You can run Neovim in a GUI such as Neovide, but the terminal is where it feels at home. Doom can also run in terminal with emacs -nw but its strength to me feels better in GUI.
Strengths and weaknesses
Doom Emacs
Strengths
- Org mode. There is still nothing else like it. My agenda,
TODO → NEXT → INPROG → DONEworkflow, capture templates and org-roam notes all live here. - Magit. The best Git interface I've used in any tool. Interactive rebase, editing history and blame are all quick.
- TRAMP. Open
/ssh:host:/etc/nginx.confand edit it as if it were local. - vterm. A real terminal emulator that runs as a buffer.
- You can inspect everything.
SPC h f,SPC h vandSPC h kshow you the source of any function, variable or key, and you can change it while the editor runs. - GUI quality. Proper fonts, ligatures, inline images and mixed-pitch text.
Weaknesses
- Startup and memory. Heavier than Neovim. Running Emacs as a daemon hides most of this, but not all.
- Single-threaded. A slow language server or a big Git status can freeze the whole UI.
- Elisp has a learning curve, and Doom's macros add another layer on top.
- Maintenance work.
doom sync, rebuilding the env file, and the occasional broken pin. I recently hitwhich-keypinned to a commit that only existed in an archived repo. - You install language servers by hand. Mine are mostly global npm installs, so they break when I switch Node versions under nvm.
Neovim + LazyVim
Strengths
- Speed. It starts almost instantly and stays fast on large files.
- Treesitter. Accurate highlighting, plus textobjects (
]f,]c,]a) and structural selection. - Mason. It installs language servers, formatters and linters the first time you open a file of that type.
- The extras system. One toggle gets you a complete language setup: LSP, formatter, linter and treesitter parser.
- Lua. A small, quick language that most people can read.
- At home in the terminal. Works the same over SSH, in tmux or inside WSL.
- Wider language coverage in my setup: Go, Lua, Docker, .NET, Julia, Terraform, TOML, Svelte and Vue.
Weaknesses
- No real Org.
nvim-orgmodeis good, but it isn't Org. - Git tooling is shallower. Neogit is modeled on Magit and handles everyday work, but Magit is still ahead on the harder jobs.
- No TRAMP. Remote editing means
scp://paths or extra plugins. - Plugins change fast. For example, nvim-treesitter's move to its
mainbranch broke a lot of configs. Also,:Lazy syncwill quietly remove plugins that no enabled extra uses, which is how I lost nvim-dap. - The terminal sets your font, so font tweaks live outside the editor.
- Terminal font means one font living in the terminal means no different fonts, or font sizes inside the editor.
- Mason keeps its own copies of tools in a separate directory, so you can end up with two of everything.
Side by side
| Doom Emacs | Neovim + LazyVim | |
|---|---|---|
| Startup speed | ⚠️ Slow cold start; daemon helps | ✅ Near instant |
| Memory use | ⚠️ Higher | ✅ Low |
| Modal editing | ✅ evil (excellent emulation) | ✅ Native |
| Config language | ⚠️ Emacs Lisp | ✅ Lua |
| Language setup | ⚠️ Module flags + manual server installs | ✅ Extras + Mason |
| Syntax accuracy | ⚠️ Regex (unless tree-sitter is on) | ✅ Treesitter |
| Git | ✅✅ Magit | ✅ Neogit / diffview / lazygit |
| Notes & tasks | ✅✅ Org, roam, agenda, capture | ⚠️ Markdown only (for now) |
| Remote editing | ✅ TRAMP | ❌ Not built in |
| Built-in terminal | ✅ vterm | ✅ snacks terminal |
| Introspection | ✅✅ Live, everything | ✅ Good (:help, :verbose map) |
| GUI / fonts | ✅ Full control | ⚠️ Depends on the terminal |
| Maintenance | ⚠️ doom sync, pins, env file | ✅ Mostly automatic |
| Things breaking | ⚠️ Occasional pin/upgrade breakage | ⚠️ Plugin churn |
| AI integration | possible with plugins | possible with plugins |
Same keys, mostly
The good news for anyone running both editors is that Doom and LazyVim agree on most of the important keys. Both use Vim motions, Space as leader, and a menu that groups commands by first letter (f for files, b for buffers, g for git, c for code, w for windows, q for quit). Doom's Evil mode enables the command line with : so most of these commands you'd use in nvim work out-of-the-box the same. ie (:s/somethingToChange/somethingDifferent)
These already match out of the box, or with very little work:
| Action | Doom | LazyVim |
|---|---|---|
| Find file in project | SPC SPC | <leader><space> |
| Grep project | SPC / | <leader>/ |
| Quick buffer switch | SPC , | <leader>, |
| Find / recent files | SPC f f / SPC f r | <leader>ff / <leader>fr |
| Alternate buffer | SPC ` | <leader>` |
| Kill buffer | SPC b d | <leader>bd |
| Window split / move | SPC w v, SPC w s, SPC w h/j/k/l | <leader>w… (same) |
| Code action / rename | SPC c a / SPC c r | <leader>ca / <leader>cr |
| Go to definition / docs | gd / K | gd / K |
| Comment | gc | gc |
| Quit | SPC q q | <leader>qq |
| Save & quit | :wq | :wq |
The trouble starts in the details. These are the differences out of the box, before any custom keymaps:
| Action | Doom (default) | LazyVim (default) | Notes |
|---|---|---|---|
| Save | SPC f s | <C-s> | Doom keeps the Spacemacs habit |
| Buffer list | SPC b b | <leader>fb / <leader>, | |
| M-x / command palette | SPC : | <leader>sC (commands) | LazyVim's <leader>: is command history |
| Help / describe key | SPC h k, SPC h f, SPC h v | <leader>sk, <leader>sh | LazyVim puts help under search |
| Toggle terminal | SPC o t | <C-/> | |
| File tree | SPC o p (needs a tree module) | <leader>e | |
| Switch project | SPC p p | <leader>fp (util.project extra) | |
| Toggles (wrap, numbers, spell) | SPC t … | <leader>u … | Remember t ↔ u |
| Diagnostics list | SPC c x | <leader>xx (trouble) | |
| Find references | gD | gr | gD means declaration in LazyVim |
| Search in buffer | SPC s s | <leader>sb | |
| Workspaces / tabs | SPC TAB … (persp-mode) | <leader><tab> … (Vim tabs) | Same key, different idea |
| Git status | SPC g g (Magit) | <leader>gg (lazygit) | |
| Diff file vs HEAD | — | — | Neither has a default |
| 2-char jump | s (evil-snipe), gs SPC (avy) | s (flash) | Flash labels the whole screen |
Visual-mode S | Surround (evil-surround) | Flash treesitter select | Easy to trip on |
| Surround | ys / cs / ds | gsa / gsr / gsd (mini.surround extra) | |
| Expand region | — | <C-space> (flash/treesitter) | |
| Line numbers | Absolute | Relative | |
| Notes | SPC n … (Org) | <leader>n (notification history) | Same letter, different meaning |
None of these are a big deal on their own. The problem is switching editors twenty times a day and hitting SPC f s in Neovim, or <leader>e in Emacs, every single time.
Making them feel the same
My rule: pick whichever key makes more sense and add it to the other editor. Usually that means bringing Doom's mnemonic keys to Neovim, since Neovim's keymaps are cheaper to change. Here's what I've done so far.
Looks
- Theme: catppuccin mocha in both. I also ported 13 of the doom-themes to Neovim so
<leader>uClets me preview the same set. - Line numbers: absolute in both (
vim.opt.relativenumber = false).- You can easily change either Neovim or Emacs on this but I prefer absolute as I use
:340to jump around more than20j.
- You can easily change either Neovim or Emacs on this but I prefer absolute as I use
- Diff colors: Doom's blue "added" faces, copied into a Neovim autocmd. They set only the background, so syntax colors still show.
- I do this for both editors and frankly everything as I'm slightly red/green colour blind so I like blue for "positive" and red for "negative"
Keys added to Neovim (lua/config/keymaps.lua)
| Key | Action | Doom equivalent |
|---|---|---|
<leader>fs | Save | SPC f s |
<leader>bb | Buffer picker | SPC b b |
<leader>: | Command palette | SPC : |
<leader>hh / <leader>hk | Help tags / keymaps | SPC h … |
<leader>ot | Toggle terminal | SPC o t |
<leader>op / <leader>o- | neo-tree / reveal file | SPC o p |
<leader>pf | Find file in project | SPC p f |
<leader>. | Find file under the current directory | SPC . (custom) |
C-} / C-{ | Expand / contract selection | expand-region (custom) |
I also added which-key group names for <leader>h, <leader>o and <leader>p, so the popup looks like Doom's.
One gotcha: the Telescope extra overrides <leader>: after keymaps.lua has run. The fix is to define that key in lua/plugins/telescope.lua instead.
Plugins chosen to match
- nvim-surround set up with evil-surround's keys (
ys,cs,ds, visualS). I moved flash's treesitter jump off visualSso it no longer clashes. - Neogit on
<leader>gg, so it lands where Magit does. Its keys follow Magit too:s/uto stage,c cto commit,P pto push. Lazygit moved to<leader>gG. - diffview on
<leader>gd, matching my customSPC g d(magit-diff-buffer-file) in Doom.
The same file search in both
The way I open files most often is: start from the file I'm in, type a few letters of a file name, and pick it from a fuzzy list. I don't want to step through directories, wait for a cache to refresh, or wade through node_modules. In my experience, fd is the easiest way to search for files inside a project. It's fast, it shows hidden files when asked, and it skips whatever the project's .gitignore lists. So I gave both editors the same key for this, running the same fd command.
Neither editor does this out of the box.
Doom Emacs, out of the box:
| Key | Command | What it does |
|---|---|---|
SPC . | find-file | The standard Emacs file prompt. You browse one directory at a time; it doesn't search subdirectories. |
SPC SPC | projectile-find-file | Searches the whole project from its root. Doom already makes projectile use fd (hidden files on, .git excluded, .gitignore respected), but the file list is cached, so new files can be missing until the cache refreshes. |
SPC f F | +default/find-file-under-here | Searches recursively from the current directory. This is the closest built-in, but it's on a three-key chord. |
LazyVim, out of the box:
| Key | Command | What it does |
|---|---|---|
<leader>. | Snacks.scratch() | Toggles a scratch buffer. Nothing to do with finding files. |
<leader><space> / <leader>ff | Find files (root dir) | Searches the whole project from its root. With the Telescope extra it runs rg --files (falling back to fd) with hidden files on and .git excluded. |
<leader>fF | Find files (cwd) | Same search, starting from Neovim's working directory instead of the project root. |
Neither has a short key for "search everything under the file I'm editing right now." Doom's SPC . doesn't search subdirectories, and LazyVim's <leader>. isn't a file search at all.
What I changed. In both editors, SPC . / <leader>. now runs this command from the current file's directory and passes the results to the editor's fuzzy picker:
fd --type f --hidden --strip-cwd-prefix --color never --exclude .git
--hiddenshows dotfiles such as.env.exampleand.github/workflows/*..gitignoreis respected by default, so build output and dependencies stay out of the list without a hand-maintained exclude list.--exclude .githides the.gitdirectory itself, which--hiddenwould otherwise show.- There's no cache, so a file you created a second ago shows up immediately.
In Doom (config.el), a small function runs fd and feeds the output to completing-read, so it gets the usual vertico and orderless matching:
(defun my/files-under (dir)
"List files under DIR via fd (hidden files on, .gitignore respected)."
(let ((default-directory dir))
(split-string
(shell-command-to-string
(concat doom-fd-executable
" --type f --hidden --strip-cwd-prefix --color never --exclude .git --print0"))
"\0" t)))
(defun my/find-file-under-here ()
"Fuzzy-find a file in the current directory and its subdirectories."
(interactive)
(let* ((default-directory (file-truename default-directory))
(files (my/files-under default-directory)))
(find-file (completing-read "Find file: " files nil t))))
(map! :leader :desc "Find file under here" "." #'my/find-file-under-here)
Plain find-file is still on SPC f f.
In Neovim (lua/config/keymaps.lua), <leader>. calls Telescope's find_files with the same command. It uses fdfind when fd isn't on the PATH, which is the binary's name on Debian and Ubuntu. It starts from the current file's directory, or from the working directory for non-file buffers:
local find_command = { vim.fn.executable("fd") == 1 and "fd" or "fdfind",
"--type", "f", "--hidden", "--strip-cwd-prefix", "--color", "never", "--exclude", ".git" }
map("n", "<leader>.", function()
local dir = vim.bo.buftype == "" and vim.fn.expand("%:p:h") or ""
if dir == "" or vim.fn.isdirectory(dir) == 0 then
dir = vim.fn.getcwd()
end
require("telescope.builtin").find_files({ cwd = dir, find_command = find_command })
end, { desc = "Find file under here" })
LazyVim's scratch buffer still has <leader>S for picking a scratch buffer.
The result: the same key and the same command, so I get the same file list in either editor.
Things that don't need fixing
Some differences are fine to leave alone. gD vs gr for references, SPC t vs <leader>u for toggles, evil-snipe vs flash: I just learned both. Every keymap you change is one more thing to maintain, and both editors' menus are easy to find your way around.
What I'd add to both, next
These suggestions come from going through my own configs side by side. Each one closes a gap in one editor, or makes the two share a single source of truth instead of keeping two copies in sync.
For both editors
Let
.gitignoredecide what file search skips. Right now the excluded directories are hardcoded twice:my/find-file-excluded-dirsin Doom'sconfig.elandfind_excludesin Neovim'skeymaps.lua. fd already skips anything in.gitignore, so drop both lists and let each project's.gitignoredecide. Then there's nothing to keep in sync. If a project shows junk likenode_modulesor.venv, add it to that project's.gitignore. The one exception is--exclude .git: with--hiddenon, the.gitdirectory can show up, and it never appears in a.gitignore.A debugger in both. Neither has one now. Enable LazyVim's
dap.coreextra and Doom's:tools debugger. Both use DAP, so the same adapters (js-debug-adapter,delve,debugpy) work in each.EditorConfig. Neovim reads
.editorconfignatively. Enable Doom'seditorconfigmodule and put one file in each project, so indentation is decided per project rather than per editor.Switch projects the same way. Enable LazyVim's
util.projectextra and map<leader>ppto its picker, to match Doom'sSPC p p.One home for snippets. yasnippet and VSCode-style JSON snippets aren't compatible. My Doom
snippets/folder is empty, so now is the cheapest time to choose. I'd put them in Neovim's JSON format, since blink, VS Code and many other tools read it.
For Neovim
Org mode via
nvim-orgmode. Point it at the same~/org/directory with the same keywords and capture templates, so I can capture a task from either editor and see it in Doom's agenda:-- lua/plugins/org.lua return { { "nvim-orgmode/orgmode", ft = { "org" }, opts = { org_agenda_files = "~/org/**/*", org_default_notes_file = "~/org/inbox.org", org_todo_keywords = { "TODO(t)", "NEXT(n)", "INPROG(i)", "|", "DONE(d)", "CANCELLED(c)" }, org_log_done = "time", org_log_into_drawer = "LOGBOOK", }, }, }Add
chipsenkbeil/org-roam.nvimlater if I want roam links as well.Highlight
{...}in Markdown. This is a custom highlight I use when writing sometimes. Doom highlights braces in Markdown with a custommy/brace-face. Neovim can do the same with a few lines:vim.api.nvim_create_autocmd("FileType", { pattern = "markdown", callback = function() vim.api.nvim_set_hl(0, "MyBraceFace", { fg = "#ff9e64", bold = true }) vim.fn.matchadd("MyBraceFace", "{[^{}]*}") end, })Set the font when running in a GUI. Set
vim.o.guifont = "Lilex Nerd Font Mono:h18"so Neovide matches Doom. In the terminal, set the same font in the terminal's own config.
For Doom Emacs
Format on save. Enable
(format +onsave). It's the biggest daily difference between the two editors, and conform.nvim spoiled me.Tree-sitter. Enable
:tools tree-sitterand add+tree-sitterto the language modules, for highlighting and textobjects closer to Neovim's.A file tree. Add
neotree(ortreemacs) under:uisoSPC o popens a tree, as<leader>opdoes in Neovim.Pin TypeScript at 6.x for now. TypeScript 7 is the new Go-based compiler and doesn't ship
tsserver, which typescript-language-server needs. Don't runnpm update -g typescriptpast 6.x until that changes. Or, better, follow suggestion #2 and let Mason handle it.
Wrapping up
Running two editors is easier than it sounds when both are Vim-keyed, Space-led distros. The differences that matter aren't the keys. They're what each editor is for:
- Neovim + LazyVim is my fast, low-maintenance, terminal-first code editor, with the better out-of-the-box language support.
- Doom Emacs is where I go for Org, Magit and remote files, the things that make Emacs more than an editor.
Match the theme, the leader keys and the file search, share the tool binaries and the ~/org/ directory, and switching between them stops feeling like switching tools.

