Modes, Navigation, Editing, Search & Replace, Macros, Plugins, Key Mappings — Vim mastery.
Vim is a modal editor — each mode interprets keystrokes differently. Understanding modes is the single most important concept. The key to Vim proficiency is learning to move between modes quickly.
| Mode | Enter | Exit / Switch | Purpose |
|---|---|---|---|
| Normal | Esc / Ctrl-[ | Default mode | Navigation, commands, operators |
| Insert | i, I, a, A, o, O | Esc | Text editing / typing |
| Visual | v, V, Ctrl-V | Esc | Text selection (char, line, block) |
| Command-line | :, /, ? | Esc / Enter | Ex commands, search, replace |
| Replace | R | Esc | Overwrite characters one by one |
| Select | gH (Neovim: Ctrl-G) | Esc | Mouse-like selection mode |
| Terminal | :terminal | Ctrl-\ Ctrl-N | Built-in terminal emulator |
# ── Entering Insert Mode ──
i # insert before cursor
I # insert at beginning of line (before first non-blank)
a # insert after cursor
A # insert at end of line
o # open new line below, enter insert
O # open new line above, enter insert
s # delete character and enter insert (substitute)
S # delete entire line content and enter insert
gi # go to last insert position and enter insert
gI # insert at column 1 of the line
<c-o> # in insert mode: execute one Normal command, return
# ── Entering Visual Mode ──
v # character-wise visual
V # line-wise visual
<c-v> # block-wise visual (column selection)
# ── Entering Replace Mode ──
R # enter replace mode (overwrites characters)
gR # virtual replace mode (handles tabs correctly)
# ── Quick Normal → Command → Normal ──
Q # enter Ex mode (command-line, deprecated)
gQ # improved Ex mode
:Esc to return. If you find yourself stuck in Insert mode, press Esc twice (or Ctrl-[) to be safe.jk or jj mapped to Esc to avoid moving your hands from the home row. Add to init.lua: vim.keymap.set('i', 'jk', '<Esc>', {noremap = true})Every edit follows the operator + motion/text-objectpattern. Vim is a language: d = delete, c = change, y = yank (copy), > = indent, etc.
| Operator | Action | Example | Result |
|---|---|---|---|
| d | Delete | dw | Delete to next word |
| c | Change (delete + insert) | ciw | Change word |
| y | Yank (copy) | yy | Copy entire line |
| p / P | Paste after / before | p | Paste after cursor |
| > | Indent right | >> | Indent current line |
| < | Indent left | << | Unindent current line |
| = | Auto-format | =ap | Auto-indent paragraph |
| gU | Uppercase | gUw | Uppercase word |
| gu | Lowercase | gu$ | Lowercase to end of line |
| g~ | Toggle case | g~iw | Toggle case of word |
| ! | Filter through external cmd | !ip | Paragraph through sort command |
| g? | Rot13 encode | g?? | Rot13 entire line |
# ── Delete Commands ──
x # delete character under cursor
X # delete character before cursor (backspace)
dd # delete entire line
D # delete from cursor to end of line (= d$)
dw # delete to start of next word
d$ # delete to end of line
d0 # delete to beginning of line
d^ # delete to first non-blank
dt) # delete till closing paren
da( # delete around parentheses (including parens)
di( # delete inside parentheses (keeping parens)
dip # delete inner paragraph
dit # delete inside HTML tag
3dd # delete 3 lines
J # join line below to current (delete newline)
gJ # join without adding space
# ── Change Commands ──
cc / S # change entire line (clear and enter insert)
cw # change word (delete word, enter insert)
C # change to end of line (= c$)
cb # change previous word
ct( # change till opening paren
ci{'"'} # change inside double quotes
ci( # change inside parentheses
cit # change inside HTML/XML tag
cfn # change text until next 'n'
r{char} # replace single character with char
R # enter replace mode (overwrite)
~ # toggle case of character under cursor
# ── Yank (Copy) Commands ──
yy # yank entire line
yw # yank word
y$ # yank to end of line
yit # yank inside HTML tag
yap # yank paragraph
{'"'}ayy # yank line into register {'"'}a{'"'}
{'"'}+y # yank into system clipboard
{'"'}+p # paste from system clipboard
0{'"'}+y$ # yank entire line to system clipboard
# ── Paste Commands ──
p # paste after cursor (or below for lines)
P # paste before cursor (or above for lines)
gp # paste after, cursor at end of pasted text
gP # paste before, cursor at end of pasted text
]p # paste after with auto-indent adjustment
[p # paste before with auto-indent adjustment
{'"'}0p # paste from last yank (after {'"'}1 becomes d register)
{'"'}1p # paste from last delete
# ── Undo / Redo ──
u # undo last change
<c-r> # redo last undo
U # undo all changes on current line
. # repeat last edit command (!!!)
:earlier 10m # go back 10 minutes in undo history
:later 5m # go forward 5 minutes
:undolist # show undo branches| Register | Access | Contents |
|---|---|---|
| "" (unnamed) | "" | Last yank or delete |
| 0 | "0 | Last yank (not delete) |
| 1-9 | "1 | Last 9 deletes (1 = newest) |
| a-z | "a | Named registers (user-defined) |
| A-Z | "A | Append to named register a-z |
| + (system) | "+ | System clipboard (OS level) |
| * (primary) | "* | Primary selection (X11/Linux) |
| _ (black hole) | "_ | Delete to void (no register) |
| = (expression) | "= | Result of Vim expression |
| / (search) | "/ | Last search pattern |
| % (file) | "% | Current file path |
| . (last insert) | ". | Last inserted text |
| - (small) | "- | Last small delete (<100 chars) |
| Combo | Action | Equivalent |
|---|---|---|
| ddp | Swap two lines | Delete, move down, paste |
| xp | Swap two characters | Delete char, paste after |
| dwwP | Move word forward | Delete word, paste before |
| ciw | Change entire word | Delete word + insert mode |
| yi"p | Duplicate quoted text | Yank inside quotes, paste |
| yap | Copy entire paragraph | Yank around paragraph |
| >ap | Indent paragraph | Indent around paragraph |
| =ap | Auto-format paragraph | Format code in paragraph |
| gUiw | Uppercase word | Make word ALL CAPS |
| g~~ | Toggle line case | Swap case of entire line |
.)is Vim's most underrated feature. It repeats the last change. Combine with motions: ciwnew<Esc>w. changes next word to "new" too. Position your cursor, make an edit, then repeat with .— it's like a macro for one action.# ── Basic Search ──
/pattern # search forward for pattern
?pattern # search backward for pattern
n # repeat search forward
N # repeat search backward
* # search forward for word under cursor
# # search backward for word under cursor
g* # search forward for partial word under cursor
g# # search backward for partial word under cursor
gd # go to local declaration of word under cursor
gD # go to global declaration of word under cursor
# ── Search Options ──
:set hlsearch # highlight all search matches
:set incsearch # incremental search (highlight as you type)
:set ignorecase # case-insensitive search
:set smartcase # case-insensitive unless uppercase used
:set nohlsearch # turn off highlighting
:noh # temporarily clear search highlighting
# ── Search in Selection ──
# 1. Visual select text
# 2. Type : to enter command mode (shows :'<,'>)
# 3. Type /pattern to search within selection
# ── Find & Replace ──
:%s/old/new/ # replace first occurrence on each line
:%s/old/new/g # replace all occurrences on each line
:%s/old/new/gc # replace all with confirmation (c = confirm)
:%s/old/new/gI # case-sensitive (I overrides ignorecase)
:%s/old/new/gn # just count matches, don't replace
:s/old/new/g # only on current line
:5,20s/old/new/g # on lines 5-20
:%s/old/new/gi # case-insensitive replace
:'<,'>s/old/new/g # in visual selection
:%s/\<old\>/new/g # whole word only (word boundaries)
:g/pattern/d # delete all lines matching pattern
:g!/pattern/d # delete all lines NOT matching pattern
:v/pattern/d # same as g!
# ── Sub-replace Special Characters ──
:%s/old/new/ | & # in replacement: the matched text
:%s/(\w+)/\1_backup/ | \1 # in replacement: capture group 1
:%s/\n/\r/g # replace \n literal with newline (in regex)
:%s/\t/ /g # replace tabs with 4 spaces
# ── Multi-line Replace ──
:%s/\n\n/\r/g # collapse double newlines to single| Pattern | Matches | Example | |
|---|---|---|---|
| . | Any single character | /c.t matches cat, cot, c3t | |
| * | Zero or more of previous | /ab*c matches ac, abc, abbc | |
| \+ | One or more of previous | /ab\+c matches abc, abbc | |
| \? | Zero or one of previous | /colou\?r matches color, colour | |
| \{ | \{n,m} | Between n and m occurrences | /a\{2,4} matches aa, aaa, aaaa |
| \< | \> | Word boundary | /\<cat\> matches cat but not scatter |
| ^ | Start of line | /^import matches lines starting with import | |
| $ | End of line | /;$ matches lines ending with ; | |
| \( | \) | Capture group | /\(foo\|bar\) captures foo or bar |
| \1 | Backreference | /\(\w\+\) \1 matches repeated words | |
| \| | Alternation (OR) | /foo\|bar matches foo or bar | |
| \[abc] | Character class | /\[aeiou] matches any vowel | |
| \[^abc] | Negated class | /\[^0-9] matches non-digit | |
| \\ | Escape special char | /\* matches literal asterisk |
# ── :g — The Global Command ──
# Syntax: :[range]g/{pattern}/{command}
:g/foo/d # delete all lines containing "foo"
:g/^$/d # delete all blank lines
:g/^\s*$/d # delete all blank/whitespace lines
:g!/foo/d # delete lines NOT containing "foo"
:v/foo/d # same as g!/foo/d (invert)
# ── Practical Examples ──
:g/^/s/$/;/ # append semicolon to every line
:g/TODO/s/^/ / # indent TODO lines
:g/DEBUG/d # remove all debug lines
:g/{/ .+1,/}/-1 sort # sort each {...} block
# ── :v (inverse of :g) ──
:v/\/\/.*/d # delete all comment lines
:v/./d # delete all empty lines
:v/^\s*\(\/\/\|$\)/d # delete lines that are empty or comments\\v (very magic) for regex that works like you expect from other languages: :%s/\\v(\\w+)\\s+(\\w+)/\\2 \\1/g swaps two words. Without \\v, you need to escape many more characters.# ── Opening Files ──
:e filename # edit file (open in current buffer)
:edit . # open file explorer (netrw)
:find file # find file in path
:Ex / :Lex # open netrw file explorer
:vsp file # open in vertical split
:sp file # open in horizontal split
:tabe file # open in new tab
# ── Saving ──
:w # write (save) current file
:w! # force write (override permissions)
:w filename # save as new file
:saveas filename # save as and edit new file
:up # write only if modified
:sav file # save as new file, edit the new file
:x # write if modified, then close (like :wq)
ZZ # same as :x (fast save and quit)
# ── Quitting ──
:q # quit (fails if unsaved changes)
:q! # quit without saving
:qa # quit all
:qa! # quit all without saving
:wq # write and quit
:wqa # write and quit all
:xa # same as :wqa
# ── Buffer Navigation ──
:ls / :buffers # list all open buffers
:bnext / :bn # next buffer
:bprev / :bp # previous buffer
:bfirst / :bf # first buffer
:blast / :bl # last buffer
:b 3 # go to buffer #3
:b filename # go to buffer by name (tab completion)
:bd 3 # close buffer #3
:bd! # force close current buffer
:%bd # close all buffers
:%bd|e# # close all buffers except current
:bw # wipe out buffer (also from list)
# ── Quick Buffer Switching ──
<c-^> # alternate between current and previous buffer
<c-6> # same as <c-^>| Key | Action |
|---|---|
| Enter | Open file / enter directory |
| - | Go up one directory |
| d | Create new directory |
| % | Create new file |
| D | Delete file/directory |
| R | Rename file/directory |
| s | Sort by name |
| S | Sort by size / time |
| x | Open with system app |
| p | Preview file in vertical split |
| i | Toggle thin/long/wide/tree listing |
| gh | Toggle hidden files |
| Type | Indicator | Description |
|---|---|---|
| Current | % | Currently visible buffer |
| Alternate | # | Previously active buffer |
| Listed | Shown in :ls output | |
| Unlisted | u | Hidden buffer (not in :ls by default) |
| Hidden | h | Buffer with unsaved changes, not visible |
| Modified | + | Buffer with unsaved changes |
| Read-only | - | Buffer opened in read-only mode |
| Terminal | [Terminal] | Buffer running a terminal |
:e, switch with Ctrl-^ or :bn/:bp, and use splits for side-by-side viewing. This is the "Vim way" — tabs are windows in Vim terminology, use them sparingly.# ── Split Commands ──
<c-w>s # horizontal split (below)
<c-w>v # vertical split (right)
:sp file # horizontal split with file
:vsp file # vertical split with file
:copen # open quickfix window (horizontal)
:copen 10 # open with 10 lines height
:lopen # open location list window
# ── Window Navigation ──
<c-w>h # move to window left
<c-w>j # move to window below
<c-w>k # move to window above
<c-w>l # move to window right
<c-w>w # cycle through windows
<c-w>W # cycle in reverse
<c-w>t # go to top window
<c-w>b # go to bottom window
<c-w>p # go to previous (last accessed) window
# ── Window Resize ──
<c-w>+ # increase height
<c-w>- # decrease height
<c-w>> # increase width
<c-w>< # decrease width
<c-w>= # equalize all windows
:c-w>_ # maximize current window height
<c-w>| # maximize current window width
:resize 20 # set current window height to 20 lines
:vertical resize 80 # set width to 80 columns
<c-w>5+ # increase height by 5
# ── Window Movement ──
<c-w>H # move window to far left (full height)
<c-w>J # move window to bottom (full width)
<c-w>K # move window to top (full width)
<c-w>L # move window to far right (full height)
<c-w>T # move window to new tab
# ── Window Close ──
<c-w>c # close current window
<c-w>o # close all other windows (only current remains)
:only # same as <c-w>o
<c-w>q # quit window (closes if last, quits vim)
:q # quit current window# ── Tab Commands ──
:tabe file # open file in new tab
:tabnew # open empty buffer in new tab
:tabc # close current tab
:tabo # close all other tabs
:tabs # list all tabs
:tabn / gt # next tab
:tabp / gT # previous tab
:tabfirst # go to first tab
:tablast # go to last tab
:tabmove 0 # move current tab to first position
:tabmove 3 # move to position 3
:tabmove $ # move to last position
:tabmove +1 # move right by one
:tabmove -1 # move left by one
{count}gt # go to tab number (e.g., 3gt)
:tab split # copy current window to new tab
# ── Tab-Related Settings ──
:set showtabline=2 # always show tab bar
:set showtabline=1 # show when 2+ tabs
:set showtabline=0 # never show tab barlazy.nvim is the modern standard plugin manager for Neovim. It features lazy-loading by default, async startup, UI for managing plugins, and automatic configuration.
-- ── lazy.nvim Bootstrap (auto-install) ──
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup("plugins") -- loads lua/plugins/*.lua-- ── Treesitter: Syntax Highlighting & Code Intelligence ──
return {
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
event = { "BufReadPost", "BufNewFile" },
config = function()
require("nvim-treesitter.configs").setup({
ensure_installed = {
"lua", "vim", "vimdoc", "python", "javascript",
"typescript", "json", "yaml", "toml", "html", "css",
"go", "rust", "c", "cpp", "bash", "markdown",
},
highlight = { enable = true },
indent = { enable = true },
incremental_selection = {
enable = true,
keymaps = {
init_selection = "gnn",
node_incremental = "grn",
scope_incremental = "grc",
node_decremental = "grm",
},
},
textobjects = {
select = {
enable = true,
lookahead = true,
keymaps = {
["af"] = "@function.outer",
["if"] = "@function.inner",
["ac"] = "@class.outer",
["ic"] = "@class.inner",
},
},
},
})
end,
}-- ── LSP: Language Server Protocol ──
return {
"neovim/nvim-lspconfig",
dependencies = {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
"hrsh7th/cmp-nvim-lsp",
{ "j-hui/fidget.nvim", opts = {} },
},
event = { "BufReadPre", "BufNewFile" },
config = function()
-- Mason: LSP installer
require("mason").setup()
require("mason-lspconfig").setup({
ensure_installed = {
"lua_ls", "pyright", "ts_ls", "gopls",
"rust_analyzer", "html", "cssls", "bashls",
},
})
-- LSP keymaps
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local buf = args.buf
local map = function(keys, func, desc)
vim.keymap.set("n", keys, func,
{'{'} buffer = buf, desc = "LSP: " .. desc {'}'})
end
map("gd", vim.lsp.buf.definition, "Go to Definition")
map("gD", vim.lsp.buf.declaration, "Go to Declaration")
map("gi", vim.lsp.buf.implementation, "Go to Implementation")
map("gr", vim.lsp.buf.references, "References")
map("K", vim.lsp.buf.hover, "Hover Documentation")
map("<c-k>", vim.lsp.buf.signature_help, "Signature Help")
map("rn", vim.lsp.buf.rename, "Rename Symbol")
map("<leader>ca", vim.lsp.buf.code_action, "Code Action")
map("<leader>fm", function()
vim.lsp.buf.format({'{'}async = true{'}'})
end, "Format Buffer")
end,
})
-- Setup servers
local lspconfig = require("lspconfig")
local capabilities = require("cmp_nvim_lsp").default_capabilities()
for _, server in ipairs({'{'} "lua_ls", "pyright", "ts_ls", "gopls" {'}'}) do
lspconfig[server].setup({'{'} capabilities = capabilities {'}'})
end
end,
}-- ── Telescope: Fuzzy Finder ──
return {
"nvim-telescope/telescope.nvim",
tag = "0.1.8",
dependencies = {'{'} "nvim-lua/plenary.nvim" {'}'},
keys = {
{'{'} "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find Files" {'}'},
{'{'} "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live Grep" {'}'},
{'{'} "<leader>fb", "<cmd>Telescope buffers<cr>", desc = "Buffers" {'}'},
{'{'} "<leader>fh", "<cmd>Telescope help_tags<cr>", desc = "Help Tags" {'}'},
{'{'} "<leader>fo", "<cmd>Telescope oldfiles<cr>", desc = "Recent Files" {'}'},
{'{'} "<leader>gs", "<cmd>Telescope git_status<cr>", desc = "Git Status" {'}'},
{'{'} "<leader>fw", "<cmd>Telescope grep_string<cr>",desc = "Word under Cursor" {'}'},
{'{'} "<leader>fd", "<cmd>Telescope diagnostics<cr>",desc = "Diagnostics" {'}'},
},
config = function()
require("telescope").setup({
defaults = {'{'}
file_ignore_patterns = {'{'} "node_modules", ".git" {'}'},
layout_config = {'{'} prompt_position = "top" {'}'},
sorting_strategy = "ascending",
{'}'},
pickers = {'{'}
find_files = {'{'} hidden = true {'}'},
live_grep = {'{'} additional_args = function()
return {'{'} "--hidden" {'}'}
end {'}'},
{'}'},
})
end,
}| Plugin | Purpose | Install |
|---|---|---|
| lazy.nvim | Plugin manager | Bootstrap (above) |
| nvim-treesitter | Syntax parsing | mason/TSInstall |
| nvim-lspconfig + mason | LSP support | lspconfig + mason.nvim |
| nvim-cmp | Autocompletion | hrsh7th/cmp-nvim-lsp |
| telescope.nvim | Fuzzy finder | plenary.nvim dep |
| nvim-tree / neo-tree | File explorer | nvim-web-devicons |
| nvim-treesitter-textobjects | Text objects | treesitter dep |
| trouble.nvim | Diagnostics panel | folke/trouble.nvim |
| which-key.nvim | Keybinding helper | folke/which-key.nvim |
| gitsigns.nvim | Git decorations | lewis6991/gitsigns.nvim |
| nvim-autopairs | Auto close brackets | windwp/nvim-autopairs |
| Comment.nvim | Toggle comments | numToStr/Comment.nvim |
| Plugin | Purpose |
|---|---|
| luasnip | Snippet engine |
| friendly-snippets | Snippet collection |
| indent-blankline.nvim | Indentation guides |
| lualine.nvim | Statusline |
| bufferline.nvim | Buffer tabs |
| alpha-nvim | Start screen / dashboard |
| noice.nvim | UI for cmdline/messages |
| flash.nvim | Jump to any position |
| surround.nvim | Add/change/delete surroundings |
| mini.nvim | Collection of mini modules |
| dressing.nvim | Improved UI for vim.ui.select |
| todo-comments.nvim | Highlight TODO/FIXME/HACK |
-- ── Leader Key ──
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- ── Basic Settings ──
vim.opt.number = true -- line numbers
vim.opt.relativenumber = true -- relative line numbers
vim.opt.scrolloff = 8 -- keep 8 lines above/below cursor
vim.opt.sidescrolloff = 8 -- keep 8 cols left/right
vim.opt.signcolumn = "yes" -- always show sign column
vim.opt.cursorline = true -- highlight current line
vim.opt.colorcolumn = "80" -- ruler at column 80
vim.opt.expandtab = true -- spaces instead of tabs
vim.opt.shiftwidth = 2 -- indent width
vim.opt.tabstop = 2 -- tab width
vim.opt.softtabstop = 2 -- backspace over tabs
vim.opt.smartindent = true -- auto-indent new lines
vim.opt.autoindent = true
vim.opt.breakindent = true -- wrapped lines maintain indent
vim.opt.wrap = false -- don't wrap long lines
vim.opt.linebreak = true -- wrap at word boundaries (if wrap=on)
vim.opt.showbreak = "↪ " -- wrapped line indicator
-- ── Search Settings ──
vim.opt.hlsearch = true -- highlight search results
vim.opt.incsearch = true -- incremental search
vim.opt.ignorecase = true -- case-insensitive search
vim.opt.smartcase = true -- case-sensitive if uppercase
vim.opt.inccommand = "split" -- show live substitute preview
-- ── File Handling ──
vim.opt.hidden = true -- allow switching unsaved buffers
vim.opt.confirm = true -- confirm unsaved changes
vim.opt.undofile = true -- persistent undo
vim.opt.backup = false -- no backup files
vim.opt.writebackup = false
vim.opt.swapfile = false -- no swap files
vim.opt.fileencoding = "utf-8"
vim.opt.encoding = "utf-8"
-- ── Performance ──
vim.opt.updatetime = 250 -- faster cursor hold events
vim.opt.timeoutlen = 300 -- mapped sequence timeout
vim.opt.redrawtime = 1500 -- max time for syntax highlighting
vim.opt.lazyredraw = false
-- ── Clipboard ──
vim.opt.clipboard = "unnamedplus" -- system clipboard
-- ── Completion ──
vim.opt.completeopt = "menu,menuone,noselect"
-- ── Netrw (file explorer) ──
vim.g.netrw_banner = 0
vim.g.netrw_liststyle = 3 -- tree style
vim.g.netrw_winsize = 25-- ── Leader Key Mappings ──
local map = vim.keymap.set
local opts = {'{'} noremap = true, silent = true {'}'}
-- Pane / Window navigation
map("n", "<c-h>", "<c-w>h", opts)
map("n", "<c-j>", "<c-w>j", opts)
map("n", "<c-k>", "<c-w>k", opts)
map("n", "<c-l>", "<c-w>l", opts)
-- Resize windows
map("n", "<c-up>", "<c-w>+", opts)
map("n", "<c-down>", "<c-w>-", opts)
map("n", "<c-left>", "<c-w><", opts)
map("n", "<c-right>", "<c-w>>", opts)
-- Buffer navigation
map("n", "<leader>bn", ":bnext<cr>", opts)
map("n", "<leader>bp", ":bprev<cr>", opts)
map("n", "<leader>bd", ":bdelete<cr>", opts)
-- Split management
map("n", "<leader>sv", ":vsplit<cr>", opts)
map("n", "<leader>sh", ":split<cr>", opts)
map("n", "<leader>sc", ":close<cr>", opts)
-- Clear search highlight
map("n", "<esc>", ":noh<cr>", opts)
-- Move selected lines (Visual mode)
map("v", "J", ":m '>+1<cr>gv=gv", opts)
map("v", "K", ":m '<-2<cr>gv=gv", opts)
-- Stay in indent mode
map("v", "<", "<gv", opts)
map("v", ">", ">gv", opts)
-- Paste without replacing register
map("x", "<leader>p", '"_dP', opts)
-- Delete to void register
map("n", "<leader>d", '"_d', opts)
map("v", "<leader>d", '"_d', opts)
-- Yank to end of line
map("n", "Y", "y$", opts)
-- J join lines with cursor at join point (not end)
-- Tip: Use gi to jump back to last edit position after J
map("n", "J", "J", opts)
-- Center cursor on half-page scroll
map("n", "<c-d>", "<c-d>zz", opts)
map("n", "<c-u>", "<c-u>zz", opts)~/.config/nvim/
├── init.lua # Entry point: require("config")
├── lua/
│ ├── config/
│ │ ├── options.lua # vim.opt settings
│ │ ├── keymaps.lua # key mappings
│ │ ├── autocmds.lua # autocommands
│ │ └── lazy.lua # lazy.nvim bootstrap
│ └── plugins/
│ ├── treesitter.lua # treesitter config
│ ├── lsp.lua # LSP + Mason config
│ ├── cmp.lua # completion config
│ ├── telescope.lua # fuzzy finder config
│ ├── gitsigns.lua # git signs config
│ ├── lualine.lua # statusline config
│ └── nvim-tree.lua # file explorer config
└── stylua.toml # Lua formatter configVim has multiple modes where keystrokes have different meanings. Normal mode is for navigation and commands,Insert mode for typing, Visual mode for selection, and Command-line mode for ex commands. Modes allow every key on the keyboard to be a shortcut in Normal mode, enabling editing without a mouse. The operator-motion paradigm (d + w = delete word) is only possible because of modal editing.
y yanks (copies), d deletes (cuts), p pastes after cursor,P pastes before. Vim has multiple registers: 0 stores last yank,1-9 store deletes, a-z are named registers, + is system clipboard. Use "add to delete into register "a". The "+y copies to OS clipboard.
. repeats the last change (not motions, not searches). It remembers the operator, motion/text object, and any count. For example: ciwfoo<Esc> changes a word to "foo", then w.changes the next word. This is Vim's macro-for-one pattern — combined with counts (3.), it can perform complex batch edits.
:%s/old/new/gc replaces all occurrences with confirmation. Add I for case-sensitive. Use \\<\\> for word boundaries. The :g/pattern/cmd command applies any command to matching lines::g/^$/d deletes blank lines, :g/TODO/s/^/ / indents TODO lines. Use \\v (very magic) for standard regex behavior.
Buffer = in-memory copy of a file (think VS Code tab). Window = a viewport into a buffer (think VS Code split). Tab = a collection of windows (think VS Code window/workspace). You can have multiple windows viewing the same buffer. A tab is a container for windows. Most Vim users primarily use buffers and windows, using tabs for separate project contexts.
Text objects are regions of text defined by structure: iw (inner word), i" (inside quotes),a( (around parentheses), it (inside HTML tag), ip (paragraph). Combine with operators: ci" changes inside quotes, da( deletes parentheses and content,yit yanks inside tag. i = inner (no delimiters), a = around (includes delimiters).
Use lazy.nvim as plugin manager with lazy-loading. Structure: init.lua requires config files, plugins in lua/plugins/*.lua (one file per plugin). Core stack: Treesitter for highlighting, nvim-lspconfig + Mason for language servers, nvim-cmp for completion, Telescope for fuzzy finding, gitsigns for git decorations. Key principle: configure in Lua, not VimScript.