Vim 的编辑语法
编辑命令是动词,移动是名词,两者相乘才是 Vim 的表达力。
动词作用于名词
d 删除、c 修改、y 复制,后面都跟一个名词表示范围。p 粘贴、u 撤销、<C-r> 重做是独立动作,不接名词。
| 按键 | 作用 | 等价写法 |
|---|---|---|
d{w} | 删除到范围末尾 | — |
c{w} | 删除后进入 Insert | d 加 i |
y{w} | 复制范围 | — |
x | 删除光标下字符 | dl |
s | 删除字符并插入 | cl |
p | 粘贴到光标后 | — |
o/O | 下方/上方开新行 | — |
u | 撤销 | — |
<C-r> | 重做 | — |
~ | 切换大小写 | — |
J | 把下一行接到本行尾 | — |
vim
dw " 删一个词
d$ " 删到行尾
d0 " 删到行首
cw " 改一个词,改完停在 Insert
yy " 复制整行
3dd " 删三行dd 是 d 加 d 的特殊形式,删整行;同理 cc 改整行,yy 复制整行。理解成「动词重复一次等于作用于整行」就够用了。
计数与文本对象
计数放在动词前:3w 前进三个词,5j 下移五行,7dw 删七个词,2dd 删两行。
文本对象是更精确的名词。i 表示内部,a 表示连同边界一起。
| 对象 | 范围 |
|---|---|
iw/aw | 当前词,含或不含空格 |
i(/a( | 圆括号内部,含或不含括号 |
i[/a[ | 方括号内部,含或不含括号 |
i"/a" | 双引号内部,含或不含引号 |
i{/a{ | 花括号内部 |
it/at | HTML 标签内部 |
ip/ap | 当前段落 |
vim
ci( " 改括号里的内容,括号不动
ci[ " 改方括号里的内容
ci" " 改双引号里的内容
da' " 连引号一起删掉单引号字符串
di{ " 清空花括号内部光标已经在括号里时,ci( 作用于包住它的那一对;光标不在括号内时,Vim 在本行向前找第一对左括号。这比先用 f( 定位再删要稳。
修一段真实的代码
讲义里的 fizz buzz 有三个 bug:main 没被调用、范围从 0 开始、5 的倍数打印成 fizz。从 Normal 模式开始,全程不碰鼠标。
python
def fizz_buzz(limit):
for i in range(limit): # 应从 1 开始
if i % 3 == 0:
print("fizz", end="")
if i % 5 == 0:
print("fizz", end="") # 应为 buzz
if i % 3 and i % 5:
print(i, end="")
print()
def main():
fizz_buzz(20) # main 从未被调用G到文件末尾,o开新行,输入if __name__ == "__main__": main(),<ESC>返回/range回车定位到range,ww移到limit,i插入1,,<ESC>返回e到词尾,a追加+ 1:6跳到第六行,ci"把fizz改成buzz
i % 3 and i % 5 这种写法只在 Python 里成立,因为它把 0 当假。别的语言里要换成显式的两个取模比较。
重复与批量化
. 重复上一次修改,包括插入的文本和删除的范围。3. 连做三次,比重新输入快得多。
vim
:%s/foo/bar/g " 全文替换
:%s/foo/bar/gc " 每次替换前确认
qa " 开始录制到寄存器 a
q " 停止录制
@a " 回放
@@ " 再次回放批量改 20 个函数签名时,宏比手写脚本快:录一遍,20@a 一次跑完。跑之前先 u 确认一次录制本身没问题。
改完一个文件的习惯是先 :w,再 u 和 <C-r> 来回看几遍。练组合时先用 vimtutor 过一遍基础,再拿 VimGolf 的题目数按键:同一处修改从 12 键压到 6 键,说明你开始按「动词加名词」思考,而不是逐字敲。