> nvim mastery

A workflow-first guide to using neovim as your primary IDE — from zero to productive in large-scale projects.

iTerm2 tmux nvim + lazy.nvim Claude Code

01 Philosophy & Mental Model

Vim is a language for editing text, not a text editor with shortcuts. The fundamental insight: you spend far more time navigating and modifying code than writing it from scratch. Vim is optimized for that ratio.

Key Insight
Don't try to memorize everything. Learn the grammar — verbs, nouns, modifiers — and you can compose any edit. 10 motions × 5 operators = 50 commands you never memorized.

Why this stack?

stack rationale
# iTerm2 — GPU-rendered terminal, native macOS, split panes, profiles # tmux — session persistence, window management, survives SSH drops # nvim — modal editing + lua ecosystem + async + LSP native # claude — AI pair programming from terminal, project-aware edits # The synergy: tmux manages sessions/windows, nvim handles code, # claude code handles complex refactors, and iTerm wraps it all # with a performant rendering layer.
Learning curve reality
Days 1–3 will be painful and slow. By day 7 you'll be at parity. By day 14 you'll be faster. By month 2 you won't understand how you worked without it. Commit to 2 full weeks before judging.

02 Modes & Motions

Everything in Vim starts with understanding modes. Your keyboard does different things depending on which mode you're in.

Your home base. You should be in Normal mode 80% of the time. Every key is a command here — not a character to type.

buffer.jsNORMAL
const fetchData = async (url) => {
  const response = await fetch(url)
  return response.json()
}
-- NORMAL --3:15

To enter Normal from any mode: press Esc or Ctrl+[ (faster, less reach).

Essential Motions (you'll use these 500× per day)

h j k l
Left, Down, Up, Right — character-level. Use sparingly.
w b e
Next word start, prev word start, word end. WBE for WORD (whitespace-delimited).
0 ^ $
Line start, first non-blank, line end.
ft + char
Jump to/till character on line. ; to repeat, , to reverse. This is the secret weapon.
gg G
File start, file end. 42G or :42 go to line 42.
{ }
Previous/next paragraph (empty line). Blazing for scanning code.
Ctrl+d / Ctrl+u
Half-page down/up. Your primary scrolling motion.
%
Jump to matching bracket/paren. Essential for navigating nested code.

Where you actually type characters. Get in, type what you need, get out. Multiple ways to enter:

KeyActionWhen to use
iInsert before cursorGeneral typing
aInsert after cursorAppending to end of word
IInsert at line startAdding to beginning of line
AInsert at line endAdding to end of line — very common
oOpen new line belowMost common way to start new line
OOpen new line aboveInserting code above current line
c+motionChange (delete + insert)Replace a text object — the power move
Pro tip
Never use arrow keys in insert mode. If you need to navigate, go back to Normal first. This builds the muscle memory that makes you fast.

Select text to operate on. Think of it as "preview mode" — see what you're about to affect before applying an operator.

v
Character-wise visual. Select individual chars.
V
Line-wise visual. Select whole lines. Use for moving/deleting blocks.
Ctrl+v
Block visual. Column selection — edit multiple lines at once.
gv
Re-select last visual selection. Indispensable.

Press : from Normal mode. The command line gives you access to Ex commands — search/replace, file operations, and plugin commands.

frequently used commands
:w # Save :q # Quit (fails if unsaved) :wq or :x # Save and quit :q! # Quit without saving :%s/old/new/g # Replace all in file :noh # Clear search highlighting :e filename # Open file :vs filename # Open in vertical split :sp filename # Open in horizontal split :terminal # Open terminal inside nvim

03 Vim Grammar — The Composable Language

This is the most important concept. Vim commands follow a grammar: Operator + [Count] + Motion/Text Object. Learn the parts and you can combine them infinitely.

Operators (Verbs)

KeyOperatorExample
dDeletedw delete word, d$ delete to end of line
cChange (delete + insert mode)cw change word, ci" change inside quotes
yYank (copy)yy yank line, y3j yank 3 lines down
>Indent>} indent to next paragraph
<Dedent dedent inside braces
guLowercaseguw lowercase word
gUUppercasegUiw uppercase inner word

Text Objects (Nouns) — This is where power lives

i/a + w
inner/around worddiw deletes word without spaces, daw includes trailing space
i/a + " ' `
inner/around quotesci" to change string content. You'll use this constantly.
i/a + ( { [
inner/around bracketsdi{ delete inside braces, da( delete including parens
i/a + t
inner/around HTML tagcit change inside tag pair
i/a + p
inner/around paragraphdap delete entire paragraph block
i/a + s
inner/around sentence — useful for prose editing
⚡ Practice: Compose these

Given const name = "hello world" with cursor anywhere on the line:

GoalCommandWhy
Change the string contentci"Change inner quotes — cursor can be anywhere between the quotes
Delete entire assignmentddDelete line (operator doubled = act on whole line)
Yank the variable nameyiwYank inner word with cursor on "name"
Delete from cursor to endDShorthand for d$
Change the function argsci(Change inner parens — works from inside the parens
The 80/20 Rule
Master ciw, ci", ci(, ci{, dap, yiw, and f/t motions. These alone cover ~80% of all edits you'll make in code.

04 Day-One Survival Kit

You can be productive from Day 1 with just these commands. Don't learn more until these are in muscle memory.

the absolute essentials
# MOVING j/k up/down lines w/b forward/back by word Ctrl-d/u half page down/up gg / G top / bottom of file /search find text, n=next, N=prev # EDITING i enter insert mode Esc back to normal o new line below + insert dd delete line u undo (!!! your safety net) Ctrl-r redo p paste yy copy line # FILES :w save :q quit :wq save + quit # THE ONLY RULE: if lost, press Esc Esc Esc then u to undo
⚡ Day-1 Exercise (do this now)

Open a terminal and run:

$ vimtutor

This built-in interactive tutorial takes ~30 minutes and teaches the basics hands-on. Do it before configuring anything. The muscle memory from this single session is worth more than any config file.

05 iTerm2 Configuration

A few critical iTerm settings before anything else.

Essential iTerm Settings
iTerm2 → Preferences
# Profiles → Keys → General Left Option Key → Esc+ # CRITICAL: enables Alt keybindings # Profiles → Terminal Scrollback lines: 10000 # or unlimited # Profiles → Text Font: JetBrains Mono Nerd Font, 14pt # Nerd Font required for nvim-tree icons, statusline glyphs # Install: brew tap homebrew/cask-fonts && brew install font-jetbrains-mono-nerd-font # Profiles → Colors # Import a theme — Catppuccin, Tokyo Night, or Kanagawa # Match this to your nvim colorscheme for visual consistency # General → Selection ✓ Applications in terminal may access clipboard # Enables OSC 52 — nvim can yank to system clipboard through tmux

06 tmux Essentials

tmux gives you persistent sessions (survive terminal crashes), split panes, and window management independent of your terminal. The prefix key is Ctrl+b by default (most remap to Ctrl+a).

iTerm window
tmux session
tmux window(s)
tmux pane(s)
nvim / shell
~/.tmux.conf — Starter Config
~/.tmux.conf
# Remap prefix to Ctrl-a (easier to reach) unbind C-b set -g prefix C-a bind C-a send-prefix # Enable true color & undercurl (required for modern nvim themes) set -g default-terminal "tmux-256color" set -ag terminal-overrides ",xterm-256color:RGB" # Start windows/panes at 1 (0 is far away) set -g base-index 1 setw -g pane-base-index 1 # Vi mode for copy (yank with y, navigate with hjkl) setw -g mode-keys vi bind -T copy-mode-vi v send -X begin-selection bind -T copy-mode-vi y send -X copy-pipe-and-cancel "pbcopy" # Vim-like pane navigation bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # Split panes with | and - bind | split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" # Mouse support (useful early on, can disable later) set -g mouse on # Reduce escape delay (critical for nvim) set -sg escape-time 0 # Increase scrollback set -g history-limit 50000 # Reload config bind r source-file ~/.tmux.conf \; display "Reloaded!"

Core tmux Commands

CommandAction
tmux new -s projectNew session named "project"
tmux attach -t projectReattach to session
tmux lsList sessions
Prefix + cNew window
Prefix + 1-9Switch to window N
Prefix + |Vertical split (with config above)
Prefix + -Horizontal split (with config above)
Prefix + dDetach (session persists)
Prefix + [Enter copy mode (scroll, search, yank)
Prefix + zZoom pane (toggle fullscreen)
Session Pattern
One tmux session per project: tmux new -s myapp. Window 1 = nvim, Window 2 = shell/build, Window 3 = logs/servers. Detach with Prefix+d, reattach anytime. Your entire workspace state is preserved.

07 lazy.nvim Setup

lazy.nvim is the modern plugin manager for neovim — it's fast, supports lazy-loading, has a lockfile, and handles dependencies. Here's the directory structure you're building toward:

~/.config/nvim/
init.lua ← entry point
lua/
config/
lazy.lua ← bootstrap lazy.nvim
options.lua ← vim.opt settings
keymaps.lua ← leader key + custom maps
autocmds.lua ← autocommands
plugins/ ← one file per plugin/group
editor.lua ← nvim-tree, telescope, etc.
lsp.lua ← LSP, completion, formatting
git.lua ← fugitive, gitsigns, diffview
ui.lua ← statusline, colorscheme, etc.
init.lua — Entry Point
~/.config/nvim/init.lua
-- Set leader key BEFORE lazy (must be first) vim.g.mapleader = " " -- Space as leader vim.g.maplocalleader = " " -- Load core config require("config.options") require("config.keymaps") require("config.autocmds") require("config.lazy") -- bootstrap + load plugins
config/lazy.lua — Bootstrap lazy.nvim
lua/config/lazy.lua
-- Auto-install lazy.nvim if not present 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) -- Load all plugin specs from lua/plugins/ require("lazy").setup({ spec = { { import = "plugins" }, -- auto-loads lua/plugins/*.lua }, defaults = { lazy = true }, install = { colorscheme = { "catppuccin" } }, checker = { enabled = true, notify = false }, performance = { rtp = { disabled_plugins = { "gzip", "tarPlugin", "tohtml", "tutor", "zipPlugin", }, }, }, })
config/options.lua — Core Settings
lua/config/options.lua
local opt = vim.opt -- Line numbers opt.number = true -- show line numbers opt.relativenumber = true -- relative numbers (essential for motions like 5j) -- Indentation opt.tabstop = 2 opt.shiftwidth = 2 opt.expandtab = true -- spaces, not tabs opt.smartindent = true -- Search opt.ignorecase = true -- case insensitive... opt.smartcase = true -- ...unless you use capitals opt.hlsearch = true opt.incsearch = true -- highlight as you type -- UI opt.termguicolors = true -- true color support opt.signcolumn = "yes" -- always show sign column (prevents layout shift) opt.cursorline = true -- highlight current line opt.scrolloff = 8 -- keep 8 lines visible above/below cursor opt.sidescrolloff = 8 opt.wrap = false -- no line wrapping -- Splits opt.splitright = true -- open vertical splits right opt.splitbelow = true -- open horizontal splits below -- System opt.clipboard = "unnamedplus" -- use system clipboard opt.undofile = true -- persistent undo across sessions opt.swapfile = false opt.updatetime = 250 -- faster CursorHold events
config/keymaps.lua — Essential Keymaps
lua/config/keymaps.lua
local map = vim.keymap.set -- Better escape map("i", "jk", "<Esc>", { desc = "Exit insert mode" }) -- Window navigation (no Ctrl-w prefix needed) map("n", "<C-h>", "<C-w>h", { desc = "Move to left window" }) map("n", "<C-j>", "<C-w>j", { desc = "Move to below window" }) map("n", "<C-k>", "<C-w>k", { desc = "Move to above window" }) map("n", "<C-l>", "<C-w>l", { desc = "Move to right window" }) -- Resize with arrows map("n", "<C-Up>", ":resize +2<CR>") map("n", "<C-Down>", ":resize -2<CR>") map("n", "<C-Left>", ":vertical resize -2<CR>") map("n", "<C-Right>", ":vertical resize +2<CR>") -- Move lines up/down in visual mode map("v", "J", ":m '>+1<CR>gv=gv") map("v", "K", ":m '<-2<CR>gv=gv") -- Stay centered when scrolling map("n", "<C-d>", "<C-d>zz") map("n", "<C-u>", "<C-u>zz") map("n", "n", "nzzzv") map("n", "N", "Nzzzv") -- Clear search highlight map("n", "<Esc>", ":noh<CR>") -- Buffer navigation map("n", "<S-l>", ":bnext<CR>") map("n", "<S-h>", ":bprevious<CR>") map("n", "<leader>bd", ":bdelete<CR>", { desc = "Delete buffer" }) -- Better paste (don't overwrite register) map("v", "p", '"_dP')

08 Core Plugins

These plugin specs go in separate files under lua/plugins/. lazy.nvim auto-discovers them.

plugins/editor.lua — Navigation & Editing
lua/plugins/editor.lua
return { -- File tree { "nvim-tree/nvim-tree.lua", dependencies = { "nvim-tree/nvim-web-devicons" }, keys = { { "<leader>e", "<cmd>NvimTreeToggle<CR>", desc = "File tree" }, { "<leader>o", "<cmd>NvimTreeFindFile<CR>", desc = "Find in tree" }, }, opts = { view = { width = 35 }, renderer = { group_empty = true }, filters = { dotfiles = false }, git = { enable = true, ignore = false }, actions = { open_file = { quit_on_open = false } }, }, }, -- Fuzzy finder (telescope) { "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim", { "nvim-telescope/telescope-fzf-native.nvim", build = "make" }, }, cmd = "Telescope", keys = { { "<leader>ff", "<cmd>Telescope find_files<CR>", desc = "Find files" }, { "<leader>fg", "<cmd>Telescope live_grep<CR>", desc = "Grep project" }, { "<leader>fb", "<cmd>Telescope buffers<CR>", desc = "Buffers" }, { "<leader>fr", "<cmd>Telescope oldfiles<CR>", desc = "Recent files" }, { "<leader>fs", "<cmd>Telescope lsp_document_symbols<CR>", desc = "Symbols" }, { "<leader>fd", "<cmd>Telescope diagnostics<CR>", desc = "Diagnostics" }, { "gr", "<cmd>Telescope lsp_references<CR>", desc = "References" }, }, config = function() local telescope = require("telescope") telescope.setup({ defaults = { file_ignore_patterns = { "node_modules", ".git/" }, layout_strategy = "horizontal", layout_config = { width = 0.9, preview_width = 0.5 }, }, }) telescope.load_extension("fzf") end, }, -- Treesitter (syntax highlighting + text objects) { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", event = "BufReadPost", opts = { ensure_installed = { "lua", "javascript", "typescript", "tsx", "python", "rust", "go", "json", "yaml", "html", "css", "bash", "markdown", }, highlight = { enable = true }, indent = { enable = true }, }, config = function(_, opts) require("nvim-treesitter.configs").setup(opts) end, }, -- Auto pairs { "windwp/nvim-autopairs", event = "InsertEnter", opts = {} }, -- Surround (ys, cs, ds) { "kylechui/nvim-surround", event = "VeryLazy", opts = {} }, -- Comment toggling { "numToStr/Comment.nvim", keys = { { "gcc", mode = "n", desc = "Toggle comment" }, { "gc", mode = "v", desc = "Toggle comment" }, }, opts = {} }, -- Which-key (shows available keybindings) { "folke/which-key.nvim", event = "VeryLazy", opts = {}, }, }
plugins/lsp.lua — Language Server & Completion
lua/plugins/lsp.lua
return { -- Mason: auto-install LSP servers { "williamboman/mason.nvim", build = ":MasonUpdate", opts = {}, }, { "williamboman/mason-lspconfig.nvim", dependencies = { "mason.nvim", "neovim/nvim-lspconfig" }, event = "BufReadPre", config = function() require("mason-lspconfig").setup({ ensure_installed = { "lua_ls", "ts_ls", "pyright", "rust_analyzer", "gopls", }, automatic_installation = true, }) local on_attach = function(_, bufnr) local map = function(keys, func, desc) vim.keymap.set("n", keys, func, { buffer = bufnr, desc = desc }) end map("gd", vim.lsp.buf.definition, "Go to definition") map("K", vim.lsp.buf.hover, "Hover docs") map("<leader>ca", vim.lsp.buf.code_action, "Code action") map("<leader>rn", vim.lsp.buf.rename, "Rename symbol") map("<leader>D", vim.lsp.buf.type_definition, "Type definition") map("[d", vim.diagnostic.goto_prev, "Prev diagnostic") map("]d", vim.diagnostic.goto_next, "Next diagnostic") end local capabilities = require("cmp_nvim_lsp") .default_capabilities() require("mason-lspconfig").setup_handlers({ function(server) require("lspconfig")[server].setup({ on_attach = on_attach, capabilities = capabilities, }) end, }) end, }, -- Autocompletion { "hrsh7th/nvim-cmp", event = "InsertEnter", dependencies = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", "L3MON4D3/LuaSnip", "saadparwaiz1/cmp_luasnip", }, config = function() local cmp = require("cmp") local luasnip = require("luasnip") cmp.setup({ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end }, mapping = cmp.mapping.preset.insert({ ["<C-n>"] = cmp.mapping.select_next_item(), ["<C-p>"] = cmp.mapping.select_prev_item(), ["<C-Space>"] = cmp.mapping.complete(), ["<CR>"] = cmp.mapping.confirm({ select = true }), }), sources = { { name = "nvim_lsp" }, { name = "luasnip" }, { name = "buffer" }, { name = "path" }, }, }) end, }, }
plugins/git.lua — Git Integration
lua/plugins/git.lua
return { -- Gitsigns: inline git hunks { "lewis6991/gitsigns.nvim", event = "BufReadPre", opts = { on_attach = function(bufnr) local gs = require("gitsigns") local map = function(mode, l, r, desc) vim.keymap.set(mode, l, r, { buffer = bufnr, desc = desc }) end map("n", "]h", gs.next_hunk, "Next hunk") map("n", "[h", gs.prev_hunk, "Prev hunk") map("n", "<leader>hs", gs.stage_hunk, "Stage hunk") map("n", "<leader>hr", gs.reset_hunk, "Reset hunk") map("n", "<leader>hp", gs.preview_hunk, "Preview hunk") map("n", "<leader>hb", gs.blame_line, "Blame line") end, }, }, -- Fugitive: full git CLI from nvim { "tpope/vim-fugitive", cmd = { "Git", "Gvdiffsplit", "Glog" }, keys = { { "<leader>gs", "<cmd>Git<CR>", desc = "Git status" }, { "<leader>gd", "<cmd>Gvdiffsplit<CR>", desc = "Git diff" }, { "<leader>gl", "<cmd>Glog<CR>", desc = "Git log" }, }, }, -- Diffview: side-by-side diffs + file history { "sindrets/diffview.nvim", cmd = { "DiffviewOpen", "DiffviewFileHistory" }, keys = { { "<leader>gD", "<cmd>DiffviewOpen<CR>", desc = "Diffview" }, { "<leader>gf", "<cmd>DiffviewFileHistory %<CR>", desc = "File history" }, }, }, }
plugins/ui.lua — Colorscheme & Statusline
lua/plugins/ui.lua
return { -- Colorscheme { "catppuccin/nvim", name = "catppuccin", lazy = false, priority = 1000, config = function() require("catppuccin").setup({ flavour = "mocha", integrations = { nvimtree = true, telescope = { enabled = true }, gitsigns = true, treesitter = true, native_lsp = { enabled = true }, which_key = true, }, }) vim.cmd.colorscheme("catppuccin") end, }, -- Statusline { "nvim-lualine/lualine.nvim", event = "VeryLazy", opts = { options = { theme = "catppuccin", component_separators = "|", section_separators = "", }, }, }, -- Buffer tabs { "akinsho/bufferline.nvim", event = "VeryLazy", opts = { options = { diagnostics = "nvim_lsp", offsets = {{ filetype = "NvimTree", text = "Explorer" }}, }, }, }, }

10 Power Editing Patterns

These are the operations that make vim users faster than mouse-driven editors. Internalize these patterns for large codebases.

Multi-edit Patterns

Search & Replace Across Files
# Within current file :%s/oldFunc/newFunc/g # replace all occurrences :%s/oldFunc/newFunc/gc # replace with confirmation per match # Project-wide: use Telescope live_grep → quickfix list # 1. <leader>fg → search for "oldFunc" # 2. Ctrl-q to send results to quickfix list # 3. :cdo s/oldFunc/newFunc/g | update # This replaces in every file from the quickfix list # Or use LSP rename for symbol-aware renaming: # Put cursor on symbol → <leader>rn → type new name
Dot Command — The Repeater

The . key repeats your last change. This is one of vim's most powerful features. Design your edits to be repeatable:

# Change a word, then repeat it elsewhere ciwnewName<Esc> # change inner word to "newName" n # jump to next search match . # repeat the change — that's it # Add semicolons to multiple lines A;<Esc> # append semicolon to current line j. # next line, repeat j. # next line, repeat
Macros — Recorded Automation

Macros record a sequence of keystrokes and replay them. Essential for repetitive refactoring.

qq # start recording into register q # ... do your edits ... q # stop recording @q # replay macro once 10@q # replay macro 10 times # Example: convert "foo: bar" to "foo = bar" on every line qq # record 0f:s = <Esc>j # go to start, find :, substitute, go down q # stop 100@q # apply to next 100 lines (stops at end)
nvim-surround — Wrapping & Changing Delimiters
ysiw" # surround word with quotes: hello → "hello" cs"' # change surrounding " to ': "hello" → 'hello' ds" # delete surrounding quotes: "hello" → hello ysiw) # surround with parens: hello → (hello) ysiw} # surround with braces: hello → {hello} ySS{ # surround line on new lines with braces # In visual mode: select text, then S" to surround selection

11 LSP & Completions

LSP (Language Server Protocol) turns nvim into a full IDE. The config from section 08 gives you all of this:

FeatureKeysNotes
Go to definitiongdWorks across files. Ctrl+o to go back.
Hover documentationKShows type info, docs popup over cursor
Code actionsSpace caQuick fixes, imports, extractions
Rename symbolSpace rnProject-wide safe rename
Find referencesgrAll usages in telescope picker
Diagnostics (errors)[d ]dJump between errors/warnings
AutocompleteCtrl+SpaceTrigger manually; auto-shows on typing
Accept completionEnterConfirms the selected item
Navigate completionsCtrl+n/pNext/prev in completion menu
Gotcha
If LSP isn't working: run :LspInfo to check if server is attached. Run :Mason to check server installation. For TypeScript projects, make sure you have a tsconfig.json in your project root.

12 Git Workflow

The git workflow in nvim is faster than any GUI once internalized. Here's the complete flow:

Daily Git Workflow

git workflow in nvim
# 1. See what changed (inline) ]h / [h # jump between changed hunks in file <leader>hp # preview hunk diff inline # 2. Stage changes <leader>hs # stage current hunk # or full git status view: <leader>gs # opens fugitive status # in fugitive: s=stage, u=unstage, ==diff, cc=commit # 3. Review diffs <leader>gd # side-by-side diff of current file <leader>gD # diffview: see ALL changed files # 4. Commit :Git commit # opens commit message editor in nvim # write message, :wq to commit # 5. Push (in tmux shell pane, or nvim terminal) :Git push # from nvim # or Prefix-2 to switch to shell window in tmux # 6. Blame <leader>hb # blame for current line <leader>gf # full file history in diffview
Branch workflow
Keep a tmux pane or window for interactive git operations (interactive rebase, merge conflicts with complex resolution, etc.). Use nvim's built-in tools for the 90% case (stage, commit, diff, blame) and drop to the shell for the rest.

13 tmux + nvim Combination Patterns

The real power of this stack emerges when tmux and nvim work as one system.

Recommended Session Layout

tmux session: "myapp"
┌─────────────────────────────────────────────────────────────┐ │ [1:nvim] [2:shell] [3:server] [4:claude] │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Window 1: nvim (full screen, this is your IDE) │ │ Window 2: shell for git, builds, one-off commands │ │ Window 3: dev server / docker / logs (split panes) │ │ Window 4: claude code (AI pair programming) │ │ │ └─────────────────────────────────────────────────────────────┘ # Quick setup script (~/.local/bin/dev): #!/bin/bash SESSION=$1 tmux new-session -d -s $SESSION -c ~/projects/$SESSION tmux rename-window -t $SESSION:1 'nvim' tmux send-keys -t $SESSION:1 'nvim .' C-m tmux new-window -t $SESSION -n 'shell' tmux new-window -t $SESSION -n 'server' tmux new-window -t $SESSION -n 'claude' tmux send-keys -t $SESSION:4 'claude' C-m tmux select-window -t $SESSION:1 tmux attach -t $SESSION

Navigation Between tmux and nvim

ActionKeys
Switch tmux window 1→4Prefix + 14
Switch nvim splitsCtrl+h/j/k/l (with our keymap)
Zoom tmux panePrefix + z (toggle)
tmux copy mode (scroll up)Prefix + [, then vim keys to navigate
Quick terminal in nvim:terminal or :sp | terminal
Pro workflow
Install vim-tmux-navigator plugin (christoomey/vim-tmux-navigator) + matching tmux config. This lets Ctrl+h/j/k/l seamlessly move between tmux panes AND nvim splits — no prefix needed. It removes the mental boundary between the two tools.

14 Claude Code Integration

Claude Code runs in the terminal and is project-aware. It reads your files, understands context, and can make edits directly. Here's how to integrate it into the workflow.

Setup

installation & setup
# Install Claude Code $ npm install -g @anthropic-ai/claude-code # Run in project root (dedicated tmux window) $ cd ~/projects/myapp $ claude # Claude Code reads your project structure, git status, # and can directly edit files in your working tree. # nvim will auto-detect file changes (with autoread).

The nvim + Claude Code Workflow

nvim: identify task
Prefix+4: claude window
Describe edit to claude
Prefix+1: review in nvim
example claude code prompts
# Scaffold "Add a new API endpoint for user preferences in src/api/. Follow the pattern in src/api/users.ts. Include types, validation, and tests." # Refactor "Refactor the authentication middleware in src/middleware/auth.ts to use the new JWT library. Keep the same interface." # Debug "The tests in tests/api/orders.test.ts are failing. Diagnose and fix." # Large refactors "Convert all the React class components in src/components/ to functional components with hooks. Do it file by file."
Important: autoread
Add this to your nvim config so files edited by Claude Code auto-refresh in your buffers:
-- in config/autocmds.lua vim.opt.autoread = true vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter" }, { command = "checktime", })

Decision Framework: nvim vs Claude Code

TaskUseWhy
Change a variable namenvim (Space+rn)LSP rename is instant and precise
Write a new module from scratchClaude CodeFaster for boilerplate + conventions
Fix a specific bug on one linenvimYou're already looking at it
Refactor across 20 filesClaude CodePattern-matching across files is its strength
Review a diffnvim (gitsigns/diffview)Interactive, visual, precise
Write tests for existing codeClaude CodeIt can read the code and generate tests
Quick search-replacenvim (:%s)One command, instant
Migrate an API versionClaude CodeContext-aware changes across codebase

15 Large Project Patterns

Patterns that matter when your codebase is 100k+ lines across hundreds of files.

Project Discovery — First 10 Minutes

onboarding a new codebase
# 1. Open project root $ cd ~/projects/big-app && nvim . # 2. Browse structure <leader>e # open tree, scan the shape # 3. Find the entry point <leader>ff → main # telescope: find main/index/app files # 4. Search for key patterns <leader>fg → "router" # grep for routing setup <leader>fg → "schema" # grep for data models # 5. Trace a flow gd # follow function definitions gr # see who calls this function Ctrl-o # go back in jump stack # 6. Check git for recent activity <leader>gl # git log — what's been changing?

Split Strategy for Complex Tasks

working on a feature that touches multiple files
# Open related files in splits :vs src/api/handler.ts # vertical split :sp src/types/api.ts # horizontal split # Navigate splits Ctrl-h/j/k/l # move between splits # Or use buffers + telescope for quick switching <leader>fb # list open buffers, fuzzy filter # Close splits you don't need :q # close current split <leader>bd # close current buffer

Quickfix List — Your Task Queue

The quickfix list is a list of locations (file + line). It's the backbone of project-wide operations.

# Populate quickfix from grep :vimgrep /TODO/g src/**/*.ts # find all TODOs # Or from Telescope: search → Ctrl-q sends to quickfix # Navigate quickfix :copen # open quickfix window :cnext # go to next item (map to ]q) :cprev # go to prev item (map to [q) # Apply operation to all quickfix items :cdo s/TODO/DONE/g | update # replace in every file :cdo normal @q | update # run macro on every location

16 Master Cheatsheet

The everything reference. Bookmark this section.

KeyActionCategory
hjklCharacter movementBasic
wbeWord forward/back/endWord
WBEWORD (whitespace-delimited)Word
f/t + charFind/till char on lineLine
; ,Repeat/reverse f/tLine
0 ^ $Line start/first char/endLine
{ }Paragraph up/downBlock
%Matching bracketBlock
gg GFile start/endFile
Ctrl+d/uHalf page down/upScroll
Ctrl+o/iJump back/forwardJump
* #Search word under cursor fwd/backSearch
/patternSearch forwardSearch
n NNext/prev search resultSearch
KeyAction
ciwChange inner word
ci" ci' ci`Change inner quotes
ci( ci{ ci[Change inner brackets
citChange inner HTML tag
dapDelete around paragraph
yiwYank inner word
dd / DDelete line / to end of line
cc / CChange line / to end of line
yy / YYank line / to end of line
p / PPaste after/before
u / Ctrl-rUndo / redo
.Repeat last change
~Toggle case
JJoin line below to current
>> / <<Indent / dedent line
gccToggle comment (Comment.nvim)
ysiw"Surround word with " (nvim-surround)
cs"'Change surrounding " to '
KeyActionPlugin
Space eToggle file treenvim-tree
Space oFind current file in treenvim-tree
Space ffFind filesTelescope
Space fgLive grep (search text)Telescope
Space fbList buffersTelescope
Space frRecent filesTelescope
Space fsDocument symbolsTelescope+LSP
Space caCode actionLSP
Space rnRename symbolLSP
Space gsGit statusFugitive
Space gdGit diff fileFugitive
Space gDDiffview all filesDiffview
Space hsStage git hunkGitsigns
Space hpPreview git hunkGitsigns
Space hbBlame lineGitsigns
Space bdClose bufferBuilt-in
Key (Ctrl-a prefix)Action
cNew window
19Switch to window
|Vertical split
-Horizontal split
h/j/k/lNavigate panes
zZoom pane toggle
dDetach session
[Copy mode (vim keys)
rReload config
⚡ Learning Path (Week by Week)
WeekFocusGoal
Week 1Survival: modes, hjkl, w/b, i/a/o, dd, u, :w/:qCan edit files without panic
Week 2Grammar: ci/di/yi + text objects, f/t, /, Ctrl-d/uEditing speed approaches old editor
Week 3Telescope + nvim-tree navigation, buffers, splitsCan navigate large projects
Week 4LSP: gd, gr, K, code actions, rename. Git workflowFull IDE replacement
Week 5+Macros, quickfix, advanced surround, Claude Code integrationFaster than any other setup