Dev In The Mountain Header
A Developer In The mountains having fun

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.el turns modules on, packages.el adds packages, config.el holds your own settings.
  • LazyVim: Lua. lazyvim.json lists the enabled "extras", lua/plugins/*.lua adds or overrides plugins, and lua/config/*.lua holds options, keymaps and autocmds.

3. How you turn features on

  • Doom uses module flags: (python +lsp +pyright) and then doom sync.
  • LazyVim uses extras: open :LazyExtras, toggle lang.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

JobNeovim (LazyVim)Doom Emacs
Completionblink.cmpcorfu + orderless
PickersTelescope (+ fzf-native)vertico + consult + marginalia
File treeneo-treedired
LSPnvim-lspconfig + Mason (installs servers for you)lsp-mode (you install servers yourself)
Formattingconform.nvimformat module (off in my config)
SyntaxTreesitterRegex font-lock (tree-sitter module off)
GitNeogit, diffview, lazygit, gitsignsMagit, vc-gutter
Terminalsnacks terminalvterm
Notesrender-markdown, markdown-previewOrg + org-roam + journal
Remote files—TRAMP
Motionflash.nvimevil-snipe + avy
Plugin updateslazy.nvim checks on its owndoom 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 → DONE workflow, 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.conf and 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 v and SPC h k show 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 hit which-key pinned 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-orgmode is 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 main branch broke a lot of configs. Also, :Lazy sync will 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 EmacsNeovim + 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 integrationpossible with pluginspossible 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:

ActionDoomLazyVim
Find file in projectSPC SPC<leader><space>
Grep projectSPC /<leader>/
Quick buffer switchSPC ,<leader>,
Find / recent filesSPC f f / SPC f r<leader>ff / <leader>fr
Alternate bufferSPC ` <leader>`
Kill bufferSPC b d<leader>bd
Window split / moveSPC w v, SPC w s, SPC w h/j/k/l<leader>w… (same)
Code action / renameSPC c a / SPC c r<leader>ca / <leader>cr
Go to definition / docsgd / Kgd / K
Commentgcgc
QuitSPC 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:

ActionDoom (default)LazyVim (default)Notes
SaveSPC f s<C-s>Doom keeps the Spacemacs habit
Buffer listSPC b b<leader>fb / <leader>,
M-x / command paletteSPC :<leader>sC (commands)LazyVim's <leader>: is command history
Help / describe keySPC h k, SPC h f, SPC h v<leader>sk, <leader>shLazyVim puts help under search
Toggle terminalSPC o t<C-/>
File treeSPC o p (needs a tree module)<leader>e
Switch projectSPC p p<leader>fp (util.project extra)
Toggles (wrap, numbers, spell)SPC t …<leader>u …Remember t ↔ u
Diagnostics listSPC c x<leader>xx (trouble)
Find referencesgDgrgD means declaration in LazyVim
Search in bufferSPC s s<leader>sb
Workspaces / tabsSPC TAB … (persp-mode)<leader><tab> … (Vim tabs)Same key, different idea
Git statusSPC g g (Magit)<leader>gg (lazygit)
Diff file vs HEAD——Neither has a default
2-char jumps (evil-snipe), gs SPC (avy)s (flash)Flash labels the whole screen
Visual-mode SSurround (evil-surround)Flash treesitter selectEasy to trip on
Surroundys / cs / dsgsa / gsr / gsd (mini.surround extra)
Expand region—<C-space> (flash/treesitter)
Line numbersAbsoluteRelative
NotesSPC 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>uC lets 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 :340 to jump around more than 20j.
  • 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)

KeyActionDoom equivalent
<leader>fsSaveSPC f s
<leader>bbBuffer pickerSPC b b
<leader>:Command paletteSPC :
<leader>hh / <leader>hkHelp tags / keymapsSPC h …
<leader>otToggle terminalSPC o t
<leader>op / <leader>o-neo-tree / reveal fileSPC o p
<leader>pfFind file in projectSPC p f
<leader>.Find file under the current directorySPC . (custom)
C-} / C-{Expand / contract selectionexpand-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, visual S). I moved flash's treesitter jump off visual S so it no longer clashes.
  • Neogit on <leader>gg, so it lands where Magit does. Its keys follow Magit too: s/u to stage, c c to commit, P p to push. Lazygit moved to <leader>gG.
  • diffview on <leader>gd, matching my custom SPC 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:

KeyCommandWhat it does
SPC .find-fileThe standard Emacs file prompt. You browse one directory at a time; it doesn't search subdirectories.
SPC SPCprojectile-find-fileSearches 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-hereSearches recursively from the current directory. This is the closest built-in, but it's on a three-key chord.

LazyVim, out of the box:

KeyCommandWhat it does
<leader>.Snacks.scratch()Toggles a scratch buffer. Nothing to do with finding files.
<leader><space> / <leader>ffFind 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>fFFind 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
  • --hidden shows dotfiles such as .env.example and .github/workflows/*.
  • .gitignore is respected by default, so build output and dependencies stay out of the list without a hand-maintained exclude list.
  • --exclude .git hides the .git directory itself, which --hidden would 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

  1. Let .gitignore decide what file search skips. Right now the excluded directories are hardcoded twice: my/find-file-excluded-dirs in Doom's config.el and find_excludes in Neovim's keymaps.lua. fd already skips anything in .gitignore, so drop both lists and let each project's .gitignore decide. Then there's nothing to keep in sync. If a project shows junk like node_modules or .venv, add it to that project's .gitignore. The one exception is --exclude .git: with --hidden on, the .git directory can show up, and it never appears in a .gitignore.

  2. A debugger in both. Neither has one now. Enable LazyVim's dap.core extra and Doom's :tools debugger. Both use DAP, so the same adapters (js-debug-adapter, delve, debugpy) work in each.

  3. EditorConfig. Neovim reads .editorconfig natively. Enable Doom's editorconfig module and put one file in each project, so indentation is decided per project rather than per editor.

  4. Switch projects the same way. Enable LazyVim's util.project extra and map <leader>pp to its picker, to match Doom's SPC p p.

  5. 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

  1. 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.nvim later if I want roam links as well.

  2. Highlight {...} in Markdown. This is a custom highlight I use when writing sometimes. Doom highlights braces in Markdown with a custom my/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,
    })
    
  3. 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

  1. Format on save. Enable (format +onsave). It's the biggest daily difference between the two editors, and conform.nvim spoiled me.

  2. Tree-sitter. Enable :tools tree-sitter and add +tree-sitter to the language modules, for highlighting and textobjects closer to Neovim's.

  3. A file tree. Add neotree (or treemacs) under :ui so SPC o p opens a tree, as <leader>op does in Neovim.

  4. 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 run npm update -g typescript past 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.

More places to find me
Mental Health
follow me on Mastodon