src/content/post/returning-to-neovim.mdx 12.4 K raw
1
---
2
title: "Returning to Neovim"
3
publishDate: "16 Mar 2026"
4
description: "Once again coming back to the editor I can't shake"
5
ogImage: "../../assets/blog-images/return-to-neovim.png"
6
tags: ["programming", "neovim", "developer"]
7
atUri: "at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mh5mi4ngdd2h"
8
---
9
10
![cover](../../assets/blog-images/return-to-neovim.png)
11
12
One of my more popular blog posts was how and why I switched to [Zed from Neovim](https://stevedylan.dev/posts/leaving-neovim-for-zed/). That was almost two years ago, and in that period Zed was my daily driver for programming. Every now and then I would still use Neovim to edit a config or make a quick edit, but outside of that, Zed was where I lived. It performed admirably, with minimal bugs that only irked me from time to time that would eventually be fixed. AI was still relatively minimal and I enjoyed using it. So why go back to Neovim?
13
14
## What Happened
15
16
The real shift happened about a week ago when Zed updated its terms and policies, including a new age restriction of 18+. The Zed team clarified that this was in regards to their online services and platform used for AI assisted coding, but at least for me, it was still a bit unnerving. A few people had already made forks of Zed through the open source licenses that the editor is under, and I did try them, but it was clear that the Zed experience was not the same. Some stuff didn't work, extensions had to be installed manually, just overall a horrible experience.
17
18
That was the wake up call. I realized I couldn't really trust Zed moving forward. I really think the team is awesome and what they're building is perhaps the best editor alternative to VSCode, but I also understood that they have to make money somehow. When it comes to writing code, the last place I want to find myself in is being held hostage or being forced off the platform. I have to be able to write code productively without my flow being interrupted by the decisions of higher management. Neovim isn't a perfect drop in replacement in this regard either, but I trust it way more as a community backed and managed project. 
19
20
Since switching back to Neovim full time, I've honestly had no regrets. I updated my config last year, and having the opportunity to daily drive it has proven how capable it truly is. There was a mental plan to adjust pieces to meet what I might have missed in Zed, but I haven't had to make any changes yet. With that said I figured it would be a good time to share what my config looks like and how effective it is.
21
22
## The Config
23
24
There's generally two ways people end up configuring Neovim. One path is using a distro like [LazyVim](https://www.lazyvim.org/), the other is writing it from scratch. I've taken the distro path before and I think it's great if you have no idea what you want, but eventually you might find yourself wanting to slim things down. If doing a config from scratch feels intimidating, I would highly recommend [this series](https://www.youtube.com/playlist?list=PLsz00TDipIffreIaUNk64KxTIkQaGguqn) which goes over all the different aspects of a config. Below is a quick overview of my config structure:
25
26
```
27
nvim
28
└── lazy-lock.json
29
└── lua
30
    └── plugins
31
        └── ai-vim.lua
32
        └── treesitter.lua
33
        └── mini.lua
34
        └── tmux-navigator.lua
35
        └── colorschemes.lua
36
    └── config
37
        └── options.lua
38
        └── keymaps.lua
39
        └── autocmds.lua
40
    └── core
41
        └── lazy.lua
42
        └── lsp.lua
43
└── lsp
44
    └── gopls.lua
45
    └── solc.lua
46
    └── asm-lsp.lua
47
    └── rust-analyzer.lua
48
    └── astro.lua
49
    └── html.lua
50
    └── json.lua
51
    └── lua_ls.lua
52
    └── tsserver.lua
53
└── init.lua
54
```
55
56
I'll do my best to go over all of the different pieces I have here. 
57
58
### Plugin Manager
59
60
I've been using [lazy.nvim](https://github.com/folke/lazy.nvim) for years (not to be confused with LazyVim, a distro that uses lazy.nvim), and it's just solid. Always works, zero issues, and boy it can go _fast_ (will go over that later). There's not much else to say due to how much of an industry standard it is. I might give the new native plugin manager coming in Neovim 12 a try, but I've already seen some people say it's not as fast as lazy.nvim, so I'll be keeping an eye on it for future development.
61
62
### LSP
63
64
The Language Server Protocol (LSP) provides a standard for different languages to provide feedback in dev workflows. Common example would be writing an incorrect type in Typescript which would cause the compiler to fail. Instead of having to run it, the editor shows some red lines saying something is wrong. Many editors set this up behind the scenes, but that's not the case for Neovim, and it can be one of the big things people struggle with. In the past I've used a few plugin combinations which were always a mess, but I was so excited that native LSP support came to Neovim last year! [This video](https://youtu.be/IZnhl121yo0) does a fantastic job walking you through how to set it all up, but its really as simple as creating a dedicated `lsp` folder with the different languages, then making a `lsp.lua` config file. Here's an example for Rust:
65
66
```lua
67
return {
68
	cmd = {
69
		"rust-analyzer",
70
	},
71
	filetypes = {
72
		"rust",
73
	},
74
	root_markers = {
75
		"Cargo.toml",
76
		"Cargo.lock",
77
		".git",
78
	},
79
	settings = {
80
		["rust-analyzer"] = {
81
			cargo = {
82
				allFeatures = true,
83
				loadOutDirsFromCheck = true,
84
				runBuildScripts = true,
85
			},
86
			-- Add other rust-analyzer specific settings here
87
			checkOnSave = true,
88
			procMacro = {
89
				enable = true,
90
				ignored = {
91
					leptos_macro = {
92
						-- "component",
93
						"server",
94
					},
95
				},
96
			},
97
		},
98
	},
99
	single_file_support = true,
100
	log_level = vim.lsp.protocol.MessageType.Warning,
101
}
102
```
103
104
This tells Neovim what files to use the rust-analyzer LSP for, what files might indicate a project, and any other options we may want to add. We follow the same structure for all languages or frameworks that have an LSP. Then inside `lsp.lua` we just add the following configuration:
105
106
```lua
107
vim.lsp.enable({
108
  "astro",
109
  "gopls",
110
  "lua_ls",
111
  "tsserver",
112
  "rust-analyzer",
113
  "asm-lsp",
114
  "solc",
115
  "html",
116
  "json"
117
})
118
119
vim.diagnostic.config({
120
  virtual_lines = false,
121
  -- virtual_text = true,
122
  underline = true,
123
  update_in_insert = false,
124
  severity_sort = true,
125
  float = {
126
    border = "rounded",
127
    source = true,
128
  },
129
  signs = {
130
    text = {
131
      [vim.diagnostic.severity.ERROR] = "󰅚 ",
132
      [vim.diagnostic.severity.WARN] = "󰀪 ",
133
      [vim.diagnostic.severity.INFO] = "󰋽 ",
134
      [vim.diagnostic.severity.HINT] = "󰌶 ",
135
    },
136
    numhl = {
137
      [vim.diagnostic.severity.ERROR] = "ErrorMsg",
138
      [vim.diagnostic.severity.WARN] = "WarningMsg",
139
    },
140
  },
141
})
142
```
143
144
The key is `vim.lsp.enable()` where we pass in the names of all our files that have configs. Everything else is just some nicer configuration for looking at diagnostics.
145
146
![nvim diagnostics icons](../../assets/blog-images/nvim-diagnostics-icons.png) 
147
148
It's that simple, and I absolutely love how minimal the experience is. Does require understanding what your LSPs are, where they live, and how to run them, but totally worth it. 
149
150
### Plugins
151
152
You might have noticed that I don't have that many plugins, but it's actually a bit deceiving. 
153
154
- `ai-vim.lua` - Small inline AI editing plugin which I don't actually use much, will probably cut it.
155
- `treesitter.lua` - Syntax highlighting, pretty standard.
156
- `tmux-navigator.lua` - Lets me use `ctrl+h/j/k/l` to switch between a Neovim session and another tmux pane.
157
- `colorschemes.lua` - Themes baby, currently on my own called [Darkmatter](https://github.com/stevedylandev/darkmatter-nvim)
158
159
The one I didn't list here is [mini.nvim](https://github.com/nvim-mini/mini.nvim), which is the true star of this config. mini.nvim is a collection of minimal plugins that are installed and setup through a single config. They're all simple, functional, and they really help lighten up your config. Here's a quick run down of some of my favorites.
160
161
**mini.completion**
162
163
<video
164
  autoPlay
165
  muted
166
  loop
167
  playsinline
168
  className="w-full aspect-video"
169
  src="/blog-images/mini-completion.mp4"
170
></video>
171
172
This would normally be handled through something heavier like coc.nvim, but it's truly awesome to have a simple and lightweight option inside mini.nvim.
173
174
**mini.files**
175
176
<video
177
  autoPlay
178
  muted
179
  loop
180
  playsinline
181
  className="w-full aspect-video"
182
  src="/blog-images/mini-files.mp4"
183
></video>
184
185
A minimal file explorer thats a fun mix between oil and netrw.
186
187
**mini.pick**
188
189
<video
190
  autoPlay
191
  muted
192
  loop
193
  playsinline
194
  className="w-full aspect-video"
195
  src="/blog-images/mini-pick-files.mp4"
196
></video>
197
198
Pick anything. Actually though. In a lot of ways this replaces telescope and lets me fuzzy find files, buffers, you name it!
199
200
### Other Bits
201
202
There are some smaller quality of life pieces I have that don't really fit into any specific category, so here's a few my favorites.
203
204
**VimEnter**
205
206
I got this one from [Adib Hanna](https://www.youtube.com/@adibhanna) a long time ago. Instead of showing a start screen when I open Neovim, instead I use `mini.pick` to fuzzy find all files within the current directory.
207
208
```lua
209
vim.api.nvim_create_autocmd("VimEnter", {
210
  callback = function()
211
    if vim.fn.argv(0) == "" then
212
      vim.defer_fn(function()
213
        require("mini.pick").builtin.files()
214
      end, 100) -- Wait 100ms
215
    end
216
  end,
217
})
218
```
219
220
**Buffer Management**
221
222
<video
223
  autoPlay
224
  muted
225
  loop
226
  playsinline
227
  className="w-full aspect-video"
228
  src="/blog-images/buffers.mp4"
229
></video>
230
231
I don't have tabs setup in Neovim as I started to realize I don't really need them. I can use my keyboard shortcuts to move between buffers horizontally, 
232
233
```lua
234
-- Navigate buffers
235
map("n", "<S-l>", ":bnext<CR>", opts)
236
map("n", "<S-h>", ":bprevious<CR>", opts)
237
```
238
239
or filter through them with `mini.pick`: 
240
241
```lua
242
map("n", "<leader>o", "<cmd>Pick buffers<CR>", opts)
243
```
244
245
Then I can just close them with the following keymap:
246
247
```lua
248
map("n", "<leader>c", ":bd<cr>", opts)
249
```
250
251
**Diagnostics**
252
253
<video
254
  autoPlay
255
  muted
256
  loop
257
  playsinline
258
  className="w-full aspect-video"
259
  src="/blog-images/nvim-diagnostics.mp4"
260
></video>
261
262
Nothing too fancy here but it is nice how versatile the experience can be. I can either use this keymap to do a hover diagnostic:
263
264
```lua
265
map("gl", vim.diagnostic.open_float, "Open Diagnostic Float")
266
```
267
268
Or I can view all diagnostics for the project with mini.pick:
269
270
```lua
271
map("n", "<leader>d", "<cmd>Pick diagnostic<CR>", opts)
272
```
273
274
**Finding Stuff**
275
276
<video
277
  autoPlay
278
  muted
279
  loop
280
  playsinline
281
  className="w-full aspect-video"
282
  src="/blog-images/nvim-find.mp4"
283
></video>
284
285
One of the most important things an editor needs is an easy way to find anything, and I'm quite pleased with what I have setup here. To start, if I wanted to search the entire codebase for a given string, I can use mini.pick with `live_grep`. 
286
287
```lua
288
map("n", "<leader>/", "<cmd>Pick grep_live<CR>", opts)
289
```
290
291
Same goes for buffers.
292
293
```lua
294
map("n", "<leader>o", "<cmd>Pick buffers<CR>", opts)
295
```
296
297
For files, I can either do a fuzzy find like so:
298
299
```lua
300
map("n", "<leader>f", "<cmd>Pick files<CR>", opts)
301
```
302
303
Or I can use the file browser with mini.files. 
304
305
```lua
306
map("n", "<leader>e", "<cmd>lua MiniFiles.open()<CR>", opts)
307
```
308
309
310
Last but not least, you can also use Pick to get all the help manuals for Neovim or the plugins installed. 
311
312
```lua
313
map("n", "<leader>hh", "<cmd>Pick help<CR>", opts)
314
  ```
315
316
### Speed
317
318
```
319
025.359  000.037: BufEnter autocommands
320
025.363  000.004: editing files in windows
321
025.381  000.019: --- NVIM STARTED ---
322
```
323
324
This configuration is incredibly fast. Startup times averages around ~25ms. A more detailed report can be found below. 
325
326
[`.nvim-startup.txt`](https://sipp.stevedylan.dev/s/dJfiLGUx9u)
327
328
### Source
329
330
By all means feel free to checkout the whole config yourself in my [dotfiles repo](https://github.com/stevedylandev/dotfiles)!
331
332
## Wrapping Up
333
334
I really do appreciate the time I had with Zed and it is a solid editor; I don't fault anyone who uses it. Personally I've just been through this kind of thing too many times, where I thoroughly enjoy and depend on a tool, just for it to go sideways and absolutely cripple my productivity. The Arc Browser was a great example of this. I would much rather jump ship early, figure out a new workflow that is dependable, and stick with it. That's exactly what I've done with Neovim, and it's good to be back.