vim 学习笔记
vim 被称为编辑器之神,学习难度很大,但是熟练掌握后可以更高效地敲代码,因此有必要学一下,但是没必要鼓捣各种插件。 主要参考:【Vim】可能是B站最系统的Vim教程
vim 介绍与版本
vim 是古董编辑器 vi 的升级版,在现代的 Linux 发行版中通常自带
vim,无需手动安装,虽然版本通常不是最新的,但是也足够使用。在很多发行版中都将
vi
命令链接到 vim
命令,因此 vi
命令和 vim
命令通常是等价的,都是在调用 vim 编辑器。
在 vim 中输入 :version
可以查看版本信息,例如
1 | VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Nov 22 2021 19:31:05) |
vim 的各种功能模块化,在编译安装时可以选择精简其中一部分功能,至少有几种安装模式:tiny, small, normal, big, huge,显然 huge 版本的功能最完整。
在 :version
的输出中包括一个列表,其中描述了当前编译的
vim 支持或不支持的功能,例如 +clipboard
意味着剪贴板功能被编译支持了,-clipboard
意味着剪贴板特性没有被编译支持。
极简使用
打开或新建文件
在命令行直接输入vim filename
即可打开或新建指定的文件:
- 对于已存在的文件会打开它;
- 对于不存在的文件,会创建一个临时文件,如果退出时选择保存,则会真实地创建该文件。
如果直接输入 vim
,不接任何文件名,vim
可能会给出如下的欢迎界面,有概率出现著名的标语:帮助乌干达的贫困儿童。
在 vim 中可能看见某些行只有
~
,这表示该行不存在,即文本行数小于显示屏幕行数,如果行首只有
@
,则表示该行有内容存在,但是软换行导致目前无法完整显示。
修改文件
进入vim之后无法直接编辑文件,因为处于 Normal
模式,此时除了上下左右键之外,还可以使用 hjkl
进行光标移动。 在 Normal
模式下,几乎所有的按键(以及按键组合)都有特殊含义,并不是通常意义下的在光标所在位置进行输入,而是进行某些操作,可以参考下面的速查表。
在 Normal 模式可以输入 i
或 a
进入 Insert
模式(下方状态栏默认会显示
-- INSERT --
),此时可以像记事本一样正常进行编辑:在光标所在位置插入或删除对应字符。
在 Insert 模式使用 <Esc>
可以退回到 Normal
模式。
关闭、保存和退出
在编辑完成之后,退出 vim 的操作首先要明确所处的模式,假设当前处于
Normal 模式中(输入 :
就会进入 Command
模式),有如下几种退出方式:
:q
(quit) 直接退出,只适合于当前文件没有被改动时,如果文件有改动,则会报错无法退出,因为必须处理对文件的修改;:wq
(write and quit) 保存并退出,也可以拆成两个命令:保存:w
,退出:q
;:q!
加感叹号表示强制操作,如果不希望对保存文件的改动,就可以强制进行不保存的退出。
使用 :x
也可以保存并退出,而且和 :wq
相比有细微的差别,它仅仅会在文件真正改动时才写入文件。 使用
:w newfilename
可以把改动另存为新的文件。 使用
:w n1,n2 newfilename
可以把 n1
行到
n2
行的内容存储到指定的文件中。
对于不熟悉 vim 的用户,几乎不可能体面地关闭 vim,不管你按什么按键。一个冷笑话是:如何生成一串完全随机字符串? 让新手退出 vim。
只读状态
如果在 Linux 中使用 vim 打开缺少写入权限的文件,会自动进入 readonly
状态,当然也可以通过 vim -R
或者别名 view
手动指定为 readonly 状态。 1
2vim -R filename
view filename
readonly 状态也可以在 vim 中进行切换 1
2:set readonly
:set noreadonly
readonly 状态代表 buffer
可以被修改,但是不能把改动写入到文件中,即不允许普通的写入命令
:w
,强制写入命令 :w!
可以突破 vim 自身的限制,
但是如果对文件确实缺少写入权限,:w!
仍然会失败。
vim 还有一个比
readonly
更强的modifiable
属性,决定 buffer 是否可以被修改,但是通常不需要使用。
配置命令
可以设置显示绝对行号:set number
(或者简写为:set nu
),效果如下图
可以设置:set nonumber
关闭行号。(一般的配置项都可以通过加no
前缀来取消)
可以使用:set number?
来查询当前行号是开启还是关闭的,会显示number
或nonumber
。(一般的配置项都可以通过加?
查询状态)
还可以设置显示相对行号:set relativenumber
,光标所在的行记作
0,上下相邻的行记作 1,以此类推,效果如下图
绝对行号和相对行号可以同时出现,效果如下图
其它命令类似,例如:
:set cursorline
可以高亮光标所在的行,这里的高亮有可能是改成红色背景,也有可能是整行添加下划线。:! command
可以暂时返回命令行界面执行指令,然后回车可以回到 vim,例如:! ls
快速查询目录。
在 Command 模式中输入相关的设置命令,仅仅对于当前窗口有效。
如果希望在每次打开 vim
时都自动采用某些设置,则应该将其写入配置文件中,通常用户自定义的配置文件为~/.vimrc
(对于
Windows 的 gvim,可能叫做_vimrc
),配置文件例如
1 | set number |
在配置文件中,以双引号"
开头的行被视作注释。
工作模式
vim 包括三种主要模式
- Normal 模式
- Insert 模式
- Command 模式
以及一个临时的 Visual 模式。
启动 vim 会直接进入 Normal
模式,此时的按键不会被视作输入字符,而是视作命令,例如输入i
会切换到
Insert 模式,进行正常的文本编辑状态;输入英文冒号:
会切换到
Command 模式,此时光标会留在最底行,可以输入复杂的命令。
几个模式的转换如下图,Normal 模式作为转换的中枢,在其他模式中可以通过
<Esc>
键可以返回 Normal 模式,也可以使用
<Ctrl+[>
替代(默认情况下,它和<Esc>
键功能完全等价)。
从 Normal 模式出发
光标移动基础
在 Normal
模式中,为了保证操作的高效连贯,我们不希望双手离开主键区,主要使用按键
hjkl
来移动光标:(当然四个方向键也可以)
h
:左移j
:下移k
:上移l
:右移
整个文件尺度的移动:
gg
:移动到当前文件的第一行G
:移动到当前文件的最后一行
按照行号定位:
{lineno}gg
/{lineno}G
:跳转到第lineno
行{percent}%
移动到当前文件对应比例的位置,例如30%
在 Command 模式中,还有如下的命令可以按行跳转(需要回车执行):
:0
移动到当前文件的第一行;:{lineno}
移动到当前文件的第lineno
行;:$
移动到当前文件的最后一行;
屏幕尺度下的移动:
H
:移动到屏幕顶端的行;M
:移动到屏幕中部的行;L
:移动到屏幕底部的行;
翻页:(尽量保持光标在屏幕中的相对位置不变)
<Ctrl-u>
:(up) 将文本上移半页<Ctrl-d>
:(down) 将文本下移半页<Ctrl-b>
:将文本上移一页<Ctrl-f>
:将文本下移一页
光标行定位:
zz
:光标行居中zt
:光标行置于屏幕第一行zb
:光标行置于屏幕最后一行
进入 Insert 模式
从 Normal 模式进入 Insert 模式,常见方法如下:
- 插入:(insert)
i
:在光标所在字符前开始插入(光标所处的字符会被挤到后面);I
:在光标所在行的行首开始插入,如果行首有空格则在空格之后插入。
- 添加:(append)
a
:在光标所在字符后开始插入(光标会自动后移一格);A
:在光标所在行的行尾开始插入。
- 替换:(substitude)
s
:删除光标所在的字符并开始插入;S
:删除光标所在行并开始插入
- 插入新行:
o
:在光标所在行的下面创建新行插入;O
:在光标所在行的上面创建新行开始插入。
大小写命令对应的操作不同,但是普遍具有相关性:
- 如果小写的操作是字符尺度的,那么大写的操作通常是行尺度的;
- 如果小写的操作具有方向性,那么大写通常是相反方向的操作。
进入 Command 模式
Normal 模式下,可以通过 :
进入 Command
模式,此时可以输入命令,例如:
:help
或:h
显示帮助(:h xxx
显示对应条目的帮助):w
保存:q
退出:wq
保存并退出:!{cmd}
:利用shell执行外部命令,支持传递参数,提供一个临时界面展示命令的输出。
在 Command
模式,输入单词开头部分,即可可以使用<Ctrl-d>
获取补全提示,也可以使用tab
获取补全建议。
进入 Visual 模式
Normal 模式下,输入 v
/V
进入 Visual
模式,常见操作:
- 移动光标选中文本进行操作;(
v
以字符为单位,V
以整行为单位) x
:剪切y
:(yank) 复制d
:(delete) 删除p
:(paste) 粘贴- 可以退出到 Normal 模式进行
- 如果直接在选中一部分内容的状态下进行粘贴,会直接覆盖掉当前选中的内容
除了 <Esc>
,再次输入 v
或者某些操作完成后也会自动退出 Visual 模式。
移动 (Motion)
vim 在 Normal 模式下提供了各种快捷键用于快速移动光标,这些按键所绑定的行为称为移动 (Motion)。
基于单词的移动
w
/W
:(word) 移动到(下一处)单词的首字母;b
/B
:(back) 移动到(上一处)单词的首字母;e
/E
:(end) 移动到(下一处)单词的尾字母;ge
:移动到(上一处)单词的尾字母; (e
的反向操作)
大小写版本的 W
/B
/E
区别在于分词逻辑:大写操作只按照空格分词,例如 open-source
在小写操作中视作三个单词(-
也视作一个单词),但是在大写操作中视作一个单词。
基于单词的操作对于中文并不友好,因为单词的分词是简单地基于空格等符号所进行的,中文需要根据语义分词,这一点很难做到,可能会把整个句子视作一个单词。
行内搜索与移动
行内搜索:
f{char}
/F{char}
:跳转到本行的下一个/上一个{char}
字符位置;t{char}
/T{char}
:跳转到本行的下一个/上一个{char}
字符位置之前;;
:继续(或切换为)向后搜索,
:继续(或切换为)向前搜索
注:
- 行内搜索只能基于指定字符,除此之外,vim 还提供了一套支持模式匹配的全局搜索命令,见下文。
;
/,
在跳转时仍然限于本行内部,但是可以通过插件实现跨行跳转。
基于标记的移动
vim 提供了一套标记系统,可以在特定位置打上标记,从而基于标记快速移动:
m{mark}
:在当前位置打一个标记mark
(mark
要求是小写字母,例如mm
)`{mark}
:跳转到到标记mark
内置的特殊标记:
``
:上次跳转前的位置`.
:上次修改的位置`^
:上次插入的位置
在 Normal 模式中,还可以使用 gi
来直接跳转到 Insert
模式中的光标最后位置,并且直接进入 Insert 模式。
基于文本的移动
(
/)
:向前/向后跳转到一个句子的开头{
/}
:向前/向后跳转到一个段落的开头%
:如果光标位于括号等配对符,会跳转到匹配的符号,否则向后跳转到下一个配对符
实用的局部跳转
0
/$
:跳转到当前行开头/末尾(含空格)^
/g_
:跳转到当前行的第一个/最后一个非空白字符+
/-
:跳转到上一行/下一行的第一个空白字符
粘贴
Vim 的粘贴命令为
p
/P
,它们会根据复制内容的类型自动选择进行“字符粘贴”或“行粘贴”,两者的区别是在光标后或光标前:
- 如果复制的是字符或词:
p
:粘贴在光标后P
:粘贴在光标前
- 如果复制的是整行(如使用
yy
或dd
):p
:将复制内容插入到当前行的下方P
:将复制内容插入到当前行的上方
重复与撤销
.
:重复上一次修改(批量操作常用)u
:撤销上一次修改<Ctrl-r>
:重做上一次修改(撤销的相反)
与 u
不同,大写的 U
对应的撤销更加彻底:恢复某一行进入编辑时的状态,这也意味着重复按
U
无效。
全局搜索匹配
文件中搜索:(pattern
可以是正则表达式)
/{pattern}
:搜索模式{pattern}
,并跳转到匹配的下一个位置;?{pattern}
:搜索模式{pattern}
,并跳转到匹配的上一个位置;n
:继续(或切换为)向后搜索N
:继续(或切换为)向前搜索*
:自动以当前光标所在单词为pattern
,向后搜索
进阶
重复移动
在移动前面加上数字就可以重复多次移动。 1
[count]{motion}
例如
5j
:向下移动 5 行;5k
:向上移动 5 行;2w
:向右移动 2 个单词;
Operator + Motion = Action
操作符和移动可以组成的一个完整编辑动作,移动前后的光标所组成的就是这次编辑所对应的范围。
1
{operator}{motion}
常见的重要操作符:
c
:(change) 修改(删除并进入 Insert 模式)d
:(delete) 删除y
:(yank) 复制v
:(visual) 选中文本,进入 Visual 模式
这里并没有剪切操作,因为 vim 实现的删除操作其实就是剪切,它会把删除内容记录下来,可以被用于粘贴。
在操作符后面加上移动即可组成完整命令,例如:
dgg
:删除到第一行ye
:复制到单词结尾d$
:删除到行尾dt;
:删除到遇到分号;
如果不确定
{motion}
的作用范围,可以先尝试v{motion}
,再执行c
/d
/y
操作。
重复两次操作符通常是作用在当前行:
cc
:修改当前行(删除并进入 Insert 模式)dd
:删除当前行yy
:复制当前行
但是 vv
会进入 Visual 模式然后退出。
由于历史兼容性等原因,这几个操作符的大写含义为:
C
=c$
:修改到行尾D
=d$
:删除到行尾Y
=yy
:复制整行(注意不是复制到行尾)
Count + Action
[count]{action}
代表重复 {action}
行为
[count]
次,这里的 {action}
也可以进一步展开,变成
1 | [count]{operator}{motion} |
例如:
5j
:向下移动5行3dw
:删除3个单词2yy
:复制2行4p
:粘贴4次
这里的组合方式其实非常灵活,例如 {motion}
通常也可以换成
[count]{motion}
,例如:
3dw
解释为dw
(删除到单词末尾)且重复3次;d3w
解释为为按照3w
的移动范围进行删除。
两者效果是一样的。
文本对象及其操作
vim 将如下文本结构视作文本对象:
- 单词:
w
/W
(word)(两者区别是单词的定义) - 句子:
s
(sentence) - 段落:
p
(paragraph) - 配对符定义的对象:
(
/)
,[
/]
,{
/}
,<
/>
,'
/"
有两种基本格式:
i
:(inner) 内部a
:(around) 完整对象,额外包括边界的空格或两边的配对符
⽂本对象使⽂本具有了结构化的语义,进而允许我们以语义对象为操作单元,进行更具体的操作。
1 | [count]{operator}{textobjects} |
例如:
diw
:删除单词ci(
:修改小括号内部yi{
:复制大括号内部
但是这里的 {textobjects}
不能独立使用,而且重复数量只能加在 {operator}
前面。
如果不确定
{textobjects}
的作用范围,可以先尝试v{textobjects}
,再执行c
/d
/y
操作。
寄存器
Vim 提供了许多寄存器⽤于存放内容,可以理解为剪贴板,
⼀个字符对应⼀个寄存器(例如a-z
,0-9
),不同的寄存器有对应的功能,具体细节非常复杂,下表只列举常用的寄存器。
名称 | 标识 | 内容或功能 |
---|---|---|
无名寄存器 | " |
默认寄存器(上一次复制或删除的内容) |
删除寄存器 | 0-9 |
文本复制和删除历史 |
小写寄存器 | a-z |
手动指定用于保存文本 |
大写寄存器 | A-Z |
用于追加内容到同名的小写寄存器 |
剪切板寄存器 | + / * |
系统剪贴板(需要 Vim 支持) |
空寄存器 | _ |
删除但不存储(黑洞) |
此外,还有一些存放特殊信息的只读寄存器:
%
:当前文件名.
:上一次插入的内容:
:上一次执行的命令
通过 :reg {register}
可以查看对应寄存器的内容,使用
:reg
则会查看所有寄存器。
在复制/删除操作前加上"{register}
就可以指定本次操作所使用的寄存器,例如:
"ayy
:复制当前行内容,保存到a
寄存器中;"bdiw
:删除单词,保存到b
寄存器中;"_dd
:删除当前行,保存到空寄存器(也就是直接丢弃,不污染剪贴板)
在粘贴时可以指定使用的寄存器,例如:
"cp
:粘贴,使用c
寄存器的当前内容。
注:即使退出 vim,寄存器中的内容通常也不会丢失,而是会存储在
.viminfo
文件中。
宏
宏指的是录制⼀系列键盘操作,并允许我们重放这些操作,常用于批量化操作文本。
基本用法:
q{register}
:开始录制一段宏,将动作记录在寄存器register
中;- 执行希望记录的动作,按
q
退出录制; @{registetr}
:重放寄存器register
中的动作;
在使用一次 @{registetr}
之后,直接使用 @@
就可以自动重放上一次宏操作,使用 [count]@{register}
可以自动重放 count
次。
注意:
.
命令对宏不⽣效,.
命令只记录上⼀次修改,⽽宏可能包含多次修改。- 由于宏的目标是重复操作,在动作序列中需要在开头或结尾跳转到需要编辑的位置,否则重放是错误的。
Command 模式
Ex Command 格式
Ex Command 的完整格式如下: 1
:[range] {excommand} [args]
其中:
[range]
:指定操作的行范围,缺省时默认为当前行;{excommand}
:具体操作命令;[args]
:命令附带的参数。
range 与 address 表示
Command 模式下的命令都可以加上对应的行范围
range
,range
由一到两个 address
组成(两个之间用,
分隔)。
下面是一些 address
的例子:
{lineno}
:具体行号,例如3
代表第3行,0
代表首行之上的虚拟行$
:最后一行.
:光标所在行/{patttern}/
:下一个pattern
所在行
还支持简单的加减运算,例如:
.+3
:当前行加上3行$-3
:倒数第4行
address
可以组合得到 ranges
,例如:
1, 3
:1行到3行., .+4
:当前行到后续4行(共5行)$-3, $
:倒数第3行到末尾行
除此之外,还有一些特殊范围:
%
:整个文件的所有行'<,'>
:Visual 模式下选中范围的开头和结尾(如果在 Visual 模式下选中范围并输入:
,会自动补充为:'<,'>
)
常见的 Ex Command
:[range] yank [x]
:复制指定行(保存到寄存器x
中)(yank
可以缩写为y
):delete [x]
:删除指定行(保存到寄存器x
中)(delete
可以缩写为d
):print
:打印指定行(print
可以缩写为p
),注意这只是在底部展示一下对应内容,并不是粘贴到文本中。:[range] copy {address}
:把指定行插入到address
:[range] move {address}
:把指定行复制到address
:[address] put [x]
:把寄存器x
的内容插入到address
后面(可以插入第0行后面)
例如
:yank a
:复制当前行,保存到寄存器a
中;:1, 3 delete a
:删除1行到3行,保存到寄存器a
中;:$-3, $ print
:打印倒数第3行到末尾行
Command 批量操作
normal 命令
normal 命令可以对 range
中的所有⾏执⾏ Normal
模式下的命令 commands
。
1 | :[range] normal {commands} |
例如:
:[range] normal .
:⽤ normal 命令在指定的⾏上重复.
记录的操作(先进行一次修改以记录到.
):[range] normal @{register}
:⽤ normal 命令在指定的⾏上重放宏register
global 命令
global 命令可以对 range
中的所有包含
pattern
的⾏执⾏ Command 模式下的 Ex 命令
commands
。
1 | :[range] global/{pattern}/[cmd] |
其中 cmd
缺省时是打印print
。
例如:
:% global /TODO/delete
:删除文件中所有包含TODO的行
替换命令
替换命令可以对 range
中的所有包含 pattern
的⾏操作,将 pattern
替换为 string
。
1 | :[range]s/{pattern}/{string}/[flags] |
其中的 flags
是可选的,用于调整匹配替换行为的细节:
g
:替换所有匹配项(默认仅替换第一个匹配项)i
:忽视大小写(默认区分大小写)c
:每次替换时都要求确认,可以执行、跳过或退出n
:计数匹配项,不进行替换
一些例子:
:s/abc/xyz
:替换当前行的abc
字符串为xyz
:s/abc/xyz/g
:替换当前行的abc
字符串为xyz
(替换所有匹配项):%s/abc/xyz/g
:替换所有行的abc
字符串为xyz
:10,20s/abc//gn
:计数指定范围内的abc
字符串
补充
命令/操作符补充
命令补充:
~
:大小写翻转J
:拼接两行r
:快速替换单个字符R
:进入字符替换的模式(<Esc
退出)
操作符补充:(操作符不能单独使用,这里的 g
也不能单独使用)
gu
/gU
:转换为小写/大写g~
:大小写翻转<
/>
:调整缩进(例如>>
和<<
调整当前行的缩进)=
:智能调整缩进(例如==
智能调整当前行的缩进,gg=G
先回到首行,然后智能调整整个文件)
vim 支持 bash 中常见的几个删除操作(甚至是在 Insert 模式下也支持!):
<Ctrl+u>
:删除至行首<Ctrl+w>
:删除光标前的单词<Ctrl+h>
:删除光标前的字符(与<Backspace>
相同)
Windows terminal + pwsh 对这些快捷键的支持好像有问题,
<Ctrl+w>
正常支持;<Ctrl+u>
似乎不支持,可以用<Esc
代替,也可以使用<Ctrl+a>
全选后删除。
vim 有着非常详细的操作历史记录:
- 使用
q:
可以查看 Command 模式的历史记录。 - 使用
q/
可以查看 Search 模式(正则匹配)的历史记录。
这些历史记录以及寄存器内容等会被存储在 .viminfo
文件中,再次打开 vim 时,这些内容会自动恢复。
从外部的粘贴
现在考虑在 windows 系统下基于 terminal+ssh 远程操作中的复制粘贴,此时显然无法使用 vim 内部的复制粘贴机制。
对于文本文件的大段粘贴,如果直接ctrl+v
会出现很多异常:
- 例如如果当前不是 Insert
模式,在开头的部分字符可能会被直接丢弃,直到出现
i
或a
等字符才会进入。 - 例如可能产生异常的缩进,还可能将某些行自动注释掉。
这是因为vim将字符流视为用户从键盘进行的输入行为,并且视作命令进行处理。
可以通过下面的选项将 vim 设置为粘贴模式,此时就可以保持原样进行粘贴
1
:set paste
在执行ctrl+v
之前,最好让 vim 保持在如下模式
1
-- INSERT (paste) --
在粘贴完成后关闭选项即可 1
:set nopaste
复制粘贴的过程实际上非常复杂,vim 和 nvim 的表现并不一致,使用各种 terminal,使用 tmux 都可能带来额外的麻烦,字符流在这些环节中传递,稍不注意就会出错,就像 windows 中的中文乱码一样麻烦。nvim 在这方面比 vim 更加友好,但是也有很多坑。
字母与常见含义的关联
字母 | 常见含义 |
---|---|
a | append:追加;around:包含边界(文本对象) |
b | back:返回;block:括号内容 |
c | change:修改 |
d | delete:删除 |
e | end:结尾 |
f | find:行内搜索 |
g | goto:跳转(不能单独使用) |
h | 左移;help:帮助 |
i | insert:插入;inner:内部(文本对象) |
j | 下移;join:合并行(J ) |
k | 上移 |
l | 右移 |
m | mark:标记 |
n | next:下一个 |
o | open:打开新行 |
p | paste:粘贴;paragraph:段落(文本对象) |
q | quit:退出(还有录制宏等功能) |
r | replace:替换(单个字符) |
s | substitute:替换;sentence:句子(文本对象) |
t | till:行内搜索(跳转到目标前) |
u | undo:撤销 |
v | visual:可视化 |
w | write:保存;word:单词 |
x | 删除单个字符 |
y | yank:复制 |
z | 滚动窗口 |