DAY 06 · VSCODE ROADMAP · 21 DAYS

正则是 永久的多光标

昨天讲多光标时说过——「多光标是临时正则,正则是永久多光标」。 今天补上这块拼图。 我们把搜索范围从「单文件」扩展到「整个工作区」,把替换从「字面量」升级到「捕获组 + 大小写转换」, 最后理解 VSCode 一个常被忽视的设计——搜索结果窗口本身就是一个可编辑的预览

DURATION 60 min THEORY 25 min HANDS-ON 25 min REVIEW 10 min POWER MOVE ⌘F + ⌥⏎
M ·

思维导图

OVERVIEW
DAY 06 · 搜索 · 替换 · 正则FILE · WORKSPACE · REGEX · LIVE-EDIT01 · FILE⌘F 单文件搜索02 · WORKSPACE⌘⇧F + glob03 · REGEX捕获组 + $1 \U04 · LIVE-EDIT结果即编辑器·Aa / Wholeword / .*·⌘G / ⌘⇧G 上下匹配·⌥⏎ 当前结果全选·在选区内查找·⌘⇧F 全工作区·⌘⇧H 多文件替换·files to include / exclude·search.exclude 全局·() 捕获 · $1 $2 引用·\U \L 大小写·(?<=) (?=) 边界·命名捕获 (?<name>)·结果直接改·× 排除单条·Open in Editor·和 ripgrep 关系DELIVERABLES三个开关肌肉记忆写出 5 个常用正则glob 限定搜索范围完整跨文件重构
FIG · Day 06 全景:从单文件搜索 → 全工作区 → 正则进阶 → 结果可编辑
★ ·

核心:作用域金字塔与捕获管道

FOUNDATION

VSCode 的搜索是两个正交的维度: 纵向是「作用域」(单文件 → 工作区 → glob 子集), 横向是「能力」(字面量 → 正则 → 捕获组替换)。 把这两个维度组合起来,就是今天要掌握的全部。

SCOPE PYRAMID + CAPTURE PIPELINESCOPE⌘⇧F · WORKSPACE整个工作区 · 所有文件FILES TO INCLUDEsrc/**/*.ts · 子集过滤⌘F · FILE当前文件IN SELECTION选中范围内NARROWINGCAPTURE PIPELINEPATTERN · search boximport (\w+) from "(.+)"MATCH · in sourceimportButtonfrom "@/components"REPLACE · with $1 $2import$1from "@/ui/$2"RESULT · in sourceimportButtonfrom "@/ui/@/components"
FIG · 左:作用域金字塔从大到小;右:捕获组从模式到结果的四步管道
VERTICAL · 作用域

从大到小不断收窄

工作区 → glob 限定 → 单文件 → 选区。每收窄一层,精度更高、误改更少。

HORIZONTAL · 能力

从字面量到正则到捕获

找一个词 → 找一个模式 → 用模式提取再重组。今天目标是把这条横轴跑到尾。

CROSS · 组合

4 × 3 = 12 种场景

4 种作用域 × 3 种能力 = 12 种典型搜索场景。后面会用 5 个真实案例覆盖最常用的几格。

01 ·

⌘F:单文件搜索的三个开关

10 MIN

⌘F 看着简单,但搜索框右侧那三个小图标(Aa / Ab| / .*) 决定了你是用「字面量」还是「正则」、是「精确」还是「模糊」。 老司机 90% 的搜索都先调好这三个开关再开始打字。

三个开关

图标含义切换快捷键使用建议
AaMatch Case · 大小写敏感C找标识符时一定开;找文档/中文时关
Ab|Whole Word · 整词匹配Wid 时必开,否则会匹配 idx / uuid
.*Regex · 正则匹配R需要捕获组、模糊匹配、跨字段时必开

⌘F 完整快捷键

动作macOSWin / Linux说明
打开搜索框FCtrlF当前文件
打开搜索 + 替换FCtrlH多一行替换框
下一个匹配G · F3 · 跳过、再跳过
上一个匹配GF3回退
把当前所有匹配变多光标AltPOWER MOVE · 比 ⌘⇧L 更精准(可加正则过滤)
在选区内搜索先选 → 再 ⌘F → 点 |...| 图标同左把搜索范围限制在选区
替换当前匹配1Ctrl1逐个确认替换
替换全部CtrlAlt当前文件全替换
关闭搜索框EscEsc退回编辑

⌘F + ⌥⏎ 黄金组合

Day 05 已经预告过——这是多光标的另一条进入通道,而且比 ⌘⇧L 更精准: 你可以先用三个开关过滤(case-sensitive / whole-word / regex), 满意后 ⌥⏎ 把当前所有匹配一次性变成多光标。

// 场景:把变量名 "id" 改成 "userId" — 但不能误改 idx / uuid
const id = req.params.id;
const idx = list.indexOf(id);
const uuid = generateUuid();
const u = users.find(u => u.id === id);

// 步骤:
// 1. ⌘F 打开搜索框
// 2. 输入 id
// 3. ⌥W 打开 Whole Word(关键!避免命中 idx / uuid)
// 4. ⌥C 打开 Match Case(避免命中 ID)
// 5. ⌥⏎ 把所有 id 变多光标
// 6. Esc 关闭搜索框,继续保留多光标
// 7. ⌘← 跳到每个 id 前 → 输入 user → 末尾大写化

// 结果:
const userId = req.params.userId;
const idx = list.indexOf(userId);     // idx 没改
const uuid = generateUuid();          // uuid 没改
const u = users.find(u => u.userId === userId);
这就是「过滤 → 转多光标 → 编辑」三步舞 —— 比 ⌘⇧L 安全得多
02 ·

⌘⇧F:全工作区搜索 + glob 限定

15 MIN

⌘⇧F 不是 ⌘F 的「升级版」—— 它是完全不同的工具。 它打开的是 Activity Bar 的 Search 视图,带左侧文件树预览,本身就是一个迷你 IDE。

全工作区搜索快捷键

动作macOSWin / Linux说明
全工作区搜索FCtrlF打开 Search 视图
全工作区替换HCtrlH展开替换行的 Search 视图
用当前选中作为搜索词选中 → F同左搜索框自动预填
展开 / 折叠所有结果点击工具栏箭头同左大量结果时收起
从结果中排除某条 / 某文件悬浮 → 点 ×同左批量替换前先排除误命中
在编辑器打开搜索结果结果窗口右上角 Open in Editor同左当作可编辑列表
回到上次搜索词 在搜索框同左搜索框有历史

files to include / exclude · glob 实战

Search 视图展开后会显示两栏 glob 输入: files to include(只搜哪里) 和 files to exclude(不搜哪里)。 glob 模式是搜索精度的第二把刀

glob含义典型用法
**/*.ts所有目录里的 .ts 文件限定 TypeScript 源码
src/**/*.{ts,tsx}src 下所有 ts/tsx排除 tests / scripts / docs
!**/node_modules/**感叹号 = 否定 · 排除 node_modules本地有 dependency 时
**/*.test.ts所有测试文件跨文件改测试用例
./packages/api/**monorepo 中的某个包限定子包
**/*.{md,mdx,html}多扩展名一次匹配跨文档格式
空着用全局 search.exclude大多数时候依赖默认就够

全局排除:search.exclude

不想每次都手填「排除 node_modules」?写进 settings.json 一劳永逸。 Day 03 的团队 .vscode/settings.json 模板里已经埋了一份,这里展开看一下。

// settings.json — User 或 Workspace 都可
{
  "search.exclude": {
    // 包管理器
    "**/node_modules": true,
    "**/__pycache__": true,
    "**/.venv": true,
    "**/vendor": true,

    // 构建产物
    "**/dist": true,
    "**/build": true,
    "**/.next": true,
    "**/coverage": true,

    // 锁文件(很大,几乎不需要搜)
    "**/pnpm-lock.yaml": true,
    "**/package-lock.json": true,
    "**/yarn.lock": true,
    "**/Cargo.lock": true,

    // 二进制/媒体(搜不出文本)
    "**/*.{png,jpg,jpeg,gif,svg,pdf,zip}": true
  },

  // 是否使用 .gitignore 自动排除(默认 true,推荐保持)
  "search.useIgnoreFiles": true,
  "search.useGlobalIgnoreFiles": true,
  "search.useParentIgnoreFiles": true
}
files.exclude 控制文件浏览器,search.exclude 单独控制搜索 —— 两者解耦

⌘F vs ⌘⇧F 决策表

场景用什么原因
当前文件改名一处变量⌘F + ⌥⏎用多光标看着改最安全
跨文件改 import 路径⌘⇧H预览结果再确认替换
找一个函数被谁调用F12 / ⇧F12 (Day 13)语义级,不要用文本搜
找一段错误日志的来源⌘⇧F跨文件文本搜最快
给 README 改一处错别字⌘F当前文件就够
monorepo 只在 packages/api 找⌘⇧F + globinclude 限定 packages/api/**
03 ·

正则:捕获组与替换语法

15 MIN

正则的核心不是元字符,而是捕获组—— 把匹配中「我感兴趣」的部分用 () 包起来, 在替换时用 $1 $2 取出来重组。 看懂这一节,你的搜索能力会跨一个台阶。

VSCode 正则要点(基于 JavaScript RegExp)

语法用法例子
(...)捕获组(\w+) from
$1 $2替换中引用第 N 个组替换框写 const $1 = ...
$0引用整个匹配// $0 把匹配做成注释
\U$1把 $1 转大写fooFOO
\L$1把 $1 转小写FOOfoo
\u$1$1 首字母大写(其余不变)fooFoo
(?:...)非捕获组(只分组不捕获)避免占用 $1
(?<name>...)命名捕获$<name> 引用
(?=...)正向先行(后面是什么)foo(?=Bar) · 只匹配后跟 Bar 的 foo
(?!...)负向先行(后面不是什么)foo(?!Bar)
(?<=...)正向后行(前面是什么)(?<=class )\w+
(?<!...)负向后行(?<!_)\d+

五个高频配方(背下来)

RECIPE 01 · IMPORT 路径

批量改 import 别名

FIND
from ['"]@/components/(.+)['"]
REPLACE
from "@/ui/$1"
@/components/Button 全部改成 @/ui/Button,保留后面的子路径
RECIPE 02 · CASE 转换

snake_case → camelCase

FIND
_(\w)
REPLACE
\U$1
user_iduserId;created_atcreatedAt建议先选区限定,避免误改字符串
RECIPE 03 · CONSOLE 清理

删除所有 console.log 行

FIND
^\s*console\.log\(.*\);?\s*\n
REPLACE
(留空)
把整行替换为空,连带换行符。^ 是行首,\s* 包容缩进
RECIPE 04 · LOOKBEHIND

给所有 class 名加前缀

FIND
(?<=class )(\w+)
REPLACE
App$1
class Userclass AppUser。lookbehind 不消费 class,只匹配后面的标识符
RECIPE 05 · 命名捕获

解析日志重排

FIND
\[(?<level>\w+)\] (?<msg>.+)
REPLACE
$<level>: $<msg>
日志格式从 [INFO] connected 重写为 INFO: connected。命名捕获让正则可读得多
RECIPE 06 · 反转

键值对调换位置

FIND
"(\w+)": "(\w+)"
REPLACE
"$2": "$1"
JSON 里所有 key:value 调换成 value:key。$1 $2 引用顺序自由

三个常见陷阱

PITFALL 01

贪婪 vs 懒惰

.+ 默认贪婪,会一直吃到行尾;加 ? 变懒惰 .+?,匹配到最近的下一个就停。

PITFALL 02

VSCode 不支持多行

VSCode 单文件搜索默认不跨行。要跨行需要 [\s\S]+? 这种「任意字符含换行」trick;或开 Search 视图的 Search Editor

PITFALL 03

转义字符要双层

正则的反斜杠在 JSON 配置里要写 \\;但在搜索框直接 \ 即可。混淆点常出现在抄博客代码时。

04 ·

搜索结果即编辑器

10 MIN · UNDERRATED

VSCode 一个极易被忽视的设计: 搜索结果窗口里的每一行都不是只读快照—— 你可以直接在结果上编辑、删除、排除,所有修改会同步回源文件。 这是「批量重构」最优雅的工作流。

三种结果操作

操作 01 · 直接编辑

把结果当代码改

点击搜索结果中的某一行匹配,直接在结果面板输入——VSCode 会同步修改源文件。看到不对劲随时改。

操作 02 · × 排除单条

选择性替换

悬浮在某一行结果上 → 出现 × 图标 → 点击排除该行。批量替换前先用 × 剔除误命中是老司机保命招式。

操作 03 · Open in Editor

结果当编辑器打开

点工具栏右上角 Open in Editor 图标 → 把结果作为 Search Editor 文件打开,可保存、可分享、可二次搜索。

完整跨文件重构工作流

// 任务:把所有 createUser(name) 改成 createUser({ name })
// — 但有些地方接收的不是字面量 name,要排除

// 1. ⌘⇧H 打开全工作区替换
// 2. 开正则 .*
// 3. Find:    createUser\((\w+)\)
//    Replace: createUser({ $1 })
// 4. 不要立刻按 Replace All!先看结果窗口

// 5. 浏览每一条匹配,发现:
createUser(name);              // ✓ 应该改
createUser(getName());        // ✗ getName() 不能放成对象 key
createUser(currentUser);       // ✓ 改
createUser(undefined);        // ✗ undefined 是 reserved

// 6. 悬浮第 2 条结果 → 点 × 排除
// 7. 悬浮第 4 条结果 → 点 × 排除
// 8. 现在结果只剩 2 条 → 点 Replace All
// 9. 完成 — 误命中 0 个

Search Editor:把搜索结果保存成文件

工具栏的 Open in Editor 把当前搜索结果另存为一个 .code-search 文件。 这是 VSCode 一个低调但强大的功能——你可以:

  • 保存调研笔记:研究开源项目时,把「所有 useEffect 调用」的搜索结果保存,边读边记
  • 跨会话复用:同一组搜索条件下次打开继续看
  • 分享给同事:.code-search 文件可以 git commit,review 别人的搜索发现
  • 二次过滤:在 Search Editor 内继续 ⌘F 在结果里再搜
许多人把 ripgrep / ack 输出贴到 Markdown 当笔记 —— Search Editor 替代了这个流程

底层引擎:ripgrep

VSCode 的全工作区搜索底层就是 ripgrep(rg)—— Rust 写的高性能 grep。所以搜大型 monorepo 时也很快、自动尊重 .gitignore、不需要装额外索引。 Day 16 讲扩展时还会回到 ripgrep——它在终端里也好用。

05 ·

动手练习

25 MIN · 3 LABS

三个 Lab 各对应今天三层能力: 单文件 + 多光标捕获组重写跨文件 + glob

Lab 1 — ⌘F + ⌥⏎ 多光标

把下面这段贴入 VSCode,用 ⌘F → ⌥⏎ 把所有纯字面量 id 改成 userId(注意保护 idx / uuid)。

function process(id) {
  const idx = list.indexOf(id);
  const uuid = generateUuid();
  if (id) {
    return { id, idx, uuid };
  }
  return id;
}

步骤:

  1. ⌘F 输入 id
  2. ⌥W 打开 Whole Word(关键!避免误命中 idx / uuid)
  3. ⌥C 打开 Match Case
  4. 右下角应显示 「6 of 6」(全是 id)
  5. ⌥⏎ 全选为多光标
  6. Esc 关搜索框
  7. 输入 userId
挑战目标:10 秒内完成,且 idx/uuid 不被误改

Lab 2 — 捕获组改 import 路径

下面这段是某次重构后产生的 import 列表,把所有 @/components/X 改成 @/ui/X

import Button from "@/components/Button";
import { Card, CardHeader } from "@/components/Card";
import Input from "@/components/forms/Input";
import { Modal } from "@/components/feedback/Modal";
import logger from "@/utils/logger";       // ← 不能改

步骤:

  1. ⌥⌘F(Win: Ctrl H)打开搜索 + 替换
  2. 开正则 ⌥R
  3. Find 框: @/components/(.+)
  4. Replace 框: @/ui/$1
  5. 逐条按 ⌘⇧1 替换,确认结果。注意第 5 行 @/utils/logger 不会匹配

Lab 3 — 全工作区 + glob 实验

用 Day 03 创建的 ~/vscode-day03-team 项目(或随便一个有几个目录的代码库)。

  1. ⌘⇧F 打开 Search 视图
  2. 展开 files to include / files to exclude 输入框
  3. 在 include 里输入 **/*.json — 只搜 JSON 文件
  4. 在 exclude 里输入 **/package*.json — 不搜 package.json / package-lock.json
  5. 搜索词输入 "recommendations"
  6. 结果窗口应只显示 .vscode/extensions.json
  7. 点工具栏 Open in Editor → 看到 .code-search 文件被打开
  8. (可选)⌘S 保存成 find-recommendations.code-search — 这就是搜索的「快照保存」
REFLECTION

三个 Lab 的纵深

从「单文件 + 字面量」到「单文件 + 正则捕获」到「跨文件 + glob 限定」。每深一层,误改概率指数下降。越大的修改越要先收窄作用域

CHALLENGE

附加挑战

把 Lab 2 的 import 重写做成全工作区替换:用 ⌘⇧H 打开,加 glob **/*.{ts,tsx} 限定,先在结果窗口检查所有匹配,排除任何看着不对的,再 Replace All。

Q ·

常见疑问

5 QUESTIONS
Q1 ⌘F⌘⇧F 究竟有什么本质区别?能不能只用一个? +
ANS

本质区别在「在哪儿搜 + 结果在哪儿展示」:⌘F嵌入式——搜索框浮在编辑器上方,结果直接高亮在当前文件里;⌘⇧F独立视图——打开 Activity Bar 的 Search 视图,结果以列表展示,可以跨多文件。能力差异:⌘F 不能跨文件、不支持 include/exclude glob、没有结果排除功能;⌘⇧F 不支持「⌥⏎ 把匹配变多光标」(因为多光标只在单文件有意义)。用一个够吗:不够。两个工作流完全不同——⌘F「找+改一处」,⌘⇧F「调研+批量改」。日常都会用到。

Q2 搜索总是出现 node_modules / dist 里的结果,我已经写了 .gitignore 为什么还在? +
ANS

三个排查点:(1) 检查 search.useIgnoreFiles 是否开启(默认 true)——关了的话 .gitignore 不会被尊重。(2) 该目录可能没有被 .gitignore 覆盖——比如 .gitignore 写的是 /node_modules(根级),但你在 monorepo 子包里有 packages/api/node_modules。改成 **/node_modules 才能全局排除。(3) 建议在 settings.json 里加一份 search.exclude 兜底——这样不依赖 .gitignore,本节 §02 的模板已经给出。临时绕过:在 Search 视图的 files to exclude 框里手动加。

Q3 正则替换里 $0 / $1 / $<name> 的编号规则到底怎么算? +
ANS

$0 是整个匹配(包括没在任何 () 里的部分);$1 / $2 / ...左括号 ( 出现顺序编号——从左到右数,无论嵌套多深。例如 ((a)(b))(c) 中:$1 = ab,$2 = a,$3 = b,$4 = c。非捕获组 (?:...) 不占编号——这是它存在的意义。命名捕获(?<name>...) 定义,用 $<name> 引用——它仍然占编号位,所以也可以用 $1 同时引用。实战建议:超过 3 个捕获组就用命名捕获,可读性差距巨大。

Q4 Replace All 后发现替换错了,怎么撤销? +
ANS

分情况:(1) 单文件 ⌘F 替换——直接 ⌘Z,VSCode 把整个替换视为一次编辑操作。(2) 全工作区 ⌘⇧H 替换——VSCode 没有「一键全部撤销」。每个文件需要分别打开按 ⌘Z。如果你替换前已经 git commit,直接 git checkout . 最干净。(3) 预防胜于补救——大型替换务必先 commit;并先在结果窗口逐条 review、用 × 排除可疑项、再点 Replace All。Day 11 学完 Git 后,这个流程会变成肌肉记忆。第三招:开启文件 timeline(Day 03 提过)——VSCode 内置的文件版本历史,允许回滚某个文件到几小时前。

Q5 VSCode 搜索和命令行 rg / grep 是什么关系?重复了吗? +
ANS

VSCode 的全工作区搜索底层就是 ripgrep(rg)——同一个工具,不同入口。所以速度、正则方言、glob 语义都和 rg 命令完全一致。区别在 UI 与工作流:VSCode 加了三层增强——(1) 结果可点击跳转到源文件;(2) 结果窗口可直接编辑替换;(3) Search Editor 可保存为 .code-search 文件分享。什么时候用命令行 rg:(a) 写脚本/CI(rg --json | jq)、(b) 远程机器没装 VSCode、(c) 需要管道到其他工具(rg foo | wc -l)。什么时候用 VSCode 搜索:99% 的日常调研、重构、修改场景。

R ·

复盘问题

5 QUESTIONS
  1. ⌘F 搜索框右侧的三个开关分别是什么?它们的快捷键是?
  2. ⌘F + ⌥⏎」组合做了什么?它和 ⌘⇧L 比有什么优势?
  3. 正则替换里 \U$1 是什么意思?$0$1 的区别是?
  4. monorepo 中只想搜索 packages/api 下的 .ts 文件,glob 应该怎么写?
  5. 大型跨文件替换最稳妥的工作流是什么?(预防替换出错)
✓ ·

今日检查清单

8 ITEMS
  • 能熟练用 ⌥C / ⌥W / ⌥R 切换三个开关
  • 掌握「⌘F + ⌥⏎ → 多光标」黄金组合
  • 区分 ⌘F (单文件) 与 ⌘⇧F (全工作区) 的使用场景
  • 能写出至少 3 个常用正则配方(import 重写 / case 转换 / 删除日志)
  • 理解 $0 / $1 / $<name> / \U / \L 的语义
  • 会用 files to include / exclude glob 收窄搜索
  • 给 settings.json 加了一份合理的 search.exclude
  • 完成 3 个 Lab,理解搜索结果窗口的可编辑性
📖 ·

推荐阅读

3 ITEMS
OFFICIAL

Searching across files

VSCode 全工作区搜索的官方文档。glob、include/exclude、Search Editor 的权威说明。

REFERENCE

JavaScript RegExp · MDN

VSCode 正则基于 JavaScript 引擎。MDN 的 RegExp 文档是查所有元字符、断言、捕获组的最佳词典。

TOOL

regex101.com

正则在线测试器。写复杂正则前先在这里调通,再粘贴到 VSCode。Flavor 选 ECMAScript/JS 才与 VSCode 一致。

» ·

Day 07 预告

NEXT
COMING NEXT

Snippets 与 Emmet — 把高频代码模板化

Day 04 的导航 / Day 05 的多光标 / Day 06 的搜索都在解决「已经写出来的代码怎么改」。 明天的 Snippets 解决「怎么快速生成新代码」—— 内置 / 用户 / 项目三层 snippet 的优先级、tab stops 与 placeholder、choice 多选项、变量($TM_FILENAME)、Emmet 缩写。 做完明天,你会发现 90% 的样板代码都不用手敲。

"搜索框是编辑器最被低估的命令行 —— 多光标只在当下,正则改未来。"

DAY 06 · VSCODE 21-DAY ROADMAP