Skip to content

Vim Шпаргалка

Highly configurable text editor for efficient text editing.

01

Getting Started

Modes & Basic Movement

Vim is modal — different modes serve different purposes. Esc returns to Normal mode from any other mode. The hjkl keys keep your hands on the home row. w/b move by word, 0/$ move to line boundaries, gg/G jump to file start/end, :N jumps to line N.

vim
# Vim Modes
# Normal mode (Esc)    - default, for navigation
# Insert mode (i)      - for typing text
# Visual mode (v)      - for selecting text
# Command mode (:)     - for Ex commands

# Basic movement (Normal mode)
h     # left
j     # down
k     # up
l     # right
w     # next word start
b     # previous word start
0     # beginning of line
$     # end of line
gg    # first line
G     # last line
:42   # go to line 42

Exiting & Saving

ZZ and ZQ are the fastest ways to exit — they don't require reaching for the colon. :x only writes if the file has changed (preserves mtime), unlike :wq which always writes. :w! overrides read-only flags if filesystem permissions allow. Use :qa! to bail out of multiple modified buffers.

vim
:w              # write (save) file
:w!             # force write (override read-only)
:q              # quit
:q!             # force quit (discard changes)
:wq             # write and quit
:x              # write only if changed, then quit
ZZ              # same as :x (write if changed, quit)
ZQ              # quit without saving (like :q!)
:w newfile.txt  # save as new file
:sav newfile.txt  # save as new file and switch to it
:wa             # write all buffers
:xa             # write all changed buffers and exit
:qa!            # force quit all (discard everything)

Help System

Vim's help is comprehensive and hyperlinked. Ctrl-] follows a tag (link) and Ctrl-T pops back. Help notation: i_CTRL-N means Ctrl-N in insert mode, 'number' (with quotes) means an option. :helpgrep searches all help files at once — use :cn/:cp to navigate matches. K invokes man for the keyword under the cursor (configurable via 'keywordprg').

vim
:help               # main help
:help subject       # help on subject (e.g. :help insert)
:help i_CTRL-N      # help on Ctrl-N in insert mode
:help 'number'      # help on the 'number' option (quotes)
:helpgrep pattern   # search all help files for pattern
:cn / :cp           # next/prev helpgrep match
K                   # man page for word under cursor (Normal mode)
Ctrl-]              # jump to tag under cursor (follow link)
Ctrl-T              # jump back from tag (pop tag stack)
:helptags ~/.vim/doc  # regenerate help tags for a doc directory

Word & Character Movement

Lowercase motions (w/b/e) treat punctuation as word separators — useful for code. Uppercase motions (W/B/E) only treat whitespace as separators — useful for prose. e/E move to the end of the next word; ge/gE move to the end of the previous word (rarely used but handy).

vim
# Word motions (punctuation-aware)
w     # next word start
b     # previous word start
e     # end of next word
ge    # end of previous word

# WORD motions (whitespace-only delimiters, uppercase)
W     # next WORD start
B     # previous WORD start
E     # end of next WORD
gE    # end of previous WORD

# Word boundaries:
# word: sequence of word-characters OR sequence of punctuation
# WORD: anything separated by whitespace

# Examples (cursor on 'a' in "foo.bar baz"):
#   w -> 'bar'  (punctuation splits words)
#   W -> 'baz'  (only whitespace splits WORDS)

Line Movement

0 and $ go to absolute line start/end (including whitespace). ^ and g_ skip leading/trailing whitespace — usually what you want. When 'wrap' is on, g0/g$ operate on screen lines while 0/$ operate on logical lines. | jumps to a specific column number.

vim
0     # first column (absolute start)
^     # first non-blank character
$     # end of line (last character)
g_    # last non-blank character
g0    # start of screen line (with wrap)
g$    # end of screen line (with wrap)
gm    # middle of screen line
gM    # middle of text line
|     # column N (e.g. 10| = column 10)
+     # first non-blank of next line
-     # first non-blank of previous line
_     # first non-blank of current line [count down]

File & Screen Movement

H/M/L jump within the visible screen. Ctrl-d/u scroll half a page (less disorienting than full-page Ctrl-f/b). zz/zt/zb scroll the current line to the middle/top/bottom of the screen without moving the cursor — extremely useful for keeping context while editing. N% jumps to a percentage of the file.

vim
gg         # go to first line
G          # go to last line
Ngg / NG   # go to line N (e.g. 50gg or 50G)
:N         # go to line N (Ex command)
H          # top of screen (High)
M          # middle of screen
L          # bottom of screen (Low)
N%         # jump to N% of the file
Ctrl-f     # scroll forward (full page)
Ctrl-b     # scroll backward (full page)
Ctrl-d     # scroll down (half page)
Ctrl-u     # scroll up (half page)
zz         # scroll current line to middle
zt         # scroll current line to top
zb         # scroll current line to bottom
02

Inserting Text

Basic Insert (i, I)

i/I insert before the cursor / at line start. a/A insert after the cursor / at line end. gi returns to the last insert position (mark '^) — handy when you Esc to do something then want to keep typing. gI goes to the absolute column 1, ignoring leading whitespace.

vim
i     # insert before cursor
I     # insert at first non-blank (same as ^i)
gi    # insert at last insert position (also sets mark)
gI    # insert at column 1 (same as 0i)
a     # insert after cursor
A     # insert at end of line (same as $a)

# Examples:
#   foo|bar  + i  ->  foo|bar (cursor stays, type before)
#   foo|bar  + I  ->  |foobar (cursor at first non-blank)
#   foo|bar  + A  ->  foobar|  (cursor at end)

# gi returns to where you last left insert mode —
# very useful after escaping to run a Normal-mode command.

Open New Lines (o, O)

o and O open a new line below/above the cursor and enter insert mode. They're the fastest way to add a line — no need to position the cursor at line end. Indentation is auto-copied from the surrounding lines (controlled by 'autoindent').

vim
o     # open new line BELOW cursor, enter insert
O     # open new line ABOVE cursor, enter insert

# Before (cursor on middle line):
#   line one
#   li|ne two
#   line three

# After 'o':
#   line one
#   line two
#   |             <- new blank line, insert mode
#   line three

# After 'O':
#   line one
#   |             <- new blank line, insert mode
#   line two
#   line three

Change Commands (c, C, s, S)

c{motion} deletes the motion target and enters insert mode — the most flexible edit command. C is c$ (change to end of line), S is cc (change whole line). s deletes one character and enters insert; combine with counts like 3s. ciw/caw use text objects (see dedicated section).

vim
cw / ce    # change to end of word
cb         # change backward to word start
c$ / C     # change to end of line
cc / S     # change entire line
c0         # change to beginning of line
c^         # change to first non-blank
ct,        # change up to (but not including) next ','
cfx        # change up to and including next 'x'
ciw        # change inner word (no surrounding whitespace)
caw        # change a word (with surrounding whitespace)
cap        # change around paragraph

s          # substitute: delete char under cursor, insert
S          # substitute: delete entire line, insert (like cc)

Replace Mode (r, R, gr)

r replaces a single character and returns to Normal mode immediately (no mode change). R enters Replace mode where typing overwrites characters — Backspace restores them, unlike Insert mode which just deletes. Use gr/gR (virtual replace) when working with tabs — it preserves tab alignment instead of shifting text.

vim
r{x}     # replace single char under cursor with x
gr{x}    # virtual replace (doesn't shift tabs)
R        # enter Replace mode (overwrite until Esc)
gR       # virtual Replace mode (tabs stay aligned)
3r{x}    # replace 3 chars with x (single char only)

# In Replace mode, typing overwrites existing text
# but Backspace restores the original characters.
# Tabs are preserved with virtual replace (gr/gR).

# Examples:
#   abc|def  + rx  ->  abc|xef  (cursor advances)
#   abc|def  + R  ->  enter Replace mode
#     type 'XYZ'  ->  abcXYZ|f
#     Backspace   ->  abcXY|def  (original restored)

Special Inserts

:r !cmd inserts shell command output into the buffer — great for pasting command output, dates, file lists. In insert mode, Ctrl-r followed by a register name inserts that register; Ctrl-r = evaluates a Vimscript expression and inserts the result. Ctrl-k inserts a digraph (special character) by typing two ASCII chars.

vim
:r file.txt        # insert contents of file below cursor
:r !command        # insert output of shell command
:0r !date          # insert date at top of file
:r !ls -la         # insert directory listing

# Insert with special registers
Ctrl-r =           # expression register: evaluate, insert result
Ctrl-r "           # insert unnamed register
Ctrl-r %           # insert current filename

# Insert from current file path
:i! !pwd           # insert pwd output (no autoindent)

# Non-ASCII / digraphs (in insert mode)
Ctrl-k {ch1}{ch2}  # digraph: e.g. Ctrl-k e' produces é
:digraphs          # list available digraphs

Insert Mode Shortcuts

Ctrl-h/w/u work like Backspace/Delete-word/Delete-line within insert mode. Ctrl-t/Ctrl-d adjust indentation without leaving insert mode — invaluable for code. Ctrl-n/Ctrl-p do keyword completion from buffers and tags. Ctrl-o runs a single Normal command then returns to insert (e.g. Ctrl-o zz to recenter). Ctrl-g u breaks the undo chain so a long insert can be undone in pieces.

vim
# In insert mode:
Ctrl-h     # delete char before cursor (Backspace)
Ctrl-w     # delete word before cursor
Ctrl-u     # delete to start of line
Ctrl-t     # indent current line (one shiftwidth)
Ctrl-d     # dedent current line (one shiftwidth)
Ctrl-n     # keyword completion (forward)
Ctrl-p     # keyword completion (backward)
Ctrl-o     # execute one Normal command, return to insert
Ctrl-r {reg}  # insert register contents
Ctrl-a     # insert text typed in last insert mode
Ctrl-@     # insert last text + Esc (like Ctrl-a then Esc)
Ctrl-g u   # break undo sequence (start new undo block)
Ctrl-]     # trigger abbreviation without inserting
03

Editing Commands (Delete/Yank/Paste)

Delete Commands

d{motion} is the universal delete operator — combine with any motion. dd deletes the whole line. x is a shortcut for dl (delete char). Deleted text goes into the unnamed register (and register 1). Use d with text objects (ciw, dib) for powerful edits. J joins the next line onto the current one with a single space.

vim
x     # delete char under cursor (also 'dl')
X     # delete char before cursor (also 'dh')
dd    # delete current line
dw    # delete to next word start
de    # delete to end of word
d$ / D  # delete to end of line
d0    # delete to beginning of line
d^    # delete to first non-blank
dgg   # delete from cursor to first line
dG    # delete from cursor to last line
J     # join next line (removes leading whitespace, inserts space)
gJ    # join next line without inserting space
3dd   # delete 3 lines
dW    # delete WORD (whitespace-separated)

Yank (Copy)

y{motion} copies text without deleting. yy yanks the line. Unlike d (which has x as a shortcut), y has no single-character shortcut — you must use a motion. Yank always updates register 0, useful for distinguishing from deletes. Use text objects: ya( yanks around parens, yi" yanks inside quotes.

vim
y      # yank {motion} (no character shortcut)
yy / Y # yank current line
yw     # yank to next word start
ye     # yank to end of word
y$     # yank to end of line
y0     # yank to beginning of line
yG     # yank to end of file
ygg    # yank to beginning of file
5yy    # yank 5 lines
ya(    # yank around parentheses
yi"    # yank inside double quotes
yt,    # yank up to (not including) next comma

# Yanked text goes to:
#   "  (unnamed register)
#   0  (yank register, only updated by y)

Put (Paste)

p pastes after the cursor (or below the line for linewise data); P pastes before. gp/gP move the cursor to the end of the pasted text — useful when chaining operations. ]p/[p reindent pasted lines to match surroundings. The clipboard register (+) shares with the system clipboard if 'clipboard' is set properly.

vim
p      # paste after cursor (linewise: line below)
P      # paste before cursor (linewise: line above)
gp     # paste after, leave cursor on last pasted char
gP     # paste before, leave cursor on last pasted char
]p     # paste after, reindent to match
[p     # paste before, reindent to match
"+p    # paste from system clipboard
"*p    # paste from primary selection (X11)

# Linewise vs charwise:
#   yy + p   ->  pasted as a new line below
#   y$ + p   ->  pasted after cursor on same line

# Counts: 3p pastes the register 3 times.

Changing Case

~ toggles a single character and advances. g~/gu/gU are operators that work with motions or text objects. In visual mode, U/u/~ act on the selection. Use gUiw to uppercase the current word, gUU for the whole line. These are non-destructive — only case changes.

vim
~      # toggle case of char under cursor (advances)
3~     # toggle case of next 3 chars
g~{motion}  # toggle case of motion
gu{motion}  # lowercase motion
gU{motion}  # uppercase motion
g~~     # toggle case of current line
guu     # lowercase current line
gUU     # uppercase current line
g~~gg   # toggle case to first line

# Visual mode:
#   U    # uppercase selection
#   u    # lowercase selection
#   ~    # toggle case of selection

# Examples:
#   hello  + gUw  ->  HELLO
#   HELLO  + guw  ->  hello
#   hello  + g~w  ->  HELLO

Joining & Splitting Lines

J joins lines with a single space; gJ joins without inserting anything. :join works on a range. gq is the format operator — it wraps lines to 'textwidth' (commonly 80), respecting 'formatoptions'. Use gqip to reflow a paragraph, gqq for one line. Set 'textwidth=80' to enable auto-wrapping.

vim
J      # join next line with current (inserts space)
gJ     # join next line without inserting space
3J     # join next 3 lines into one
:[range]join  # join lines in range (also :j)
:[range]join! # join without inserting space

# Splitting a line:
#   In insert mode, press Enter (autoindent applies)
#   In normal mode: r<Enter> replaces char with newline

# Break a long line at column 80 (with 'textwidth'):
gq{motion}    # format motion text to 'textwidth'
gqq           # format current line
gqip          # format paragraph
gvgq          # format visual selection

# Set text width:
:set textwidth=80

Repeat & Undo

. repeats the last change — the single most useful key in Vim. u/Ctrl-r are undo/redo. U undoes all changes on the current line. :earlier/:later can time-travel by time or count, even across branches (Vim keeps an undo tree). Use :undolist to see branches. @: repeats the last Ex command, & repeats the last substitute.

vim
.        # repeat last change (most powerful key in Vim)
u        # undo
Ctrl-r   # redo
U        # undo all changes on current line
:Nu      # undo N changes
:earlier 10m   # revert to state 10 minutes ago
:later 5       # redo 5 changes
:undolist      # show undo tree branches

# Repeat with counts:
5.        # repeat last change 5 times

# Repeat Ex commands:
@:        # repeat last :command
&         # repeat last :s (substitute)

# Repeat macros/marks across files:
:&        # repeat last substitute on current line
04

Search & Substitute

Basic Search

/ and ? search forward/backward; n/N repeat. * searches for the whole word under the cursor (word boundaries), g* searches for any substring match. :noh clears current highlighting until the next search. Set 'hlsearch' to highlight all matches persistently, 'incsearch' for incremental matching as you type.

vim
/pattern     # search forward for pattern
?pattern     # search backward
n            # repeat search (same direction)
N            # repeat search (opposite direction)
*            # search forward for word under cursor
#            # search backward for word under cursor
g*           # search forward (loose, matches substring)
g#           # search backward (loose)

# Search history:
#   Press / then Up/Down to recall previous searches

# Clear search highlighting:
:nohlsearch   # or :noh

# Toggle 'hlsearch' permanently:
:set hlsearch!

Search Options

'ignorecase' + 'smartcase' is the recommended combo: case-insensitive unless your pattern contains an uppercase letter. \c/\C override case per-search. \v enables 'very magic' mode where many characters become special without escaping — cleaner regex syntax. Search offsets (e, b) place the cursor relative to the match.

vim
:set ignorecase   # case-insensitive search (ic)
:set smartcase    # case-sensitive if pattern has uppercase
:set hlsearch     # highlight all matches (hls)
:set incsearch    # show match as you type (is)
:set wrapscan     # wrap around end of file (ws)
:set gdefault     # add 'g' flag to all :s by default

# Per-search case override:
/foo\c    # case-insensitive search for foo
/foo\C    # case-sensitive search for foo

# Use very-magic mode (less escaping):
/foo\v(.+)@<=(bar)   # very magic regex

# Search offsets:
/foo/e     # cursor on last char of match
/foo/e+2   # cursor 2 chars past match
/foo/b-1   # cursor 1 char before match

Search Patterns (Regex)

Vim regex differs from PCRE — backslashes are needed for +, =, {, (, |. Use \v prefix for 'very magic' mode where these become special without escaping (closer to PCRE). \< \> mark word boundaries. \d/\w/\s are character classes. Use \(...\) for capturing groups, \1/\2 for backreferences.

vim
# Standard Vim regex (default magic mode)
.           # any char
*           # zero or more (preceding atom)
\+          # one or more
\=          # zero or one (optional)
\{n,m}      # between n and m
^  $        # start/end of line
\<  \>      # word boundaries
[abc]       # char class (a, b, or c)
[^abc]      # negated char class
\( \)      # group
\1 \2       # backreference
\|          # or

# Predefined classes:
\d \D      # digit / non-digit
\w \W      # word char / non-word
\s \S      # whitespace / non-whitespace

# Very magic (\v prefix) — less escaping:
/\v\w+\s+\w+   # word, spaces, word (no backslashes needed)
/\v(%d+)      # capture one-or-more digits

Substitute Command

:[range]s/pattern/replacement/flags is the substitute command. :% means the whole file. Without 'g' flag, only the first match per line is replaced; 'g' replaces all. 'c' asks for confirmation (y/n/a/q/l). Use \v for very-magic to simplify grouping syntax. Capture groups are referenced as \1, \2 in the replacement.

vim
:[range]s/old/new/[flags]

# Basic:
:s/old/new/        # replace first 'old' on current line
:s/old/new/g       # replace all 'old' on current line
:%s/old/new/g      # replace all in whole file
:%s/old/new/gc     # confirm each replacement
:%s/old/new/gi     # case-insensitive

# Range examples:
:5,10s/old/new/g   # lines 5-10
:.,$s/old/new/g    # current line to end
:'a,'bs/old/new/g  # from mark a to mark b
:'<,'>s/old/new/g  # visual selection
:1,$s/old/new/g    # whole file (same as %)

# Replace with capture groups:
:%s/\(\w\+\),\(\w\+)/\2,\1/   # swap two comma-separated words
:%s/\v(\w+),(\w+)/\2,\1/   # same with very-magic

Substitute Flags & Special Replacement

Common flags: g (all), c (confirm), i (case-insensitive), n (count only). & in the replacement inserts the matched text. \u/\U/\l/\L modify case of the replacement. Use \r to insert a newline (\n is a null byte in replacement). The 'n' flag is great for counting matches without changing anything.

vim
# Flags:
g     # all occurrences on each line (not just first)
c     # confirm each substitution
i     # ignore case
I     # don't ignore case
e     # don't error if no match
p     # print last substituted line
n     # count matches, don't substitute

# Special chars in replacement:
&         # the matched text
\0        # the matched text (same as &)
\1..\9    # capture groups
\u         # next char uppercase
\U         # rest of string uppercase (until \e or \E)
\l \L      # lowercase (single / until \e)
\r         # split into new line
\n         # null byte (use \r for newline)

# Examples:
:%s/foo/&bar/g        # foo -> foobar
:%s/\v(\w+)/\U\1/g   # uppercase every word
:%s/, /,\r/g          # split lines on ', '
:%s/old/new/gn        # count matches only

Global Command (:g)

:g runs an Ex command on every line matching a pattern — a powerful 'global' editor. :v (or :g!) runs on non-matching lines. Combine with any Ex command: d (delete), m (move), t (copy), s (substitute), normal (run normal-mode command). Useful for batch operations on tagged lines. Order matters: :g processes top-to-bottom by default.

vim
:[range]g/pattern/cmd    # run cmd on each matching line
:[range]g!/pattern/cmd   # run cmd on NON-matching lines
:v/pattern/cmd           # same as :g! (inverse)

# Common uses:
:g/^$/d                  # delete all blank lines
:g/TODO/d                # delete all lines with TODO
:g/pattern/p             # print all matching lines
:g/pattern/normal @a     # run macro a on each matching line
:g/pattern/m0            # move matching lines to top (reverse order)
:g/pattern/t$            # copy matching lines to end
:g/pattern/s/old/new/g   # substitute on matching lines only

# Combined with Ex range:
:g/^#/s/foo/bar/g        # in lines starting with #, replace foo with bar

# Multiple commands per match:
:g/pattern/d|t0          # delete then copy (chained with |)
05

Visual Mode

Visual Mode Types

v/V/Ctrl-v select characters/lines/block. gv reselects the last visual selection — handy for re-applying an operation. o moves to the other end of the selection (so you can extend in either direction). For blockwise, O moves to the other corner of the same line.

vim
v       # charwise visual (select characters)
V       # linewise visual (select whole lines)
Ctrl-v  # blockwise visual (select rectangle)
gv      # reselect last visual selection

# In visual mode:
o / O   # move cursor to other end of selection (corner for block)
Esc / v # exit visual mode (toggle)

# Quick selections from normal mode:
vap     # select around paragraph
vip     # select inside paragraph
i"      # then press v... actually ciw etc. work directly

# Switching between types:
#   v then V   ->  linewise (extends to whole lines)
#   V then v   ->  charwise
#   Ctrl-v     ->  blockwise

Visual Operations

Once you have a selection, d/c/y/x work as expected. = auto-indents (great for code reformatting). >/< indent/dedent one shiftwidth. ! pipes the selection through a shell command and replaces it with the output. Pressing : in visual mode auto-fills '<,'> as the range, so :s/old/new/g runs only on the selection.

vim
# In visual mode, press:
d / x    # delete selection
c / s    # change selection (delete, enter insert)
y        # yank (copy) selection
~        # toggle case of selection
u / U    # lowercase / uppercase selection
>        # indent selection
<        # dedent selection
=        # auto-indent selection (reformat)
!cmd     # filter through external command
:r !cmd  # (after yank) paste command output

# Replace entire selection with a char:
r{x}     # replace every char with x

# Print line numbers of selection:
g Ctrl-g  # count words/chars/lines in selection

# Search within selection (after selecting, type :):
# '<,'>s/old/new/g  -- range auto-filled

Visual Block Tricks

Visual block (Ctrl-v) is one of Vim's most powerful features. I and A insert before/after the block — text is replicated to every line after Esc. This is the easiest way to add a comment prefix to multiple lines or append a suffix. c changes the entire block; r{x} replaces every char with x. Block edits only work cleanly when lines are similar length.

vim
# Enter block visual: Ctrl-v
# Select a column rectangle, then:

I         # insert BEFORE block (text typed appears in front)
          # Type text, then Esc -> inserted on every line of block
A         # insert AFTER block (text appears at end of each line)
          # Esc propagates the insert to all lines

c         # change block (delete, enter insert; Esc propagates)
r{x}      # replace every char in block with x

# Common use cases:
# 1. Add a comment to a column of lines:
#    Ctrl-v select first column, I, type '# ', Esc

# 2. Add semicolons at end of selected lines:
#    Ctrl-v select end column, $A;, Esc

# 3. Delete a column:
#    Ctrl-v select, d

# 4. Replace a column with same char:
#    Ctrl-v select, r;

Visual Line Mode

V selects whole lines. After V, common operations are d/y/>/</= to manipulate them. :m moves the selection (e.g. :'<,'>m0 moves it to the top). :t duplicates. '< and '> are marks at the start/end of the last visual selection — useful in scripts. gv reselects the last visual range.

vim
V         # enter linewise visual
5V        # select 5 lines (current + 4 below)
ggVG      # select entire file
:'<,'>    # range referring to last visual selection

# After selecting lines (V), useful commands:
d         # delete lines
y         # yank lines (linewise)
>         # indent lines
<         # dedent lines
=         # auto-indent lines
:m +2     # move selection down 2 lines
:m -3     # move selection up 3 lines
:t .      # duplicate lines (copy below selection)
:g/^/m0   # reverse line order (whole file)

# Reselect last visual:
gv        # reselect last visual selection
'<        # mark: start of last visual
'>        # mark: end of last visual

Visual with Text Objects

Text objects work in visual mode too — they extend the current selection. vi( selects inside the parens; va( includes the parens. Repeating a text object (vawaw) extends further. This is often faster than manually positioning the cursor. Combine with operators: ciw changes the inner word regardless of cursor position within it.

vim
# In visual mode, text objects EXTEND the selection:
#   After pressing v (charwise), type:

aw   # extend selection to include next 'a word'
iw   # extend selection to inner word
ab   # around brackets () 
ib   # inside brackets ()
aB   # around braces {}
iB   # inside braces {}
a"   # around double-quoted string
i"   # inside double-quoted string
ap   # around paragraph
ip   # inside paragraph

# Repeated text objects extend selection:
#   vaw   ->  select word + trailing space
#   vawaw ->  extend to next word + space

# Switch between inner and around:
#   vi( ->  select inside parens
#   va( ->  extend to include the parens

Visual Search & Numbering

Pressing : in visual mode pre-fills '<,'> as the range — substitutes only run on the selection. To search for the visual selection, use y/ then Ctrl-r " Enter (paste register into search). g Ctrl-a in visual block creates a running sequence — invaluable for generating numbered lists (1, 2, 3, ...) from a column of identical numbers.

vim
# Search within visual selection:
# 1. Visually select (v/V/Ctrl-v)
# 2. Press :  ->  :'<,'> appears
# 3. Type s/old/new/g  ->  substitute only in selection

# Search for current visual selection (forward):
#  In visual mode, type:
#   \V then /  (very-nomagic, escapes regex chars)
# Or use a plugin (e.g. vim-visual-star-search)
# Manual method:
#   y/ then Ctrl-r " Enter  ->  search for selection

# Increment/decrement numbers (visual block):
#  Select a column of numbers (Ctrl-v)
Ctrl-a    # increment every number in selection by 1
Ctrl-x    # decrement every number in selection by 1
g Ctrl-a  # increment with running counter (1,2,3,...)
g Ctrl-x  # decrement with running counter

# Example: turn 0,0,0,0 into 1,2,3,4:
#  Ctrl-v select column, g Ctrl-a
06

Buffers

Buffer Basics

Buffers are in-memory files. :e opens a file into a new buffer. :ls lists all buffers with status flags (% = current, # = alternate, + = modified, h = hidden). :b N switches by number; :b name by unique prefix. Ctrl-6 (or :b#) toggles between current and alternate buffers — extremely common.

vim
:e file.txt     # open file in new buffer (current window)
:enew           # new empty buffer
:ls / :buffers  # list all buffers
:b N            # go to buffer N (by number from :ls)
:b name         # go to buffer by name (unique prefix)
:bn             # next buffer
:bp             # previous buffer
:b# / Ctrl-6    # alternate buffer (last edited)
:ball           # open all buffers in horizontal splits
:vertical ball  # open all buffers in vertical splits

# Buffer list symbols:
#   %   current window's buffer
#   #   alternate buffer
#   a   active (loaded and visible)
#   h   hidden (loaded but not visible)
#   -   not modifiable
#   =   read-only
#   +   modified

Buffer Navigation

:bn/:bp cycle through buffers. Ctrl-6 toggles between two most recent — the fastest way to flip back and forth. With 'hidden' set, switching away from a modified buffer doesn't force a save — Vim hides it instead. Use :ls then :b N if you forget buffer numbers. Plugins like fzf.vim make fuzzy buffer switching much faster.

vim
:bn / :bnext      # next buffer (wraps around)
:bp / :bprev      # previous buffer
:bf / :bfirst     # first buffer
:bl / :blast      # last buffer
:b#               # alternate buffer (Ctrl-6)
:N b              # go to buffer number N (e.g. 3b)
:b name<tab>      # tab-complete buffer name
:ls then :b N     # two-step navigation

# Faster navigation (set in vimrc):
:nmap <C-n> :bn<CR>
:nmap <C-p> :bp<CR>

# Jump to a buffer with fuzzy matching (with fzf.vim):
:Buffers          # fuzzy-select from buffer list

# Hide current buffer without saving (if 'hidden' is set):
:hide             # or :bn with 'set hidden'

Buffer Operations

:bd removes a buffer from the list (file is closed). :bw also wipes marks and buffer-local variables. :%bd | e# deletes all but the current buffer — a common cleanup pattern. :bufdo runs an Ex command in every buffer — powerful for global substitutions, but watch for errors (use ! to ignore, or add 'e' flag to :s).

vim
:bd / :bdelete       # delete current buffer
:bd N               # delete buffer N
:bd name            # delete buffer by name
:bw / :bwipeout     # wipe buffer (also clears marks, vars)
:N,N bd             # delete buffers in range
:%bd                # delete all buffers
:%bd | e#           # delete all but current

:bn!                # force switch (don't save current)
:wall               # write all buffers
:bufdo cmd          # run cmd in every buffer
:bufdo! cmd         # same but ignore errors

# Useful patterns:
:bufdo %s/old/new/ge   # substitute in all buffers
:bufdo setlocal nowrap # apply setting to all buffers

# Save and quit current buffer:
:wq                 # write current and quit (closes window)

Hidden Buffers

With 'set hidden' (highly recommended), you can switch away from modified buffers without saving — Vim keeps them in memory. Without 'hidden', Vim blocks switching until you save or use ! to discard. :ls shows 'h' for hidden buffers and '+' if modified. This setting is essential for productive multi-file editing.

vim
:set hidden         # allow switching from modified buffers
:set nohidden       # force save/abandon when switching

# With 'hidden' set:
#   :bn             # switch even if current is modified (no save)
#   :q              # if buffer is modified, prompts to save
#   :qa!            # quit all without saving
#   :wa             # write all (hidden + visible)

# Buffer with changes still in memory:
:ls                  # shows 'h' flag for hidden, '+' for modified

# Recover list of hidden buffers:
:ls h               # only hidden buffers

# Save a hidden buffer:
:w                  # save current (if visible)
:N w                # save buffer N
:wall               # save all buffers

# Common workflow with hidden:
#   :e file1  -> make changes -> :e file2 -> :e file1 (changes preserved)

Argument List

The arglist is a separate list from the buffer list — it's the set of files passed on the command line or set with :args. :n/:prev navigate it. :argdo runs an Ex command in every arglist file — perfect for batch processing like :argdo %s/foo/bar/ge | update. The arglist is often more focused than the buffer list.

vim
# Open files in arglist:
vim file1.txt file2.txt    # initial args
:args file1 file2 file3    # replace arglist
:argadd file4              # add to arglist
:arga *.py                 # add all Python files
:argdelete file2           # remove from arglist

# Navigate arglist:
:n / :next         # next file in arglist
:prev / :Next      # previous file
:first             # first file
:last              # last file
:rewind            # rewind to first file

# Process all files:
:argdo %s/old/new/ge    # substitute in every arg file
:argdo set ff=unix | w  # convert line endings in all

# Show current arglist:
:args              # show files (current marked with [ ])

Buffer List Management

:ls lists buffers, :ls! includes unlisted (deleted but remembered). Use :sb to open a buffer in a split without changing the current window's buffer. There's no built-in 'recently closed buffer' undo, but plugins exist. :bufdo vimgrepadd followed by :copen lets you search across all open buffers via the quickfix list.

vim
:ls                 # list all buffers
:ls!                # list including unlisted buffers
:files              # alias for :ls

# Move current buffer to a specific position:
:N b N              # buffer number N

# Quickly inspect related files:
:b <tab>            # tab-complete buffer names
:b #<tab>           # buffers matching '#'

# Find buffer containing a pattern:
:vimgrep /pattern/ %   # current buffer only
:bufdo vimgrepadd /pattern/ %  # all buffers (then :copen)

# Open buffer in a specific window:
:sb N              # open buffer N in horizontal split
:vertical sb N     # open buffer N in vertical split
:tab split +b N    # open buffer N in new tab

# Restore closed buffers:
# (No built-in, but plugins like vim-molten or
# vim-restore-buffers can help)
07

Marks & Jumps

Setting Marks

Marks are named positions in files. Lowercase (a-z) are buffer-local; uppercase (A-Z) are file marks that persist across Vim sessions. Numbered marks (0-9) are set automatically — 0 is the position when Vim last closed, 1-9 are recent closed files. :marks lists them, :delm deletes.

vim
m{a-zA-Z}    # set mark at cursor position
ma           # set mark 'a' (lowercase, buffer-local)
mA           # set mark 'A' (uppercase, file mark)
m'           # set the unnamed mark (jump before/after)

# Lowercase marks (a-z):
#   Local to current buffer
#   Lost when buffer is unloaded

# Uppercase marks (A-Z):
#   File marks — preserved across sessions
#   Work even after the file is closed

# Numbered marks (0-9):
#   Set automatically by Vim
#   0 = position when Vim was last closed
#   1-9 = positions of last 9 closed files

# View marks:
:marks       # list all marks
:marks a b c # specific marks
:delm! a     # delete mark 'a' (force)

Jumping to Marks

` (backtick) jumps to the exact position (line + column); ' (apostrophe) jumps to the first non-blank of the marked line. `A opens the file containing mark A even if it was closed. `. jumps to where you last changed text, `[ and `] bound the last yanked/deleted region, `< and `> bound the last visual selection.

vim
\`{a-zA-Z}    # jump to mark (line AND column)
'{a-zA-Z}     # jump to mark (line only, column 1)

\`a          # jump to mark 'a' (exact position)
'a           # jump to line of mark 'a'
\`A          # jump to file mark 'A' (opens file if closed)
\`.          # jump to position of last change in this buffer
\`"          # jump to position when file was last closed
\`[          # jump to start of last yanked/changed text
\`]          # jump to end of last yanked/changed text
\`<          # jump to start of last visual selection
\`>          # jump to end of last visual selection
\`^          # jump to last insert position (also gi)

# Jump to next/prev mark in alphabetical order:
:[count]next  # (no built-in, requires plugin)

Jumplist

Vim keeps a jumplist of significant cursor movements. Ctrl-o/Ctrl-i move back/forward through it. 'Significant' means jumps (G, gg, search, mark jumps, tag jumps) — not regular motions like j/k/w. :jumps shows the list. The jumplist is per-window and persists across sessions in viminfo/shada.

vim
Ctrl-o    # jump BACK in jumplist (older positions)
Ctrl-i    # jump FORWARD in jumplist (newer positions)
Tab       # same as Ctrl-i
:ju       # show jumplist (:jumps)

# What counts as a jump?
#   - Movement commands: G, gg, H, M, L, %
#   - Search: /, ?, n, N, *, #
#   - Mark jumps: 'x, `x (NOT mm)
#   - Buffer switch: Ctrl-6, :b, :e
#   - Tag jumps: Ctrl-]
#   NOT counted: j, k, w, b, h, l (regular motions)

# Max jumplist size: 100 entries by default
:set jumpoptions=stack   # treat jumplist like a stack

# Clear jumplist:
:clearjumps   # or :clj

Changelist

The changelist tracks edit positions (not navigation). g; jumps to the previous edit, g, to the next. Useful for walking back through your changes: 'where was I just working?'. :changes shows the list. Unlike the jumplist, the changelist is per-buffer. Combine with `.` for the very last change in the current buffer.

vim
g;        # go to OLDER position of last change
g,        # go to NEWER position of last change
:changes  # list changelist

# Each entry shows:
#   change number, line, column, text of change

# What counts as a change?
#   - Any insertion, deletion, or substitution
#   - Records the position BEFORE the change

# Differences from jumplist:
#   - Changelist tracks EDITS, not movements
#   - Per-buffer (not per-window)
#   - Limited to 100 entries

# Combine with marks:
\`.       # last change position in current buffer
g;         # previous change position

# Common pattern: g;g;g; to walk back through edits

File Marks (A-Z)

Uppercase marks (A-Z) are file marks — they remember the file AND position, persisting across Vim sessions (stored in viminfo). Setting mA in foo.txt lets you return to that position from anywhere with `A, even after restarting Vim. Use them as bookmarks for important files/positions in a project.

vim
# Setting a file mark:
mA          # set mark 'A' at cursor (in current file)
mB          # set mark 'B' (in current file)

# Jumping to a file mark:
\`A          # if file is open: jump to position
\`A          # if file is closed: open it, then jump
'A          # jump to first non-blank of mark's line

# File marks persist across Vim sessions:
#   Stored in viminfo (or shada in Neovim)
#   Restored on next Vim launch

# Use cases:
#   1. Bookmark a function: mA in function definition
#   2. Quick jump between files: `A, `B
#   3. Project navigation: set marks in important files

# Listing file marks:
:marks A-Z   # show only file marks

# Deleting:
:delm A      # delete file mark A (must be in its file)

Built-in Special Marks

Vim sets special marks automatically. `.` is the last change, `[ and `] bound the last yanked/deleted region, `< and `> bound the last visual selection, `^ is where insert mode last ended (gi jumps there and re-enters insert). `" is the position when the file was last closed — useful for resuming work.

vim
\`.    # position of last change in current buffer
\`"    # position when file was last exited
\`[    # start of last yanked/changed text
\`]    # end of last yanked/changed text
\`<    # start of last visual selection
\`>    # end of last visual selection
\`^    # position where insert mode was last stopped
\`'    # position before latest jump (where you came FROM)

# Common uses:
\`[\`]  # operate on last yanked range: \`[v\`]U  (uppercase it)
\`<\`>  # re-select last visual: \`<v\`> or gv

# Re-select last visual selection:
gv        # easier than \`<v\`>

# Re-select last pasted text:
\`]v\`[   # or use a mapping: nnoremap gp \`]v\`[

# Return to last insert position:
gi        # jump to `^ and enter insert mode
08

Text Objects

Word & Sentence Objects

Text objects let you operate on semantic units. 'aw' (around word) includes trailing whitespace — daw closes the gap. 'iw' (inner word) leaves whitespace intact — diw leaves a blank. as/ap are sentence/paragraph versions. Repeating an object (dawaw) extends the selection. Most useful with d/c/y/v operators.

vim
# Operators (d/c/y/v) + text object:
aw   # 'a word' - word + trailing whitespace
iw   # 'inner word' - word only (no whitespace)
aW   # 'a WORD' - WORD + trailing whitespace
iW   # 'inner WORD' - WORD only
as   # 'a sentence' - sentence + trailing space
is   # 'inner sentence' - sentence only
ap   # 'a paragraph' - paragraph + trailing blank line
ip   # 'inner paragraph' - paragraph only

# Examples:
daw    # delete word + trailing space (closes gap)
diw    # delete word only (leaves whitespace)
cas    # change sentence (with trailing space)
yap    # yank paragraph (with trailing blank line)
gqip   # format (wrap) inner paragraph

# Repeated: dawaw extends to next word

Bracket Objects

Bracket objects work on (), [], {}, <>. a (around) includes the brackets; i (inner) excludes them. ci( changes the contents of the parentheses without removing them — perfect for editing function args. Works on the innermost pair when nested. at/it operate on HTML/XML tags (e.g. cit changes inner tag content).

vim
# Brackets (any of ( [ { <):
ab / ib   # around / inside ()
aB / iB   # around / inside {} (also a{ / i{)
a( / i(   # same as ab / ib
a[ / i[   # around / inside []
a{ / i{   # same as aB / iB
a< / i<   # around / inside <>

# Examples:
ci(      # change text inside ( ... )
daB      # delete {...} block including braces
yi[      # yank text inside [...]
vaB      # visually select around {...}

# Special: 'tag' objects (HTML/XML):
at / it  # around / inside <tag>...</tag>

# Tip: works with mismatched brackets too:
#   ci) on (foo bar) changes 'foo bar'

# Nested: ci( in ((a)(b)) changes innermost

Quote Objects

Quote objects (a"/i", a'/i', a`/i`) operate on quoted strings. The cursor doesn't need to be inside the quotes — Vim finds the nearest pair. ci" changes the contents of a string without removing the quotes — invaluable for editing string literals. Includes the quotes with 'a', excludes them with 'i'.

vim
a"   # around double-quoted string (includes quotes)
i"   # inside double-quoted string (no quotes)
a'   # around single-quoted string
i'   # inside single-quoted string
a\`   # around backtick-quoted string
i\`   # inside backtick-quoted string

# Examples:
ci"     # change text inside "..."
da"     # delete "..." (including quotes)
yi'     # yank text inside '...'
va\`    # visually select around \`...\`

# Cursor can be ANYWHERE on the line — Vim finds the
# nearest quote pair containing or following the cursor.

# Special: with counts
3a"     # around 3 consecutive quoted strings

# Tip: ci" when cursor is on the quote itself works too

Sentence & Paragraph Objects

Sentences end with . ! ? followed by whitespace (or paragraph break). Paragraphs are blocks separated by blank lines. 'a' includes trailing whitespace, 'i' excludes it. Useful with operators: gqap reformats a paragraph, >ip indents it, das deletes a sentence. The ( and ) motions jump between sentences.

vim
# Sentence: ends with . ! ? followed by whitespace
as / is    # around / inner sentence

# Paragraph: blocks of text separated by blank lines
ap / ip    # around / inner paragraph

# Examples:
das        # delete a sentence
cip        # change inside paragraph
vap        # select around paragraph (with trailing blank)

# Differences:
#   'a' (around): includes trailing whitespace/blank line
#   'i' (inner): no trailing whitespace

# Use cases:
#   gqap    # reformat (wrap) the paragraph
#   >ap     # indent the paragraph
#   vip     # select inside paragraph
#   :'<,'>s/old/new/g   # substitute in selected paragraph

# Sentence motion commands (not objects):
(   # previous sentence start
)   # next sentence start

Tag & URL Objects (with plugins)

Built-in at/it operate on HTML/XML tags. cit changes inner tag content, dat deletes the whole tag including the open/close. For nested tags, it selects the innermost, then vat extends outward. Plugins add more objects: vim-textobj-url (au/iu), vim-indent-object (ai/ii for same-indent blocks — excellent for code).

vim
# Built-in tag object (HTML/XML):
at / it    # around / inside <tag>...</tag>

# Examples:
cit        # change inner tag text: <p>text</p> -> <p>|</p>
dat        # delete entire tag: <p>text</p> -> (gone)
vit        # visually select inner tag text
yat        # yank entire tag including <tag>...</tag>

# For nested tags, 'it' selects the innermost:
#   <div><p>text</p></div>
#   cit on 'text' changes 'text' only
#   vat extends selection outward

# URL objects (require a plugin like vim-textobj-url):
#   au / iu  # around / inside URL

# Indent objects (require vim-indent-object):
#   ai / ii  # around / inside same-indent block
#   aI / iI  # entire indentation level

# Common plugin combos:
#   vai      # select around indent block (great for code)

Using Text Objects (Recipes)

Text objects combine with any operator (d/c/y/v/>/</=) for powerful edits. ciw changes a word without you needing to position the cursor exactly. ci"/ci(/ci[ change inside quotes/brackets. gqip reformats paragraphs. Mastering these is the key to editing at the speed of thought in Vim.

vim
# Common editing recipes using text objects:

# Word:
ciw      # change word (regardless of cursor position in it)
daw      # delete word + space (closes gap)
yiw      # yank inner word (no whitespace)

# Quotes:
ci"      # change inside "..."
ci'\`    # change inside '...' or \`...\`
da"      # delete the entire string (with quotes)

# Brackets:
ci(      # change inside (...) -- great for args
daB      # delete {...} block (function body)
ci[      # change inside [...]

# Sentences & paragraphs:
cas      # change a sentence
dap      # delete a paragraph
gqip     # reformat paragraph

# HTML tags:
cit      # change tag content
vat      # select tag, then vat again to extend

# Combos with counts:
2daw     # delete two words
c2aw     # change two words
09

Registers

Register Basics

Registers are Vim's clipboards. "ayy yanks a line into register a, "ap pastes from a. There are many registers: named (a-z), append (A-Z), numbered (1-9), and special (unnamed ", yank 0, clipboard +, etc.). :reg lists them. In insert mode, Ctrl-r {reg} inserts a register. Uppercase letter APPENDS to a register.

vim
# Registers store text. 10 named (a-z), 10 append (A-Z),
# 9 numbered (1-9), and several special ones.

"ay   # yank into register 'a' (then motion: "ayy, "ayw)
"ap   # paste from register 'a'
"ad   # delete into register 'a' (e.g. "add)
"Ay   # APPEND to register 'a' (uppercase = append)

# View all registers:
:reg           # list all registers
:reg a b c     # list specific registers
:reg +         # show clipboard register

# Use a register with operators:
"aD            # delete to end of line into 'a'
"byy           # yank line into 'b'
"cp            # paste register 'c'

# Insert mode: Ctrl-r + register
Ctrl-r a       # insert contents of register 'a'
Ctrl-r =       # expression register (compute, insert)

# Search from a register:
/ then Ctrl-r a   # search register 'a' contents

Named & Numbered Registers

Named registers (a-z) are user-controlled; A-Z append to a-z. Numbered registers (1-9) auto-track recent deletes — 1 is most recent, each new delete shifts them down. Register 0 holds only the last YANK (deletes don't overwrite it) — useful for yank-then-delete workflows. "- holds small (sub-line) deletes.

vim
# Named registers (a-z, A-Z):
"ayy      # yank line into 'a'
"byiw     # yank inner word into 'b'
"Ayy      # append yanked line to 'a' (uppercase)

# Numbered registers (1-9) — auto-filled by deletes:
"1        # last delete (largest)
"2        # second-to-last delete
...       # shift older with each new delete
"9        # oldest tracked delete

# Small delete register:
"-        # stores deletes < 1 line (e.g. dw)

# Yank register (separate from deletes):
"0        # last YANK only (not affected by deletes)

# Examples:
"0p       # paste last yank (won't paste a delete)
"1p       # paste last delete
".        # last inserted text (read-only)
"%        # current file name (read-only)

Special Registers

Special registers: ". = last inserted text, "% = current file, "/ = last search, ": = last command. "_ is the black hole — writing to it discards text (useful for deletes you don't want polluting other registers). "+ is the system clipboard, "* is X11 primary selection. "= evaluates a Vimscript expression and inserts the result.

vim
# Read-only registers:
".        # last inserted text
"%        # current file name
"#        # alternate file name
":        # last Ex command
"/        # last search pattern

# Read/write registers:
"0        # last yank (not overwritten by deletes)
"-        # last small delete (less than one line)
"_        # black hole register (discard, like /dev/null)
"=        # expression register (evaluate Vimscript)

# Clipboard / selection:
"+        # system clipboard (Ctrl-C / Ctrl-V)
"*        # primary selection (X11 middle-click)

# Examples:
"_dd      # delete line without saving to register
"+y       # yank to system clipboard (then Ctrl-V elsewhere)
"*p       # paste from X11 primary selection

# Expression register (in insert mode):
Ctrl-r =strftime('%Y-%m-%d')<CR>
# inserts today's date

# Black hole: "_ prevents updating other registers.

Macros in Registers

Macros live in registers — recording with qa writes keystrokes to register a, @a plays it back. Because they're registers, you can inspect them (:reg a), edit them by pasting/modifying/yanking back, or save them to your vimrc with :let @a = '...'. Escape special keys with \<Esc> in string literals.

vim
# Macros are stored in registers, just like yanked text.
# Record a macro into register 'a':
qa        # start recording into 'a'
...       # perform actions
q         # stop recording

# Play back the macro:
@a        # play macro 'a' once
@@        # play last macro again (the one just played)
5@a       # play macro 'a' 5 times
:reg a    # view the recorded keystrokes

# Edit a macro:
:let @a = "..."     # set register 'a' directly
# Or: paste the register, edit, yank back:
"ap       # paste macro keystrokes
# ... edit the text ...
"ayy      # yank edited version back into 'a'

# Save a macro to vimrc:
:let @a = "0iHello \<Esc>"   # escaped special keys

# Apply macro to a range:
:%normal @a   # run macro 'a' on every line

Append & Black Hole

Uppercase register letters APPEND: "Ayy appends to register a (very useful for building up text or multi-step macros). The black hole register "_ discards anything written to it — deletes don't update the numbered/unnamed/yank registers. Use it when you want to delete text without clobbering your last yank.

vim
# Append to a register (use UPPERCASE letter):
"ayy      # yank line into 'a'
"Ayy      # APPEND another line to 'a'
"Ayiw     # APPEND a word to 'a'

# After: register 'a' contains both.

# Black hole register "_:
"_d       # delete without affecting other registers
"_dd      # delete line, doesn't update "1, "0, "-
"_c       # change without saving deleted text
"_x       # delete char, nothing saved

# Why use black hole?
#   - Keep your yank register ("0) intact while deleting
#   - Avoid polluting numbered registers

# Example: yank a line, then delete another (without
# losing the yank):
"ayy      # yank into 'a'
"_dd      # delete line WITHOUT touching 'a' or '0
"ap       # paste the original yank

# When clipboard=unnamed, "_d still bypasses clipboard.

Clipboard & Selection

"+ is the system clipboard (Ctrl-C/Ctrl-V in GUI apps), "* is the X11 primary selection (middle-click paste). With :set clipboard=unnamedplus, yanks and pastes go directly to the system clipboard — no need for the "+ prefix. On Linux, you may need xclip/xsel (X11) or wl-clipboard (Wayland) installed.

vim
# System clipboard register:
"+y       # yank to system clipboard
"+p       # paste from system clipboard
"+dd      # delete line to clipboard
"+P       # paste before cursor from clipboard

# X11 primary selection (Linux, middle-click):
"*y       # yank to primary selection
"*p       # paste from primary selection

# Make the system clipboard the default:
:set clipboard=unnamedplus   # use '+ for unnamed register
:set clipboard=unnamed       # use '* for unnamed register

# Then yank/paste go to clipboard automatically:
yy        # yanks to system clipboard (with above setting)
p         # pastes from system clipboard

# Check what's in the clipboard:
:reg +    # show clipboard contents

# On Linux, install a clipboard tool if missing:
#   xclip or xsel for X11
#   wl-clipboard for Wayland
10

Macros

Recording Macros

q{letter} starts recording into a register; press q again to stop. The recorded keystrokes can be replayed with @{letter}. When recording, use motions that work consistently — prefer 0, ^, $, w, e over j/k which depend on column. Test on a few lines before bulk-applying.

vim
q{a-z}    # start recording into register {a-z}
...       # perform actions (keystrokes recorded)
q         # stop recording

# Example: wrap each word in quotes on a line:
qa        # start recording into 'a'
0         # go to first column
i"        # insert opening quote
Esc
e         # go to end of word
a"        # append closing quote
Esc
w         # move to next word
q         # stop recording

# Now @a will wrap one more word.

# Recording tips:
#   - Use 0/^-positioned motions (not j/k)
#   - Use commands that work regardless of line length
#   - Test on a few lines before applying broadly

# View the recorded macro:
:reg a    # see keystrokes in register 'a'

Playing Macros

@a plays the macro once, @@ replays the last macro, N@a plays N times. To apply a macro to many lines, visually select them and run :normal @a, or use :%normal @a for the whole file. Macros stop on error (e.g. when a search fails), so 999@a safely processes to end of file. :g/pattern/normal @a runs on matching lines only.

vim
@a        # play macro 'a' once (in current buffer)
@@        # replay the LAST macro played
5@a       # play macro 'a' 5 times
100@a     # play 'a' up to 100 times (stops on error)

# Play across multiple lines:
# 1. Visually select lines (V)
# 2. :normal @a    # play 'a' on each selected line

# Or:
:%normal @a        # play on every line in file
:5,20normal @a     # play on lines 5-20
:g/pattern/normal @a  # play on lines matching pattern

# Recursive macros:
#   If macro 'a' contains @a, it'll recurse.
#   Add a 'j' at the end to move to next line.

# Common pattern: process all matching lines:
#   qa0f:x...jq        # record macro moving down 1 line
#   :%s/pattern//gn    # count matches
#   100@a               # play up to 100 times

Editing Macros

Because macros are stored in registers, you can edit them. Paste the register ("ap), modify the text, then yank it back ("ay$ or "ayy). Or use :let @a = '...' with key escapes (\<Esc>, \<CR>). This is much easier than re-recording a complex macro from scratch when you make a small mistake.

vim
# Macros are just text in registers — you can edit them.

# Method 1: paste, edit, yank back:
"ap       # paste register 'a' into buffer
# ... edit the pasted text (modify keystrokes) ...
0"ay$    # yank the line back into 'a'

# Method 2: set the register directly with :let
:let @a = "0iHello \<Esc>"
# Note: special keys need \<Esc> notation in strings

# Method 3: edit in a scratch buffer:
:edit
"ap       # paste macro
# edit
"ayy      # yank back to 'a'
:bdelete

# Common edits:
#   - Add 'j' at the end to advance to next line
#   - Remove a step that you don't need
#   - Replace a hard-coded search with a generic one

# Show the macro as keystrokes:
:reg a    # view current 'a' contents

Recursive Macros

Recursive macros call themselves until they error out — typically when a search fails. CRITICAL: clear the register first (let @a = '') before recording, otherwise the first @a call inside the macro plays the OLD contents. The macro self-terminates when the failing search stops the recursion. Great for global find-replace patterns.

vim
# A macro can call itself, enabling recursion.

# Example: change every 'foo' to 'bar' in a column:
#   (search-based macro)

# Steps to set up recursive macro 'a':
#   1. Clear register 'a':
:let @a = ''
#   2. Start recording:
qa
#   3. Search for 'foo':
/foo<CR>
#   4. Replace with 'bar' (ciwbar<Esc>):
ciwbar<Esc>
#   5. Call itself:
@a
#   6. Stop recording:
q

# Now @a runs until search fails (no more 'foo').
# The recursion stops automatically on the first error
# (when /foo finds nothing).

# Tip: clear the register first, otherwise the FIRST
# call also replays the old contents of 'a'.

# Run:
@a    # processes all 'foo' in file

Macros with Counts

N@a plays the macro N times — but it stops early on any error, so 999@a safely processes to end-of-file. :bufdo normal @a runs the macro in every buffer, :argdo in every arglist file. :g/pattern/normal @a runs it on matching lines only. Counts INSIDE a macro (e.g. 5j) repeat per-play, not per-macro.

vim
# Apply a count to a macro:
5@a       # play macro 'a' 5 times
N@a       # play up to N times (stops on error)

# Useful: large count to process until end-of-file
999@a     # play 'a' up to 999 times (errors stop it)

# Macros with counts INSIDE:
#   If your macro starts with '5j', each play jumps 5 lines

# Combine with visual selection (block):
#   Ctrl-v to select, then :normal @a
#   plays 'a' on each line in the block

# Process every Nth line:
:let i = 0
:g/^/if i % 3 == 0 | normal @a | endif
# (plays 'a' on every 3rd line)

# Apply with a delay (avoid blocking):
#   No built-in, but plugins can defer

# Repeat across buffers:
:bufdo normal @a   # play 'a' in every buffer
:argdo normal @a   # play 'a' in every arg file

Macros across Files & Lines

:[range]normal @a runs a macro on each line in the range — the most common way to apply a macro to many lines. End your macro with 'j' so each iteration advances to the next line. :bufdo/:argdo run it across buffers/files. Add | update to save each modified file. Use ! to continue past errors (e.g. :bufdo!).

vim
# Apply a macro to every line in a range:
:[range]normal @a
:%normal @a           # whole file
:5,20normal @a        # lines 5-20
:'<,'>normal @a       # visual selection
:g/pattern/normal @a  # only matching lines

# Apply across buffers:
:bufdo normal @a      # every buffer
:argdo normal @a      # every arglist file

# Save modified buffers after macro run:
:argdo normal @a | update   # save each that changed

# Best practice: include 'j' (move down) at end of macro
# so it advances to the next line for :normal usage.

# Example macro for processing CSV rows:
#   qa 0f,dt,j q    # cut field at comma, move down
#   :%normal @a     # apply to every line

# Abort on error vs. continue:
:bufdo! normal @a     # continue even if a buffer errors

# Debug: play macro step-by-step (no built-in, but
# :set lazyredraw off + slow key timing helps).
11

Folding

Fold Methods

foldmethod controls how folds are created. 'manual' lets you create folds with zf. 'indent' auto-folds by indentation (great for code). 'syntax' uses syntax rules (language-aware). 'marker' uses {{{ }}} in the file text. 'expr' for custom logic. Manual folds are lost when you close the file unless you :mkview to save them.

vim
:set foldmethod=manual    # fold by hand (zf)
:set foldmethod=indent    # fold by indent level
:set foldmethod=expr      # fold by 'foldexpr'
:set foldmethod=syntax    # fold by syntax rules
:set foldmethod=marker    # fold by {{{ }}} markers
:set foldmethod=diff      # fold unchanged text in diff

# Recommended defaults:
:set foldmethod=indent    # great for code
:set foldlevel=2          # show first 2 levels

# Per-method notes:
#   manual  - zf to create, zd to delete
#   indent  - automatic, based on shiftwidth
#   syntax  - automatic, requires syntax file support
#   marker  - persistent (saved in file as {{{ }}})
#   expr    - custom logic via 'foldexpr'

# Persistent manual folds across sessions:
:mkview    # save folds (and other view state)
:loadview  # restore them
# Or set:
:set viewoptions=folds   # what :mkview saves

Manual Folding

In manual mode, zf creates a fold (e.g. zf3j folds the next 3 lines). zd deletes one fold, zD deletes recursively, zE eliminates all folds in the window. Visual mode + zf folds the selection. With foldmethod=marker, you put {{{ and }}} in comments to create persistent folds stored in the file — useful for sharing fold structure in version control.

vim
# Manual fold commands (foldmethod=manual):
zf{motion}    # create fold over motion (e.g. zf3j)
zF            # create fold over current line(s)
zd            # delete fold at cursor
zD            # delete fold recursively (nested)
zE            # eliminate ALL folds in window
zj / zk       # move to next/prev fold start
[z / ]z       # move to fold start/end

# Select then fold (visual):
V  zf         # visually select lines, then fold

# Create fold for a range:
:5,20fold     # fold lines 5-20

# Fold using a marker (manual mode):
# Place markers in the file (with foldmethod=marker):
function Foo() " {{{
  echo "hi"
endfunction " }}}

# When foldmethod=marker, the {{{ }}} create persistent
# folds stored in the file text itself.

Fold Open/Close Commands

zo/zc open/close a fold; za toggles. zO/zC/zA act recursively on nested folds. zr/zm adjust the fold level globally (one level at a time). zR opens everything, zM closes everything — quick ways to reset. zj/zk jump between folds, [z/]z jump to the start/end of the current fold.

vim
zo    # open fold (one level)
zc    # close fold (one level)
za    # toggle fold (open <-> closed)
zO    # open fold recursively (all levels)
zC    # close fold recursively (all levels)
zA    # toggle fold recursively
zr    # reduce fold level by 1 (open one level everywhere)
zm    # increase fold level by 1 (close one level everywhere)
zR    # open ALL folds (reduce to level 0)
zM    # close ALL folds (fold everything)

# Navigation in/around folds:
zj    # next fold start (down)
zk    # previous fold end (up)
[z    # start of current open fold
]z    # end of current open fold

# Move with folds intact (don't open):
#   Use j/k normally — they may skip closed folds
#   Set 'foldopen' to control what opens folds

Fold Navigation & Editing

j/k may skip over closed folds depending on 'foldopen'. zj/zk navigate between fold boundaries. yy/dd on a folded line operate on ALL the folded lines as a unit (linewise). Typing inside a closed fold opens it. Configure 'foldopen' to control which motions open folds (e.g. remove 'search' to keep folds closed when searching).

vim
# Fold-related motions:
zj    # next fold start (move down)
zk    # previous fold end (move up)
[z    # start of current open fold
]z    # end of current open fold

# What opens a fold when triggered?
:set foldopen=block,hor,mark,percent,quickfix,
             search,tag,undo
# Defaults open folds on most motions.
# To make j/k NOT open folds:
:set foldopen-=block

# Editing a closed fold:
#   Typing inside a closed fold opens it.
#   'o' or 'O' on a fold line opens a new line.

# Search inside closed folds:
#   Default behavior depends on 'foldopen'.
#   :set foldopen-=search  # don't open on search

# Yank/paste on folded lines:
#   yy yanks ALL lines in the fold (linewise)
#   dd deletes ALL lines in the fold

# Paste over a fold:
#   p with a linewise register replaces the folded region

Fold Options

foldlevel controls how deep folds stay open (higher = more open). foldlevelstart=99 starts with everything open. foldcolumn shows a sidebar with fold state indicators. foldminlines prevents tiny folds. To persist folds across sessions, :mkview on BufWinLeave and :loadview on BufWinEnter — common autocmd pattern.

vim
:set foldmethod=indent     # folding method
:set foldlevel=2           # show folds deeper than this
:set foldlevelstart=99     # start with all folds open
:set foldminlines=3        # min lines to form a fold
:set foldcolumn=4          # 4-char column showing fold state
:set foldenable            # enable folding (default on)
:set foldclose=all         # auto-close folds when cursor leaves
:set foldopen=block,hor,mark,percent,quickfix,search,tag,undo
:set foldtext=foldtext()   # text to show on a closed fold
:set fillchars=fold:\ ,   # chars for fold display

# Common combo for code:
:set foldmethod=indent
:set foldlevelstart=10
:set foldnestmax=10        # max nesting depth

# Custom fold text:
:set foldtext=MyFoldText()
function MyFoldText()
  return '+++ ' . (v:foldend - v:foldstart + 1) . ' lines +++'
endfunction

# Persistent folds across sessions:
:set viewoptions=folds,cursor
:autocmd BufWinLeave * mkview
:autocmd BufWinEnter * silent loadview

Nested Folds & Fold Level

Folds nest hierarchically. 'foldlevel' determines the deepest level that stays open — foldlevel=0 closes everything, 99 opens everything. zr/zm adjust by 1, zR/zM are extremes. 'foldnestmax' caps nesting depth to avoid runaway folds in deeply-nested code. Custom foldtext functions let you control what's shown on a closed fold.

vim
# Folds can nest. 'foldlevel' controls which are open:
:set foldlevel=0   # all folds closed
:set foldlevel=1   # only top-level open
:set foldlevel=99  # all open

# Display current fold level:
:echo &foldlevel

# Increase/decrease by 1:
zr    # reduce (open one level everywhere)
zm    # more (close one level everywhere)
zR    # open ALL folds (set foldlevel to 99)
zM    # close ALL folds (set foldlevel to 0)

# Limit nesting:
:set foldnestmax=10

# Each indent level adds a fold level.
# Useful for deeply-nested code:
#   function outer() {
#     function inner() {       <- foldlevel 2
#       function deep() {      <- foldlevel 3
#       }
#     }
#   }

# Custom foldtext for nested folds:
:set foldtext=NeatFoldText()
function! NeatFoldText()
  let line = getline(v:foldstart)
  let n = v:foldend - v:foldstart + 1
  return line . ' (' . n . ' lines)'
endfunction
12

Split Windows

Splitting Windows

:split/:vsplit divide the current window — both show the same buffer by default (changes sync). :split file opens a file in a new split. :new/:vnew create empty buffers. Ctrl-w s/v are the keyboard shortcuts. Adding a number prefix sets the new window's size (:10split creates a 10-row window).

vim
:split / :sp        # horizontal split (current file)
:vsplit / :vs       # vertical split (current file)
:split file.txt     # split + open file
:vsplit file.txt    # vsplit + open file
:new                # horizontal split with empty buffer
:vnew               # vertical split with empty buffer
:5split / :5vsplit  # split with 5 rows/cols

# Quick splits (Normal mode):
Ctrl-w s    # split horizontally (same as :split)
Ctrl-w v    # split vertically (same as :vsplit)
Ctrl-w n    # new horizontal split (empty)
Ctrl-w q    # close window (same as :q)

# Split with size hints:
:10split file.txt    # split with height 10
:vert 30split file   # vertical split with width 30

# Reopen current file in a new split:
Ctrl-w s    # same buffer, two views (sync scroll optional)

Window Navigation

Ctrl-w followed by h/j/k/l navigates between windows (mnemonic: hjkl like movement). Ctrl-w w cycles to the next window. For speed, many users map Ctrl-h/j/k/l directly to window navigation. Ctrl-w p returns to the previously-used window — useful when bouncing between two windows.

vim
Ctrl-w h    # left window
Ctrl-w j    # below window
Ctrl-w k    # above window
Ctrl-w l    # right window
Ctrl-w w    # cycle to next window (wraps)
Ctrl-w W    # cycle to previous window
Ctrl-w t    # top-left window
Ctrl-w b    # bottom-right window
Ctrl-w p    # previous (last accessed) window

# Faster (with mapping):
:nmap <C-h> <C-w>h
:nmap <C-j> <C-w>j
:nmap <C-k> <C-w>k
:nmap <C-l> <C-w>l

# In terminal mode:
Ctrl-w N    # exit terminal mode (to Normal)
Ctrl-w h/j/k/l  # navigate (after exiting terminal mode)

# List windows in current tab:
:ls        # also shows windows' buffers

Window Resizing

Ctrl-w +/-/</> adjust size by 1; prefix with a count (10 Ctrl-w +) for bigger steps. Ctrl-w _ maximizes height, Ctrl-w | maximizes width. Ctrl-w = equalizes all windows. For precision, use :resize N (height) and :vertical resize N (width). With 'mouse=a', you can drag status lines and separators directly.

vim
Ctrl-w =    # equalize all window sizes
Ctrl-w +    # increase height by 1
Ctrl-w -    # decrease height by 1
Ctrl-w >    # increase width by 1
Ctrl-w <    # decrease width by 1
Ctrl-w _    # maximize height (current window)
Ctrl-w |    # maximize width (current window)
10 Ctrl-w + # increase height by 10

# Ex commands for precise resize:
:resize 20        # set height to 20 (horizontal split)
:resize +5        # increase height by 5
:resize -5        # decrease height by 5
:vertical resize 80   # set width to 80 (vertical split)
:vertical resize +10  # increase width by 10

# Mouse: drag the status line / vertical separator
# (with :set mouse=a)

# Auto-resize on split:
:set equalalways    # default: auto-equalize on split/close
:set noequalalways  # don't auto-resize

Window Movement

Ctrl-w H/J/K/L move the current window to the far edges (capital = full-screen on that side). Ctrl-w r/R rotate windows within the layout. Ctrl-w x swaps with the next window. Ctrl-w T moves a window to a new tab page. A neat trick: Ctrl-w K/H also converts between horizontal and vertical orientation.

vim
Ctrl-w H    # move current window to far LEFT (full height)
Ctrl-w J    # move current window to BOTTOM (full width)
Ctrl-w K    # move current window to TOP (full width)
Ctrl-w L    # move current window to far RIGHT (full height)
Ctrl-w r    # rotate windows DOWN/RIGHT
Ctrl-w R    # rotate windows UP/LEFT
Ctrl-w x    # exchange current window with next
Ctrl-w T    # move current window to a NEW tab page

# Move split orientation:
Ctrl-w K    # if vertical, become horizontal (and move to top)
Ctrl-w H    # if horizontal, become vertical (and move to left)

# Example: convert a vertical split to horizontal:
:vsplit        # creates vertical split
Ctrl-w H       # moves it to the left, becoming horizontal

# Reopen current buffer in a new tab:
Ctrl-w T       # current window moves to a new tab

Window Arrangement

Ctrl-w = equalizes window sizes. :ball opens one window per buffer — useful for seeing everything at once. Ctrl-w r/R rotate windows within the layout. To save and restore a complete window layout (including buffers, folds, etc.), use :mksession and :source. Each tab page maintains its own window layout.

vim
# Equalize window sizes:
Ctrl-w =      # equalize height and width

# Stack layouts:
# Vertical splits side by side, horizontal splits stacked
# Example setup:
#   :vsplit        -> | win1 | win2 |
#   :split         -> | win1 | win3 |
#                       |     | win2 |

# Cycle through layouts (with plugins like golden-ratio,
# or use built-in rotation):
Ctrl-w r      # rotate down/right
Ctrl-w R      # rotate up/left

# Distribute buffers across windows:
:ball         # one window per buffer (horizontal)
:vertical ball  # one window per buffer (vertical)

# Layouts persist within a tab page.
# Save current layout to a session:
:mksession mylayout.vim
# Restore:
:source mylayout.vim

# Tab page = a collection of windows
:tabnew       # new tab with one window

Closing Windows

:q closes a window (and exits Vim if it's the last). :close (or Ctrl-w c) closes a window but won't close the very last one. :only (Ctrl-w o) closes all other windows. With 'hidden' set, closing a window with a modified buffer hides it instead of forcing a save decision. :only! force-closes others even if modified.

vim
:q / :quit       # close current window (errors if last)
:q!              # close without saving
:wq              # save and close
:close / Ctrl-w c   # close window (won't close last)
:only / Ctrl-w o    # close all OTHER windows
:hide            # hide current buffer in window (close window)

# Save and close splits:
:xa              # save all and exit (close all windows)
:wqa             # same as :xa

# Closing a window with modified buffer:
#   - :q  prompts: "Save changes?"
#   - :q! discards
#   - :wq saves and closes
#   - :hide (with 'hidden' set) keeps buffer in memory

# Close every window except the current:
:only            # or Ctrl-w o
# Errors if other windows have unsaved changes:
:only!           # force-close other windows

# When you close the last window, Vim exits.
# To prevent accidental exit:
:q! on last window still exits Vim.
13

Tabs

Tab Creation

:tabnew/:tabedit create a new tab page. vim -p file1 file2 opens each file in its own tab at startup. Each tab page contains one or more windows — a tab is a layout, not a single file. Ctrl-w T moves the current window into a new tab page. Tabs are great for separating unrelated work contexts (e.g. one tab per project area).

vim
:tabnew             # new tab with empty buffer
:tabedit file.txt   # new tab opening file
:tab split          # new tab with current buffer in a split
:tabfind file.txt   # new tab, find file in 'path'

# Open multiple files in tabs at startup:
vim -p file1.txt file2.txt file3.txt

# From inside Vim, open files in tabs:
:args *.py | argdo tabe %

# Or one-by-one:
:tabe file1.txt
:tabe file2.txt

# Keyboard shortcut (common mapping):
:nmap <C-t> :tabnew<CR>

# Open current buffer in a new tab (move window):
#   Ctrl-w T    (move current window to a new tab page)

# Each tab page has its own window layout
# (multiple splits/windows per tab allowed)

Tab Navigation

gt/gT switch tabs (Normal mode). Ngt jumps directly to tab N (1-indexed). :tabs lists all tab pages and their windows. Common to map Ctrl-Tab/Ctrl-S-Tab or <leader>1/2/3 for fast tab switching. Tabs wrap around by default. Each tab maintains its own window layout (splits, etc.).

vim
:tabnext / :tabn   # next tab
:tabprevious / :tabp  # previous tab
:tabfirst / :tabr     # first tab
:tablast / :tabl      # last tab
gt                   # next tab (Normal mode)
gT                   # previous tab
3gt                  # go to tab 3
:tabn 3              # go to tab 3 (Ex command)

# Common mappings (Vim default):
:nmap <C-Tab> :tabnext<CR>
:nmap <C-S-Tab> :tabprevious<CR>

# Or use leader keys:
:nmap <leader>1 1gt
:nmap <leader>2 2gt
:nmap <leader>3 3gt

# Cycle behavior:
#   gt at last tab wraps to first
#   gT at first tab wraps to last
# To disable wrap:
:set tabpagemax=50  # max tabs (not wrap-related)

# Show all tabs:
:tabs   # list tabs with their windows

Tab Operations

:tabc closes the current tab; :tabo closes all others. :tabm N moves the current tab to position N (0 = first). :tabdo runs an Ex command in every tab — useful for global operations across tab-separated workspaces. Closing a tab closes all its windows; modified buffers are preserved if 'hidden' is set.

vim
:tabclose / :tabc   # close current tab
:tabclose! / :tabc!  # force close (discard changes)
:tabonly / :tabo     # close all OTHER tabs
:tabonly! / :tabo!   # force close others

# Reorder tabs:
:tabmove / :tabm 0   # move current tab to position 0 (first)
:tabm              # move to last position
:tabm 2            # move to position 2
:tabm +1           # move right by 1
:tabm -1           # move left by 1

# Quick keyboard reordering (no default, common mapping):
:nmap <leader>tm :tabmove<Space>

# Run a command in every tab:
:tabdo cmd         # run cmd in every tab page
# Example:
:tabdo %s/old/new/ge   # substitute in every tab's first window

# Each tab can hold multiple windows/splits.
# 'Tabclose' closes all windows in the tab.

Tab Layout & Multi-Window

Each tab page maintains an independent window layout (splits, sizes, positions). You can have multiple windows per tab. Ctrl-w T moves a window into its own new tab. :set showtabline=2 always shows the tab bar. Tabs are best used as workspace separators (e.g. one tab per project area), each with its own layout.

vim
# Each tab page has its own window layout.

# Create a tab with a 3-window layout:
:tabnew            # new tab
:vsplit            # split vertically (2 windows)
:split             # split horizontally (3 windows)
# Now tab has: [win1] [win2]
#                      [win3]

# Add a buffer to current tab:
:e file.txt        # replaces current window's buffer

# Move a window to its own tab:
Ctrl-w T           # window becomes a new tab page

# Show tab line (always):
:set showtabline=2  # 0=never, 1=multi-tab, 2=always

# Tab page-local options:
:setlocal ...       # option applies only to current tab's windows
:tabdo setlocal ... # apply across all tabs

# Tab label customization (status line):
:set guitablabel=%t  # show filename in GUI tab label

# Common use case: one tab per project area:
#   Tab 1: source files
#   Tab 2: tests
#   Tab 3: docs
# Each with its own split layout.

Tab Options & Labels

showtabline controls the tab bar (0=never, 1=only with multiple tabs, 2=always). You can fully customize the tabline with a function returning the format string — useful to show modified status, buffer names, tab numbers. guitablabel/guitabtooltip customize GUI Vim. tabs are numbered from 1.

vim
:set showtabline=2    # always show tab bar (0=never, 1=auto)
:set tabpagemax=50    # max tabs creatable
:set hidden           # allow switching tabs with unsaved changes

# Custom tab labels (terminal Vim):
:set tabline=%!MyTabLine()
function! MyTabLine()
  let s = ''
  for i in range(tabpagenr('$'))
    let n = i + 1
    let s .= '%' . n . 'T'
    let s .= (n == tabpagenr() ? '%#TabLineSel#' : '%#TabLine#')
    let s .= ' [' . n . '] '
    let buflist = tabpagebuflist(n)
    let bufname = bufname(buflist[0])
    let s .= empty(bufname) ? '[No Name]' : fnamemodify(bufname, ':t')
    let s .= ' '
  endfor
  let s .= '%T%#TabLineFill#'
  return s
endfunction

# GUI tab labels:
:set guitablabel=%t   # show filename
:set guitabtooltip=%f # show full path on hover

# Quickly switch with number:
:nmap <leader>t :tabs<CR>

Tab Page Commands

:tabs lists tabs and their windows. :tabdo runs an Ex command in every tab. :tab ball opens each buffer in its own tab. <C-w>gf opens the file under the cursor in a new tab. Use tabs to group related files (one tab per feature/area). For cross-tab operations, :tabdo is the workhorse — pair with windo for multi-window tabs.

vim
:tabs                # list all tabs and their windows
:tabdo cmd           # run cmd in every tab
:tab ball            # open each buffer in its own tab
:tab help subject    # open help in a new tab
:tab split           # split current window into new tab

# Quickly duplicate a tab's layout:
# (no built-in, but possible with sessions)
# Or manually: create a new tab, re-open buffers

# Open file under cursor in a new tab:
:nmap gf :tabe <cfile><CR>
# (or use the built-in <C-w>gf)

# Common workflow:
# 1. Group related files in tabs
# 2. Use gt/gT to switch
# 3. Use :tabdo for cross-tab operations

# Save all tabs' buffers:
:tabdo windo w   # write every window in every tab
# (or simpler)
:wall            # write all buffers (across all tabs)

# Close all tabs but keep buffers:
# (no single command — use :tabo + :b then :bd)
14

Configuration (.vimrc)

vimrc Basics

$MYVIMRC is the path to your vimrc file — :edit $MYVIMRC opens it, :source $MYVIMRC reloads after edits. The runtimepath controls where Vim looks for syntax files, plugins, etc. Use :verbose set option? to see where an option was last set — invaluable for debugging conflicting plugins.

vim
# vimrc location:
#   Unix:    ~/.vimrc       (or ~/.vim/vimrc)
#   Windows: ~/_vimrc       (or ~/vimfiles/vimrc)
#   Neovim:  ~/.config/nvim/init.vim

# Reload vimrc after editing:
:source $MYVIMRC    # or :so %
# Edit vimrc quickly:
:edit $MYVIMRC      # $MYVIMRC = path to your vimrc

# Runtime path (where Vim looks for scripts):
:set runtimepath?
# Add a custom path:
:set runtimepath+=~/.vim/custom

# Conditional config (only in terminal Vim):
if !has('gui_running')
  set mouse=a
endif

# Per-filetype settings (in vimrc or ftplugin/):
:autocmd FileType python setlocal expandtab shiftwidth=4

# Check where an option was set:
:verbose set tabstop?

Basic Settings

These are common quality-of-life settings. relativenumber + number shows hybrid numbering — line numbers relative to cursor for easy jumps, with absolute on the current line. showmatch flashes matching brackets. wildmenu enhances :command completion with a menu. scrolloff keeps context visible around the cursor — never edit at the screen edge.

vim
set nocompatible      # Vim mode (not Vi)
set number            # show line numbers
set relativenumber    # relative line numbers (great for jumps)
set cursorline        # highlight current line
set cursorcolumn      # highlight current column
set showcmd           # show partial commands in status
set showmode          # show current mode (insert, visual...)
set showmatch         # highlight matching brackets
set wildmenu          # better command-line completion
set wildmode=longest:full,full  # completion style
set laststatus=2      # always show status line
set ruler             # show cursor position
set backspace=indent,eol,start  # backspace through everything
set encoding=utf-8    # default encoding
set scrolloff=5       # keep 5 lines above/below cursor
set sidescrolloff=5   # keep 5 cols left/right of cursor
set mouse=a           # enable mouse (all modes)

Indentation

tabstop controls how many columns a tab character displays as. shiftwidth is the indent amount used by <<, >>, and autoindent. softtabstop makes Backspace remove the right number of columns even when expandtab is off. expandtab converts tabs to spaces — recommended for consistency. :retab converts existing tabs/spaces after changing settings.

vim
set autoindent       # copy indent from previous line
set smartindent      # smart auto-indent for code (basic)
set cindent          # C-style indenting (more language-aware)
set expandtab        # use spaces instead of tabs
set tabstop=4        # tab key inserts 4-column tabs visually
set shiftwidth=4     # indent/outdent amount (<<, >>, autoindent)
set softtabstop=4    # backspace removes 4 cols even with tabs
set shiftround       # round indent to multiple of shiftwidth
set smarttab         # tab key respects shiftwidth at line start

# Toggle tabs vs spaces:
:set expandtab!     # toggle
:retab              # convert existing tabs<->spaces

# Filetype-specific indent:
:filetype plugin indent on
# Then in ~/.vim/ftplugin/python.vim:
#   setlocal expandtab shiftwidth=4

# Quick indent/dedent in Normal mode:
>>     # indent current line
<<     # dedent current line
5>>    # indent 5 lines

Search Settings

ignorecase + smartcase is the recommended combo: case-insensitive unless your pattern has an uppercase letter. hlsearch highlights all matches; incsearch shows matches incrementally as you type. Common mapping: <leader>h toggles hlsearch, or :noh clears the current highlight. Vim 8.2+ can show search count via shortmess.

vim
set ignorecase       # case-insensitive search
set smartcase        # case-sensitive if pattern has uppercase
set hlsearch         # highlight all matches (hls)
set incsearch        # show match as you type (is)
set wrapscan         # wrap search around end of file (ws)

# Toggle search highlight:
:nmap <leader>h :set hlsearch!<CR>
# Or clear current highlight:
:nmap <leader>nh :nohlsearch<CR>

# Show search count (Vim 8.2+):
:set shortmess-=S
:set shortmess+=S    # search stats in command line

# Search in visual selection:
:vmap * y/\V<C-R>"<CR>

# Use very-magic mode by default (advanced):
# No built-in, but a search mapping can prepend \v

# Improved '*' (don't jump on first press):
:nmap * *N

# Disable highlighting until next search:
:nohlsearch

Display Settings

wrap + linebreak wrap long lines at word boundaries (not mid-word) for readability. list + listchars reveal tabs, trailing whitespace, and non-breaking spaces. colorcolumn marks a column (e.g. 80 for line length limits). termguicolors enables true 24-bit color in modern terminals. splitright/splitbelow control where new splits appear.

vim
set wrap             # wrap long lines
set linebreak        # wrap at word boundaries (not mid-word)
set textwidth=80     # auto-wrap at 80 chars when typing
set showbreak=+++    # marker for wrapped lines
set list             # show invisible chars
set listchars=tab:>-,trail:.,extends:>,precedes:<
set fillchars=fold:-,vert:\|   # chars for folds/vsplits
set colorcolumn=80   # highlight column 80
set signcolumn=yes   # always show sign column (LSP)
set conceallevel=0   # don't conceal chars
set splitright       # new vsplit on the right
set splitbelow       # new split below
set termguicolors    # 24-bit color (if terminal supports)
set background=dark  # or 'light' — adjusts color scheme

# Color scheme:
:colorscheme desert
# Or with a plugin (e.g. vim-colors-solarized):
:colorscheme solarized

Mapping Keys

ALWAYS use the *noremap variants (nnoremap, vnoremap, etc.) for personal mappings — they don't recurse, avoiding unexpected behavior. <leader> is a customizable prefix key (default backslash; many set it to comma or space). <CR> is Enter, <Esc> is escape, <C-x> is Ctrl-X. Map leader shortcuts for common commands like save/quit.

vim
# Map families:
:map      # normal, visual, select, operator-pending
:nmap     # normal mode only
:imap     # insert mode only
:vmap     # visual + select mode
:xmap     # visual mode only
:smap     # select mode only
:omap     # operator-pending mode only
:cmap     # command-line mode only
:tmap     # terminal mode only

# Non-recursive (RECOMMENDED):
:nnoremap    # normal mode, non-recursive
:vnoremap    # visual mode, non-recursive
:inoremap    # insert mode, non-recursive
:cnoremap    # command-line mode, non-recursive

# Examples:
nnoremap <leader>w :w<CR>         # save with leader-w
nnoremap <leader>q :q<CR>         # quit
inoremap jk <Esc>                 # Esc with jk
nnoremap <C-h> <C-w>h              # window nav

# Leader key (default is \):
:let mapleader = ','
:let maplocalleader = '\\'

# Special keys:
# <CR> <Esc> <Tab> <Space> <C-x> <A-x> <M-x> <F1>-<F12>
# <Up> <Down> <Left> <Right> <leader> <localleader>
15

Plugins (vim-plug)

vim-plug Basics

vim-plug is the most popular modern plugin manager. Declare plugins between call plug#begin and plug#end. Each Plug line specifies a GitHub repo (user/repo). After adding plugins, :source $MYVIMRC then :PlugInstall downloads them. You can pin to a tag, branch, or commit for stability. Plugins install to the 'plugged' directory.

vim
# Installation (Unix):
# curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
#     https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

# In vimrc:
call plug#begin('~/.vim/plugged')
  Plug 'junegunn/fzf.vim'           " GitHub repo: user/repo
  Plug 'tpope/vim-surround'         " another plugin
  Plug 'morhetz/gruvbox'            " colorscheme
  Plug 'preservim/nerdtree'         " file explorer
call plug#end()

# After editing vimrc:
:source $MYVIMRC    # or restart Vim
:PlugInstall        # install all declared plugins

# Plugin directory structure:
#   ~/.vim/plugged/<plugin-name>/

# Pin to a specific commit/tag/branch:
Plug 'tpope/vim-fugitive', { 'tag': 'v3.0' }
Plug 'user/repo', { 'branch': 'dev' }
Plug 'user/repo', { 'commit': 'abc123' }

Plugin Management Commands

:PlugInstall installs new plugins, :PlugUpdate updates all, :PlugClean removes unlisted ones, :PlugStatus shows their state. The 'do' hook runs a command after install — useful for plugins that need to build or install binaries. The 'on'/'for' options enable lazy loading (load only when a command/filetype triggers it) — improves startup time.

vim
:PlugInstall        # install newly added plugins
:PlugUpdate         # update all plugins
:PlugClean          # remove plugins no longer in vimrc
:PlugStatus         # show plugin status (loaded, errored)
:PlugUpgrade        # update vim-plug itself
:PlugDiff           # review changes from last update
:PlugSnapshot       # generate a snapshot of plugin versions

# Post-install hooks:
Plug 'user/repo', { 'do': ':GoInstallBinaries' }
Plug 'user/repo', { 'do': './install.sh' }
Plug 'user/repo', { 'do': 'npm install' }

# Lazy loading (load only when needed):
Plug 'user/repo', { 'on': 'CommandName' }    " on command
Plug 'user/repo', { 'for': 'python' }        " for filetype
Plug 'user/repo', { 'on': ['Cmd1', 'Cmd2'] }

# Frozen plugin (don't update):
Plug 'user/repo', { 'frozen': 1 }

# Disable a plugin temporarily:
# (no built-in, but comment out the Plug line and :PlugClean)

Common Essential Plugins

Common essentials: fzf for fuzzy finding files/buffers/text (much faster than :find), vim-surround for editing quotes/brackets/tags around text, vim-fugitive for Git integration, vim-commentary for fast commenting (gc operator), NERDTree for a file-explorer sidebar, vim-airline for a nicer status line, and a good colorscheme like gruvbox.

vim
" Fuzzy finder (requires fzf binary):
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
"   :Files     :GFiles     :Buffers    :Rg

" Surround (cs/ds/ys for surroundings):
Plug 'tpope/vim-surround'
"   cs"'    change " to '
"   ds"     delete surrounding "
"   ysiw)   surround word with ()

" Fugitive (Git integration):
Plug 'tpope/vim-fugitive'
"   :G       :Gstatus   :Gcommit   :Gpush

" Commentary (gc to toggle comments):
Plug 'tpope/vim-commentary'
"   gcc      comment line
"   gc{motion}  comment motion

" NERDTree (file explorer sidebar):
Plug 'preservim/nerdtree'
"   :NERDTree  :NERDTreeToggle

" Airline (status line):
Plug 'vim-airline/vim-airline'

" Gruvbox (colorscheme):
Plug 'morhetz/gruvbox'

Plugin Configuration

Use 'on'/'for' to lazy-load plugins — they only load when triggered, improving startup time. Plugin-specific config typically uses g:pluginname_var variables set in vimrc, OR files in ~/.vim/after/plugin/. The 'do' hook runs after install/update for build steps. Setting g:loaded_pluginname=1 in vimrc prevents a plugin from loading at all.

vim
" Lazy load on command:
Plug 'tpope/vim-fugitive', { 'on': 'Git' }

" Lazy load on filetype:
Plug 'fatih/vim-go', { 'for': 'go' }

" Multiple triggers:
Plug 'junegunn/fzf.vim', { 'on': ['Files', 'Buffers', 'Rg'] }

" Post-install hook:
Plug 'dense-analysis/ale', { 'do': 'npm install' }

" Configuration AFTER plugin loads:
Plug 'preservim/nerdtree'
let g:NERDTreeShowHidden = 1
let g:NERDTreeWinSize = 30
nmap <leader>n :NERDTreeToggle<CR>

" Configuration in AFTER/plugin-name.vim:
" (alternative to setting g: vars in vimrc)
" ~/.vim/after/plugin/nerdtree.vim:
"   let g:NERDTreeShowHidden = 1

" Disable plugin entirely:
let g:loaded_nerdtree = 1   " prevent loading

Plugin Loading & Runtime

Vim sources plugin/*.vim files at startup in directory order, then after/plugin/*.vim last (for overrides). :scriptnames lists everything sourced — invaluable for debugging. :verbose map <key> shows where a mapping was defined; :verbose set option? does the same for options. autoload/ files load lazily when their functions are first called.

vim
" Plugin loading sequence:
" 1. Vim starts, reads vimrc
" 2. plug#begin / plug#end registers plugins
" 3. Runtime path is updated to include plugin dirs
" 4. plugin/*.vim files are sourced (in directory order)
" 5. after/plugin/*.vim files are sourced last

" Check if a plugin is loaded:
:echo exists('g:loaded_fugitive')
:scriptnames        # list all sourced scripts in order

" Manually source a plugin:
:source ~/.vim/plugged/foo/plugin/foo.vim

" Find where a mapping was defined:
:verbose map <leader>w

" Find where an option was set:
:verbose set formatprg?

" Plugin runtime files:
"   plugin/      - loaded at startup
"   ftplugin/    - loaded for specific filetypes
"   syntax/      - syntax highlighting
"   indent/      - indentation rules
"   autoload/    - lazily-loaded functions
"   after/       - sourced last (overrides)

" Helptags for plugin docs:
:Helptags   (vim-plug generates these on install)

Alternative Plugin Managers

Beyond vim-plug, options include Vundle (similar API, less feature-rich), Pathogen (simplest — just adds bundle dirs to runtimepath), Dein.vim (fast, lazy-loading focused), and Vim 8+'s built-in packages (no plugin manager needed — drop plugins in pack/<name>/start/). For Neovim users, Packer (Lua-based) is popular. Built-in packages are great for minimalists.

vim
" 1. Vundle (older, similar to vim-plug):
" set rtp+=~/.vim/bundle/Vundle.vim
" call vundle#begin()
"   Plugin 'tpope/vim-surround'
" call vundle#end()

" 2. Pathogen (just adds dirs to runtimepath):
" Execute: 
"   git clone https://github.com/tpope/vim-pathogen ~/.vim/bundle
" In vimrc:
"   execute pathogen#infect()
" Then clone plugins into ~/.vim/bundle/

" 3. Dein.vim (modern, lazy-loading focused):
" set runtimepath+=~/.cache/dein/repos/github.com/Shougo/dein.vim
" if dein#load_state('~/.cache/dein')
"   call dein#begin('~/.cache/dein')
"     call dein#add('tpope/vim-surround')
"   call dein#end()
"   call dein#save_state()
" endif

" 4. Vim 8+ built-in packages (NO plugin manager needed):
" Place plugins in:
"   ~/.vim/pack/<name>/start/<plugin>/    " loaded at startup
"   ~/.vim/pack/<name>/opt/<plugin>/      " loaded on demand
" Load an opt plugin: :packadd <plugin>

" 5. Packer (Neovim Lua):
"   require('packer').startup(function()
"     use 'tpope/vim-surround'
"   end)
16

Search & Navigation

Built-in Search

Built-in search uses / and ? with Vim regex (use \v for very-magic). Search offsets (/foo/e places cursor on match end) persist across n/N — useful for placing the cursor at the right spot after each match. \%V confines a search to the current visual selection. / then Up recalls search history; :history / lists it.

vim
/pattern      # search forward
?pattern      # search backward
n / N         # next / previous match
* / #         # search word under cursor (forward / backward)
g* / g#       # search word under cursor (substring match)

# Search with offsets:
/foo/e        # cursor on last char of match
/foo/e+1      # cursor 1 char past match
/foo/b-1      # cursor 1 char before match
/foo/s+2      # cursor 2 chars after start of match

# Search in current visual selection:
# 1. Visually select (v/V/Ctrl-v)
# 2. Press /  ->  search forward
# 3. Pattern auto-confines to selection? NO.
#    Workaround: /\%Vpattern  (\%V = inside visual selection)

# Repeat last search:
/ then Up arrow    # recall from history
:history /         # list search history

# Search offset persists with n/N.

grep with Quickfix

:grep runs an external grep (configurable via 'grepprg') and puts results in the quickfix list. :copen shows the list; :cn/:cp navigate. Setting grepprg=rg --vimgrep makes it use ripgrep — much faster than grep, with better defaults. :cdo runs an Ex command on each quickfix entry — perfect for bulk substitutions across matches.

vim
# Built-in :grep (uses external grep):
:grep pattern *.py        # search Python files
:grep -r pattern .        # recursive search
:grepadd pattern *.c      # append to existing quickfix list

# After :grep, results appear in the quickfix window:
:copen / :cw   # open quickfix window (cw only if non-empty)
:cnext / :cn   # next match
:cprev / :cp   # previous match
:cfirst / :clast  # first / last match
:cc N          # jump to match N
:cclose        # close quickfix window
:clist         # list all matches (no window)

# Customize the grep program:
:set grepprg=rg\ --vimgrep   " use ripgrep instead
:set grepformat=%f:%l:%c:%m,%f:%l:%m

# With ripgrep, super fast:
:grep pattern   # uses grepprg

# Multi-file edit from quickfix:
:cdo s/old/new/ge | update

vimgrep

:vimgrep uses Vim's internal search engine — slower than external grep but portable (works on any OS without external tools) and uses Vim regex syntax. Pattern goes between // delimiters; **/*.py matches Python files recursively. Results populate the quickfix list — :copen to view, :cn/:cp to navigate. Use :vimgrepadd to append to an existing list.

vim
# :vimgrep uses Vim's internal search (slower than
# external grep, but uses Vim regex and works everywhere).

:vimgrep /pattern/ **/*.py     # search recursively
:vimgrep /pattern/ *.txt       # current directory
:vimgrep /pattern/ file.txt    # single file
:vimgrep /\cFoo/ **/*          # case-insensitive

# Append to existing list:
:vimgrepadd /pattern/ *.c

# Open results in quickfix:
:copen

# Notes:
#   - ** means recursive
#   - Pattern uses Vim regex (same as /search)
#   - Slower than external grep (loads each file into Vim)
#   - Works without external tools (portable)

# Limit search to current buffer only:
:vimgrep /pattern/ %

# Search in visual selection across files: not directly,
# use :grep or external tool instead.

# Jump directly to first match (skip quickfix):
#   :vimgrep /pattern/ **/* | cfirst

ctags Integration

ctags generates an index of identifiers (functions, classes, etc.) that Vim uses for code navigation. Ctrl-] jumps to the definition under the cursor; Ctrl-T pops back. :ts lists matching tags when ambiguous. Run ctags -R . in your project root to generate tags. Add `set autochdir` or configure 'tags' path for project-aware navigation.

vim
# Generate tags file (run in shell):
#   ctags -R .
# (or with Universal Ctags for more languages)

# In Vim, the tags file is auto-read from 'tags' option:
:set tags?        # default: ./tags,./TAGS,tags,TAGS

# Jump to definition (uses tags):
Ctrl-]            # jump to tag under cursor
:tag funcname     # jump to specific tag
:ts funcname      # list matching tags, pick one
:tn / :tp         # next / previous matching tag
:tl               # last tag
Ctrl-T            # pop back from tag jump
:tags             # show tag stack
:pop              # same as Ctrl-T

# Split window before jumping:
Ctrl-W ]          # split, then jump to tag
:stag funcname    # split, then jump

# Preview tag (open in preview window):
Ctrl-W }          # preview tag under cursor
:ptag funcname    # preview specific tag

# Regenerate tags from inside Vim:
:!ctags -R .

cscope Integration

cscope provides more code navigation than ctags — it can find callers, callees, and include relationships, not just definitions. Build with `cscope -Rb`. In Vim, :cscope add links the database, then :cs find X symbol queries it. Combined with cscopequickfix, results populate the quickfix list for easy navigation.

vim
# Build cscope database (shell):
#   cscope -Rb       # creates cscope.out
# Or:
#   find . -name '*.c' > cscope.files
#   cscope -b

# In Vim, add the database:
:cscope add cscope.out
:cscope add /path/to/cscope.out /path/to/project

# Query types (use :cs find X symbol):
#   0 or s: symbol (this token)
#   1 or g: definition (same as Ctrl-])
#   2 or d: functions called by this function
#   3 or c: functions calling this function
#   4 or t: string (text)
#   6 or e: egrep pattern
#   7 or f: file
#   8 or i: includes (files #including this file)

:cs find 0 main         # find symbol 'main'
:cs find 3 myfunc       # find callers of myfunc
:cs find e pattern      # egrep search

# Auto-jump on result:
:set cscopequickfix=s-,c-,d-,i-,t-,e-,g-,f-

# Reset connection:
:cscope reset
:cscope kill <connection>

Netrw File Browser

Netrw is Vim's built-in file explorer — no plugin needed. :Explore opens it in the current window; :Lexplore toggles a left sidebar. Inside, use Enter to open, '-' to go up, 'D' to delete, 'R' to rename, 'd' to mkdir, '%' to create a file. Customize with g:netrw_* variables (tree view, hide banner, window size).

vim
# Netrw is Vim's built-in file explorer.

:Explore / :E       # open explorer in current window
:Sexplore / :Sex    # explorer in horizontal split
:Vexplore / :Vex    # explorer in vertical split
:Lexplore / :Lex    # explorer in left-side toggle

# Inside Netrw:
<CR>      # open file/directory
-         # go up one directory
D         # delete file
R         # rename file
d         # create directory
%         # create new file
i         # cycle view (thin/long/wide/tree)
r         # reverse sort
s         # select sort key
x         # open with system viewer
gh        # toggle hidden files
q         # close explorer

# Useful settings:
:let g:netrw_liststyle = 3      # tree view by default
:let g:netrw_winsize = 25       # window size 25%
:let g:netrw_banner = 0         # hide banner

# Toggle explorer:
:nmap <leader>e :Lexplore<CR>
17

Advanced Movement

Character Search (f/F/t/T)

f/F find the next/previous character on the current line (cursor lands on it); t/T move until just before/after it. ; repeats the last f/F/t/T in the same direction, , in the opposite. Combine with operators: dt, deletes up to (not including) the next comma, df. deletes through the next period. Fast and precise for in-line navigation.

vim
f{x}    # find next char 'x' on line (cursor ON x)
F{x}    # find previous char 'x' (cursor ON x)
t{x}    # till next char 'x' (cursor BEFORE x)
T{x}    # till previous char 'x' (cursor AFTER x)
;       # repeat last f/F/t/T (same direction)
,       # repeat last f/F/t/T (opposite direction)

# Examples (cursor on 'a' in "hello world abc"):
#   fw  ->  cursor on 'w' (world)
#   tw  ->  cursor on space before 'w'
#   Fo  ->  cursor on 'o' (hello) - backwards
#   ;   ->  next 'w'... or next match of last

# Combined with operators:
dt,    # delete up to (not including) next ','
df.    # delete up to AND including next '.'
ct;    # change up to next ';'
yt:    # yank up to next ':'

# Find on a long line (no built-in multi-line f):
#   Use :s or search for multi-line patterns.

# Set 'hlsearch' to highlight f/F/t/T targets:
:set hlsearch   " doesn't highlight f, but / does.

Sentence & Paragraph Movement

( and ) move by sentence (sentences end with . ! ? plus whitespace); { and } move by paragraph (blank-line separated). For wrapped lines, gj/gk/g0/g$ operate on visual screen lines while j/k/0/$ operate on logical lines — essential when 'wrap' is on. Combine ( ) { } with operators: das deletes a sentence, yap yanks a paragraph.

vim
(     # previous sentence start
)     # next sentence start
{     # previous paragraph start (blank-line delimited)
}     # next paragraph start

# Sentence boundaries: ends with . ! ? followed by
# whitespace or end of paragraph.

# Paragraph: block of lines separated by blank lines.

# Examples:
#   Hello world.  This is a sentence.
#   ^         ^  ^
#   (         )  ( moves to sentence starts

# Combine with operators:
das   # delete a sentence
cis   # change inside sentence
yap   # yank a paragraph
dip   # delete inside paragraph

# Move by visual line (with 'wrap' on):
gj    # down one screen line (wrap-aware)
gk    # up one screen line
g0    # start of screen line
g$    # end of screen line
g_    # last non-blank of screen line

# These respect wrapping where j/k don't.

Match Pairs (%)

% jumps between matching brackets (default: (), [], {}). It also works on #if/#endif and /* */ if 'matchpairs' is set appropriately. Combine with operators: d% deletes from cursor to the match, v% selects it. [( [{ ]) ]} jump to unmatched brackets — useful for finding the enclosing function/class block.

vim
%    # jump to matching bracket: ( ) [ ] { }
     # also works on #if/#endif, /* */ with matching pairs

# In Normal mode:
%    # if cursor on (, jumps to matching )
%    # if cursor on ), jumps to matching (

# Combine with operators:
d%    # delete from cursor to matching bracket
c%    # change from cursor to matching bracket
y%    # yank from cursor to matching bracket
v%    # visually select from cursor to matching bracket

# Extend matching to other pairs:
:set matchpairs+=<:>
:set matchpairs+=«:»
# Default: (:),[:],{:}

# Move to enclosing bracket:
#   [(    # back to unmatched (
#   ])    # forward to unmatched )
#   [{    # back to unmatched {
#   ]}    # forward to unmatched }
#   [} / ]{  similar for braces

# Example: jump to outer function's opening brace:
[{   " previous unmatched {

Screen Movement

H/M/L jump to the top/middle/bottom of the visible screen. Ctrl-f/b scroll full pages; Ctrl-d/u half pages (less disorienting); Ctrl-e/y scroll one line without moving the cursor. zz/zt/zb reposition the cursor line to the middle/top/bottom of the screen — essential for keeping context. 'scrolloff' ensures the cursor never reaches the screen edge.

vim
H    # top of screen (High) — first non-blank
M    # middle of screen
L    # bottom of screen (Low) — first non-blank
N H  # Nth line from top (e.g. 5H = 5 lines from top)
N L  # Nth line from bottom

# Scrolling:
Ctrl-f   # forward (down) full screen
Ctrl-b   # backward (up) full screen
Ctrl-d   # down half screen
Ctrl-u   # up half screen
Ctrl-e   # scroll down 1 line (cursor stays)
Ctrl-y   # scroll up 1 line (cursor stays)

# Reposition cursor on screen:
zz   # cursor line to middle of screen
zt   # cursor line to top of screen
zb   # cursor line to bottom of screen
z.   # cursor to middle, first non-blank
z+   # cursor to bottom, scroll up (like zb but accepts count)

# Keep cursor off edges:
:set scrolloff=5   # always 5 lines above/below cursor
:set sidescrolloff=5  # 5 cols left/right of cursor

Scroll Bind & Sync

:set scrollbind synchronizes scrolling across windows — set it in each window you want to sync. :vimdiff opens two files in diff mode with scrollbind on, syntax-highlighted differences, and [c/]c to navigate hunks. do (diff obtain) pulls changes from the other window, dp (diff put) pushes them. Great for comparing versions of a file.

vim
# Bind windows to scroll together:
:set scrollbind      # current window syncs scroll
:set syncbind        # force immediate sync
:scrollbind          # same as :set scrollbind

# In each window you want synced:
#   :set scrollbind

# Unbind:
:set noscrollbind

# Common use: diff two files side by side
:vimdiff file1 file2    # opens both, scrollbound, diff highlighted
:diffthis               # mark current window as a diff
:diffoff                # turn off diff mode for current window
:diffupdate             # refresh diff highlighting

# Vertical diff split:
:vert diffsplit file2.txt

# In diff mode:
[c    # previous diff hunk
]c    # next diff hunk
do    # diff obtain (pull from other window)
dp    # diff put (push to other window)

# Bind but at different starting positions:
#   Set scrollbind AFTER scrolling to desired positions.

Going to Positions

Many ways to position the cursor: :N for line N, N% for percentage, `.`/`"`/[`]/`</`> for special marks (last change, last exit, last yank, last visual). Ctrl-o/Ctrl-i navigate the jumplist. `" restores the position when the file was last closed — combined with a viminfo setting, this persists across sessions.

vim
:N          # go to line N (e.g. :42)
Ng / N G    # go to line N (e.g. 42G or 42gg)
gg          # first line
G           # last line
N%          # go to N% of file (e.g. 50% = middle)
:-N         # N lines before current (e.g. :-5)
:+N         # N lines after current (e.g. :+10)

# Marks and jumps:
\`.            # position of last change in this buffer
\`"            # position when file was last closed
\`[            # start of last yanked/changed region
\`]            # end of last yanked/changed region
\`<            # start of last visual selection
\`>            # end of last visual selection
\`^            # last insert position (gi goes here + insert)

# Recent jumps:
Ctrl-o      # back in jumplist
Ctrl-i      # forward in jumplist
\`'            # position before last jump (where you came from)

# File position on save:
:set viminfo^=%   # remember buffer positions across sessions

# Cursor hold (no movement for 'updatetime' ms):
# CursorHold event triggers (advanced)
18

Command-Line Mode

Range Commands

Ranges specify which lines a command affects. :% is the whole file (1,$). . is current line, $ is last line. '<,'> is the last visual selection (auto-filled when you press : in visual mode). 'a,'b is from mark a to mark b. You can also use search patterns as range boundaries: :/foo/,/bar/d deletes from line matching foo to line matching bar.

vim
# Range syntax: :{start},{end}command

# Range forms:
:%          # whole file (1,$)
:1,10       # lines 1-10
:.,$        # current line to end of file
:1,.        # first line to current
:'a,'b      # from mark a to mark b
:'<,'>      # last visual selection
:.          # current line only
:N          # line N only
:.,+5       # current line + 5 below
:.,-2       # current line - 2 above

# Examples:
:1,10d          # delete lines 1-10
:%s/old/new/g   # substitute whole file
:'<,'>s/old/new/g  # substitute visual selection
:.,$j           # join from current to end into one line
:5,10y a        # yank lines 5-10 into register a
:g/pattern/m$   # move matching lines to end of file

# Search-based range:
:/start/,/end/d    # delete from 'start' to 'end' patterns

File Commands

:w saves, :e opens a new file, :r inserts another file's content (or shell command output via :r !cmd). :saveas saves and switches to the new file. :e! reloads the file discarding changes. ++enc/++ff specify encoding/line-endings explicitly — useful for converting files. :browse e opens a GUI file dialog (in GUI Vim).

vim
:w / :write          # save current file
:w file.txt          # save as new file
:w! file.txt         # overwrite existing file
:w >> file.txt       # append to file
:saveas / :sav file  # save as new file and switch to it
:e / :edit file      # open file (replace current buffer)
:e!                  # reload current file (discard changes)
:e #                 # edit alternate file
:r / :read file      # insert file below cursor
:r !command          # insert command output below cursor
:0r !date            # insert date at top of file

# Multiple files:
:wa / :wall          # write all buffers
:xa / :xall          # write all and quit
:qa!                 # quit all without saving

# Browse file system:
:browse e            # GUI file open dialog
:Explore             # Netrw file browser

# File encoding:
:w ++enc=utf-8       # save as UTF-8
:e ++enc=latin1 file # open with specific encoding

# File format (line endings):
:w ++ff=unix         # save as Unix line endings

Buffer & Window Commands

All multi-buffer/window/tab operations have Ex-command forms. :b switches buffers, :sp/:vs split windows, :tabnew creates tabs. The :*do family (bufdo, argdo, tabdo, windo, cdo) runs a command across many targets — extremely powerful for batch operations. :ls shows buffers; :tabs shows tabs; :args shows the arglist.

vim
# Buffer commands:
:b N / :buffer N     # switch to buffer N
:b name              # switch by name (tab-completes)
:bd / :bdelete       # delete buffer
:bw / :bwipeout      # wipe buffer (clears marks)
:ls / :buffers       # list buffers
:bn / :bp            # next / previous buffer
:b#                  # alternate buffer

# Window commands:
:sp / :split         # horizontal split
:vs / :vsplit        # vertical split
:new                 # split with empty buffer
:close               # close window
:only                # close all other windows
:qa / :qa!           # quit all windows

# Tab page commands:
:tabnew              # new tab
:tabe file           # new tab with file
:tabc                # close tab
:tabo                # close other tabs
:tabs                # list tabs
:tabn / :tabp        # next / previous tab

# Argument list commands:
:args                # show arg list
:n / :next           # next arg file
:prev                # previous arg file
:argdo cmd           # run cmd on each arg file

Setting Options

:set toggles boolean options with !, sets values with =, appends/removes with +=/-=, and resets to defaults with &. :set option? shows the current value. :setlocal sets a buffer/window-local value (doesn't affect other buffers). :set with no args lists all changed options; :set all shows everything. Tab completes option names.

vim
:set option           # turn boolean option on
:set nooption        # turn boolean option off
:set option!         # toggle boolean
:set option?         # show current value
:set option=value    # set value
:set option+=value   # append to value
:set option-=value   # remove from value
:set option&         # reset to default
:set all             # show all options
:set                 # show changed options

# Local vs global:
:setlocal option     # buffer/window-local only
:setglobal option    # global value

# Examples:
:set number          # show line numbers
:set nonumber        # hide line numbers
:set number!         # toggle
:set tabstop=4       # set tabstop to 4
:set formatoptions+=r  # add 'r' to formatoptions
:set ignorecase&     # reset ignorecase to default

# View all set options:
:set                 # shows only non-default values

# Find option with substring:
:set ic<tab>         # tab-complete (ignorecase)

Map Commands

Use the *noremap variants for personal mappings — they don't recurse, preventing unexpected behavior. <leader> is a customizable prefix (default backslash; many set it to space or comma). <buffer> makes a mapping local to the current buffer. Special keys use <notation> like <CR>, <Esc>, <C-x> (Ctrl-X), <A-x> (Alt-X).

vim
:map <key> <action>        # generic map (most modes)
:nmap <key> <action>       # normal mode
:imap <key> <action>       # insert mode
:vmap <key> <action>       # visual mode
:xmap <key> <action>       # visual mode only
:smap <key> <action>       # select mode
:omap <key> <action>       # operator-pending mode
:cmap <key> <action>       # command-line mode
:tmap <key> <action>       # terminal mode

# Non-recursive (RECOMMENDED for personal mappings):
:nnoremap <key> <action>   # normal mode
:vnoremap <key> <action>   # visual mode
:inoremap <key> <action>   # insert mode
:onoremap <key> <action>   # operator-pending
:cnoremap <key> <action>   # command-line
:tnoremap <key> <action>   # terminal

# Buffer-local mappings:
:nnoremap <buffer> <key> <action>

# Clear a mapping:
:nunmap <key>   # or :nmapclear (clears all normal maps)

# Special keys in mappings:
# <CR> <Esc> <Tab> <Space> <BS> <C-x> <A-x> <M-x>
# <F1>-<F12> <Up> <Down> <Left> <Right>
# <leader> <localleader> <C-r> <C-w>
:let mapleader = ' '
:let maplocalleader = '\\'

Useful Ex Commands

:!cmd runs a shell command; :r !cmd inserts its output into the buffer. :sort sorts lines (u=unique, n=numeric, !=reverse). :retab converts between tabs and spaces. :normal runs normal-mode keystrokes as Ex — add ! to ignore custom mappings. :%!cmd pipes the whole file through an external command and replaces it with the output.

vim
:!command        # run shell command
:r !command     # insert command output below cursor
:!!              # repeat last :! command
:sh              # open a shell (exit to return)
:terminal / :term  # open terminal in a window
:read file       # insert file contents below cursor
:sort            # sort selected lines (visual mode)
:sort u          # sort and remove duplicates
:sort n          # numeric sort
:sort!           # reverse sort
:retab           # convert tabs to spaces (and vice versa)
:retab!          # also convert in strings
:normal {cmds}   # run normal-mode commands
:normal! {cmds}  # ignore mappings (use raw keys)

# Execute Ex commands from a register:
:@a              # run Ex commands in register 'a'
:@@              # repeat last :@

# Range + command examples:
:5,20s/old/new/g  # substitute in lines 5-20
:%!sort           # pipe whole file through sort
:'<,'>!column -t  # format visual selection as columns

# Help:
:help :command   # help on a specific command
19

Vimscript Basics

Variables

Vimscript uses let for assignment and unlet to delete. Variables have scopes indicated by prefixes: g: (global), b: (buffer), w: (window), t: (tab), s: (script), l: (function-local), v: (built-in). const creates immutable values (Vim 8+). exists() checks if a variable is defined. Vim 8.2+ supports string interpolation with $"...{expr}...".

vim
let var = value          # assignment
let var = "hello"        # string
let var = 42             # number
let var = 3.14           # float
let var = [1, 2, 3]      # list
let var = {"key": "val"} # dictionary
unlet var                # delete a variable

# Variable scopes (prefix with letter + colon):
let g:global_var = 1     # global (everywhere)
let b:buffer_var = 1     # buffer-local
let w:window_var = 1     # window-local
let t:tab_var = 1        # tab-local
let s:script_var = 1     # script-local (this file only)
let l:local_var = 1      # function-local
let v:count              # built-in Vim variable

# Constants (Vim 8+):
const PI = 3.14159
let g:CONFIG = #{name: "vim"}  # locked dictionary

# Check if a variable exists:
if exists('var')
if exists('g:plugin_var')

# String interpolation (Vim 8.2+):
let name = "World"
echo $"Hello, {name}!"

Functions

function! Name(args) ... endfunction defines a function — the ! allows redefinition. a: accesses arguments; a:000 is the list of variadic args. Default arguments (Vim 8+) use =value. Lambdas (Vim 8+) are {args -> expr}. function('Name') returns a funcref you can pass around. Use :call to invoke a function for its side effects.

vim
# Define a function:
function! MyFunc(arg1, arg2)
  echo a:arg1 . ' ' . a:arg2
  return a:arg1 + a:arg2
endfunction

# The ! in function! overrides an existing function
# (without ! Vim errors if function already exists).

# Call a function:
call MyFunc("hello", "world")
let result = MyFunc(1, 2)

# Variadic arguments (...):
function! Sum(...)
  let total = 0
  for i in a:000
    let total += i
  endfor
  return total
endfunction

# Default arguments (Vim 8+):
function! Greet(name, greeting="Hello")
  echo a:greeting . ', ' . a:name
endfunction

# Lambda / anonymous functions (Vim 8+):
let Square = {x -> x * x}
echo Square(5)   " 25

# Function as a value:
let Fn = function('MyFunc')
echo Fn(1, 2)

Conditionals & Loops

if/elseif/else/endif for conditionals. for loops over a list (range(N) generates 0..N-1), and can unpack [key, val] pairs from items(). while/endwhile for condition loops. break/continue work as expected. Common condition checks: has() for features, exists() for variables/commands, filereadable() for files, empty() for empty values, type() for type comparison.

vim
# Conditionals:
if x > 10
  echo "big"
elseif x > 5
  echo "medium"
else
  echo "small"
endif

# Ternary:
let result = (x > 0) ? "positive" : "non-positive"

# Loops:
for i in range(5)       # 0,1,2,3,4
  echo i
endfor

for [key, val] in items({'a': 1, 'b': 2})
  echo key . '=' . val
endfor

while x > 0
  let x -= 1
endwhile

# Loop control:
break       # exit loop
continue    # skip to next iteration

# Match-related conditionals:
if has('python3')          # feature check
if exists(':Command')      # command exists
if filereadable('file')    # file is readable
if empty(var)              # variable is empty
if type(var) == type([])   # type check

Strings

. concatenates strings. Vim 8.2+ adds $"...{expr}..." interpolation. len/strlen give byte length; strcharlen gives character count (important for multi-byte). split/join convert between strings and lists. substitute() is the programmatic :s command. matchstr() extracts a regex match. =~ is the regex match operator.

vim
# String concatenation:
let s = "Hello" . " " . "World"

# String interpolation (Vim 8.2+):
let name = "Vim"
echo $"Welcome to {name}!"

# Common functions:
len("hello")              # 5
strlen("hello")           # 5 (byte length)
strcharlen("hello")       # 5 (char length)
tolower("HELLO")          # "hello"
toupper("hello")          # "HELLO"
strpart("hello", 1, 3)    # "ell" (substring)
matchstr("foo123bar", '\d\+')  # "123"
substitute("foo", 'o', '0', 'g')  # "f00"
split("a,b,c", ',')       # ['a', 'b', 'c']
join(['a', 'b', 'c'], '-')  # "a-b-c"
trim("  hello  ")         # "hello"

# printf-style formatting:
printf("%d + %d = %d", 1, 2, 3)  # "1 + 2 = 3"
printf("%05.2f", 3.14159)        # "03.14"

# Regex matching:
if "hello" =~ '^h'
  echo "starts with h"
endif

Lists & Dictionaries

Lists are ordered (like arrays); dictionaries are key-value maps. List slicing uses [start:end]. add/insert/remove modify lists in place. sort/reverse also mutate. For dicts, both .key and ["key"] access work; keys()/values()/items() return lists. has_key() checks membership. #{...} (Vim 8+) is a cleaner dict literal that doesn't require quoting keys.

vim
# Lists (like arrays):
let nums = [1, 2, 3, 4, 5]
echo nums[0]              # 1 (first)
echo nums[-1]             # 5 (last)
echo nums[1:3]            # [2, 3, 4] (slice)

# List operations:
call add(nums, 6)         # append 6
call insert(nums, 0, 0)   # insert 0 at index 0
call remove(nums, 0)      # remove index 0
echo len(nums)            # length
call sort(nums)           # sort in place
call reverse(nums)        # reverse in place
call extend(nums, [7, 8]) # concatenate

# Iterate:
for n in nums
  echo n
endfor

# Dictionaries (like hash maps / objects):
let d = {"name": "Vim", "version": 9}
echo d.name               # "Vim"
echo d["version"]         # 9
let d.lang = "Vimscript"  # add a key
call remove(d, "version") # remove a key
echo keys(d)              # ['name', 'lang']
echo values(d)            # ['Vim', 'Vimscript']
echo items(d)             # [['name', 'Vim'], ['lang', 'Vimscript']]
echo has_key(d, "name")   # 1 (true)

# Dictionary literal (Vim 8+, cleaner):
let d2 = #{key: "value", count: 42}

Autocmds & Custom Commands

autocmd runs code on Vim events (BufRead, BufWritePre, FileType, VimEnter, CursorMoved, etc.). Always wrap autocmds in augroup with autocmd! to clear old definitions on reload - prevents duplication. Custom commands use :command! -nargs=N -range -bang Name action. <args>, <line1>, <line2>, <bang> access the user's input in the command definition.

vim
# Autocmds: run code on events
:autocmd BufWritePre *.md :%s/\s\+$//e   " trim trailing whitespace

# Group autocmds (recommended - prevents duplicates on reload):
augroup markdown_settings
  autocmd!
  autocmd BufRead,BufNewFile *.md setlocal spell
  autocmd FileType markdown setlocal textwidth=80
  autocmd BufWritePre *.md :%s/\s\+$//e
augroup END

# Common events:
# BufReadPost   - after loading a file
# BufWritePre   - before saving
# BufWritePost  - after saving
# FileType      - when filetype is set
# VimEnter      - after Vim startup
# CursorMoved   - cursor moved in normal mode
# InsertEnter   - entering insert mode

# Custom Ex commands:
command! -nargs=1 Grep !grep <args> %
command! -range Sum echo <line1> + <line2>
command! -bang Q quit<bang>

# Use them:
:Grep pattern
:Sum

# Show all autocmds:
:autocmd
# Show autocmds for a specific event:
:autocmd BufWritePre

Was this helpful?