Code Editing Workflows
Code Editing Workflows
Vim's editing model is fundamentally different from other editors. Instead of selecting with a mouse and typing, you compose operations using a grammar of operators, motions, and text objects.
The Vim Grammar
Every Vim editing command follows this pattern:
[count] [operator] [count] [motion/text-object]
Examples:
d3w= delete 3 wordsc$= change to end of line2dd= delete 2 linesdi(= delete inside parentheses>5j= indent next 5 lines
Operators (Verbs)
Operators perform actions on text.
| Operator | Action | Mnemonic |
|---|---|---|
d | Delete (cut) | delete |
c | Change (delete + insert mode) | change |
y | Yank (copy) | yank |
> | Indent right | shift > |
< | Indent left | shift < |
= | Auto-indent | = format |
gU | Uppercase | go Uppercase |
gu | Lowercase | go uppercase |
~ | Toggle case | ~ |
Motions (How Far)
Motions define where the operator acts.
Word Motions
| Motion | Moves to... |
|---|---|
w | Start of next word |
e | End of word |
b | Back to start of word |
W | Next WORD (ignores punctuation) |
E | End of WORD |
B | Back to start of WORD |
Example:
javascriptconst userName = "John"; β β β w moves here e moves here w moves here // w = treats userName as one word // W = treats everything as one WORD until space
Line Motions
| Motion | Moves to... |
|---|---|
0 | Start of line |
^ | First non-blank character |
$ | End of line |
g_ | Last non-blank character |
gg | First line of file |
G | Last line of file |
{number}G | Go to line number |
Search Motions
| Motion | Action |
|---|---|
f{char} | Find next char on line |
F{char} | Find previous char on line |
t{char} | To (before) next char |
T{char} | To (before) previous char |
; | Repeat f/F/t/T forward |
, | Repeat f/F/t/T backward |
Example:
javascriptconst result = calculateTotal(price, tax); β β fa finds 'a' t) goes before ')'
Paragraph/Block Motions
| Motion | Moves to... |
|---|---|
} | Next paragraph/block |
{ | Previous paragraph/block |
% | Matching bracket/paren |
Text Objects (What to Act On)
Text objects define boundaries intelligently.
Inner vs Around
i= inner (excludes surrounding delimiters)a= around (includes surrounding delimiters)
Common Text Objects
| Object | Meaning | Example |
|---|---|---|
iw | Inner word | diw = delete word under cursor |
aw | Around word | daw = delete word + space |
is | Inner sentence | cis = change sentence |
as | Around sentence | das = delete sentence |
ip | Inner paragraph | yip = yank paragraph |
ap | Around paragraph | dap = delete paragraph |
i( or i) | Inside parens | di( = delete inside () |
a( or a) | Around parens | da( = delete () and contents |
i{ or i} | Inside braces | di{ = delete inside {} |
a{ or a} | Around braces | da{ = delete {} and contents |
i[ or i] | Inside brackets | ci[ = change inside [] |
a[ or a] | Around brackets | ya[ = yank [] and contents |
i" | Inside quotes | ci" = change inside "" |
a" | Around quotes | da" = delete "" and contents |
i' | Inside single quotes | di' = delete inside '' |
a' | Around single quotes | ca' = change '' and contents |
it | Inside tag (HTML) | dit = delete inside |
at | Around tag | dat = delete |
Practical Examples
Changing Text Inside Delimiters
javascript// Cursor anywhere inside the quotes const message = "Hello World"; ci" β Change inside quotes β Result: const message = "|"; (in insert mode) // Cursor anywhere in the function call calculate(price, tax) ci( β Change inside parens β Result: calculate(|) (in insert mode)
Deleting Function Arguments
javascriptfunction greet(name, age, location) { // ... } // Cursor on "name" di( β delete inside () β Result: function greet() { // Or delete including the parens da( β Result: function greet {
Changing HTML Tag Contents
html<div class="container">Old content here</div> <!-- Cursor anywhere in the tag --> cit β Change inside tag β Result: <div class="container">|</div> (insert mode) <!-- Delete tag and contents --> dat β Result: (empty line)
Yanking (Copying) and Pasting
javascriptconst userName = "John"; // Yank word yiw β Yanks "John" // Move cursor somewhere else p β Paste after cursor P β Paste before cursor // Yank entire line yy β Yank line Y β Also yanks line // Yank 3 lines y3j β Yank current + 3 down
Duplicating Lines
javascriptconst name = "John"; // Duplicate line yy β Yank line p β Paste below β Result: const name = "John"; const name = "John";
Indenting Code Blocks
javascriptfunction hello() { const x = 1; const y = 2; return x + y; } // Cursor inside function >i{ β Indent inside {} β Indents all lines inside braces // Or indent 3 lines 3>> β Indent current + next 2 lines
Commenting Out Code (with nvim-surround)
LazyVim includes nvim-surround for quick wrapping.
javascriptconst value = 42; // Add single-line comment ys$c β Surround to end of line with comment β Result: // const value = 42; // Surround word with quotes ysiw" β Surround inner word with " β Result: const "value" = 42; // Change surrounding quotes cs"' β Change surrounding " to ' β Result: const 'value' = 42; // Delete surrounding quotes ds" β Delete surrounding " β Result: const value = 42;
The Dot Command (Repeat)
The . command repeats your last change.
This is one of Vim's most powerful features.
Example 1: Change Multiple Words
javascriptconst oldName = "test"; const oldName2 = "test"; const oldName3 = "test"; // On first line: ciwnewName<Esc> β Change "oldName" to "newName" // Move to second line: j^ β Down, to start . β Repeat (changes oldName2 to newName) // Move to third line: j^ . β Repeat again
Example 2: Delete Function Calls
javascriptconsole.log("debug 1"); console.log("debug 2"); console.log("debug 3"); // On first line: dd β Delete line // On second line: . β Repeat (delete line) // On third line: . β Repeat (delete line)
Example 3: Indent Multiple Blocks
javascriptfunction a() { code; } function b() { code; } // On first function: >i{ β Indent inside braces // Navigate to second function: } β Jump to next block . β Repeat indent
Visual Mode (Selection)
Sometimes you need to see what you're operating on.
Visual Modes
| Key | Mode | Description |
|---|---|---|
v | Character-wise visual | Select characters |
V | Line-wise visual | Select full lines |
Ctrl+v | Block visual | Select rectangular blocks |
Visual Mode Workflow
javascriptconst userName = "John"; // Select "userName" and change it: v β Enter visual mode iw β Select inner word c β Change (deletes and enters insert) newName<Esc> β Type new name // Or more simply: ciw β Same thing without visual mode!
Visual mode is useful when:
- You're learning and want to see the selection
- You need to verify what you're about to delete
- Working with complex, irregular selections
But usually it's faster to skip visual mode:
ciwinstead ofviw+cdapinstead ofVap+d
Multiple Cursors (Visual Block)
Visual block mode (Ctrl+v) lets you edit multiple lines at once.
Example: Add semicolons to multiple lines
javascriptconst a = 1 const b = 2 const c = 3 // On first line, end of line: $ β Go to end Ctrl+v β Visual block mode jj β Select down 2 lines $ β Extend to end of each line A β Append (insert at end) ; β Type semicolon <Esc> β Apply to all lines β Result: const a = 1; const b = 2; const c = 3;
Example: Comment multiple lines
javascriptconst x = 1; const y = 2; const z = 3; // At start of first line: Ctrl+v β Visual block jj β Select 3 lines I β Insert at start // <Esc> β Add comment, apply to all β Result: // const x = 1; // const y = 2; // const z = 3;
Search and Replace
Within File
vim:%s/old/new/g " Replace all in file :%s/old/new/gc " Replace with confirmation :s/old/new/g " Replace in current line :'<,'>s/old/new/g " Replace in visual selection
Across Multiple Files (with Telescope)
vim" 1. Find all occurrences Space + s + g β Type "oldFunction" " 2. Send to quickfix Ctrl+q " 3. Replace in all files :cdo s/oldFunction/newFunction/g " 4. Save all :wa
Macros (Record and Replay)
Macros record a sequence of commands and replay them.
Record a Macro
vimqa " Start recording to register 'a' <commands> " Do your edits q " Stop recording
Replay Macro
vim@a " Replay macro 'a' @@ " Replay last macro 10@a " Replay 10 times
Example: Format JSON Lines
jsonname: "John" age: 30 city: "NYC" " Want to add quotes around keys: " Record macro: qa " Start recording ^ " Go to start i"<Esc> " Add opening quote f: " Find colon i"<Esc> " Add closing quote j " Move to next line q " Stop recording " Replay: @a " Runs on second line @a " Runs on third line β Result: "name": "John" "age": 30 "city": "NYC"
Essential Workflow Patterns
Pattern 1: Change Inside Thing
Problem: Change text inside delimiters
Solution: ci + delimiter
javascriptconst msg = "old text"; ci" β Change inside quotes function(arg1, arg2) ci( β Change inside parens <div>content</div> cit β Change inside tag
Pattern 2: Delete Around Thing
Problem: Delete function call entirely
Solution: da + delimiter
javascriptconsole.log("debug"); da( β Delete including parentheses β Result: console.log
Pattern 3: Yank and Duplicate
Problem: Duplicate a block
Solution: yap + p
javascriptfunction test() { return 42; } yap β Yank around paragraph (entire function) p β Paste below
Pattern 4: Indent Block
Problem: Indent nested code
Solution: >i{ or >ap
javascriptfunction test() { code; more code; } >i{ β Indent inside braces
Pattern 5: Quick Word Change
Problem: Change one word
Solution: ciw
javascriptconst oldName = 42; β cursor here ciwnewName<Esc> β Result: const newName = 42;
Common Mistakes and Fixes
β Using backspace to delete words
β Use db (delete back word) or daw (delete around word)
β Selecting with visual mode for simple changes
β Use text objects: ciw instead of viwc
β Not using the dot command
β After any change, repeat with . on similar items
β Repeating the same manual edit
β Record a macro with qa, do the edit, q, then @a to repeat
β Not learning text objects
β ci", di(, ca{ are the most powerful Vim features
Keybinding Cheat Sheet
| Action | Command | Example |
|---|---|---|
| Change word | ciw | Change inner word |
| Delete word | daw | Delete around word |
| Change inside quotes | ci" | Change text in "" |
| Delete inside parens | di( | Delete inside () |
| Yank line | yy | Copy line |
| Paste | p / P | After/before cursor |
| Repeat | . | Repeat last change |
| Undo | u | Undo |
| Redo | Ctrl+r | Redo |
| Indent | >> | Indent line |
| Record macro | qa | Record to register a |
| Replay macro | @a | Replay macro a |
Practice Exercise
Try these edits on this code:
javascriptfunction calculateTotal(price, tax) { const subtotal = price; const total = subtotal + tax; console.log("Debug: total is " + total); return total; }
Tasks:
- Change
pricetoamount(useciw) - Delete the
console.logline (usedd) - Change the string inside the console.log before deleting (use
ci") - Indent the entire function body (use
>i{) - Duplicate the function (use
yapthenp) - Change function name (use
ciwon function name)
Solutions:
- Cursor on "price",
ciw, type "amount",<Esc> - Cursor on console.log line,
dd - Cursor in string,
ci", type new text,<Esc> - Cursor in function,
>i{ - Cursor in function,
yap,p - Cursor on "calculateTotal",
ciw, type new name,<Esc>
Related:
- Neovim - The Vim Ecosystem - Understanding modal editing
- File Navigation in LazyVim - Finding files before editing
- LSP and Code Intelligence - Automated refactoring
- Git Integration in LazyVim - Committing your changes