Vim 常用技巧

Vimnrformats 决定了 CTRL-ACTRL-X 增减数字时所识别的数字格式,其一般被设置成 bin,hex 表示会将 0b0B 开头的数字识别成二进制,0x0X 开头的数字识别成十六进制。在这些数字上使用 CTRL-ACTRL-X 时会按相应的进制进行增减。也可以增加 octal 来识别 0 开头的八进制数字。

可视模式下可以通过 [N]g_CTRL-A 来格式化 Markdown 中的列表。如果在只选择一行的情况下,其功和普通的 CTRL-A 相同;如果选择了多行,则每一行将会比上一行多加 N。例如:

1.
1.
1.
1.

如果你选择了后三行然后按下 g_CTRL-A,则会变成:

1.
2.
3.
4.

如果要给一段文字的每一行进行编号,可以使用 vip 进入可视模式选择整段文字,然后再使用 :norm I0. 给每一行的开头插入 0. ,接着使用 gv 重新选择刚刚的内容,最后用 g_CTRL-A 进行编号。


Vim 中可以用 CTRL-K {cahr1} {char2} 来插入一个特殊字符, 其中 {char1}{char2} 是该特殊字符的两个字母缩写。 例如 CTRL-K << 会插入 « 字符,CTRL-K >> 会插入 » 字符。 可以通过 :help digraphs-table 来查看所有可用的字符缩写。这个功能用的并不多。

在普通模式下,可以通过 ga 来查看光标下字符的 ASCII 码值和 Unicode 码值。


Vim 中,CTRL-H 在插入模式和命令行模式下其功能等同于 Backspace 键, 推荐使用 CTRL-H 来实现删除前一个字符的功能,因为其比 Backspace 要好按, 双手可以不用离开键盘的主区域。

Vim 推荐定义以下的功能键来实现快速删除:

按键 功能
CTRL-U 删除光标前的整行
CTRL-W 删除光标前的一个单词
CTRL-K 删除光标后的整行

注意CTRL-KVim 中默认是用于插入特殊字符的,但这个功能很少用到,所以推荐将其映射为删除光标后的整行。


Vim 的可视模式下可以使用 o 键在选区的起始位置和结束位置之间切换光标, 这在进行大块文本选择时非常有用,可以快速调整选区的范围。


Vim 中在命令行模式下可以使用 UPDOWN 来浏览之前输入的命令历史, 如果已经输入了一部分命令,使用 UPDOWN 进行过滤的时候则只会显示以输入内容开头的命令。

注意:我发现在命令行模式下使用 CTRL-PCTRL-NALT-PALT-N 也可以实现同样的功能。


Vim 中可以使用 CTRL-G 在可视模式下和选择模式下进行切换。 选择模式和可视模式的一个主要区别是,在选择模式下输入的内容会替换选中的文本, 而在可视模式下如果要替换文本需要先使用 c 命令进入插入模式。

选择模式往往是用于实现 Snippets 相关功能,实际使用中不太需要自己主动进入选择模式。

CTRL-G 在普通模式下可以用来打印当前文件的文件名、鼠标位置以及文件的状态。


当使用 :e {file} 指定一个不存在目录下的文件时,Vim 会给出报错但是依然会打开缓冲区供我们编辑,只是在使用 :w 命令进行保存时会提示无法保存,这时我们先可以使用 :!mkdir -p %:h 来保证目录全部被创建,再使用 :w 即可正常保存文件。

:w 命令可能会遇到需要 root 权限的情况,这时可以直接使用 :w !sudo tee % > /dev/null 来进行保存。


Vim 中可以使用 @: 来重复执行上一次在命令行模式下输入的命令。 也可以使用 & 来重复执行上一次使用 :substitute 命令进行的替换操作。

@@ 可以重复执行上一次在普通模式下使用 @{0-9a-z":*} 寄存器执行的宏命令。


Vim 中使用 grgR (单字符和连续替换) 可以进入 Virtual Replace 模式。 Virtual ReplaceReplace 模式的主要区别在于, Virtual Replace 模式是按照屏幕上显示宽度进行替换的, 而 Replace 模式是按照实际字符进行替换的。

这在替换包含制表符的文本时非常有用。 在 Virtual Replace 模式下,如果当前位置是一个显示宽度为多个空格的制表符, 那么输入的字符会插入到制表符之前,当输入的字符数量达到制表符的显示宽度时, 制表符才会被替换掉。


Vim 中可以使用 CTRL-R {register} 在插入模式或者命令行模式下插入寄存器中的内容。除此之外,CTRL-R CTRL-P {register} 也可以实现类似的功能,且它会自动调整缩进。

CTRL-R = 则可以在插入模式或者命令行模式下插入一个表达式的计算结果。


Vim 中可以使用 CTRL-V {digit} 来插入特殊字符:

按键组合 说明
CTRL-V {digit} 以十进制插入字符
CTRL-V u{digit} 以十六进制插入 Unicode 字符
CTRL-V U{digit} 以十六进制插入 Unicode 字符
CTRL-V o{digit} 以八进制插入字符
CTRL-V O{digit} 以八进制插入字符
CTRL-V x{digit} 以十六进制插入字符
CTRL-V X{digit} 以十六进制插入字符

CTRL-V 后面可以跟一个非数字字符来插入对应的控制字符,例如 CTRL-V CTRL-M 可以插入回车符。


下面列出了一些常用的 Ex 命令:

命令 说明
:[r]d [x] 删除指定范围的行并将其存入寄存器 x
:[r]y [x] 复制指定范围的行并将其存入寄存器 x
:[l]pu [x] 在指定行之后粘贴寄存器 x 中的内容
:[l]pu! [x] 在指定行之前粘贴寄存器 x 中的内容
:[r]t {addr} 复制指定范围的行并将其存入寄存器 x
:[r]m {addr} 移动指定范围的行到目标地址处
:[r]j 将指定范围的行连接成一行
:[r]norm {cmd} 对指定范围的行执行普通模式下的命令
:[r]s/{p}/{s}/[flags] 对指定范围的行执行替换操作
:[r]g/{p}/[cmd] 对指定范围内匹配模式的行执行 Ex 命令

注意

  • [r] 表示可选的范围,可以是行号、搜索模式等。
  • [l] 表示可选的行号,默认是当前行。
  • [x] 表示可选的寄存器名称,默认是无名寄存器。
  • {addr} 表示目标地址,可以是行号、搜索模式等。
  • {cmd} 表示要执行的 Ex 命令。
  • {p} 表示搜索模式。
  • {s} 表示替换字符串。
  • {flags} 表示替换标志,例如 g 表示全局替换。

下面给出常用的地址表示方法:

地址 说明
1 第一行
$ 最后一行
0 虚拟行,位于第一行之前
. 当前行
% 当前文件的所有行
N N
+N 当前行向下 N
-N 当前行向上 N
/pattern/ 向下搜索匹配模式的行
?pattern? 向上搜索匹配模式的行

对于搜索的范围可以写出 /<html>/,/<\/html>/ 来表示从第一个 <html> 标签所在行到第一个 </html> 标签所在行的范围。

例如: :/<html>/,/<\/html>/p 表示打印从第一个 <html> 标签所在行到第一个 </html> 标签所在行的内容。

而搜索范围可以和 +N-N 结合使用,例如: :/<html>/+1,/<\/html>/-1d 表示删除从第一个 <html> 标签下一行到第一个 </html> 标签上一行的内容。

对于这些命令我们也可以在 Visual 模式下选中后使用 : 进入命令行模式, 此时命令行中会自动插入 '<,'> 来表示选中的范围,我们只需要在后面添加命令即可。

这里再给出几个例子:

  • :%norm A; 在每一行的末尾添加分号
  • :%norm I// 在每一行的开头添加注释符号 //

可以使用下面的命令来操作缓冲区:

命令 说明
:ls 列出当前打开的所有缓冲区
:b {bufnum} 切换到指定编号的缓冲区
:bp 切换到上一个缓冲区
:bn 切换到下一个缓冲区
:bf 切换到第一个缓冲区
:bl 切换到最后一个缓冲区
:bd unload 一个缓冲区并将其从缓冲区列表中移除
:bun unload 一个缓冲区但不将其从缓冲区列表中移除
:bufdo {cmd} 对所有缓冲区依次执行指定的命令

列出的缓冲区中 % 符号表示当前缓冲区,# 符号表示轮换文件。开头的数字则表示缓冲区的编号。CTRL-^ 可以在当前文件和轮换文件之间切换。

轮换文件:在一个窗口打开一个新的缓冲区时,当前缓冲区就对应的文件就成为了轮换文件,如果使用 :bd 将一个轮换文件对应的缓冲区从缓冲区列表中移除,那么当前窗口就没有对应的轮换文件了,轮换文件是相对于每个窗口独立的。


可以使用下面的命令来操作一批文件:

命令 说明
:ar 查看参数列表
:ar {arglist} 重新设置参数列表
:n 切换到上一个文件
:prev 切换到下一个文件
:arga 向参数列表中添加一个文件
:argd 从参数列表中删除指定文件
:argdo {cmd} 对所有文件依次执行指定的命令

例如 :args *.js 会将当前目录下的所有 js 文件添加到参数列表中,然后可以通过 :next:prev 来切换这些文件。

我们可以使用反引号来将命令的输出作为参数列表,例如:

:args `find . -name '*.js'`

在使用 :argdo {cmd} 或者 :bufdo {cmd} 时,其会按照如下的逻辑进行执行:

  • :first 或者 :bf
  • :{cmd}
  • :next 或者 :bn
  • :{cmd}
  • ……

当执行过程中 :{cmd} 进行了修改但是没有保存的时候, :next:bn 就会执行失败, 我们可以通过设置 hidden 选项(Neovim 中默认开启)来允许切换未保存修改的缓冲区。

当然我们也可以在 :{cmd} 结尾加上 | update 来保存修改,例如: :bufdo %s/var/let/g | update 会对所有缓冲区进行替换并保存修改。


下面的一些命令可以用来处理有修改的缓冲区:

命令 说明
:w 保存当前缓冲区
:wa 保存所有缓冲区
:e 重新加载当前缓冲区
:qa 关闭所有缓冲区
:up 保存当前缓冲区(如果有修改)

上面的命令都可以在结尾加上 ! 来强制执行,例如 :qa! 会强制关闭所有缓冲区而不保存修改。


Vim 中可以使用 q:q/q? 来打开命令行窗口, 分别用于查看和编辑命令行模式下的命令历史、向下搜索历史和向上搜索历史。

在命令行窗口中,你可以像编辑普通文件一样编辑命令历史,当你在任意一行上按下 ENTER 的时候, 该行命令将会被执行。CTRL-C 则会返回到命令行模式。使用 :q 则可以关闭命令行窗口。

如果你当前处在命令行模式下,可以直接按下 CTRL-F 来打开命令行窗口。


Vim 的命令行模式中可以使用 SHIFT-LEFT(或 CTRL-LEFT)和 SHIFT-RIGHT(或 CTRL-RIGHT) 将光标按照单词进行移动,类似于普通模式下的 bw 按键。

推荐进行如下的按键映射:

原按键 映射后
CTRL-B LEFT
CTRL-F RIGHT
ALT-b SHIFT-LEFT
ALT-f SHIFT-RIGHT

Vim 的命令行模式下可以用 | 来连接多个命令,例如: :w | !gcc % -o %< | ./%< 会先保存当前文件,然后编译该文件,最后运行生成的可执行文件。 可以通过 :h :bar 来查看更多的内容。

在执行 SHELL 命令的时候,Vim 会将 % 解析成当前文件名, %< 解析成当前文件名去掉扩展名后的名称。 可以通过 :h cmdline-special 来查看所有可用的特殊符号。


Vim 中可以通过 :!{cmd} 的方式来执行外部命令,当直接运行 :!ls 时,vim 会回显命令的输出, 当命令的输出比较长时,可以用 :r !{cmd} 的方式来将命令的输出读取到当前光标所在位置。

当前我们也可以通过 :enew | r !{cmd} 的方式来在一个新的缓冲区中读取命令的输出。

:[range]w !{cmd} 则做相反的操作, 它会将当前缓冲区的指定内容通过管道传递给外部命令 cmd 的标准输入。 如果不指定范围则默认是整个缓冲区的内容。


Vim 窗口大小调整相关的命令:

命令 快捷键 说明
:wnc = CTRL-W_= 使所有窗口大小相等
:wnc _ CTRL-W__ 最大化当前窗口的高度
:wnc | CTRL-W_| 最大化当前窗口的宽度
:wnc [N] [N]CTRL-W__ 将当前窗口高度设置成 N
:wnc [N]| [N]CTRL-W_| 将当前窗口宽度设置成 N

Vim 窗口移动相关的命令:

命令 快捷键 说明
:wnc r CTRL-W_r 旋转窗口位置
:wnc R CTRL-W_R 反向旋转窗口位置
:wnc x CTRL-W_x 交换当前窗口和下一个窗口
:wnc K CTRL-W_K 将当前窗口移动到最上面
:wnc J CTRL-W_J 将当前窗口移动到最下面
:wnc H CTRL-W_H 将当前窗口移动到最左边
:wnc L CTRL-W_L 将当前窗口移动到最右边
:wnc T CTRL-W_T 将当前窗口移动到一个新标签页

Vim 窗口控制相关的命令:

命令 快捷键 说明
:clo CTRL-W_c 关闭当前窗口
:on CTRL-W_o 关闭其他窗口只保留当前窗口

Vim 中每个窗口有自己的工作目录,可以合用 :pwd 来查看当前窗口的工作目录。使用 :cd {path} 可以全局改变工作目录,而使用 :lcd {path} 则只会改变当前窗口的工作目录。

:argdo:bufdo 类似,Vim 也提供了 :windo {cmd} 来对所有窗口执行指定的命令。


Vim 使用下面的命令来管理标签页:

命令 快捷键 说明
:tabe N/A 在新标签页中打开一个文件
:tabn gt 切换到下一个标签页
:tabp gT 切换到上一个标签页
:tabn {N} {N}gt 切换到 N 号标签页
:tabc N/A 关闭当前标签页
:tabo N/A 关闭其他标签页只保留当前标签页
:tabm [N] N/A 移动当前标签页到 N 号后,默认为$
:tabm -[N] N/A 将当前标签页向左移动 N 个位置,默认为 1
:tabm +[N] N/A 将当前标签页向右移动 N 个位置,默认为 1

Vim 在命令行模式下可以进行下列展开:

符号 说明
% 当前文件的完整路径
# 轮换文件的完整路径

在进行展开的时候可以使用下面的修饰符:

符号 说明
:p 转换成完整路径
:~ ~ 代替用户主目录
:. 用当前工作目录代替路径前缀
:h 去掉文件名,保留目录路径
:t 去掉目录路径,保留文件名
:r 去掉文件扩展名
:e 只保留文件扩展名
:s?p?s? 替换路径中的第一个匹配模式 ps
:gs?p?s? 替换路径中所有匹配模式 ps
:S 对路径进行 shell 转义

在上面的 :s?p?s?:gs?p?s? 中,? 可以替换成任意未在 ps 中出现的字符。


Vim 中可以使用 m{a-zA-Z} 来创建一个标记,小写字母的标记是每个缓冲区局部可见的,而大写字母的标记则是全局可见的。我们可以使用 '{mark} 来跳转到 {mark} 所在行的第一个非空字符上,使用 `{mark} 则会跳转到定义 {mark} 的精确位置。

下面给出一些 Vim 自带的位置标记:

标记 说明
` 上次跳转位置
. 上次修改位置
" 上次退出文件位置
^ 上次插入位置
[ 上次修改或复制的开始位置
] 上次修改或复制的结束位置
< 可视模式下选区的开始位置
> 可视模式下选区的结束位置

在使用与 quick-fix 列表相关的命令(如 :grep:vimgrep:make) 或使用与缓冲区列表、参数列表有关的命令(如 :args:argdo)前设置一个全局标记可以在操作后快速返回。

可以使用 gi 命令跳转到上次插入的位置并进入插入模式。


Vim 中可以使用以下的默认 operator

按键 说明
c 更改
d 删除
y 复制
~ 切换大小写
g~ 切换大小写
gu 转换为小写
gU 转换为大写
! 过滤
= 自动缩进
g? ROT13 编码
> 向右缩进
< 向左缩进
zf 创建折叠
g@ 调用用户设置的 operatorfunc

当使用各自最后一个按键时会对当前行进行操作,例如 gUU 会让当前行的字母全部变成大写。在可视模式下使用上述命令会作用于选择的内容。

tildeop 选项关闭时(默认关闭),~ 在普通模式下只会切换光标下字符的大小写;当开启时,~ 在普通模式下和 g~ 效果相同。

uU~ 在可视模式下与 gugUg~ 效果相同。

过滤是指将指定范围内的内容通过管道传递给外部命令 {filter} 的标准输入,并将外部命令的输出替换掉指定范围内的内容。例如输入 !G 后会进命令行模式且会自动填入 :.,$! 这个时候我们可以将其补充完整 :.,$!sort -t',' -k2 会对当前行到最后一行的内容按逗号分隔的第二列进行排序。

Operator-pending 的阶段可以先输入 vV 、或者 CTRL-V 来将本次的 motion 操作强制设置成 charwiselinewise 或者 blockwise 模式。在使用 v 的时候如果后续 motion 已经是 charwise 模式则会在 insidearound 语义间切换,而如果后续 motionlinewise 则始终是以 inside 的语义进行执行。

下面是常用 motion

按键 说明
gM 移动到行中
g_ 移动到行尾最后一个非空字符
{N}| 移动到第 N
gm 移动到折行屏幕中间
g<End> 移动到折行最后一个非空字符
- 移动到上一行第一个非空字符
+ 移动到下一行第一个非空字符
( 移动到上一个 sentence 的开头
) 移动到下一个 sentence 的开头
{ 移动到上一个 paragraph 的开头
} 移动到下一个 paragraph 的开头
H 移动到屏幕顶部行的第一个非空字符
M 移动到屏幕中间行的第一个非空字符
L 移动到屏幕底部行的第一个非空字符
gf 移动到光标下文件的开头
CTRL-] 跳转到光标下标识符的定义处
{N}_ 向下移 N-1 行,且到第一个非空字符
[( 移动到上一个未匹配的 (
]) 移动到下一个未匹配的 )
[{ 移动到上一个未匹配的 {
]} 移动到下一个未匹配的 }
[m 移动到上一个方法的开头
]m 移动到下一个方法的开头
[M 移动到上一个方法的结尾
]M 移动到下一个方法的结尾
[# 移动到上一个未匹配的 #if#else
]# 移动到下一个未匹配的 #if#else
[*[/ 移动到上一个 C 语言风格的注释开头 /*
]*]/ 移动到下一个 C 语言风格的注释结尾 */
[[ 移动到上一个 section 的开头或上一个行首 {
]] 移动到下一个 section 的开头或下一个行首 {
[] 移动到上一个 section 的开头或上一个行首 }
][ 移动到下一个 section 的开头或下一个行首 }

word 是由 iskeyword 中的字符组成,可以简单认为数字、下划线、字母属于单词的一部分。

WORD 则由非空字符组成。

sentence 则是以.?! 结尾加上至少一个空白字符的文本块。在 sentence 的标点和空格之间可以有多个 )]"`。需要注意的是 paragraphsection 的边界也是 sentence 的边界。

paragraph 则可以简单地认为是以一个或多个空行作为分隔符的文本块。需要注意的是 section 的边界也是 paragraph 的边界。

section 则必须以 sections 中定义的标记作为分隔符,这个概念平时用得不多,在写 Vim 的相关文档的时候可能会用到。

下面是一些 text-objects

按键 说明
is inside sentence
as around sentence
ip inside paragraph
ap around paragraph
it inside tag
at around tag
ib inside block
ab around block
iB inside Block
aB around Block

tag 指的是成对出现的标签,例如 <div>...</div>inside tag 会选择标签内的内容,而 around tag 则会选择包括标签本身在内的内容。在 HTMLXML 文件中,itat 非常有用。

block 是指 () 围成的区域,Block 则是指 {} 围成的区域。inside block 会选择括号内的内容,而 around block 则会选择包括括号本身在内的内容。


Vim 中的 path 变量会控制一些命令的查找路径,默认值为 .,, 其中的 . 代表当前文件所在目录,, 是分隔符,两个逗号一起表示增加一个空路径,空路径表示当前的工作目录。

subffixesadd 变量则会控制一些命令在查找文件时所使用的后缀名列表。其往往会根据打开的文件类型被自动设置。


Vim 中可以使用 :ju 来查看当前窗口的跳转列表,使用 :cle 可以清空当前窗口的跳转列表。

跳转列表里面的每一项记录了一个跳转位置,可以使用 CTRL-OCTRL-I 来在跳转列表中向后和向前跳转。需要注意的是 CTRL-I<Tab> 是等价的,这意味着你绑定 <Tab> 键为其他功能后将无法使用 CTRL-I 进行跳转。


Vim 中可以使用 :c 来查看当前文件的修改列表。修改列表记录了每一次对文件的修改位置。g;g, 则可以在修改列表中向后和向前跳转。


Vim 中使用 d 进行删除时会将删除的内容放入匿名寄存器中,如果想要在删除时不放入任何寄存器,则可以使用 "_d 来进行删除。

Vim 中我们有时候在使用 p 的时候可能会发现匿名寄存器中的内容已经被覆盖了,这个时候我们可以使用 "0p 来粘贴最近一次使用 y 进行复制的内容。0 寄存器专门用于保存最近一次复制的内容。

下面给出一些寄存器的说明:

寄存器 说明
% 当前文件名
# 轮换文件名
. 上次插入的文本
: 上次命令行模式下的命令
/ 上次搜索的模式

Vim 中的字母寄存器是不区分大小写的,我们只能使用小写字母来操作它们,当我们使用大写字母时则表示将内容追加到对应的小写字母寄存器中。比如我们使用 qa 给寄存器 a 录制一个宏,然后使用 qA 则会将后续录制的内容追加到寄存器 a 中。


Vim 中在可视模式使用 p 实际上会将选中的内容替换掉并将其放入匿名寄存器中,也正是因为这个特性我们可以使用如下的步骤来交换两段内容:

  1. 选择第一段内容并使用 d 删除,使用 mm 来创建一个标记 m 记录删除位置
  2. 选择第二段内容并使用 p 进行粘贴
  3. 使用 `m 跳转到标记 m 处并使用 p 进行粘贴

netrwVim 自带的一个文件管理插件,可以使用 :Explore 来打开,在这样进行操作时,其与传统的文件管理器有一些不同,其会直接在当前的窗口打开一个目录,当我们使用 ENTER 选择一个文件时,其会在当前的窗口打开该文件,如果我们打开错了,我们可以使用 CTRL-^ 在当前文件和 netrw 之间进行切换。

netrw 的强大之处在于其支持通过 scpftpcurlwget 来访问远程文件系统,例如我们可以使用 :e scp://user@host//path/to/file 来编辑远程主机上的文件。


Vim 中可以使用 :vimgrep 命令来将所有的匹配项放入到 quickfix 列表中,例如 :vimgrep /pattern/ **/*.js 会在当前目录及其子目录下的所有 js 文件中搜索 pattern 并将所有的匹配项放入到 quickfix 列表中。使用 :copen 可以打开 quickfix 窗口查看所有的匹配项,使用 :cnext:cprev 可以在匹配项之间进行跳转。


Vim 中在使用 pP 的时候会将指针放置到拷贝内容的开头,而使用 gpgP 可以将指针放置到拷贝内容的结尾。


我们有时候可能会需要频繁地在多行内依次使用 ;.,我们可以按照以下的步骤来实现:

  1. 通过 qq;.q 来录制一个宏到寄存器 q 中;
  2. 在可视模型下选择需要执行的行;
  3. 使用 :normal @99q 来对选中的每一行执行多次宏命令。

当我们想要对一个宏进行修改的时候,我们可以在插入模式下通过 CTRL-R_{reg} 先读取出宏的内容到当前的位置进行修改,修改完成后我们可以直接使用 "{reg}D 来将修改后的内容重新保存到寄存器中。

实际上 Vim 的宏里面存储的就是一个字符串,我们当然可以使用 Vim 内置的函数对其进行修改,例如:

:let @q = substitute(@q, ';', ',','g')

会将寄存器 q 中的所有分号替换成逗号。


Vim 可以使用 vim -u NONE -N 来以不加载任何配置文件的方式启动,并且以 nocompatible 模式运行。




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • 奇怪的知识增加了
  • 文学摘抄
  • Go 学习笔记
  • 鞋带公式
  • 多重背包优化