Vim 的文本对象怎么用?
为什么你一直在用 dw 而不是文本对象
很多 Vim 用户学了 dw 删除单词、dd 删除行,就觉得够用了。但当你需要删除一对括号里的内容、改写一个 HTML 标签内的文字、或者复制整个段落时,还在用 dw 一个个删就太慢了。文本对象(Text Object)是 Vim 里最被低估的特性——它让你不移动光标就能对"语义单元"做操作,而不只是对"字符位置"做操作。
i 和 a:文本对象的核心逻辑
所有文本对象都遵循一个模式:操作符 + i/a + 对象标识。
i= inner,选"内部",不包含分隔符a= a/around,选"一个",包含分隔符(有时还包含周围空格)
举一个最直观的例子,假设光标在 hello 上:
shellsay hello world
diw→say world(只删单词,留两个空格)daw→say world(删单词加后面空格,句子依然通顺)
记住这个区别,后面所有文本对象都是同一套逻辑。
单词:iw / aw / iW / aW
iw 选中当前单词(不含空格),aw 选中当前单词加相邻空格。
w 和 W 的区别在于分词规则:
w按标点和空白分词,hello-world是三个单词W只按空白分词,hello-world是一个整体
实际场景:代码里改变量名用 ciw,删除函数参数用 daw。
vim" 光标在 foo 上 const result = foo + bar ciw→ 输入 baz const result = baz + bar
句子:is / as
is 选中当前句子内部,as 选中句子含后面的空格。Vim 以 .、!、? 后跟空格或换行来识别句子边界。
日常编辑中用得不算多,但在写文档或 Markdown 时,cis 可以快速重写一句话,yas 可以复制整句去引用。
段落:ip / ap
ip 选中当前段落(不含前后空行),ap 选中段落加上下面的空行。
段落在 Vim 中的定义是:由空行分隔的连续非空行。这在编辑 Markdown、邮件、纯文本文档时特别好用:
vip可视选中当前段落yap复制整段dap删除整段(包含段间空行,删除后不会多出空行)gqip对当前段落重新排版
括号家族:i( / a( / i{ / a{ / i[ / a[
这是写代码用得最多的一组。所有成对括号都支持 i/a 变体:
| 文本对象 | 选中范围 | 典型用法 |
|---|---|---|
i( 或 i) | 括号内内容,不含括号 | ci( 改写函数参数 |
a( 或 a) | 包含括号本身 | da( 删除整个括号及内容 |
i{ 或 i} | 花括号内内容 | vi{ 选中函数体 |
a{ 或 a} | 包含花括号本身 | ya{ 复制整个块 |
i[ 或 i] | 方括号内内容 | ci[ 改写数组元素 |
a[ 或 a] | 包含方括号本身 | da[ 删除整个数组 |
注意:光标不需要在括号上,只要在括号包围的范围内即可。所以你可以站在函数体中间,直接 ci( 改写参数列表。
引号家族:i" / a" / i' / a' / i / a
和括号类似,引号也有完整的 i/a 支持:
| 文本对象 | 选中范围 | 典型用法 |
|---|---|---|
i" | 双引号内内容 | ci" 改写字符串值 |
a" | 包含双引号 | da" 删除整个字符串 |
i' | 单引号内内容 | ci" 改写字符 |
a' | 包含单引号 | ya' 复制含引号 |
| `i`` | 反引号内内容 | `yi`` 复制模板字符串内容 |
| `a`` | 包含反引号 | `da`` 删除模板字符串 |
前端开发时 ci" 改写属性值、`ci`` 改写模板字符串内容,是最高频的操作之一。
HTML 标签:it / at
it 选中标签内部内容,at 选中包含标签本身。
html<p class="intro">Hello Vim</p>
- 光标在
Hello上,dit→<p class="intro"></p>(删内容留空标签) - 光标在
Hello上,dat→ 整行消失(连标签一起删) cit→ 删内容并进入插入模式,直接输入新内容
这对编辑 HTML、XML、JSX/Vue 模板非常实用。配合 vat 先选中标签块再操作,比手动找开闭标签快得多。
与操作符组合:真正的威力所在
文本对象单独用(如 iw)没有意义,它必须和操作符组合才能发挥作用。常见的操作符:
ddelete:diw删单词、dib删括号内、dit删标签内cchange:ciw改单词、ci"改字符串、cip改段落yyank:yiw复制单词、yi{复制花括号内容vvisual:viw选中单词、vip选中段落>/<缩进:>ip缩进段落、>a{缩进整个块gU/gu大小写:gUiw单词转大写
也可以加数字前缀:2daw 删除两个单词,3ip 选中三段。
一些高频组合速查:
shellciw — 改写当前单词 ci" — 改写双引号内字符串 ci( — 改写括号内参数 cit — 改写 HTML 标签内容 diw — 删除当前单词 dib — 删除括号内内容(b 等同于 ( ) dat — 删除整个 HTML 标签 yiw — 复制当前单词 yi{ — 复制花括号内内容 vip — 选中当前段落
自定义文本对象与 vim-textobj-user
Vim 内置的文本对象已经覆盖了大部分场景,但你可能会想定义自己的——比如选中函数名、选中注释块、选中 CamelCase 的某个部分。
vim-textobj-user 是 Kana Natsuno 写的插件,提供了一个声明式的 API 来创建自定义文本对象,不用手写复杂的 onoremap 映射。
最简单的用法——用正则定义一个文本对象:
vimcall textobj#user#plugin('line', { \ '-': { \ 'select-i': 'il', \ 'select-a': 'al', \ 'pattern': '.* ', \ }, \ })
这样就有了 il(行内)和 al(含换行)两个文本对象,可以用 vil、dal 等操作。
社区基于 vim-textobj-user 构建了大量插件,常用的有:
- vim-textobj-comment:
ic/ac选中注释块 - vim-textobj-function:
if/af选中函数体 - vim-textobj-entire:
ie/ae选中整个文件 - vim-textobj-indent:
ii/ai选中同缩进层 - vim-textobj-anyblock:自动匹配最近的括号/引号对
如果你用 Neovim,还可以通过 Lua 和 treesitter 定义更智能的文本对象(如 @function.inner、@class.outer),这是另一个话题了。
常见实战场景
改函数参数:光标在参数列表中间,ci( 清空参数重新输入。
改字符串值:光标在字符串上,ci" 删值进插入模式,不用移光标到引号里。
删 HTML 标签但保留内容:dat 会连标签一起删。如果只想删标签保留内容,可以 vat 选中后手动删标签行,或者用 vim-surround 插件的 dst(delete surrounding tag)。
复制整个代码块:yi{ 复制花括号内所有内容,比 V{ 手动选快得多。
重排段落:写 Markdown 时 gqip 对当前段落自动换行重排。
批量缩进:>ap 缩进当前段落,>a{ 缩进整个代码块。
在可视化模式下确认范围:不确定文本对象会选中什么?先用 vi( 看看高亮范围,确认后再换成 di( 或 ci(。
从今天开始用
如果你之前只用 dw 和 dd,从这几个操作开始:
ciw— 改写当前单词(日常最频繁)ci"— 改写字符串内容ci(— 改写括号内容dit— 删除标签内容vip— 选中当前段落
不用一次全记,先把 ciw 用熟,你会发现越来越不想回到逐字符操作了。文本对象的本质就是:告诉 Vim 你要操作"什么",而不是"从哪到哪"。