Essential commands at a glance. The most frequently used Vim operations organized by category.
Vim is a modal editor. Each mode changes how the keyboard behaves. Mastering mode transitions is the foundation of Vim fluency.
| Mode | Enter | Exit | Purpose |
|---|---|---|---|
| Normal | Esc or Ctrl+[ | (default mode) | Navigate, operate on text, compose commands |
| Insert | i a o I A O s S c{motion} | Esc | Type text into the buffer |
| Visual | v (char) V (line) Ctrl+v (block) | Esc | Select text for operations |
| Command-line | : (Ex) / (search fwd) ? (search back) | Esc or Enter | Execute Ex commands, search |
| Replace | R (overtype) r (single char) | Esc | Overwrite existing text in place |
Movement commands are the building blocks of Vim's composable editing language. Every motion can be combined with operators like d, c, and y.
| Key | Action | Notes |
|---|---|---|
| h | Move left | |
| j | Move down | |
| k | Move up | |
| l | Move right | |
| w | Next word start | Punctuation-delimited |
| W | Next WORD start | Whitespace-delimited |
| b | Previous word start | |
| B | Previous WORD start | |
| e | End of word | |
| E | End of WORD | |
| ge | End of previous word |
Vim's editing grammar follows the pattern: [count][operator][count][motion/text-object]. Learn operators and motions separately, then combine them freely.
Text objects define structured regions of text. They are used with operators (d, c, y, v) and come in two flavors: inner (i) excludes delimiters, around (a) includes them.
| Inner | Around | Selects | Example Usage |
|---|---|---|---|
| iw | aw | Word | ciw change inner word, daw delete word + surrounding space |
| iW | aW | WORD (whitespace-delimited) | diW delete WORD including punctuation |
| is | as | Sentence | das delete sentence and trailing space |
| ip | ap | Paragraph | yap yank paragraph with surrounding blank lines |
| i" | a" | Double-quoted string | ci" change text inside double quotes |
| i' | a' | Single-quoted string | di' delete text inside single quotes |
| i` | a` | Backtick-quoted string | ci` change inside backticks |
| i( or ib | a( or ab | Parentheses | di( delete inside parens |
| i[ | a[ | Square brackets | ci[ change inside brackets |
| i{ or iB | a{ or aB | Curly braces | daB delete braces and their content |
| i< | a< | Angle brackets | ci< change inside angle brackets |
| it | at | HTML/XML tags | cit change inner tag content, dat delete entire tag |
# Given: function("hello world", 42)
# ^cursor here
ci" # Changes "hello world" to whatever you type
da( # Deletes ("hello world", 42) including parens
yi( # Yanks: "hello world", 42
# Given: <div class="box">content here</div>
# ^cursor here
cit # Changes "content here" inside the tags
dat # Deletes the entire <div>...</div>
vat # Visually selects the entire tag block
Visual mode lets you select text and then perform operations on the selection. Three sub-modes cover character, line, and block (column) selections.
Visual Block mode (Ctrl+v) enables powerful column editing. Select a rectangular block, then use these commands:
# 1. Position cursor at start of first line
# 2. Ctrl+v to enter visual block
# 3. j j j to extend selection down 3 lines
# 4. I to insert before block
# 5. Type "// " (comment prefix)
# 6. Esc -- all 4 lines now have "// " prepended
Vim has powerful search with full regex support, incremental highlighting, and flexible substitution commands.
| Command | Scope | Description |
|---|---|---|
| :s/old/new/ | Current line | Replace first occurrence on current line |
| :s/old/new/g | Current line | Replace all occurrences on current line |
| :%s/old/new/g | Entire file | Replace all occurrences in file |
| :%s/old/new/gc | Entire file | Replace all with confirmation prompt |
| :%s/old/new/gi | Entire file | Replace all, case-insensitive |
| :'<,'>s/old/new/g | Visual selection | Replace within visual selection |
| :5,20s/old/new/g | Lines 5-20 | Replace within line range |
| :.,$s/old/new/g | Here to end | Replace from current line to end of file |
The global command :g applies an Ex command to all lines matching a pattern. The inverse :v applies to non-matching lines.
# Delete trailing whitespace
:%s/\s\+$//e
# Convert tabs to 4 spaces
:%s/\t/ /g
# Wrap each line in quotes
:%s/.*/"&"/
# Remove duplicate blank lines
:%s/\n\{3,}/\r\r/g
# Search for whole word "foo" (case-insensitive)
/\v<foo>\c
Registers are Vim's named storage slots. They hold yanked/deleted text, macros, and special values. Macros record and replay sequences of keystrokes.
| Register | Name | Description |
|---|---|---|
| "" | Default (unnamed) | Last delete or yank goes here |
| "0 | Yank register | Last yank only (not affected by deletes) |
| "1 - "9 | Numbered | Delete history (1=most recent, shifts down) |
| "a - "z | Named | User-controlled, explicit storage |
| "A - "Z | Append to named | Append to existing register content |
| "+ | System clipboard | OS clipboard (requires clipboard support) |
| "* | Selection clipboard | X11 primary selection (Linux) |
| "_ | Black hole | Delete without affecting any register |
| "/ | Last search | Contains the last search pattern |
| ": | Last command | Contains the last Ex command |
| ". | Last insert | Contains text from last insert |
| "= | Expression | Evaluate expression and use result |
| "% | Current filename | Name of the current file |
Macros record a sequence of keystrokes into a register, then replay them. They are Vim's most powerful automation tool.
# Goal: Wrap each word in quotes on a list of words
# Before: apple, banana, cherry
# After: "apple", "banana", "cherry"
# Record macro:
qa # Start recording into register a
f, # Find next comma (or use w to move)
i"<Esc> # Insert quote before word
ea"<Esc> # Append quote after word
q # Stop recording
# Then: @a to play, or 10@a to repeat 10 times
Vim uses three layers of view management: buffers (open files), windows (viewport splits), and tabs (collections of windows). A buffer can be displayed in multiple windows.
Opening, saving, reading, and managing files from within Vim.
Inside Netrw:
Ex commands accessed with : provide powerful file-wide and multi-file operations. Most accept ranges to specify which lines to act on.
| Range | Meaning |
|---|---|
| . | Current line |
| $ | Last line of file |
| % | Entire file (same as 1,$) |
| 5 | Line 5 |
| 5,20 | Lines 5 through 20 |
| .,+5 | Current line through 5 lines below |
| '<,'> | Visual selection (auto-inserted) |
| 'a,'b | From mark a to mark b |
# Sort selected lines through external sort
:'<,'>!sort
# Format JSON in buffer using jq
:%!jq .
# Run current Python file
:!python %
# Insert today's date below cursor
:r !date
# Pipe visual selection to clipboard
:'<,'>w !pbcopy
Neovim extends Vim with built-in LSP, Tree-sitter, Lua scripting, and a rich plugin ecosystem. These features make Neovim a full-fledged IDE.
Neovim's built-in LSP client provides IDE features. Configure with nvim-lspconfig plugin and bind these functions:
| Lua Function | Action | Common Binding |
|---|---|---|
vim.lsp.buf.definition() | Go to definition | gd |
vim.lsp.buf.declaration() | Go to declaration | gD |
vim.lsp.buf.hover() | Show hover docs | K |
vim.lsp.buf.implementation() | Go to implementation | gi |
vim.lsp.buf.references() | Find references | gr |
vim.lsp.buf.rename() | Rename symbol | <leader>rn |
vim.lsp.buf.code_action() | Code actions | <leader>ca |
vim.lsp.buf.format() | Format buffer/range | <leader>f |
vim.diagnostic.goto_next() | Next diagnostic | ]d |
vim.diagnostic.goto_prev() | Previous diagnostic | [d |
Tree-sitter provides fast, incremental parsing for syntax highlighting, code folding, text objects, and more.
Tree-sitter enables smart text objects like function parameters, class bodies, and conditionals via nvim-treesitter-textobjects.
-- Set options
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
-- Set keymaps
vim.keymap.set('n', '<leader>w', ':w<CR>', { desc = 'Save file' })
vim.keymap.set('n', '<leader>q', ':q<CR>', { desc = 'Quit' })
vim.keymap.set('n', '<Esc>', ':noh<CR>', { desc = 'Clear highlights' })
-- Set leader key (must be before lazy.nvim setup)
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
-- Autocommands
vim.api.nvim_create_autocmd('BufWritePre', {
pattern = '*',
callback = function()
vim.cmd [[%s/\s\+$//e]]
end,
})
-- Run Vim commands from Lua
vim.cmd('colorscheme habamax')
vim.cmd.colorscheme('habamax') -- alternative syntax
| Plugin | Purpose |
|---|---|
lazy.nvim | Plugin manager (lazy-loading, lockfile, UI) |
nvim-lspconfig | LSP server configurations |
nvim-cmp | Autocompletion engine |
telescope.nvim | Fuzzy finder (files, grep, buffers, etc.) |
nvim-treesitter | Tree-sitter integration |
mason.nvim | LSP/DAP/linter/formatter installer |
gitsigns.nvim | Git integration (hunks, blame, signs) |
neo-tree.nvim | File explorer sidebar |
which-key.nvim | Keymap hints popup |
nvim-dap | Debug Adapter Protocol client |
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.loop.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)
-- Setup plugins
require('lazy').setup({
{ 'neovim/nvim-lspconfig' },
{ 'nvim-telescope/telescope.nvim', dependencies = { 'nvim-lua/plenary.nvim' } },
{ 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate' },
-- Add more plugins here
})
Essential settings for both Vim (~/.vimrc) and Neovim (~/.config/nvim/init.lua). These form a solid baseline for any setup.
" === UI ===
set number " Show line numbers
set relativenumber " Relative line numbers
set cursorline " Highlight current line
set signcolumn=yes " Always show sign column
set scrolloff=8 " Keep 8 lines visible above/below cursor
set sidescrolloff=8 " Keep 8 columns visible left/right
set colorcolumn=80 " Show column guide at 80
set showmode " Show current mode
set showcmd " Show partial command
set laststatus=2 " Always show status line
set wildmenu " Enhanced command-line completion
set wildmode=longest:full,full
" === Search ===
set hlsearch " Highlight search results
set incsearch " Incremental search
set ignorecase " Case-insensitive search...
set smartcase " ...unless uppercase is used
" === Tabs & Indentation ===
set tabstop=4 " Tab width = 4 spaces
set shiftwidth=4 " Indent width = 4 spaces
set expandtab " Use spaces instead of tabs
set smartindent " Smart auto-indentation
set autoindent " Copy indent from current line
" === Behavior ===
set hidden " Allow hidden buffers with changes
set mouse=a " Enable mouse in all modes
set clipboard=unnamedplus " Use system clipboard
set splitright " New vertical splits go right
set splitbelow " New horizontal splits go below
set undofile " Persistent undo across sessions
set noswapfile " Disable swap files
set updatetime=250 " Faster CursorHold events
set timeoutlen=300 " Faster key sequence timeout
" === Leader Key ===
let mapleader = " " " Space as leader key
" === Key Mappings ===
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <Esc> :noh<CR>
nnoremap <leader>e :Ex<CR>
" Move lines up/down
vnoremap J :m '>+1<CR>gv=gv
vnoremap K :m '<-2<CR>gv=gv
" Keep cursor centered on search navigation
nnoremap n nzzzv
nnoremap N Nzzzv
-- Leader key (set before plugins)
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
-- UI
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
vim.opt.signcolumn = 'yes'
vim.opt.scrolloff = 8
vim.opt.sidescrolloff = 8
vim.opt.colorcolumn = '80'
vim.opt.termguicolors = true
vim.opt.showmode = false -- hide mode (use statusline)
-- Search
vim.opt.hlsearch = true
vim.opt.incsearch = true
vim.opt.ignorecase = true
vim.opt.smartcase = true
-- Tabs & Indentation
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
-- Behavior
vim.opt.hidden = true
vim.opt.mouse = 'a'
vim.opt.clipboard = 'unnamedplus'
vim.opt.splitright = true
vim.opt.splitbelow = true
vim.opt.undofile = true
vim.opt.swapfile = false
vim.opt.updatetime = 250
vim.opt.timeoutlen = 300
-- Keymaps
local map = vim.keymap.set
map('n', '<leader>w', ':w<CR>', { desc = 'Save' })
map('n', '<leader>q', ':q<CR>', { desc = 'Quit' })
map('n', '<Esc>', ':noh<CR>', { desc = 'Clear search' })
map('n', '<leader>e', ':Ex<CR>', { desc = 'File explorer' })
-- Move lines in visual mode
map('v', 'J', ":m '>+1<CR>gv=gv", { desc = 'Move line down' })
map('v', 'K', ":m '<-2<CR>gv=gv", { desc = 'Move line up' })
-- Keep cursor centered
map('n', 'n', 'nzzzv')
map('n', 'N', 'Nzzzv')
map('n', '<C-d>', '<C-d>zz')
map('n', '<C-u>', '<C-u>zz')
Advanced techniques and mental models that separate Vim beginners from power users.
The . command repeats the last change. Structure your edits to maximize repeatability: use one atomic editing command that captures the full intent of your change.
# BAD: Multiple steps, not repeatable as one unit
f"lr'f"lr' # Find quote, replace, find next, replace
# GOOD: Repeatable single change
f"r' # Replace one quote...
;. # ; repeats f", . repeats r' -- pure magic!
# BEST: Use :s for bulk, but for scattered edits:
ciw<new_word>Esc # Change word, then use n. to find-and-replace
# (after searching with * first)
One of Vim's most powerful patterns. gn is a motion that selects the next search match. Combined with c, it becomes a targeted find-and-replace you control with .
# Step 1: Search for the target
/oldFunction # or use * on the word
# Step 2: Change first occurrence
cgn # Deletes match and enters insert mode
newFunction # Type replacement
Esc # Back to normal mode
# Step 3: Repeat selectively
. # Changes next match automatically
. # And the next...
n # Skip one (don't change it)
. # Change this one
# Why this is better than :%s/old/new/gc:
# - You see each change in context before deciding
# - Works across multiple files with :bufdo
# - Fully undoable one at a time
Vim's editing language follows a grammar: [count] operator [count] motion/text-object. Learning operators and motions separately gives you multiplicative power.
| w word | $ end | iw inner word | i" inner quotes | ip inner para | |
|---|---|---|---|---|---|
| d delete | dw | d$ | diw | di" | dip |
| c change | cw | c$ | ciw | ci" | cip |
| y yank | yw | y$ | yiw | yi" | yip |
| v select | vw | v$ | viw | vi" | vip |
| > indent | >w | >$ | >iw | >i" | >ip |
| gU upper | gUw | gU$ | gUiw | gUi" | gUip |
# Add text to multiple lines at once:
Ctrl+v # Enter visual block
jjjj # Select 5 lines
I # Insert mode (at block start)
const # Type prefix
Esc # Applied to all 5 lines!
# Append to lines of different lengths:
Ctrl+v # Enter visual block
jjjj # Select lines
$ # Extend to end of each line
A # Append mode
; # Type suffix
Esc # Applied to all lines!
# Change column of text:
Ctrl+v # Enter visual block
jjjj # Select block region
llll # Extend selection right
c # Change block
newtext # Type replacement
Esc # Applied to all lines!
You don't always need to leave insert mode to perform quick actions: