Vim 修炼场
CHAPTER 04 · 语法篇 上(全站核心)

文本对象:给「东西」起名字

预计阅读 45 分钟(含练习约 1.5 小时)· 前置:第 1、2 章 · 本章演示 9 个

学习目标

  • 理解文本对象在编辑语言里的身份:一类新的名词,和移动键并列
  • 掌握两条边界:i(inside,里面)与 a(around,周围)
  • 记住四族常用对象:词 iw/aw、括号 i(/a(、引号 i"/a"、标签 it/at
  • 会用 ciw(改这个词)、ci"(改字符串内容)、ci((改参数列表)这三个高频句子
  • 建立关键直觉:光标只需在对象内部任意位置,不用先跳到边界上
  • 理解嵌套规则:块对象永远作用于光标所在的最内层

1. 从一个别扭的瞬间说起

第 2 章结尾你学会了 dt,(删到逗号前)。用几天你会碰到三个新烦恼:

  • 想把 render(header, footer) 里的参数整个清空重写 —— dt) 删不干净,df) 又把右括号也误伤;
  • 想改 "Ada" 引号里的内容 —— 先 f" 跳进去、再 c$ 删到引号前?三步还容易失手;
  • 删一个词用 dw 挺好,可光标停在词中间时,它只删半个词,剩下的半截更难看。

这些烦恼的共同点:你在用「移动」描述范围 —— 从这里、到那里。但大脑里真正想说的是:「作用于这个东西」:这个词、这对括号里的东西、这对引号里的东西。鼠标时代的你早就习惯了这个思路:双击选中一个词、点在括号里让 IDE 高亮配对。Vim 给这个思路一个正式的名字 —— 文本对象(text object),并且把它做成了一类新的名词,任何动词都能用。

先看一眼它解决开头三个烦恼的样子:di( 删掉括号里全部内容(括号完好)、ci" 清空字符串内容原地重打、daw 删掉光标所在的整个词(光标在词里随便哪个字母上都行)。三个键,说一句话。本章就把这套「给东西起名字」的词典讲全。

值得先想清楚一次思维层面的差别。「移动思维」是路径式的:从这里出发、经过哪些词、到哪里停 —— 你在描述一段旅程,光标是旅行者,所以你必须关心它的起点和终点。「对象思维」是指物式的:那个词、那对括号、那段引号 —— 你在指认一个东西,至于光标具体站在这个东西的哪个字母上,无关紧要。这就是为什么对象能把「光标位置」从方程里消掉:它不问你在哪,只问你在谁的肚子里。练习本章时的自检标准也只有一条:每次按对象键之前,你脑子里出现的是「路线」还是「东西」 —— 前者说明还在用第 2 章的脑子,后者才算是换挡成功。

2. 语法:动词 + i/a + 对象

文本对象的构词法只有两段:

[动词] i 或 a + 对象字符

  • i = inside(里面):只含对象本身,不含边界;inner 的缩写;
  • a = around(周围):含边界,并且连带一侧的空白(词对象连尾部空格,块对象连包裹符号本身)。

对象字符目前你能用到的全家族如下,一张表收走:

对象指什么对象指什么
w / W词 / 大词( 或 b( ) 括号块
s句子(英文句号分隔)[[ ] 方括号块
p段落(空行分隔){ 或 B{ } 花括号块
" ' `引号串(三种引号各自)< >< > 尖括号块
tHTML/XML 标签) ] }同各自开括号(闭括号也能用)

注意一个重要身份区别:第 2 章的移动键是「到达某处」的名词(回答「到哪」),文本对象是「框住某段」的名词(回答「哪一段」)。前者把光标送过去,后者原地圈地 —— 所以文本对象不能单独用来移动,它天生是给动词当宾语的。

💡 读法训练:像读中文一样读缩写 把 diw 读作「删·内·词」,ci( 读作「改·内·括号」,ya" 读作「复制·整个·引号串」。读得顺,按得才顺 —— 哑巴 Vim 是学不会的。

3. 词对象:iw 与 aw

第一站是最常用的 w。iw = 词本身;aw = 词 + 尾部空格。两者的差异在删除后立刻可见:

看清楚那个细节了么?diw 留下了两个连续空格(词没了,它两侧的空格都还在),而 daw 把尾部空格一起带走,只剩一个空格,排版干净。记住一个不成文的口味偏好:在代码行里删词,优先 aw(不留双空格);要动"词本身"而保留间隔时用 iw(比如 ciw 改名时,两侧空格根本不该动)。

再往上配一个动词 c(change,改 = 删 + 进插入),就是你以后每天要按几十次的组合:

句子读法什么时候用
ciw改·内·词变量改名 —— 光标在词内任意位置,不用 b 回词头
daw删·整·词删掉一个词且不留双空格
yiw复制·内·词抓一个词进寄存器(下一章细讲 y)
caw改·整·词少用:会把尾部空格也吞进插入点
💡 为什么 ciw 比 dw 高明 dw 的射程从光标处开始算 —— 光标在词中间就只删半截;ciw 的射程是整个对象 —— 光标在词里哪里都一样。把「光标位置」从方程里消掉,这就是对象对移动的降维打击。

4. 括号家族:i( 与 a(

代码里最多的结构就是配对符:( )、{ }、[ ]。对象语法对它们一视同仁 —— 按 (、[、{(或对应的闭括号,效果相同;b/B 是 (/{ 的别名,b = block)。先看最重要的性质:

演示里光标自始至终停在 header 的字母中间 —— 不在括号上,更不在行首。di( 照样精准清空括号内容。这条性质值得单独加粗:块对象要求光标在块内(边界上也行),但不要求在任何特定位置。对比第 2 章的 f:你得先「看到」锚点字符再跳过去 —— 对象把这一步完全省了。

三种高频用法,配不同的动词:

句子含义典型场景
di(清空括号内容,括号保留参数不要了,重写;清空调用参数做占位
ci(清空并直接重打改参数列表(下一章实战的主力)
da(连括号一起删去掉一层「包裹」:把 f(x) 变 f
yi(复制括号内全部内容把参数抄到别处(第 9 章寄存器配合)

花括号同理:di{ 清空函数体、ci{ 重写函数体、yi{ 复制整个块。嵌套时只有一条规则:

永远取光标所在的最内层。光标在 y() 上,那是 if 块的地盘;光标挪到 if 这个词上(已经在 if 块外、函数块内),同一个 di{ 就改判为函数体。想让外层响应?把光标挪出内层再按就是 —— 用 jk 移动几格就能换目标,这个「换挡」成本低到可以忽略。

⚠️ 光标在块外,对象就装聋 括号是配对高亮看得见的,但光标如果停在括号外面(比如上一行行尾),di( 会直接失败(响一声铃,什么也不删)。这不是 bug:对象只认「光标在谁肚子里」。先 j/w 进去,再说话。

5. 引号家族:i" 与 a"

三种引号各成一个对象:"、'、`(模板字符串)。改字符串内容是写业务代码的日常 —— 日志文案、错误消息、UI 文案,全是它:

ci" 的体验值得回味:不用 f 找引号、不用数长度,光标放在引号串里任何一个字符上,三个键,内容清空、引弧保留、光标停在原地等你打字。配合 Esc,一次文案修改 4 个动作封顶。而 da" 连引号端走,适合「这个字符串整个不要了」。

两条使用规则:①光标必须在这对引号之间(引号上也算);②引号必须同种配对 —— "…' 不算一对。另外注意转义的坑:字符串里的 \" 会被 Vim 正确跳过,但 IDE 的语法解析和 Vim 的朴素匹配偶有出入,极复杂的情况(引号里嵌引号)还是用第 8 章的替换更稳。

💡 引号对象的隐藏福利 光标在引号外面(比如刚好停在开引号前一个字符)时 ci" 会失败 —— 这时很多老手会先按 f" 再 ci"。其实更快的做法:直接 w 或 l 进去一个字符,对象就生效了。记住:对象要的不是「精确位置」,是「在不在屋里」。

6. 段落对象:ip 与 ap

「段落」在 Vim 里的定义朴素得可爱:空行分隔出来的一坨连续文本。代码文件里,函数与函数之间、配置块与配置块之间恰好都用空行隔开 —— 于是 ip(inner paragraph)天然对应「这一坨代码」:

看到那个容易踩的坑了吗 —— 这里的 p 是 paragraph(段落对象),不是下一章的粘贴键 p(paste)。同一个字母,位置决定身份:跟在 i/a 后面是对象,单独按是粘贴。读出声就不会混:「删·内·段落」。

句子含义什么时候用
dip删本段(可能残留一个空行)清掉一段注释、一段废弃逻辑
dap删本段 + 段尾空行同上,但要求删完排版干净(更常用)
vip选中本段第 7 章可视化模式预告:对象同样能当选择的名词
yip复制本段把一个代码块整体搬走

段落对象和 i{ 的分工:函数体有花括号包着,用 i{ 更精确;没有统一包裹符的「一坨」(连续的 import、一段 markdown、一串配置项),用 ip。有壳用壳,没壳用段。

段落对象还有一个被低估的用法:阅读时按 ]} 或大写 } 跳到下一段(后者是第 3 章的移动,这里只借个概念)—— 空行分段的代码,一段往往是一个完整的小意图,按段阅读比按行阅读快得多。把「段」当作代码的段落符号,是 ip 带给你的副产品直觉。写 markdown 的人更会立刻爱上它:一个自然段就是一个对象,cip 重写整段,dap 删掉整段连空行,再也不用 V 然后数行。

7. 标签对象:it 与 at(前端福音)

写 HTML / JSX / Vue 模板的同学专享:t = tag。it 是标签里面的内容(不含开闭标签),at 是整个元素(含标签):

cit(change inner tag)可能是前端 Vim 用户按得最爽的键:改标题、改按钮文案、改链接文字,全是它 —— 标签结构毫发无损,内容就地重写。dat 则删掉整个元素,相当于 IDE 里「删除标签」的重构,但不用碰菜单。嵌套标签同样遵守「最内层优先」:光标在 <span> 里,cit 改的是 span 的内容;想改外层 <div> 的?先把光标挪到 span 外面(div 内)再按。

⚠️ 自闭合标签没有 inner <br/>、<img/> 这类自闭合标签没有内容,it 无从谈起;at 倒是可以删掉它。另外 Vim 的标签匹配是朴素正则,遇到 JSX 里大小写组件、展开属性写得很长的标签,偶尔会认错边界 —— 常规场景放心用,怪异结构用 vat 先看一眼选中范围再决定删不删(第 7 章)。

8. 对象不分行:跨行的括号一样罩得住

「一行放不下拆成多行的参数列表」是格式化后的常态。文本对象对此毫无怨言 —— 它按的是结构,不是行:

四行的调用,di( 一键清成两行;换成 ci( 还能直接重写。对比一下没有对象的年月:光标挪到第一个参数、djj 或者按三次 dd 还得防多删 —— 现在你只说「清空括号」,几行是括号自己的事。配合第 3 章会讲的 %(配对跳转),配对结构在 Vim 里处处是VIP。

跨行能力也让对象成了格式化不友好代码的救星:IDE 一开自动换行,行级操作(按行数删、按列选)经常失手,但结构不变 —— 括号还是那对括号,字符串还是那个字符串。养成习惯:想动「结构里的东西」,永远先找对象,行数交给机器。

9. 名词不变,动词随便换

本章你其实一直在用三个动词(d 删、c 改、y 复制),但注意:我们从没为某个动词单独学过对象。词典是一本,菜单随便点:

动词 ↓ / 对象 →iwi(i"it
d 删diw 删词di( 清参数di" 清字符串dit 清标签内容
c 改ciw 改词 ⭐ci( 重写参数 ⭐ci" 改文案 ⭐cit 改标签内容 ⭐
y 复制yiw 抓词yi( 抓参数yi" 抓字符串—

这张 3×4 的矩阵就是 12 条命令,再加 a 版本直接翻倍 —— 而你要背的只有「两个边界字母 + 五六个对象字符」。下一章我们把动词全表补齐(还有 p 粘贴、r 单换、大小写、缩进……),并把这些零件组装成完整的语法公式。届时你会发现:这一章的词典,一个字都不用重学。

10. 每日训练计划(30 分钟 × 3 天)

天时间盒内容通过标准
第 1 天0–10 分钟重放演示 1–2,读 §1–§3;靶场里 diw/daw/ciw 各 10 次能脱口说出 i 和 a 删完的差别
10–30 分钟练习 1:词敏感训练光标故意放词中间,次次删干净
第 2 天0–10 分钟重放演示 3–5,读 §4–§5;括号/引号三件套各 10 次ci( 与 ci" 不假思索
10–30 分钟练习 2 + 练习 3嵌套换挡成功 8/10 次
第 3 天0–10 分钟重放演示 6–9,读 §6–§9能背出四族对象各自的场景
10–30 分钟练习 4:盲测毕业考9 题对 7 题,本章毕业

11. 如果本章只记住三件事

  1. i 是里面,a 是带壳(还多吃一口空白):删完留不留「壳」和双空格,选前想一秒;
  2. 光标进去就行:词内、括号内、引号内 —— 任意位置,对象自动框住整个结构,嵌套取最内层;
  3. 词典一本,动词随便点:d/c/y × iw/i(/i"/it 的矩阵,先把三颗 ⭐(ciw、ci(、ci")练成本能,其余随用随查。

下一章把动词讲全,组装出完整的语法公式 —— 到那时,你按的每一组键,都是一句完整的中文。

练习

练习 1:词敏感训练(10 分钟)

靶场找 20 行代码,光标故意停在标识符的中间字母上,然后:①diw 删掉、u 悔回;②daw 删掉、观察双空格消失、u 悔回;③ciw 输入 tmp、Esc、u 悔回。每个词三连,做 20 组。目标:手指记住「词中间也能整词操作」的安全感。

练习 2:括号全家桶 + 嵌套换挡(10 分钟)

找一个有两层嵌套花括号的函数(或自己造一个),光标放进最内层:di{ → u;k 挪出内层 → 再 di{(这次是外层)→ u。然后对函数调用练 di( / ci((重打一遍原参数) / da(,各 5 次,每次光标换一个奇怪的位置起手。通过线:10 次里 8 次一次到位、不用 u。

练习 3:ci" 文案马拉松(10 分钟)

找一段有 10 个以上字符串的代码(日志、报错、UI 文案都行),逐个:不移动光标到引号上(在字符串内部任意字符即可),ci" → 随便改两个字 → Esc。10 个为一组,做完 u 回滚所有修改。进阶:换成 da" 感受「连壳端走」,再 u。

练习 4:盲测毕业考(10 分钟)

准备好纸笔(或让同事出题),对着一屏真实代码,以下 9 个任务先说按键、再看答案:①删光标所在的词(不留双空格)②改光标所在的词 ③清空某函数调用的参数 ④重写参数列表 ⑤删掉某字符串内容(留引号)⑥删掉某字符串整体(连引号)⑦改 h2 标签里的文案 ⑧删掉空行隔开的一段注释 ⑨复制括号里的全部参数。9 题对 7 题毕业。答案:daw / ciw / di( / ci( / di" / da" / cit / dap / yi(。

常见陷阱

🕳 i 和 a 傻傻分不清 删完发现双空格 / 或该留的空格没了 —— 这是没想清楚边界。记一句口诀:「a 多吃一口」(around 会把边界和一侧空白一起算上)。代码里删词默认 daw,改词一律 ciw,90% 的场景这两条就够了。
🕳 光标在块外按 di( 没反应 响铃、不动、以为插件坏了 —— 其实是光标在括号外面。块对象只认「光标在谁肚子里」。先 w/j 进去再按。反向的坑:想删外层,光标却还在内层里 —— 挪出去(哪怕一格)再 di{。
🕳 中文全角标点不是对象 ()、「」、"" 这些全角字符 Vim 不认 —— di( 找的是半角 ()。写中文文案、改全角引号时,对象会失灵,这是少数该回到可视化模式或替换命令的场景(第 7、8 章)。代码本体几乎全是半角,日常不受影响,但要有这个预期。
🕳 在 JSX/复杂标签上指望它万无一失 it 的匹配是朴素的:遇到跨多行、属性里带 >、泛型 <T> 混在组件里的场景,边界偶尔认错。防御性习惯:先 vat(第 7 章)看高亮范围,确认后再下手;拿不准就退回 di{ 或行级操作。

VSCode / IDEA 差异

VSCode(VSCodeVim)

  • 文本对象原生全支持,包括 it/at 标签对象(JSX 亦可一试)
  • 配对高亮(editor.matchBrackets,默认开)是肉眼找块的好帮手 —— 但记住按对象时不需要先看高亮
  • 想给对象加「改包围符」能力(如把 " 换 '),装 Vim 插件的 surround 扩展即可,第 12 章配置里讲
  • VSCode 原生的「扩展选定」(Ctrl+Shift+→)与 ai{ 是同赛道:前者鼠标脑,后者语言脑 —— 练后者

IntelliJ IDEA(IdeaVim)

  • IdeaVim 支持 iw/i(/i{/i" 全家;it 标签对象近年版本也已内置(老版本需在 .ideavimrc 确认无禁用)
  • IDEA 自带的结构化选择(Cmd+W 扩大选择 / Alt+Cmd+T Surround With)与文本对象功能重叠 —— 建议:编辑动作用 Vim 对象,重构类操作(包裹 if/try)用 IDE 菜单,各吃各的长处
  • IDEA 的语法感知比 Vim 朴素匹配强:JSX 复杂标签上,IDEA 自己的高亮可以帮你预判 it 的边界
  • 遇到对象失灵(尤其嵌套模板字符串),先检查光标位置,再考虑用 IDEA 的结构选择兜底

本章速记

按键作用按键作用
i / a里面 / 周围(含边界+空白)ciw改光标所在的词 ⭐
iw / aw词 / 词+尾部空格ci(重写括号内(参数)⭐
i( i{ i[各括号的内部ci"改字符串内容 ⭐
a( 等连括号一起(拆壳)cit改标签内容(前端)
ip / ap段落 / 段落+尾空行dap删一整段(干净)

一句话心法:i 是里面,a 是带壳 —— 想作用于「这个东西」,先喊它的名字,光标在屋里就行。