ERENVIM
I finally did it. After years of using other people’s editors and telling myself “this is fine,” I sat down and built my own Neovim config from scratch. Just Lua, lazy.nvim, and a concerning amount of free time.
Here’s what I ended up with and why.
Why Not a Distribution
I tried them. I really did. But every time I wanted to change something, I found myself three abstraction layers deep in someone else’s opinions. You want to tweak how your file tree behaves? Good luck finding which of the 47 override files controls that.
A handrolled config is more work upfront, but you own every line. When something breaks, you know exactly where to look. When you want to add something, you know exactly where it goes. No magic.
What We’re Building
The goal was simple: replace VS Code entirely. That means:
- File tree on the left (because I have eyes and I like seeing my project)
- LSP support for Python, Rust, GDScript, JavaScript, TypeScript, Kotlin, HTML, CSS, Markdown
- Autocompletion that actually feels fast
- Git integration without leaving the editor
- Debugging without leaving the editor
- A colorscheme that matches my website because obviously
The Stack
Everything runs on lazy.nvim for plugin management. Each plugin gets its own file under lua/plugins/. No 500 line monolith config. Each file returns a lazy.nvim spec table and that’s it.
Here’s the full lineup:
| What | Plugin |
|---|---|
| File tree | neo-tree.nvim |
| Fuzzy finder | telescope.nvim |
| LSP | nvim-lspconfig + mason.nvim |
| Completion | blink.cmp |
| Syntax | nvim-treesitter |
| Git | gitsigns.nvim + neogit |
| Debugging | nvim-dap + dap-ui |
| Statusline | lualine.nvim |
| Theme | unruled (custom) |
| Keybinding help | which-key.nvim |
| Autopairs | nvim-autopairs |
| Comments | Comment.nvim |
| Dashboard | alpha-nvim |
The File Structure
nvim/
├── init.lua
├── lua/
│ ├── config/
│ │ ├── options.lua
│ │ ├── keymaps.lua
│ │ └── autocmds.lua
│ └── plugins/
│ ├── ui.lua
│ ├── neo-tree.lua
│ ├── telescope.lua
│ ├── treesitter.lua
│ ├── lsp.lua
│ ├── completion.lua
│ ├── git.lua
│ ├── dap.lua
│ ├── editor.lua
│ ├── dashboard.lua
│ └── unruled/
│ ├── colors/unruled.lua
│ └── lua/unruled/
│ ├── init.lua
│ └── palette.lua
init.lua does three things: load options (leader key has to be set before lazy.nvim boots), bootstrap lazy.nvim, and then load keymaps and autocmds. That’s the entire entry point.
Mason Does the Heavy Lifting
One of the best decisions was using Mason to auto install LSP servers. You open a Python file, Mason checks if pyright is installed, installs it if not, and you’re writing code with full LSP support in about 30 seconds. Same for rust_analyzer, the TypeScript language server, Kotlin, and everything else.
The only exception is GDScript. Godot runs its own language server, so the config just connects to it on localhost:6005. You need Godot open for that to work, which makes sense because if you’re writing GDScript you’re probably also running Godot.
The Unruled Theme
I wasn’t going to use someone else’s colorscheme. I pulled the exact colors from my website and turned them into a Neovim theme.
The palette:
| Role | Color |
|---|---|
| Background | #141414 |
| Surface | #000000 |
| Text | #F0F0F0 |
| Accent | #ED3192 |
| Strings | #03bfac |
| Types | #087fbf |
| Constants | #e34600 |
| Search | #f7ea00 |
Functions and keywords get the hot pink. Strings get teal. Types get blue. Errors get orange. It’s dark, it’s vibrant, and it’s mine.
The theme lives as a local plugin inside the config itself. No external dependency. The palette.lua file defines every color once, and init.lua maps them to highlight groups. Covers everything: editor, treesitter, LSP diagnostics, telescope, neo-tree, which-key, git signs, DAP, and the dashboard.
Lualine gets its own theme table too. Normal mode is pink, insert is teal, visual is yellow, command is blue. You always know what mode you’re in.
Blink.cmp Over Native Completion
Neovim 0.11 has built in completion now, which is cool. But blink.cmp is just better. It’s written in Rust so it’s fast, the fuzzy matching is excellent, and the documentation popup is actually useful. It pulls from LSP, file paths, snippets, and buffer text. The default keybindings work well enough that I didn’t bother customizing them.
The Dashboard
When you open Neovim with no file, you get a welcome screen with the ERENVIM logo and a full shortcut reference. I got tired of forgetting my own keybindings, so now they’re right there every time I start the editor.
The shortcuts are grouped by category: file tree, find, git, debug, LSP, editor. Everything uses Space as the leader key.
Quick reference for the ones I use most:
Space e toggle file tree
Space ff find files
Space fg live grep
Space gg git ui
Space db toggle breakpoint
Space dc start/continue debugging
gd go to definition
K hover docs
Space ca code actions
gc toggle comment
Debugging Actually Works
This was the part I was most worried about. DAP (Debug Adapter Protocol) has a reputation for being painful to set up. But mason-nvim-dap handles the adapter installation, dap-ui gives you a proper debugging interface, and dap-python just works with debugpy.
For Rust, codelldb gets installed automatically and handles everything. For GDScript, it connects to Godot’s debug server on port 6006.
The UI opens automatically when you hit a breakpoint and closes when the session ends. No manual window management needed.
Two Things I Learned
Neovim 0.11 changed things. The old require("nvim-treesitter.configs") module is gone. Treesitter highlighting and indentation is built in now. The plugin just manages parser installation. Similarly, require("lspconfig").server.setup() is deprecated in favor of vim.lsp.config() and vim.lsp.enable(). If you’re building a config in 2026, don’t follow tutorials from 2024.
Telescope’s 0.1.x branch is dead. If you’re on Neovim 0.11, you need the master branch. The old branch uses ft_to_lang which was removed. Took me a minute to figure out why my previewer was crashing.
The Result
It starts in under 100ms. Every plugin lazy loads. LSP servers install themselves. The theme is consistent across every panel and popup. And I can debug Python, Rust, and GDScript without leaving the editor.
Is it overkill for editing a config file? Absolutely. Do I care? No. It’s my editor and it works on my machine.
The full config is on my GitHub if you want to steal it. Or don’t. Build your own. That’s the whole point.