Switching to Nvim as My Main Text Editor
A text editor is one of the most important tools for an engineer. A good one can meaningfully improve your productivity. There are a lot of text editors out there, and I've tried a bunch of them. As of 2026, Nvim is my main text editor.
It all started around the end of 2025, when I was learning to build a home server. I bought a Beelink S12 Pro for it and decided to install Debian headless, since my original idea was to be in and out of that machine purely over SSH. It's ergonomic since I can tuck the machine in the corner of the room without a monitor, and I don't have to lug a keyboard and screen over to it every time I need to touch something. I wrote about that homelab experience in a separate post, which you can read here.
Below I'll walk through some of the features I use most often in my Nvim setup.
The Headless Machine
Back then, when I first got the machine running headless Debian, I was still using the nano text editor. Occasionally I'd use Vim, but the only syntax I knew was :wq to write and quit. It got the job done, but I felt like it could be better. One obstacle was that with nano or Vim, I couldn't just point and click to place my cursor. I had to use arrow keys in nano, or h/j/k/l for cursor navigation in Vim, which felt really slow in comparison.
Around that time I was also using Visual Studio Code for lightweight text edits, and Android Studio when developing Android applications.
It's not a replacement
So I managed to SSH in and out of the machine, but I found that whenever I wanted to edit a project, I'd usually edit it on my local MacBook, push the changes, then SSH into the server to pull and rebase. This worked for a while.
Eventually I decided to learn Vim properly. At first I focused on the basics and fundamentals. Nvim is built on Vim's foundations, so if you can do the basic things in Vim, you can use Nvim just fine.
What is Vim
Vim is a modal, keyboard-driven text editor that traces back to vi, one of the original Unix editors from the 1970s. "Modal" means the editor has distinct modes for different tasks:
- Normal mode for moving around and issuing commands
- Insert mode for typing text
- Visual mode for selecting text
- Command mode for things like saving or searching
Instead of reaching for a mouse, you compose commands out of small building blocks (motions, operators, and text objects) to navigate and edit text entirely from the keyboard. It ships by default on nearly every Unix-like system, which is part of why so many engineers end up learning at least the basics.
What is Nvim
Neovim (Nvim) is a fork of Vim that started in 2014 with the goal of modernizing its codebase and making it easier to extend. It kept Vim's core editing model (the same modes, motions, and muscle memory) but rebuilt the internals around a more maintainable architecture, added first-class Lua scripting, built-in LSP (Language Server Protocol) support, and an async plugin API. It has a large ecosystem of useful plugins, and you can even build your own.
That extensibility, combined with a large and active community, is a big part of why I chose it. Some of my favorite plugins are Telescope, Mason, and Treesitter, and I'll walk through the use cases and workflows I rely on throughout the rest of this post.
The Learning Curve
To be honest, learning Vim isn't that straightforward once you're used to GUI text editors. When I SSH into my headless server and use Vim, I can't just point my mouse at some text and click there. It doesn't work that way. You need to move the cursor deliberately, using Vim's motion syntax. There are a lot of motions, but fundamentally you move the cursor with h/j/k/l.
Vim Basics
Vim's fundamentals are covered extensively elsewhere, so I won't re-explain them all in depth here. If you're starting from zero, Vim's built-in :Tutor command and Vim Adventures are great places to start. Here's a quick reference for the terms I use throughout the rest of this post:
- Buffer: Vim's in-memory copy of a file while you edit it. :w writes it back to disk.
- Motion: a command that moves the cursor, like w, b, gg, or h/j/k/l. Pair it with an operator (d, c, y), or start visual mode with v, to act on the text it covers.
- Text object: describes a chunk of text by structure rather than movement, like a word, a paragraph, or everything inside a pair of brackets. Combine i/a (inside/around) with a delimiter, e.g. ci{.
- Saving: :w to write, :wq to write and quit.
- Undo/Redo: u to undo, Ctrl+r to redo, both in Normal mode only.
My Daily Use Cases
Here are a few real editing moments from my day-to-day, the kind where reaching for a mouse would've been slower. Most of them lean on the same idea: a text object (i{, a{, and friends) that describes a chunk of text, combined with an operator like c or d (or v to just select it) that says what to do with it.
Selecting Code Inside Brackets
This motion is one I use whenever I want to change something in code. Let's say you have this piece of code:
fun updateUserEmail(userId: Int, newEmail: String): Boolean {
val user = getUserById(userId) ?: return false
val index = users.indexOf(user)
users[index] = user.copy(email = newEmail)
return true
}
Say you want to change the implementation inside the brackets, or just select it. With a mouse or trackpad you'd:
- Direct your mouse to the opening bracket
- Left click and hold to the end of the closing bracket
This sounds pretty simple, but if you do this frequently you'll keep finding yourself reaching for the mouse. With Vim there's a faster way to do it: starting from Normal mode, place your cursor anywhere inside the brackets and use vi{.
The way I remember this motion is v for visual, i for inside, and { is just the literal bracket. So if you compose a sentence, it will be "Visual Inside {". Quite intuitive, right?
Selecting Code Around Brackets
Now the scenario is a bit different. Instead of using i you use a as in "around", so starting from Normal mode, it will be va{. It will look like this:
Now it includes the bracket when you do va{, as in "Visual Around {".
Change Inside Brackets
I think you pretty much get the gist at this point. To change, the command we're using is c, so starting from Normal mode, to change something inside { you can use ci{, as in "Change Inside {".
Deleting follows the exact same pattern, again starting from Normal mode, just swap the operator: di{ deletes everything inside the brackets, da{ deletes the brackets along with it.
More
The i/a text objects aren't just for {}. The same pattern works for quotes (ci"), parentheses (da(), square brackets (di[), and even HTML/JSX tags (cit). Once you've internalized "operator + i/a + delimiter," you can compose your way through almost any edit without ever reaching for the mouse.
The same "small building blocks" idea shows up elsewhere in Vim too. A couple more worth knowing:
- Macro: record a sequence of edits into a register with qa ... q, then replay the whole thing with @a (or @@ to repeat the last macro). Turns a repetitive multi-line edit into a single motion.
- Dot repeat: . repeats your last change, no recording required.
A lot of people get discouraged with Vim because they think it's about memorizing a huge list of keyboard shortcuts. It's not. It's a small, consistent grammar, and once the grammar clicks, new motions stop feeling like things you memorize and start feeling like things you can guess.
Building Muscle Memory
It takes practice to get comfortable with the interface, navigation, and manipulating text. I recommend creating a sample project specifically for practicing these fundamentals. If you're interested, you can try my practice repo along with the included cheatsheet here: vimground.
Use Vim Mode in Your Existing Editor or IDE
When I first started learning Vim, I didn't jump in and ditch my IDE entirely. I started small, using Vim mode inside the tools I already had.
- VSCode/Cursor: install the VSCodeVim extension, it adds Vim keybindings and modal editing on top of the regular editor
- Android Studio or IntelliJ: there's a Vim plugin you need to enable in the IDE settings
- Xcode: Xcode 13+ ships with a built-in Vim mode, toggle it from the Editor menu → Vim Mode
- Terminal/Shell: add set -o vi to your .bashrc (or bindkey -v for zsh) for vi-style line editing in the shell itself. Most pagers, like less (which man uses by default to display its pages), already default to vi-style navigation (j/k, / to search), and tmux supports it too with setw -g mode-keys vi in .tmux.conf
Most modern text editors support Vim mode. Zed is a personal favorite here since it has first-class support built in.
Start small in your existing setup. It'll feel uncomfortable at first, but that's okay: that's how you learn.
Nvim
As mentioned above, if you're already comfortable getting around in Vim, you'll do just fine in Nvim. That doesn't mean you need to use Vim first before installing Nvim, though. In fact, once I got familiar with Vim mode in my existing IDE, I went straight to installing Nvim on my computer. Nvim is essentially a highly configurable, highly extensible version of Vim.
You don't need to add every plugin on day one. You can start with a bare-bones Nvim setup and build your configuration out as your needs grow.
Getting Started
You can start with the default Nvim config, but I'd recommend beginning with this getting-started video for beginners from one of the Nvim maintainers.
It's built around kickstart.nvim, a single-file launch point for your own personal Nvim configuration. TJ DeVries walks you through navigating around, searching help documentation, trying out themes, and more.
It's also a solid starting point if you'd rather build on top of a community-backed config instead.
What is <leader>
In Nvim, most custom keymaps are prefixed with a "leader" key: a key you designate to mean "custom command incoming." It exists because nearly every letter is already bound to a built-in Vim motion or command, so plugins and personal configs need a free prefix to build their own shortcuts on top of.
You set it once with vim.g.mapleader, and every <leader> in your keymaps refers back to it. Space is a popular choice (it's what kickstart.nvim uses by default) because it's easy to reach and does nothing on its own in normal mode:
vim.g.mapleader = " "
With that set, vim.keymap.set("n", "<leader>ff", ":Telescope find_files<CR>") means: in normal mode, pressing Space then f then f runs the file finder.
Lua Script
Nvim configuration is written in Lua instead of Vimscript, which is the main thing that trips people up coming from Vim. The good news is you only need a small slice of the language to get productive:
- Variables are untyped: local name = "value"
- Tables ({}) are Lua's one data structure: they double as arrays, dictionaries/objects, and even "classes." Most of your config will be tables passed to setup functions
- Functions are values too: local function greet() print("hi") end, or anonymous ones inline: function() ... end
In Nvim, this shows up in a few recurring patterns:
-- setting options
vim.opt.number = true
vim.opt.tabstop = 2
-- global variables (often used by plugins, e.g. mapleader)
vim.g.mapleader = " "
-- keymaps
vim.keymap.set("n", "<leader>ff", ":Telescope find_files<CR>")
-- plugin configuration, passed as a table
require("telescope").setup({
defaults = {
sorting_strategy = "ascending",
},
})
Once you recognize vim.opt, vim.g, and vim.keymap.set as the main entry points, and see plugin config as "just a table of settings," most of a kickstart.nvim-style config stops looking like magic and starts looking like a list of small, readable statements: the same "small building blocks" idea from Vim motions, just applied to configuration.
Portable Config with chezmoi
One of the things I like about Nvim is that my configuration is just plain text under ~/.config/nvim, so it drops straight into a git repository. I use chezmoi to manage and sync that repository across machines (I've written about the setup separately here), which means restoring my exact Nvim config on a new machine is one command, not an afternoon of reconfiguring.
That portability extends past Mac and Linux to Termux on Android: since Termux gives me a real shell and package manager, I can install Nvim and apply the same chezmoi-managed dotfiles to it, and get the same editing experience on my Android phone.
Other Nvim Features I Use
Beyond the core motions and text objects, here are a few plugins I lean on daily.
Mason for LSP
Mason is a package manager for LSP servers, linters, and formatters, right inside Nvim. Instead of manually installing language servers one by one and wiring each into Nvim's config, :Mason gives you a UI to install, update, or remove them, and mason-lspconfig.nvim bridges whatever you've installed into nvim-lspconfig, which wires them up to Nvim's built-in LSP client. Once a server is running for a filetype and I've mapped a few keys to the built-in LSP functions, I get:
- go-to-definition and find-references (vim.lsp.buf.definition(), vim.lsp.buf.references())
- inline diagnostics: compiler errors and warnings as I type
- autocomplete based on the actual project context, not just static text matching
Together with Treesitter, this is what makes Nvim feel like an IDE instead of a plain text editor.
Treesitter for Syntax Highlighting
Treesitter replaces Vim's old regex-based syntax highlighting with a parser that actually understands your code as a syntax tree. Instead of highlighting matching patterns, it understands the structure: a string is a string, a function name is a function name, a comment is a comment. That holds even in files that mix languages, like JSX inside a .tsx file or SQL inside a template string.
That structural awareness doesn't stop at highlighting. It's also what powers:
- smarter indentation that understands nested blocks
- incremental selection that expands by syntax node (select the current word, then the expression, then the enclosing block, each with one keypress)
- text objects like cif/daf for functions, courtesy of the companion nvim-treesitter-textobjects plugin, so the same "operator + i/a + delimiter" grammar from Vim motions extends to language-aware chunks of code, not just brackets and quotes
Once installed, it's mostly invisible: you just get more accurate highlighting and a few extra motions for free.
Telescope for Fuzzy-Finding Everything
Telescope is a fuzzy finder over pretty much anything you can list: files, text inside files, buffers, git status, help tags, and more. The one I reach for most is finding files by name, which lets me jump to any file in the repo without leaving the keyboard.
"Fuzzy" here means the match doesn't have to be exact. If I only remember part of a filename or roughly where it lives, Telescope narrows the list down as I type instead of me needing to recall the exact path.
Here's the keymap I use:
local builtin = require "telescope.builtin"
-- get_cwd() is a small local helper I use to resolve the project root
vim.keymap.set("n", "<leader>sf", function()
builtin.find_files {
find_command = { "fd", "--type", "f", "--hidden", "--follow", "--exclude", ".git" },
cwd = get_cwd(),
}
end, { desc = "[S]earch [F]ile" })
<leader>sf opens a panel that lets me fuzzy find any file in the current working directory (sf as in "Search File"). Under the hood it shells out to whatever find_command you configure; here I'm using fd, a simple, fast alternative to the built-in find.
Panel Management
This isn't an Nvim-exclusive feature (plain Vim has it too), but it pairs well with tmux, which I already use for persistent sessions on my headless server. Nvim handles the splits inside a tab, tmux handles everything around it, and together they cover a full multiplexed workflow without touching a mouse.
vim.keymap.set("n", "<leader>uv", ":vsplit<CR>", { noremap = true, silent = true, desc = "Vertical split" })
vim.keymap.set("n", "<leader>uth", ":split | term<CR>", { noremap = true, silent = true, desc = "Open terminal in horizontal split" })
vim.keymap.set("n", "<leader>uh", ":split<CR>", { noremap = true, silent = true, desc = "Horizontal split" })
- <leader>uv splits the current window vertically (handy for viewing two files side by side)
- <leader>uth opens a horizontal split and drops a terminal straight into it, so I don't have to leave Nvim to run a command
- <leader>uh splits it horizontally
Once multiple splits are open, Ctrl+w followed by a direction (h/j/k/l) jumps between them, and Ctrl+w followed by +/-/</> resizes them: the same modal muscle memory as everything else in this post, just applied to window management instead of text.
Nvim with AI Tools
AI has been a great learning tool for me and a reliable coding assistant. Integrating it into my workflow is crucial. There are a lot of plugins out there for Nvim to integrate LLM tools, such as codecompanion, which gives you a buffer to work with your AI assistant.
Though for me, plugin-based integrations weren't a great experience. Getting one working meant setting up ACP (Agent Client Protocol) support, and the panel it opens is intrusive: it blocks the whole buffer until you accept or reject its suggestion. I believe there's a setting to change that behavior, but I just didn't bother looking into it.
So I stick to the CLI tool each provider ships natively instead. Right now that's the Claude Code CLI, but the approach isn't tied to it. It works the same with Codex or the Cursor CLI.
I just spin up a terminal in a separate panel and work my way through it. Then you might be wondering: how do you point AI to a specific file, or a specific piece of code in a file?
Copying File and Line References
The fundamental idea is that you want to point your AI to a specific line or line range to add to the context window. I created a shortcut in Nvim to copy the line reference the cursor is currently on:
-- Copy absolute file path
vim.keymap.set("n", "<leader>ufa", function()
local path = vim.fn.expand "%:p"
vim.fn.setreg("+", path)
vim.notify "Copied absolute file path"
end, { desc = "Copy [A]bsolute file path" })
-- Copy file:line reference for AI tools
vim.keymap.set("n", "<leader>uar", function()
local ref = vim.fn.expand "%:p" .. ":" .. vim.fn.line "."
vim.fn.setreg("+", ref)
vim.notify "Copied file:line reference"
end, { desc = "Copy file:line [R]eference" })
-- Copy file:line-range reference for AI tools (visual mode)
vim.keymap.set("v", "<leader>uar", function()
local start_line = vim.fn.line "v"
local end_line = vim.fn.line "."
if start_line > end_line then
start_line, end_line = end_line, start_line
end
local ref = vim.fn.expand "%:p" .. ":" .. start_line .. "-" .. end_line
vim.fn.setreg("+", ref)
vim.notify "Copied file:line range reference"
end, { desc = "Copy file:line range [R]eference" })
- <leader>ufa copies the current file's absolute path to the system clipboard
- <leader>uar does the same, but appends the current line number (: plus vim.fn.line "."), giving a path:line reference that tools like Claude Code understand as "look here"
In Normal mode, uar copies a single line; in Visual mode, it copies the whole selected range instead.
The keymap itself is entirely up to you. Use whatever key sequence you want, as long as it doesn't collide with an existing mapping. I settled on uar as in "User AI Reference," and ufa as in "User File Absolute." Silly mnemonics, I know, but they're memorable for me.
Sample output looks like this:
## absolute path (ufa)
/Users/rifqi/.config/nvim/lua/config/editing.lua
## single line (uar, from Normal mode)
/Users/rifqi/.config/nvim/lua/config/editing.lua:16
## line range (uar, from Visual mode)
/Users/rifqi/.config/nvim/lua/config/editing.lua:16-20
Once it's on the clipboard, I go back to the AI tool and paste the file path and line reference as context. I like this approach because it's flexible. I can reference a file outside the current working directory just as easily.
Referencing Skills and Commands
The same trick isn't limited to code. I keep a centralized repo of reusable skills and commands on my machine, written as markdown files, since they're generic enough to use across projects. When I want one, I copy its path from that repo the same way, and paste it into the AI tool: it picks up whatever is written in the file automatically.
Tip
In my experience, most AI CLI tools (including Claude Code, Codex, and the Cursor CLI) support a Vim mode inside their prompt input, usually via Ctrl+g. It drops you into a real Vim buffer where you can edit with normal Vim motions, then :wq to save and submit. Handy when a prompt spans multiple lines and you need real newlines instead of the tool treating Enter as submit. Check your specific tool's docs if Ctrl+g doesn't do it.
Trade-offs
Nvim isn't free. Here's what it actually costs you:
- Steep learning curve. I covered this above, but it bears repeating here: modal editing is unintuitive at first, and there's no shortcut around the time it takes to build the muscle memory. You will be slower than you were in your old editor before you're faster.
- No GUI. Everything is text and keyboard. There's no built-in file explorer you can click through, no visual diff view, no drag-and-drop. Plugins like Telescope paper over a lot of this, but you're still composing commands instead of pointing and clicking, and some workflows (native Android/iOS development, for instance) are still better served by a real IDE.
- The config is on you. Nvim ships closer to a blank slate than an IDE. That's the appeal, but it also means you own everything: picking plugins, wiring up LSP servers through Mason, keeping your Lua config working across updates. A plugin can change its API or get abandoned, and now it's your problem to fix, not a vendor's. Tools like chezmoi make the config portable across machines, but they don't make it maintenance-free.
Conclusion
Nvim isn't a replacement for every text editor, but it has become my daily driver for day-to-day engineering work involving text. Some tools are still best used through their GUI, for example Android Studio for Android development, or Xcode for iOS development. For pretty much everything else, I'm using Nvim.
The learning curve can be quite steep, so start small. Practice on an isolated project, and use Vim mode in your existing text editor or IDE; most modern IDEs support it. Keep it simple: you don't need every plugin, and you don't have to copy someone else's config. It's unique to you and your use case. You can build your setup as you go, adding new features and tweaking things based on what you actually need. There's no single best setup. Everyone ends up with their own.