Vi is a text editor written by Bill Joy for BSD Unix in 1976. It was a groundbreaking tool that enabled efficient editing using only the keyboard in terminal environments. Vi's core idea of Modal Editing lives on to this day.
Vim (Vi IMproved) is a greatly enhanced version of Vi released by Bram Moolenaar in 1991. Hundreds of features were added including syntax highlighting, a plugin system, multiple windows, macros, and regex substitution. Vim became the standard editor used daily by developers and system administrators worldwide, as it comes pre-installed on GNU/Linux servers.
Neovim is a refactored fork of Vim that began in 2014. It offers Lua-based configuration, a built-in LSP (Language Server Protocol), and an asynchronous plugin API, aiming to deliver a VS Code-level IDE experience within the terminal.
| Editor | Released | Key Features |
|---|
| Vi | 1976 | Pioneer of modal editing, lightweight |
| Vim | 1991 | Plugins, syntax highlighting, macros |
| Neovim | 2014 | Lua API, built-in LSP, async support |
- Available everywhere: Remote servers via SSH, Docker containers, embedded devices — Vi/Vim is installed on virtually every Unix environment.
- Your hands never leave the keyboard: All operations are possible without a mouse, dramatically improving editing speed.
- Powerful composable language: Complex operations can be expressed concisely with a verb+count+noun structure, such as
d3w (delete 3 words) or ci" (change contents inside quotes). - Macros and automation: Record repetitive tasks as macros and transform thousands of lines in an instant.
- Universal Vim keybindings: Dozens of tools support Vim keybindings including VS Code, IntelliJ, Obsidian, Jupyter, bash readline, and tmux copy mode. Learning Vim makes every tool faster.
The most important feature of Vim is modal editing. The behavior when you press a key changes completely depending on the current mode.
2.1 Major Modes and Transitions
| Mode | Description | How to Enter | How to Exit |
|---|
| Normal | Navigation, commands | <Esc>, <C-c> | — (default mode) |
| Insert | Text input | i, a, o, I, A, O, s, c | <Esc>, <C-c> |
| Visual | Character-wise selection | v | <Esc>, v |
| Visual Line | Line-wise selection | V | <Esc>, V |
| Visual Block | Block (column) selection | <C-v> | <Esc>, <C-v> |
| Command-line | : command input | :, /, ?, ! | <Esc>, <CR> |
| Replace | Overwrite characters | R | <Esc> |
┌─────────────────────────────────────────┐
│ Normal Mode │
│ (Default at startup / return via <Esc>) │
└────┬───────┬───────┬────────────────────┘
│ │ │
i,a,o│ v,V│ :,/,?│
│ │ │
┌────▼──┐ ┌──▼────┐ ┌▼────────────┐
│Insert │ │Visual │ │Command-line │
│ Mode │ │ Mode │ │ Mode │
└───────┘ └───────┘ └─────────────┘
Rule: Pressing <Esc> at any time returns you to Normal Mode. Pressing <Esc> twice when you are lost is Vim survival rule number 1.
3. Navigation Commands
| Key | Action |
|---|
h | Move left one character |
j | Move down one line |
k | Move up one line |
l | Move right one character |
5j | Move down 5 lines (numeric prefix) |
10k | Move up 10 lines |
| Key | Action |
|---|
w | Move to start of next word (punctuation delimited) |
W | Move to start of next WORD (whitespace delimited) |
b | Move to start of previous word |
B | Move to start of previous WORD |
e | Move to end of current/next word |
E | Move to end of current/next WORD |
ge | Move to end of previous word |
Lowercase (w, b, e) treats punctuation as word boundaries, while uppercase (W, B, E) only treats whitespace as boundaries. In hello_world, w requires 2 presses while W requires 1.
| Key | Action |
|---|
0 | Move to beginning of line (column 0) |
^ | Move to first non-whitespace character of line |
$ | Move to end of line |
g_ | Move to last non-whitespace character of line |
gg | Move to first line of file |
G | Move to last line of file |
42G | Move to line 42 |
:42 | Move to line 42 (command mode) |
% | Jump to matching parenthesis/brace/bracket |
| Key | Action |
|---|
<C-u> | Scroll half screen up |
<C-d> | Scroll half screen down |
<C-f> | Scroll full screen down (Page Down) |
<C-b> | Scroll full screen up (Page Up) |
H | Move cursor to top of screen (High) |
M | Move cursor to middle of screen (Middle) |
L | Move cursor to bottom of screen (Low) |
zz | Center current line on screen |
zt | Align current line to top of screen |
zb | Align current line to bottom of screen |
| Key | Action |
|---|
/pattern | Search forward for pattern |
?pattern | Search backward for pattern |
n | Next match in same direction |
N | Next match in opposite direction |
* | Search forward for word under cursor |
# | Search backward for word under cursor |
f{char} | Find char forward on current line, move to it |
F{char} | Find char backward on current line |
t{char} | Move to just before char (till) |
T{char} | Move to just after char (reverse direction) |
; | Repeat f/F/t/T in same direction |
, | Repeat f/F/t/T in opposite direction |
| Key | Action |
|---|
<C-o> | Jump to previous cursor position (jump back) |
<C-i> | Jump to next cursor position (jump forward) |
'' | Return to last jump position (two single quotes) |
m{a-z} | Set mark a-z at current position |
'{a-z} | Jump to mark |
gd | Go to local declaration |
gD | Go to global declaration |
4. Editing Commands
| Key | Action |
|---|
i | Enter Insert Mode before cursor |
I | Enter Insert Mode before first non-whitespace character |
a | Enter Insert Mode after cursor (append) |
A | Enter Insert Mode at end of line |
o | Open new line below and enter Insert Mode |
O | Open new line above and enter Insert Mode |
gi | Enter Insert Mode at last edit position |
4.2 Delete Commands
| Key | Action |
|---|
x | Delete character under cursor |
X | Delete character before cursor (Backspace) |
dd | Delete current line |
3dd | Delete 3 lines starting from current line |
D | Delete from cursor to end of line (same as d$) |
dw | Delete from cursor to start of next word |
de | Delete from cursor to end of word |
d0 | Delete from cursor to start of line |
dG | Delete from current line to end of file |
dgg | Delete from current line to start of file |
Deleted text is stored in a register and can be pasted with p. Vim's delete is equivalent to "Cut."
4.3 Change Commands
| Key | Action |
|---|
r{char} | Replace character under cursor with char (stay in Normal Mode) |
R | Enter Replace Mode (continuous overwrite) |
s | Delete character under cursor and enter Insert Mode |
S | Delete current line contents and enter Insert Mode (same as cc) |
cw | Change from cursor to end of word |
cc | Change current line contents |
C | Change from cursor to end of line (same as c$) |
c0 | Change from cursor to start of line |
~ | Toggle case of character under cursor |
g~w | Toggle case of word |
guw | Convert word to lowercase |
gUw | Convert word to uppercase |
4.4 Copy and Paste
| Key | Action |
|---|
yy | Yank (copy) current line |
Y | Yank current line (same as yy) |
3yy | Yank 3 lines starting from current line |
yw | Yank from cursor to end of word |
y$ | Yank from cursor to end of line |
y0 | Yank from cursor to start of line |
p | Paste after (below) cursor |
P | Paste before (above) cursor |
gp | Same as p but moves cursor to end of pasted text |
"ayy | Yank current line to register a |
"ap | Paste contents of register a |
| Key | Action |
|---|
u | Undo last change |
3u | Undo last 3 changes |
<C-r> | Redo (undo the undo) |
U | Undo all changes on current line |
| Key | Action |
|---|
>> | Indent current line |
<< | Outdent current line |
3>> | Indent 3 lines |
= | Auto-indent (after Visual selection or == for current line) |
== | Auto-indent current line |
gg=G | Auto-indent entire file |
| Key | Mode | Description |
|---|
v | Visual (char) | Character-wise selection |
V | Visual Line | Line-wise selection |
<C-v> | Visual Block | Column-wise block selection |
gv | — | Reselect previous Visual region |
| Key | Action |
|---|
d / x | Delete selection |
y | Yank selection |
c | Delete selection and enter Insert Mode |
> / < | Indent / outdent selection |
= | Auto-indent selection |
~ | Toggle case of selection |
u | Convert selection to lowercase |
U | Convert selection to uppercase |
: | Apply command to selection (:'<,'>) |
o | Move cursor to opposite end of selection |
Visual Block (<C-v>) is extremely useful for inserting or deleting text across multiple lines simultaneously.
Example: Adding // comments to the beginning of multiple lines
1. Enter Visual Block with <C-v>
2. Press j to select the desired number of lines
3. Press I to enter block Insert Mode
4. Type
5. Press <Esc> and
Example: Deleting content at a specific column across multiple lines
1. Enter Visual Block with <C-v>
2. Select the desired range (adjust column range with hjkl)
3. Press d to delete
Text objects are a core concept of Vim editing. They form commands in a "verb + preposition + noun" structure using {operator}{a|i}{object}.
a (around): Includes the object and surrounding whitespace/delimitersi (inner): Selects only the inside of the object
| Object | i (inner) | a (around) |
|---|
w | Inside word | Word + surrounding spaces |
W | Inside WORD | WORD + surrounding spaces |
s | Inside sentence | Sentence + trailing space |
p | Inside paragraph | Paragraph + surrounding blank lines |
" | Contents inside "..." | Entire "..." |
' | Contents inside '...' | Entire '...' |
` | Contents inside `...` | Entire `...` |
( / ) | Inside (...) | Entire (...) |
[ / ] | Inside [...] | Entire [...] |
{ / } | Inside {...} | Entire {...} |
< / > | Inside <...> | Entire <...> |
t | Inside XML/HTML tag | Entire tag including markers |
| Command | Action | Example Scenario |
|---|
diw | Delete word under cursor | On hello - deletes the word |
daw | Delete word + surrounding spaces | Completely remove a word from a sentence |
ci" | Change contents inside "..." | "old" - replace content keeping quotes |
ca" | Change entire "..." | "old" - replace including quotes |
di( | Delete contents inside (...) | Delete all function arguments |
da( | Delete entire (...) | Delete including parentheses |
yip | Yank current paragraph | Copy entire paragraph |
dap | Delete current paragraph | Delete entire paragraph |
vit | Visual select inside HTML tag | Select content in <div>content</div> |
cit | Change inside HTML tag | Replace tag content |
=i{ | Auto-indent inside curly braces | Align entire function body |
>i{ | Add indent inside curly braces | |
7. Command-line Mode
| Command | Action |
|---|
:w | Save |
:w filename | Save as different name |
:q | Quit (when no changes) |
:q! | Force quit ignoring changes |
:wq | Save and quit |
:wq! | Force save and quit |
:x | Save and quit only if changes exist (same as ZZ) |
ZZ | Same as :x |
ZQ | Same as :q! |
:e filename | Open file |
:e! | Discard current file changes and reload |
:r filename | Insert file contents at current position |
:r !command | Insert command output at current position |
7.2 Search and Replace
| Command | Action |
|---|
:%s/old/new/g | Replace all old with new in entire file |
:%s/old/new/gc | Replace all with confirmation for each |
:%s/old/new/gi | Replace all ignoring case |
:5,20s/old/new/g | Replace in lines 5-20 |
:'<,'>s/old/new/g | Replace in Visual selection |
:%s/\s\+$//e | Remove trailing whitespace |
:%s/^/ / | Add 4 spaces to beginning of all lines |
7.3 Running External Commands
| Command | Action |
|---|
:!command | Run external command (e.g., :!ls -la) |
:!python % | Run current file with Python |
:%!command | Pipe entire file through command and replace with output |
:.!command | Pipe current line through command and replace with output |
:shell | Temporarily exit to shell (return with exit) |
| Range Expression | Meaning |
|---|
:5 | Line 5 |
:5,10 | Lines 5 through 10 |
:% | Entire file (same as 1,$) |
:. | Current line |
:.,$ | From current line to end of file |
:'<,'> | Visual selection |
:.,+5 | 5 lines from current line |
8. Buffers, Windows, and Tabs
A buffer is a file loaded into memory. You can open and edit multiple files simultaneously.
| Command | Action |
|---|
:ls / :buffers | List open buffers |
:b2 | Switch to buffer 2 |
:bn | Switch to next buffer |
:bp | Switch to previous buffer |
:bd | Close current buffer |
:ba | Open all buffers in split windows |
| Command | Action |
|---|
:sp / :split | Horizontal split |
:vsp / :vsplit | Vertical split |
:sp filename | Open file in new horizontal split |
:vsp filename | Open file in new vertical split |
<C-w>s | Horizontal split (Normal Mode) |
<C-w>v | Vertical split (Normal Mode) |
<C-w>w | Move focus to next window |
<C-w>h/j/k/l | Move to window in direction |
<C-w>H/J/K/L | Move window to that direction |
<C-w>= | Equalize all window sizes |
<C-w>+ / <C-w>- | Adjust horizontal size |
Ctrl-w > / Ctrl-w < | Adjust vertical size |
Ctrl-w _ | Maximize current window vertically |
Ctrl-w | | Maximize current window horizontally |
<C-w>q | Close current window |
<C-w>o | Close all other windows (only) |
| Command | Action |
|---|
:tabnew | Open new tab |
:tabnew filename | Open file in new tab |
gt | Move to next tab |
gT | Move to previous tab |
2gt | Move to tab 2 |
:tabclose | Close current tab |
:tabonly | Close all other tabs |
:tabs | List tabs |
9. Macros and Registers
Macros are a feature that records a sequence of keystrokes for repeated execution.
| Key | Action |
|---|
q{a-z} | Start recording macro to register a-z |
q | Stop recording macro |
@{a-z} | Execute macro from register |
@@ | Re-execute last executed macro |
10@a | Execute macro in register a 10 times |
Practical Macro Example: Adding quotes to the first field of each CSV line
Original: hello,world
Target: "hello",world
1. q a (start recording to register a)
2. 0 (move to start of line)
3. i"<Esc> (insert " at start of line)
4. f, (move to first ,)
5. i"<Esc> (insert " before ,)
6. j (move to next line)
7. q (stop recording)
8. 99@a (repeat on remaining lines)
| Register | Description |
|---|
" (unnamed) | Default register. Automatically used on delete/yank |
0 | Last yank content |
1-9 | Recent delete/change content (cycled) |
a-z | User-defined registers |
A-Z | Append to lowercase register |
+ | System clipboard |
* | Primary selection (Linux X11) |
_ | Black hole register (delete without polluting registers) |
% | Current filename |
: | Last executed command |
/ | Last search pattern |
Register usage examples:
"+yy " Yank current line to system clipboard
"+p " Paste from system clipboard
"_dd " Delete line without polluting registers
"ayy " Yank current line to register a
"Ayy " Append current line to register a
:reg
:reg a
set nocompatible
set encoding=utf-8
set fileencoding=utf-8
set number
set relativenumber
set cursorline
set showmatch
set nowrap
set scrolloff=8
set signcolumn=yes
set hlsearch
set incsearch
set ignorecase
set smartcase
set tabstop=4
set shiftwidth=4
set softtabstop=4
set expandtab
set autoindent
set smartindent
set lazyredraw
set updatetime=250
set timeoutlen=300
set hidden
set history=1000
set undolevels=1000
filetype plugin indent on
syntax on
set background=dark
set clipboard=unnamedplus
" set clipboard=unnamed " macOS
set nobackup
set nowritebackup
set noswapfile
set laststatus=2
set ruler
set showcmd
set mouse=a
let mapleader = " "
nnoremap <Leader>w :w<CR>
nnoremap <Leader>q :q<CR>
nnoremap <Leader>wq :wq<CR>
nnoremap <Leader>ev :e ~/.vimrc<CR>
nnoremap <Leader>/ :nohlsearch<CR>
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l
nnoremap <A-j> :m .+1<CR>==
nnoremap <A-k> :m .-2<CR>==
vnoremap <A-j> :m '>+1<CR>gv=gv
vnoremap <A-k> :m '<-2<CR>gv=gv
vnoremap < <gv
vnoremap > >gv
tnoremap <Esc> <C-\><C-n>
nnoremap Y y$
nnoremap n nzzzv
nnoremap N Nzzzv
xnoremap <Leader>p
nnoremap <Leader>bn :bnext<CR>
nnoremap <Leader>bp :bprevious<CR>
nnoremap <Leader>bd :bdelete<CR>
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
call plug#begin('~/.vim/plugged')
Plug 'preservim/nerdtree'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'
Plug 'tpope/vim-fugitive'
Plug 'tpope/vim-surround'
Plug 'tpope/vim-commentary'
Plug 'airblade/vim-gitgutter'
Plug 'morhetz/gruvbox'
call plug#end()
| Plugin | Function | Key Commands |
|---|
| NERDTree | File tree explorer | :NERDTreeToggle (<C-n>) |
| fzf.vim | Fuzzy file/content search | :Files, :Rg, :Buffers |
| coc.nvim | LSP, autocomplete, diagnostics | gd (go to definition), K (docs) |
| vim-surround | Surround with brackets/quotes | cs"', ds", ysiw" |
| vim-commentary | Comment toggle | gcc (line), gc (Visual) |
| vim-fugitive | Git integration | :G, :Gblame, :Gdiff |
| vim-gitgutter | Gutter change indicators | ]c / [c (hunk navigation) |
| vim-airline | Enhanced status bar | Auto-activated |
| gruvbox | Retro color theme | colorscheme gruvbox |
cs"' → Change "Hello" to 'Hello' (change surrounding)
cs({ → Change (Hello) to { Hello }
ds" → Remove quotes from "Hello" (delete surrounding)
ysiw" → Add "" around word under cursor (you surround inner word)
yss) → Wrap entire line with ()
S" → Wrap Visual selection with ""
Neovim is compatible with Vim while offering the following advantages.
| Feature | Vim | Neovim |
|---|
| Config Language | Vimscript | Vimscript + Lua |
| Built-in LSP | None (plugin required) | Built-in support |
| Async API | Limited | Full async |
| Treesitter | None | Built-in support |
| Config File | ~/.vimrc | ~/.config/nvim/init.lua |
| Recommended Distros | — | LazyVim, AstroNvim, NvChad |
brew install neovim
sudo apt install neovim
sudo dnf install neovim
git clone https://github.com/LazyVim/starter ~/.config/nvim
12.1 Top Priority Commands to Memorize
| Category | Command | Description |
|---|
| Escape | <Esc> | Return to Normal Mode |
| Save/Quit | :wq | Save and quit |
| Force Quit | :q! | Quit ignoring changes |
| Navigation | gg / G | File start / end |
| Navigation | /pattern | Search |
| Navigation | n / N | Next/previous match |
| Editing | dd | Delete line |
| Editing | yy | Yank line |
| Editing | p | Paste |
| Editing | u | Undo |
| Editing | <C-r> | Redo |
| Editing | ciw | Change word |
| Mode | v / V / <C-v> | Visual modes |
| Window | <C-w>s / <C-w>v | Split |
| Replace | :%s/old/new/g | Global replace |
{count}{operator}{motion or text object}
Examples:
3dd → Delete 3 lines
2yy → Yank 2 lines
d3w → Delete 3 words
ci" → Change contents inside quotes
da( → Delete including parentheses
>ip → Indent paragraph
| Key | Action |
|---|
<C-w> | Delete previous word |
<C-u> | Delete to start of line |
<C-h> | Delete one character (Backspace) |
<C-t> | Add indent |
<C-d> | Remove indent |
<C-r>{reg} | Insert register contents |
<C-r>= | Insert expression result (e.g., <C-r>=2+2<CR> yields 4) |
<C-o>{cmd} | Execute one Normal Mode command then return to Insert Mode |
12.4 Essential Command Mode Patterns
| Command | Description |
|---|
:%s/\n/,/g | Replace newlines with commas |
:%s/^\s*$\n//g | Remove blank lines |
:%s/\s\+$//e | Remove trailing whitespace |
:%!sort | Sort entire file |
:%!sort -u | Sort entire file and remove duplicates |
:g/pattern/d | Delete lines containing pattern |
:v/pattern/d | Delete lines not containing pattern (inverse) |
:g/pattern/y A | Yank all lines containing pattern to register A |
All commands covered in this post can be explored in detail within Vim using :help {command}. For example: :help text-objects, :help registers, :help macros