VS Code can replace text that spans several lines, but only in regex mode — you turn it on with the .* button in the search box, then use \n for a line break. That one detail is what most people are missing. This guide covers that, capture groups, multi-cursor editing as an alternative, and replacing across an entire project safely.
The setting that makes it work#
Open find and replace with Ctrl+H (Cmd+Option+F on a Mac), then click the .* icon at the right of the search field, or press Alt+R. Without regex mode on, the search box treats everything literally and a newline cannot be typed at all.
Once it is on, these escapes work:
| Escape | Matches |
|---|---|
\n |
a line break |
\r\n |
a Windows line break |
\s |
any whitespace, newlines included |
\t |
a tab |
. |
any character except a newline |
[\s\S] |
genuinely any character |
That last row is the important one. A dot does not match line breaks, so a pattern like start.*end will never span lines. Use [\s\S]*? instead.
A first example#
Turning two lines into one:
Find: console.log\(\n\s*(.+)\n\s*\);
Replace: console.log($1);
And collapsing blank lines — three or more become one:
Find: \n{3,}
Replace: \n\n
You can also press Shift+Enter inside the find box to insert a literal newline, which is sometimes easier to read than typing the escape.
Lazy versus greedy#
This is the mistake that produces alarming results:
Find: <div>[\s\S]*</div> Greedy - matches to the LAST closing tag
Find: <div>[\s\S]*?</div> Lazy - stops at the FIRST one
The question mark after the star makes the match as short as possible. On a file with twenty divs, the greedy version selects everything from the first opening tag to the last closing one — and replacing that deletes most of the file.
Capture groups#
Anything in brackets is captured and available as $1, $2 and so on in the replacement.
Swapping two lines:
Find: (import .+)\n(import .+)
Replace: $2\n$1
Converting a function declaration to an arrow function:
Find: function (\w+)\((.*?)\) \{
Replace: const $1 = ($2) => {
Turning a two-line assignment into a single line:
Find: const (\w+) =\n\s+(.+);
Replace: const $1 = $2;
Named groups read better in long patterns:
Find: (?<key>\w+):\s*(?<value>.+)
Replace: $<value> belongs to $<key>
Case conversion in replacements#
VS Code supports four modifiers that other editors do not:
$1 the captured text
\u$1 uppercase the first character
\l$1 lowercase the first character
\U$1 uppercase all of it
\L$1 lowercase all of it
Find: const (\w+) =
Replace: const \U$1 = turns names into CONSTANT_CASE
Multi-cursor editing, often the better tool#
A regex is not always the answer. For repetitive edits on similar lines, multiple cursors are faster and much easier to verify:
| Shortcut | Does |
|---|---|
| Ctrl+D | select the next occurrence of the current selection |
| Ctrl+Shift+L | select every occurrence at once |
| Alt+Click | add a cursor wherever you click |
| Ctrl+Alt+Down | add a cursor on the line below |
| Shift+Alt+I | a cursor at the end of every selected line |
| Alt+Shift+drag | column selection |
Select several lines and press Shift+Alt+I, and you get one cursor at the end of each — ideal for adding a comma or a semicolon to every line at once. On a Mac these use Cmd and Option.
Sorting and joining lines#
Two commands worth knowing, from the palette (Ctrl+Shift+P):
- Join Lines (Ctrl+J) — merges the selected lines into one
- Sort Lines Ascending — useful for import blocks and lists
Both are often quicker than the regex you were about to write.
Across a whole folder#
Ctrl+Shift+H opens search across files. The same regex rules apply, plus two fields that matter a great deal:
files to include: src/**/*.ts
files to exclude: **/node_modules/**, **/dist/**, **/*.min.js
VS Code respects .gitignore by default, but being explicit costs nothing and prevents replacing text inside built output or dependencies.
The results panel shows every match grouped by file before you replace. Expand it and read a few. You can also dismiss individual files or matches from the results with the cross icon, which excludes them from the replace.
Patterns worth keeping#
Trailing whitespace
Find: [ \t]+$
Replace: (empty)
Empty lines at the end of a block
Find: \n\s*\n(\s*\})
Replace: \n$1
Remove a whole multi-line comment
Find: /\*[\s\S]*?\*/
Replace: (empty)
Wrap every line in quotes and a comma
Find: ^(.+)$
Replace: "$1",
Change double-quoted imports to single quotes
Find: from "(.+?)"
Replace: from '$1'
When not to use find and replace#
For renaming a symbol, use F2 instead. It understands the language, so it renames the variable and every reference to it without touching a string that happens to contain the same word. A regex cannot tell those apart.
Similarly, for reformatting, run the formatter (Shift+Alt+F) rather than writing patterns to fix indentation.
Questions people ask#
Why does my newline escape not work?
Regex mode is off. The .* button must be highlighted, or the escapes are treated as literal characters.
Can I search across lines without regex?
You can paste multi-line text directly into the find box and VS Code will match it literally. That works for exact blocks but gives you no flexibility.
How do I match a literal dollar sign in the replacement?
Write $$. A single dollar introduces a capture group reference.
Does this work in the terminal too?
No — the terminal has its own search with no replace. For file-wide edits from the command line, sed or ripgrep with a replacement flag are the equivalents.
Where to go next#
- VS Code setup for beginners — the settings worth changing first.
- VS Code problems beginners face — when the editor is the obstacle.
- Git and GitHub explained — so a bad replace is always recoverable.