Vim / Neovim 完全指南 / 04 - 动作与文本对象
“Vim’s power comes from the composability of operators, motions, and text objects.”
4.1 动作(Motion)深入
4.1.1 动作的本质
动作(Motion)是 Vim 中移动光标的命令。它不仅用于浏览,还能与操作符组合形成操作。
" 动作可以独立使用(移动光标)
w " 跳到下一词
b " 跳到上一词
" 动作可以与操作符组合(操作范围)
dw " 删除到下一词
yw " 复制到下一词
cw " 修改到下一词
4.1.2 动作分类总览
Motion(动作)
├── 字符级:h l
├── 查找级:f F t T ; ,
├── 搜索级:/ ? n N * #
├── 单词级:w W b B e E ge gE
├── 行级: 0 ^ $ g_ | f{c} t{c}
├── 段落级:( ) { }
├── 文件级:gg G H M L % [[ ]]
├── 标记级:m{a-z} '{a-z} `{a-z}
└── 跳转级:<C-o> <C-i> gd gD gf
4.1.3 单词级动作详解
word(小写)以非字母数字字符为分隔:
hello-world example.test
^ ^ ^ ^
| | | |
word1 word2 word3 word4
WORD(大写)仅以空白字符为分隔:
hello-world example.test
^ ^
| |
WORD1 WORD2
" 实际对比
let url = "https://example.com/path?q=1&a=2"
" 使用 w(按 word 移动)
" https://example.com/path?q=1&a=2
" ^ ^ ^ ^ ^ ^ ^ ^ ^
" 每次跳过一个 word 片段
" 使用 W(按 WORD 移动)
" https://example.com/path?q=1&a=2
" ^ ^
" 一次跳过整个 URL
4.1.4 查找动作(Character Find)
f{char} " 跳到当前行下一个 {char}
F{char} " 跳到当前行上一个 {char}
t{char} " 跳到当前行下一个 {char} 之前(till)
T{char} " 跳到当前行上一个 {char} 之后
; " 重复上次 f/F/t/T
, " 反向重复
业务场景:
# 假设光标在行首,要修改函数名
result = calculate_total_price(items, tax_rate)
# ^
# 目标:修改 calculate_total_price
# 方法:用 f 跳到 ( 然后回退
f(b # 跳到 ( ,再跳到上一个词首
ciw # 修改这个词
4.1.5 搜索跳转
/pattern " 向前搜索
?pattern " 向后搜索
n " 下一个匹配
N " 上一个匹配
* " 搜索光标下的单词(向前)
# " 搜索光标下的单词(向后)
g* " 搜索部分匹配(不加词边界)
g# " 搜索部分匹配(向后)
4.1.6 标记(Mark)
标记让你在文件中设置"书签":
m{a-z} " 在文件内设置标记(小写标记,仅当前文件有效)
m{A-Z} " 在文件间设置标记(大写标记,全局有效)
'{mark} " 跳到标记所在行的首个非空字符
`{mark} " 跳到标记的精确位置(行和列)
" 常用标记
ma " 在当前位置设置标记 a
'a " 跳到标记 a 所在行
`a " 跳到标记 a 的精确位置
" 自动标记
'. " 跳到上次修改的位置
'" " 跳到上次退出时的光标位置
'[ " 跳到上次修改/粘贴区域的开始
'] " 跳到上次修改/粘贴区域的末尾
'' " 跳到上次跳转前的位置
业务场景:
" 在函数头部标记,编辑函数尾部后跳回
ma " 标记函数头部
} " 跳到段落末尾
...编辑...
`a " 精确跳回标记位置
4.2 文本对象(Text Object)
4.2.1 什么是文本对象
文本对象是 Vim 对结构化文本的抽象。它们是 操作符 + i/a + 对象 的组合:
[操作符] [i(内部) | a(含界定符)] [对象类型]
i = inner(内部,不含界定符)
a = a / around(含界定符)
4.2.2 文本对象一览表
| 对象 | i(内部) | a(含界定符) |
|---|---|---|
w | 单词 | 单词 + 空格 |
W | WORD | WORD + 空格 |
s | 句子 | 句子 + 空格 |
p | 段落 | 段落 + 空行 |
" | 双引号内 | 含双引号 |
' | 单引号内 | 含单引号 |
` | 反引号内 | 含反引号 |
( / ) | 小括号内 | 含小括号 |
b | 等同 i( | 等同 a( |
{ / } | 花括号内 | 含花括号 |
B | 等同 i{ | 等同 a{ |
[ / ] | 方括号内 | 含方括号 |
< / > | 尖括号内 | 含尖括号 |
t | XML 标签内 | 含 XML 标签 |
4.2.3 i vs a 的区别
示例文本: (hello world)
ci( → 修改后: () " 括号内被替换为空
ca( → 修改后: " 括号和内容一起被替换
示例文本: "hello world"
ci" → 修改后: "" " 引号内被替换
ca" → 修改后: " 引号和内容一起被替换
4.2.4 文本对象实战
场景一:修改函数参数
// 原始代码
const result = processData(input, options, callback);
// ^ 光标在此
// 修改第二个参数
f, " 跳到第一个逗号
w " 移到下一个词
ciw " 修改这个单词
config " 输入新名称
场景二:修改引号内容
message = "Hello, World!"
# ^ 光标在此
ci" # 修改引号内内容
Goodbye, Vim! # 输入新内容
场景三:删除整个函数调用
alert("This is a message");
// ^ 光标在此
da( " 删除整个括号及其内容(含括号)
场景四:修改 HTML 标签
<div class="container">
<p>Hello World</p>
</div>
# ^ 光标在 p 标签内的文本上
cit # 修改标签内内容
<span>New Content</span>
场景五:复制整个段落
yip " 复制当前段落(inner paragraph)
yap " 复制当前段落 + 空行(a paragraph)
4.3 组合的艺术
4.3.1 操作符 + 动作
operator + motion = 操作
d + w = 删除一个单词
c + $ = 修改到行尾
y + ip = 复制段落
> + } = 缩进到段落末尾
= + G = 自动缩进到文件末尾
4.3.2 操作符 + 文本对象
operator + text-object = 操作
d + i( = 删除括号内容
c + a" = 修改引号及内容
y + iw = 复制单词
gU + it = 标签内转大写
= + i{ = 花括号内自动缩进
4.3.3 计数 + 操作符 + 动作/文本对象
count + operator + motion/text-object = 操作
2 + d + aw = 删除 2 个词(含空格)
3 + c + w = 修改 3 个词
2 + > + ap = 缩进 2 个段落
4.3.4 常用组合速查表
| 组合 | 操作 |
|---|---|
diw | 删除光标所在的单词 |
daw | 删除单词(含空格) |
ciw | 修改光标所在的单词 |
ci" | 修改双引号内内容 |
ci( | 修改小括号内内容 |
ci{ | 修改花括号内内容 |
dit | 修改 XML 标签内内容 |
da( | 删除括号及其内容 |
yi" | 复制引号内内容 |
>i{ | 缩进花括号内内容 |
=ip | 自动缩进段落 |
gUiw | 单词转大写 |
gu$ | 到行尾转小写 |
g~ip | 段落内大小写翻转 |
!ipsort | 对段落内容排序 |
4.4 搜索模式进阶
4.4.1 搜索选项
/pattern/e " 光标停在匹配末尾
/pattern/b " 光标停在匹配开头
/pattern/+2 " 光标停在匹配下方 2 行
/pattern/-1 " 光标停在匹配上方 1 行
4.4.2 搜索历史
/ " 然后按 ↑/↓ 浏览历史
:history / " 查看搜索历史
q/ " 打开搜索历史窗口(可编辑)
4.4.3 增量搜索技巧
" 设置增量搜索
set incsearch
set hlsearch
" 搜索时使用 \v(very magic)模式避免转义
/\vfunction\s+\w+ " 等同于标准正则
/function\s\+\w\+ " 需要转义特殊字符
" 搜索光标下的单词
* " 向前搜索单词(精确匹配)
g* " 向前搜索部分匹配
# " 向后搜索单词(精确匹配)
g# " 向后搜索部分匹配
4.5 跳转列表与变更列表
4.5.1 跳转列表(Jump List)
:jumps " 查看跳转列表
<C-o> " 后退(较旧的跳转)
<C-i> " 前进(较新的跳转)
注意:
j、k等逐行移动不算跳转。只有长距离移动才算跳转(如gg、G、搜索、标记跳转等)。
4.5.2 变更列表(Change List)
:changes " 查看变更列表
g; " 跳到较旧的变更位置
g, " 跳到较新的变更位置
'. " 跳到上次修改的行
`. " 跳到上次修改的精确位置
业务场景:
" 在大文件中修改了多处代码,想快速跳回修改点
g; " 跳到上一个修改位置
g; " 再跳到更早的修改位置
g, " 跳回较新的修改位置
4.6 实战练习
练习一:函数参数重命名
# 原始代码(光标在第一行 def 处)
def calculate(user_name, order_list, discount_rate):
total = sum(order_list)
result = total * (1 - discount_rate)
return format_result(user_name, result)
# 任务:将 user_name 改为 customer_name
# 步骤:
/customer_name<CR> " 跳到第一个 user_name
ciwcustomer_name<Esc> " 修改为 customer_name
n. " 下一个匹配并重复修改
n. " 继续下一个
练习二:括号内内容操作
// 原始
const data = parseJSON(getResponse(url, headers));
// 光标在 getResponse 内的某个位置
// 任务:修改 getResponse 的参数
f(a " 跳到 ( 后开始插入
params, timeout " 输入新参数
练习三:快速导航长行
" 一个很长的 JSON 行
{"name":"John","age":30,"city":"New York","country":"USA"}
" 快速跳到 city 字段
fc " 跳到 c
; " 继续找下一个 c
; " 直到目标 city 的 c
4.7 业务场景
| 场景 | 推荐组合 |
|---|---|
| 重命名变量 | *ciw新名称<Esc>n.n. |
| 修改函数参数 | f,ciw新名称 |
| 删除引号内内容 | di" 或 da" |
| 修改 HTML 标签内容 | cit新内容 |
| 复制整个函数体 | ]y 或 Va{y |
| 大写 SQL 关键字 | gUiw |
4.8 总结
| 分类 | 核心命令 |
|---|---|
| 单词移动 | w b e W B E |
| 行内查找 | f F t T ; , |
| 搜索 | / ? n N * # |
| 文本对象 | iw aw i" a" i( a( it at ip ap |
| 标记 | m{a-z} 'm \m` |
| 跳转历史 | <C-o> <C-i> g; g, |
下一步:第 05 章 - 编辑命令 → 掌握修改(change)、宏(macro)、寄存器(register)、撤销(undo)等核心编辑功能。
扩展阅读
:h motion.txt— 完整动作参考:h text-objects— 文本对象参考:h jump-motions— 跳转相关- Vim Text Objects: The Definitive Guide
- Learn Vimscript the Hard Way - Motions