env.dev

Neovim: A Developer Guide to the Modern Vim

Master Neovim: modes, motions, text objects, registers, built-in LSP, Tree-sitter, Lua configuration, lazy.nvim plugins, and macros.

Last updated:

Neovim is a modern, extensible fork of Vim that keeps full backward compatibility while adding first-class Lua scripting, a built-in LSP client, Tree-sitter integration, and asynchronous job control. It is designed for developers who want a fast, keyboard-driven editor that can be shaped into a full IDE — see the Neovim cheat sheet for a quick reference.

What are Neovim's modes?

Neovim is a modal editor — the meaning of each key depends on which mode you are in. Understanding modes is the single most important concept for productive editing.

ModeEnter withPurpose
NormalEscNavigate, delete, copy, paste, run commands
Inserti / a / oType text into the buffer
Visualv / V / Ctrl-vSelect text (character, line, or block)
Command-line:Execute Ex commands, search & replace
Terminal:terminalRun a shell inside a Neovim buffer

How do Neovim operators and motions combine?

Neovim commands follow the grammar operator + [count] + motion. Learning a handful of operators and motions gives you a combinatorial explosion of editing power.

Common Operators

OperatorAction
dDelete
cChange (delete + enter Insert mode)
yYank (copy)
>Indent right
<Indent left
gqFormat / rewrap text
gu / gULowercase / uppercase

Common Motions

text
w / b       → forward / backward by word
e           → end of word
0 / $       → start / end of line
gg / G      → top / bottom of file
f{char}     → jump to next {char} on the line
t{char}     → jump to just before {char}
/{pattern}  → search forward
?{pattern}  → search backward
%           → matching bracket

Combine them: d2w deletes two words, ci" changes the text inside quotes, yG yanks from the cursor to the end of the file.

What are Neovim text objects?

Text objects let you operate on structured chunks of text. Prefix with i for inner (contents only) or a for around (including delimiters).

iw / aw

Inner / around word

diw

i" / a"

Inside / around double quotes

ci"

i( / a(

Inside / around parentheses

da(

i{ / a{

Inside / around curly braces

yi{

it / at

Inside / around HTML/XML tag

cit

ip / ap

Inner / around paragraph

dap

How do Neovim registers work?

Registers are named storage slots for yanked or deleted text. Prefix a command with "{reg} to target a specific register.

RegisterDescription
"Default (unnamed) register
0Last yank
1-9Last 9 deletes (stack)
a-zNamed registers (you choose)
+System clipboard
_Black hole (discard)
.Last inserted text
%Current filename
:Last command-line command

Example: "ayy yanks the current line into register a, and "ap pastes it. Use "+y to copy to the system clipboard.

Buffers, Windows & Tabs

Neovim separates the concepts of files (buffers), viewports (windows), and layouts (tabs). A single file can be displayed in multiple windows across multiple tabs.

text
# Buffers
:e file.lua          Open a file in a new buffer
:bnext / :bprev      Navigate buffers
:ls                  List all buffers
:bd                  Close current buffer

# Windows (splits)
:split / :vsplit     Horizontal / vertical split
Ctrl-w h/j/k/l      Move between windows
Ctrl-w =             Equalize window sizes
Ctrl-w o             Close all other windows

# Tabs
:tabnew              New tab
gt / gT              Next / previous tab

How do I enable the built-in LSP in Neovim?

Neovim ships with a built-in Language Server Protocol client that provides IDE features like go-to-definition, autocomplete, hover docs, rename, and diagnostics — all without plugins.

lua
-- init.lua: minimal LSP setup (Neovim 0.11+)
vim.lsp.enable('lua_ls')       -- enable lua-language-server
vim.lsp.enable('ts_ls')        -- enable typescript-language-server

-- Common keymaps (set in an LspAttach autocmd)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition)
vim.keymap.set('n', 'gr', vim.lsp.buf.references)
vim.keymap.set('n', 'K',  vim.lsp.buf.hover)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename)
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next)

For automatic server installation, the popular mason.nvim and mason-lspconfig.nvim plugins can manage language server binaries for you.

What does Tree-sitter give Neovim?

Neovim integrates Tree-sitter for fast, accurate syntax highlighting and code-aware text objects. Instead of regex-based highlighting, Tree-sitter builds a real parse tree of your code.

lua
-- Install parsers (via nvim-treesitter plugin)
:TSInstall lua typescript python rust

-- Tree-sitter powered text objects (with nvim-treesitter-textobjects)
-- vaf  → select outer function
-- vic  → select inner class
-- ]m   → jump to next method

How do I configure Neovim with Lua?

Neovim uses init.lua (or the legacy init.vim) located in ~/.config/nvim/. Lua is the recommended language for all new configuration.

lua
-- ~/.config/nvim/init.lua

-- Options
vim.opt.number         = true       -- line numbers
vim.opt.relativenumber = true       -- relative line numbers
vim.opt.tabstop        = 2          -- tab width
vim.opt.shiftwidth     = 2          -- indent width
vim.opt.expandtab      = true       -- spaces instead of tabs
vim.opt.signcolumn     = 'yes'      -- always show sign column
vim.opt.clipboard      = 'unnamedplus'  -- use system clipboard
vim.opt.ignorecase     = true       -- case-insensitive search
vim.opt.smartcase      = true       -- unless uppercase is used

-- Leader key
vim.g.mapleader = ' '

-- Keymaps
vim.keymap.set('n', '<leader>w', ':w<CR>')
vim.keymap.set('n', '<leader>q', ':q<CR>')
vim.keymap.set('n', '<Esc>', ':nohlsearch<CR>')

How do I manage Neovim plugins?

lazy.nvim is the de facto standard plugin manager. It supports lazy loading, lockfiles, a UI dashboard, and automatic dependency resolution.

lua
-- Bootstrap lazy.nvim (place at top of init.lua)
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.uv.fs_stat(lazypath) then
  vim.fn.system({
    'git', 'clone', '--filter=blob:none',
    'https://github.com/folke/lazy.nvim.git',
    '--branch=stable', lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require('lazy').setup({
  { 'nvim-telescope/telescope.nvim',  -- fuzzy finder
    dependencies = { 'nvim-lua/plenary.nvim' } },
  { 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate' },
  { 'neovim/nvim-lspconfig' },         -- LSP configs
  { 'hrsh7th/nvim-cmp' },              -- autocompletion
})

Essential Plugins

telescope.nvim

Fuzzy finder for files, grep, buffers, and more

:Telescope find_files

nvim-cmp

Autocompletion engine with LSP, snippet, and path sources

Ctrl-n / Ctrl-p

oil.nvim

File explorer as an editable buffer

:Oil

gitsigns.nvim

Git signs in the gutter, inline blame, hunk actions

:Gitsigns preview_hunk

conform.nvim

Formatter integration (prettier, stylua, etc.)

:ConformFormat

nvim-lint

Async linter integration (eslint, ruff, etc.)

Runs on save

What are common Neovim pitfalls?

  • Clipboard not working. Setting vim.opt.clipboard = 'unnamedplus' requires a system provider: install xclip or xsel on X11, wl-clipboard on Wayland, or use pbcopy/pbpaste on macOS. Run :checkhealth provider to confirm.
  • Skipping :checkhealth. :checkhealth surfaces missing providers, broken Tree-sitter parsers, LSP server issues, and clipboard problems. Run it after every major config change.
  • Leader-key conflicts. Set vim.g.mapleader before loading lazy.nvim and any plugin specs — otherwise plugin keymaps bind to the wrong leader and your custom mappings silently fail.
  • Lazy-loading order. Plugins lazy-loaded by event or filetype won't run their config on startup. If a colorscheme or LSP plugin must load eagerly, set lazy = false and use priority = 1000 for the colorscheme.
  • init.vim shadows init.lua. Neovim loads only one entry point. If both ~/.config/nvim/init.vim and init.lua exist, init.vim wins and your Lua config is ignored. Delete the legacy file when migrating.

Macros

Record a sequence of keystrokes and replay them. This is one of Neovim's most powerful features for repetitive edits.

text
q{reg}       Start recording into register {reg}
q            Stop recording
@{reg}       Play macro from register {reg}
@@           Repeat last played macro
5@a          Play macro 'a' five times

# Example: wrap each line in quotes
qa           Record into register a
0i"<Esc>     Go to start, insert "
A"<Esc>      Go to end, append "
j            Move to next line
q            Stop recording
99@a         Apply to next 99 lines

References

See the Neovim Cheat Sheet for a quick reference of common keybindings.

Was this helpful?

Read next

Neovim Lua: Env Variables, vim.api & init.lua

Pass env vars to vim.fn.jobstart, learn the vim.* namespace (vim.fn, vim.api, vim.opt), and build a clean init.lua with autocommands and vim.uv async I/O.

Continue →

Frequently Asked Questions

What is the difference between Vim and Neovim?

Neovim is a fork of Vim with a focus on extensibility and modern features. Key differences: Lua scripting (in addition to Vimscript), built-in LSP client, built-in terminal emulator, Tree-sitter integration, and asynchronous plugin architecture.

How do I install plugins in Neovim?

Use a plugin manager like lazy.nvim (recommended), packer.nvim, or vim-plug. Define plugins in your init.lua configuration file. lazy.nvim is the most popular choice for its lazy-loading support and lockfile.

How do I migrate from Vim to Neovim?

Neovim reads your existing ~/.vimrc and most Vim plugins work unchanged. To start fresh, create ~/.config/nvim/init.lua and gradually move settings from Vimscript to Lua. Run :checkhealth to validate your setup, and use :help nvim-from-vim for the full migration guide covering removed options and behaviour differences.

What is lazy.nvim?

lazy.nvim is the de facto plugin manager for Neovim, written by folke. It supports lazy loading by event, command, filetype, or keymap; a lockfile (lazy-lock.json) for reproducible installs; a UI dashboard (:Lazy); and automatic dependency resolution. It has largely replaced packer.nvim and vim-plug in modern Neovim configs.

Stay up to date

Get notified about new guides, tools, and cheatsheets.