“怎么退出 vim”是程序员圈子里流传最广的梗之一——一个编辑器要是连怎么关掉都成了笑话,说明它一定有什么地方和你以为的不一样。

这个”不一样”,就是 vim 最核心的概念:模式。搞懂这一件事,剩下的操作都顺理成章。

这是终端效率小系列第二篇,承接 不熟悉的命令怎么快速搞懂:man 页面结构、apropos 搜索与 –help 速查

vim 最核心的概念:模式

大部分编辑器打开就能直接打字。vim 不是——它默认打开的是 Normal 模式(也叫命令模式),这时候敲键盘不是在输入文字,是在下命令。想打字,得先按 i 切换到 Insert 模式;打完字想回去下命令,按 Esc 切回 Normal 模式。

1
2
3
打开 vim → 默认在 Normal 模式(敲字母是命令)
按 i → 切到 Insert 模式(可以打字了)
按 Esc → 切回 Normal 模式(打字模式关闭,字母重新变成命令)

新手最常见的翻车场景就是这个:不知道自己在 Normal 模式,对着屏幕一通乱按,结果文件被改得面目全非——因为那些字母全被当成了命令,不是文字。记住一条:不确定自己在哪个模式,先按 Esc,回到 Normal 模式再说,这是最安全的复位动作。

先学怎么退出

呼应开头的梗,这个必须先讲:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
:q
# 退出。文件没有改动过才能用,改过没保存会报错拒绝退出

:q!
# 强制退出,不保存任何改动——改错了想放弃这次编辑,用这个

:w
# 只保存,不退出,继续编辑

:wq
# 保存并退出,不管文件有没有改动过,都会写入磁盘

:x
# 也是保存并退出,但只在文件真的被改动过时才写入磁盘;
# 如果没有改动,:x 不会碰这个文件,文件的修改时间也不会变
# 日常用 :wq 和 :x 效果几乎一样,区别只在"文件没改动时要不要触碰修改时间"这种边缘场景

所有这些命令都以 : 开头,输入前记得先确认自己在 Normal 模式(如果还在 Insert 模式,先按 Esc)。

Normal 模式下移动光标

vim 不太依赖方向键,用几个字母键代替(键盘老式布局上这几个键离主键位更近,不用挪手,很多人这么解释这个设计,具体历史已经不可考):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
h j k l
# 依次是 左 下 上 右,代替方向键
# 记不住就想 j 像个往下的箭头,k 在它上面对应往上

w
# 跳到下一个单词的开头

b
# 跳到上一个单词的开头(back)

e
# 跳到当前/下一个单词的结尾

0
# 跳到当前行的行首

$
# 跳到当前行的行尾

gg
# 跳到文件开头

G
# 跳到文件结尾

前面加数字表示重复次数:

1
2
3
4
5
5j
# 往下移动 5 行,比按 5 次 j 快

3w
# 往后跳 3 个单词

行内跳转:按字符查找与括号匹配

hjkl/w/b/e 这些是逐字符或逐单词移动,遇到”跳到这一行某个具体符号上”的场景(比如跳到某个逗号、跳到配对的括号),还有更快的办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
f,
# 向右查找光标所在行里下一个 "," 字符,光标直接停在它上面(find)

t,
# 向右查找下一个 "," 字符,光标停在它前面一位(till,"直到"但不包含)

F,
# 向左查找上一个 "," 字符,光标停在它上面

T,
# 向左查找上一个 "," 字符,光标停在它后面一位

;
# 重复上一次 f/t/F/T 查找,跳到下一个匹配,不用重新敲一次 f 加字符

%
# 跳到光标所在括号 ( { [ 的配对括号上,来回按 % 可以在一对括号之间横跳

一行很长的代码里要定位到某个符号,f 加上那个字符,比一个字符一个字符挪快得多;改代码时经常要确认括号有没有配对齐,把光标放在括号上按 % 直接跳到另一半,比肉眼数括号可靠。

Normal 模式下编辑文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
i
# 在光标当前位置之前插入,进入 Insert 模式

a
# 在光标当前位置之后插入(append),进入 Insert 模式

o
# 在当前行下面新起一行,进入 Insert 模式

O
# 在当前行上面新起一行,进入 Insert 模式

dd
# 删除光标所在的整行(同时会把这行内容存到剪贴板,等于剪切)

yy
# 复制光标所在的整行(yank,不删除)

p
# 粘贴——把上一次 dd 或 yy 存下来的内容,粘贴到光标下面

u
# 撤销上一步操作

Ctrl-r
# 重做(撤销"撤销")

x
# 删除光标所在的这一个字符——精简版的 dd,只删一个字符不是一整行

r字符
# 把光标所在字符替换成指定字符,替换完自动回到 Normal 模式,不用再按 Esc
# 比如光标停在一个打错的字母上,按 rx 就把它换成 x

cw
# 修改一个单词——删除光标到当前单词结尾的内容,直接进入 Insert 模式等着输入新内容
# 等于先 dw 再 i,但 cw 是更常用的固定搭配,一步到位

.
# 重复上一次的修改操作——改完一处后,把光标移到下一个类似的地方按 .,
# 不用重新敲一遍命令,改多处同样的错误时特别省事

i/a/o/O 这四个进 Insert 模式的方式,区别就在光标最终停在哪:i 停在原地之前,a 停在原地之后,o/O 分别在下面/上面新开一行。

Visual 模式:选中一段范围再操作

前面的编辑命令大多是”整行”或”一个单词”级别,想自己框选一段范围再操作,要用 Visual(可视)模式。进入方式有三种,选中范围的方式不一样:

1
2
3
4
5
6
7
8
v
# 进入字符可视模式,逐字符选中——像鼠标拖选一样,选中范围不受行/单词边界限制

V
# 进入行可视模式,整行整行地选,光标上下移动就是按行扩大选区

Ctrl-v
# 进入块可视模式,选出一个矩形范围,常用来同时给多行的相同列位置加字符

选中范围之后,跟前面学过的编辑命令组合使用,效果作用在整个选区上:

1
2
3
4
5
6
7
8
d
# 删除选中的范围

y
# 复制选中的范围(yank)

c
# 删除选中的范围,直接进入 Insert 模式,改成新内容

举例:用 V 选中三行按 d,一次删掉三行;用 v 选中一段文字(不是整行)按 y,只复制这一小段。选错了想取消,按 Esc 退出 Visual 模式,不会做任何改动。

查找和替换

搜索用 /,和上一篇讲 man 页面查找less 的搜索快捷键是同一套逻辑——vim 的分页和搜索机制本来就深受这套习惯影响:

1
2
3
4
5
6
7
8
/关键词
# 向下搜索,回车确认

n
# 跳到下一个匹配

N
# 跳到上一个匹配(大写,方向和 n 相反)

全局替换用 :%s

1
2
3
4
:%s/旧文本/新文本/g
# % 表示作用范围是整个文件(不加 % 只替换当前光标所在行)
# s 是 substitute,替换命令本身
# g 表示每一行里所有匹配都替换,不加 g 的话每行只替换第一个匹配到的

%g 这两个符号经常被忽略,结果就是”明明替换了,怎么还有没改的地方”——十有八九是漏了 %(只在当前行生效)或者漏了 g(每行只替换了第一处)。

常见操作场景

几个高频的实际场景,用到的都是前面没提过、但一旦知道就回不去的操作。

跳转后把光标行挪到屏幕舒服的位置:zz / zt / zb

只滚动屏幕、不动光标,把光标所在行摆到屏幕中间/顶部/底部。

gg/G/搜索跳到目标行后,那一行经常落在屏幕最顶上或最底下,看上下文很费劲。这时候不用挪光标,直接调整视口:

1
2
3
4
5
6
7
8
zz
# 把光标所在行滚动到屏幕正中间——跳转后最常用的一下

zt
# 把光标所在行滚动到屏幕顶部(top),适合接下来要往下读代码

zb
# 把光标所在行滚动到屏幕底部(bottom),适合要往上看前文

这三个命令只滚动屏幕,光标始终停在原来那一行——在编辑器/IDE 里用 vim 插件跳转到函数定义后,按一下 zz 让目标行居中,是最顺手的组合。

光标不动只滚视野:Ctrl-e / Ctrl-y

一行一行地滚动屏幕,光标钉在原地不动。

想瞄一眼当前屏幕外的上下文,但不想让光标离开正在编辑的位置:

1
2
3
4
5
6
Ctrl-e
# 视口向下滚动一行,光标留在原来的行上不动
# (光标行快被推出屏幕时才会被迫跟着走)

Ctrl-y
# 视口向上滚动一行,同样不动光标

半页半页地翻:Ctrl-d / Ctrl-u

上下滚动半页,光标跟着视口走,浏览长文件的主力翻页方式。

比整页翻(Ctrl-f/Ctrl-b)保留更多上下文,浏览长文件时更常用:

1
2
3
4
5
Ctrl-d
# 向下滚动半页(down),光标跟着视口走,保持在屏幕上的相对位置

Ctrl-u
# 向上滚动半页(up)

注意 Ctrl-d/Ctrl-u 和上一个场景不一样:它们滚动时光标会跟着视口移动,适合”人在翻文件”;Ctrl-e/Ctrl-y 是”光标钉在原地只挪视野”,适合编辑中途瞄一眼别处。

终端效率小系列:下一篇

下一篇讲 Emacs 风格快捷键——包括一个进阶技巧:在 shell 命令行敲了一半的命令,直接甩进 vim/nvim 里编辑,改完存盘退出,编辑结果自动回到命令行。用到的正是这篇学的基础操作。