前言
本篇文章转载的GitHub上一个很火的项目
英文版本
全局命令
到目前为止,您已经了解了如何使用点命令(.
)重复上一次更改,如何使用宏(q
)重播动作以及将文本存储在寄存器中("
)。
在本章中,您将学习如何在全局命令中重复命令行命令。运行一次,应用于任何地方(在缓冲区中)。
全局命令概述
Vim的全局命令用于同时在多行上运行命令行命令。
顺便说一句,您之前可能已经听说过“ Ex命令”一词。在本书中,我将它们称为命令行命令,但Ex命令和命令行命令是相同的。它们是以冒号(:
)开头的命令。在上一章中,您了解了替代命令。这是一个Ex命令的示例。它们之所以称为Ex,是因为它们最初来自Ex文本编辑器。在本书中,我将继续将它们称为命令行命令。有关Ex命令的完整列表,请查看:h ex-cmd-index
。
全局命令具有以下语法:
1 | :g/pattern/command |
pattern
匹配包含该模式的所有行,类似于替代命令中的模式。command
可以是任何命令行命令。全局命令通过对与pattern
匹配的每一行执行command
来工作。
如果您具有以下表达式:
1 | const one = 1; |
要删除所有包含“控制台”的行,可以运行:
1 | :g/console/d |
结果:
1 | const one = 1; |
全局命令在与“控制台”模式匹配的所有行上执行删除命令(d
)。
运行g
命令时,Vim对文件进行两次扫描。在第一次运行时,它将扫描每行并标记与/console/
模式匹配的行。一旦所有匹配的行都被标记,它将进行第二次运行,并在标记的行上执行d命令。
如果要删除所有包含“const”的行,请运行:
1 | :g/const/d |
结果:
1 | console.log("one: ", one); |
逆向比赛
要在不匹配的行上运行全局命令,可以运行:
1 | :g!/{pattern}/{command} |
要么
1 | :v/{pattern}/{command} |
如果运行:v/console/d
,它将删除_不_包含“console”的所有行。
模式
全局命令使用与替代命令相同的模式系统,因此本节将作为更新。随意跳到下一部分或继续阅读!
如果您具有以下表达式:
1 | const one = 1; |
要删除包含“one”或“two”的行,请运行:
1 | :g/one\|two/d |
要删除包含任何一位数字的行,请运行以下任一命令:
1 | :g/[0-9]/d |
要么
1 | :g/\d/d |
如果您有表达式:
1 | const oneMillion = 1000000; |
要匹配包含三到六个零的行,请运行:
1 | :g/0\{3,6\}/d |
传递范围
您可以在g
命令之前传递一个范围。您可以通过以下几种方法来做到这一点:
:1,5/g/console/d
在第1行和第5行之间匹配字符串”console”并将其删除。:,5/g/console/d
如果逗号前没有地址,则从当前行开始。它在当前行和第5行之间寻找字符串”console”并将其删除。:3,/g/console/d
如果逗号后没有地址,则在当前行结束。它在第3行和当前行之间寻找字符串”console”并将其删除。:3g/console/d
如果只传递一个地址而不带逗号,则仅在第3行执行命令。在第3行查找,如果字符串为”console”,则将其删除。
除了数字,您还可以将这些符号用作范围:
.
表示当前行。范围.,3
表示当前行和第3行之间。$
表示文件的最后一行。3,$
范围表示在第3行和最后一行之间。+n
表示当前行之后的n行。您可以将其与.
结合使用,也可以不结合使用。3,+1
或3,.+1
表示在第3行和当前行之后的行之间。
如果您不给它任何范围,默认情况下它将影响整个文件。这实际上不是常态。如果您不传递任何范围,Vim的大多数命令行命令仅在当前行上运行。两个值得注意的例外是全局(:g
)和save(:w
)命令。
普通命令
您可以将全局命令和:normal
命令行命令一起运行。
如果您有以下文字:
1 | const one = 1; |
要添加”;”运行到每一行的末尾:
1 | :g/./normal A; |
让我们分解一下:
:g
是全局命令。/./
是“非空行”的模式。回想一下正则表达式中的点(.
)表示_任何字符_。它将行与至少一个字符匹配,因此将行与“const”和“console”匹配。它不匹配空行。normal A;
运行:normal
命令行命令。A;
是普通模式命令,用于在该行的末尾插入”;”。
执行宏
您也可以使用全局命令执行宏。宏只是普通模式下的操作,因此可以使用:normal
来执行宏。如果您有以下表达式:
1 | const one = 1 |
请注意,带有”const”的行没有分号。让我们创建一个宏,以在寄存器”a”的这些行的末尾添加逗号:
1 | qa0A;<esc>q |
如果您需要复习,请查看有关宏的章节。现在运行:
1 | :g/const/normal @a |
现在,所有带有”const”的行都将带有”;”在末尾。
1 | const one = 1; |
递归全局命令
全局命令本身是命令行命令的一种,因此您可以从技术上在全局命令中运行全局命令。
给定表达式:
1 | const one = 1; |
如果您运行:
1 | :g/console/g/two/d |
首先,“g”将查找包含模式“console”的行,并找到3个匹配项。然后,第二个“g”将从那三个匹配项中查找包含模式“two”的行。最后,它将删除该匹配项。
您也可以将“g”与“v”结合使用以找到正负模式。例如:
1 | :g/console/v/two/d |
而不是查找包含模式”two”的行,它将查找_不_包含”two”的行。
更改定界符
您可以像替代命令一样更改全局命令的定界符。规则是相同的:您可以使用任何单字节字符,但字母,数字,"
, |
, 和 \
除外。
要删除包含”console”的行:
1 | :g@console@d |
如果在全局命令中使用替代命令,则可以有两个不同的定界符:
1 | g@one@s+const+let+g |
此处,全局命令将查找包含“one”的所有行。 替换命令将从这些匹配项中将字符串”const”替换为”let”。
默认命令
如果在全局命令中未指定任何命令行命令,会发生什么?
全局命令将使用打印(:p
)命令来打印当前行的文本。如果您运行:
1 | :g/console |
它将在屏幕底部打印所有包含”console”的行。
顺便说一下,这是一个有趣的事实。因为全局命令使用的默认命令是p
,所以这使g
语法为:
1 | :g/re/p |
g
= 全局命令re
= 正则表达式模式p
= 打印命令
它拼写_”grep”_,与命令行中的grep
相同。但这 不 是巧合。 g/re/p
命令最初来自第一行文本编辑器之一的Ed编辑器。 grep
命令的名称来自Ed。
您的计算机可能仍具有Ed编辑器。从终端运行ed
(提示:要退出,请键入q
)。
更多示例
反转整个缓冲区
要撤消整个文件,请运行:
1 | :g/^/m 0 |
^
是“行的开始”的模式。使用^
匹配所有行,包括空行。
如果只需要反转几行,请将其传递一个范围。要将第五行到第十行之间的行反转,请运行:
1 | :5,10g/^/m 0 |
要了解有关move命令的更多信息,请查看:h :move
。
汇总所有待办事项
当我编码时,有时我会想到一个随机的绝妙主意。不想失去专注,我通常将它们写在我正在编辑的文件中,例如:
1 | const one = 1; |
跟踪所有已创建的TODO可能很困难。 Vim有一个:t
(copy)方法来将所有匹配项复制到一个地址。要了解有关复制方法的更多信息,请查看:h :copy
。
要将所有TODO复制到文件末尾以便于自省,请运行:
1 | :g/TODO/t $ |
结果:
1 | const one = 1; |
现在,我可以查看我创建的所有TODO,找到时间来完成它们或将它们委托给其他人,然后继续执行下一个任务。
另一种选择是使用m
:
1 | :g/TODO/m $ |
结果:
1 | const one = 1; |
一旦决定要删除列表,就可以删除它。
黑洞删除
从寄存器一章回想一下,已删除的文本存储在已编号的寄存器中(允许它们足够大)。每当运行:g/console/d
时,Vim都会将删除的行存储在编号寄存器中。如果删除许多行,则可以快速填充所有编号的寄存器。为了避免这种情况,您始终可以使用黑洞寄存器(“ _
)_不_将删除的行存储到寄存器中。
1 | :g/console/d _ |
通过在d
之后传递_
,Vim不会将删除的行保存到任何寄存器中。
将多条空行减少为一条空行
如果您的文件带有多个空行,如下所示:
1 | const one = 1; |
您可以快速将每条长长的空行减少为一条空行。运行:
1 | :g/^$/,/./-1j |
结果:
1 | const one = 1; |
让我们分解一下:
:g
是全局命令/^$/
是空行的模式。回想一下,^
表示行的开始,$
表示行的结束。^ $
匹配一个空行(一个零字符长的行)。,/./-1
是j
命令的范围。由于您没有传递起始范围的值,因此它从当前行开始。您之前已经了解到/./
是非空行的模式。,/./
是从当前行到下一个非空行的范围。全局命令的范围/^$/
会将您带到console.log("one: ", one);
下面的第一行。这是当前行。/./
匹配第一条非空行,即const two = 2;
行。最后,-1
将其偏移一行。第一次匹配的有效范围是console.log("one: ", one);
下方的空行和const two = 2;
上方的空行。j
是连接命令:j
。您可以加入所有作为其范围的行。例如,:1,5j
连接第一到第五行。
请注意,您正在j
命令之前传递范围(,/./-1
)。仅仅因为您在全局命令中使用了命令行命令,并不意味着您不能给它一个范围。在这段代码中,您将传递给j
命令自己的范围来执行。您可以在执行全局命令时将范围传递给任何命令。
顺便说一句,如果您想将多条空行减少为无行,而不是将,/./-1
用作j
命令的范围,只需使用,/./
作为范围:
1 | :g/^$/,/./j |
或更简单:
1 | :g/^$/-j |
您的文字现在减少为:
1 | const one = 1; |
高级排序
Vim有一个:sort
命令来对一个范围内的行进行排序。例如:
1 | d |
您可以通过运行:sort
对它们进行排序。如果给它一个范围,它将只对该范围内的行进行排序。例如,:3,5sort
仅在第三和第五行之间排序。
如果您具有以下表达式:
1 | const arrayB = [ |
如果需要排序数组中的元素,而不是数组本身,可以运行以下命令:
1 | :g/\[/+1,/\]/-1sort |
结果:
1 | const arrayB = [ |
这很棒!但是命令看起来很复杂。让我们分解一下。该命令包含三个主要部分:全局命令模式,排序命令范围和排序命令。
:g/\[/
是全局命令模式。
:g
是全局命令。/\[/
是全局命令使用的模式。\[
查找文字”[“字符串。
+1,/\]/-1
是排序命令的范围。
- 范围可以有开始和结束地址。在这种情况下,
+1
是起始地址,/\]/-1
是结束地址。 +1
表示当前行之后的行,这是与全局命令中的模式“ [”匹配的行。 “ +1”将当前行偏移一行。因此,在第一个匹配项中,范围实际上是在const arrayB = [
文本_之后_的一行。/\]/-1
是结束地址。\]
代表一个文字的右方括号“]”。-1
将其偏移一行。结束地址是”]”上方的行。
sort
是sort命令行命令。它对给定范围内的所有内容进行排序。 “[“到上方的行”]”之后的所有内容均已排序。
如果您仍然对该命令感到困惑,请不要担心。我花了很长时间才掌握它。稍事休息,离开屏幕,然后重新思考。
聪明地学习全局命令
全局命令针对所有匹配的行执行命令行命令。有了它,您只需要运行一次命令,Vim就会为您完成其余的工作。要精通全局命令,需要做两件事:良好的命令行命令词汇表和正则表达式知识。随着您花费更多的时间使用Vim,您自然会学到更多的命令行命令。正则表达式知识将需要更积极的方法。但是,一旦您对正则表达式感到满意,您将领先于很多。
这里的一些例子很复杂。不要被吓到。真是花时间了解它们。学习阅读模式。确保您知道每个命令中的每个字母代表什么。不要放弃。
每当需要在多个位置应用命令时,请暂停并查看是否可以使用g
命令。寻找最适合工作的命令,并编写一个模式以同时定位多个目标。然后重复执行此操作,直到您无需考虑即可进行操作。下次,看看是否有更快,更有效的方法。
既然您已经知道全局命令的功能强大,那么让我们学习如何使用外部命令来增加工具库。
外部命令
在Unix系统内部,您会发现许多小型的,超专业化命令,每个命令都能很好地完成一件事情。您可以将这些命令链接在一起以共同解决一个复杂的问题。如果可以从Vim内部使用这些命令,那不是很好吗?
在本章中,您将学习如何扩展Vim以使其与外部命令无缝协作。
Bang 命令
Vim有一个Bang(!
)命令,可以执行三件事:
1.将外部命令的STDOUT读入当前缓冲区。
2.将缓冲区的内容作为STDIN写入外部命令。
3.从Vim内部执行外部命令。
将STDOUT作为命令读入Vim
将外部命令的STDOUT读入当前缓冲区的语法为:
1 | :r !{cmd} |
:r
是Vim的读命令。如果不带!
使用它,则可以使用它来获取文件的内容。如果当前目录中有文件file1.txt
并运行:
1 | :r file1.txt |
Vim会将file1.txt
的内容放入当前缓冲区。
如果您运行:r
命令,然后再执行!
和外部命令,则该命令的输出将插入到当前缓冲区中。要获取ls
命令的结果,请运行:
1 | :r !ls |
它返回类似:
1 | file1.txt |
您可以从curl
命令读取数据:
1 | :r !curl -s 'https://jsonplaceholder.typicode.com/todos/1' |
r命令也接受一个地址:
1 | :10r !cat file1.txt |
现在,将在第10行之后插入来自运行cat file.txt
的STDOUT。
将缓冲区内容写入外部命令
除了保存文件,您还可以使用写命令(:w
)将当前缓冲区中的文本作为STDIN传递给外部命令。语法为:
1 | :w !cmd |
如果您具有以下表达式:
1 | console.log("Hello Vim"); |
确保在计算机中安装了node,然后运行:
1 | :w !node |
Vim将使用node
执行Javascript表达式来打印“ Hello Vim”和“Vim is awesome”。
当使用:w
命令时,Vim使用当前缓冲区中的所有文本,与global命令类似(大多数命令行命令,如果不给它传递范围,则仅对当前行执行该命令)。如果您通过:w
来指定地址:
1 | :2w !node |
Vim只使用第二行中的文本到node
解释器中。
:w !node
和:w! node
之间有一个细微但重要的区别。节点。使用
:w !node,您可以将当前缓冲区中的文本“写入”到外部命令
node中。用
:w! node`,则您将强制保存文件并将其命名为”node”。
执行外部命令
您可以使用bang命令从Vim内部执行外部命令。语法为:
1 | :!cmd |
要以长格式查看当前目录的内容,请运行:
1 | :!ls -ls |
要终止在PID 3456上运行的进程,可以运行:
1 | :!kill -9 3456 |
您可以在不离开Vim的情况下运行任何外部命令,因此您可以专注于自己的任务。
过滤文字
如果给!
范围,则可用于过滤文本。假设您有:
1 | hello vim |
让我们使用tr
(translate)命令将当前行大写。运行:
1 | :.!tr '[:lower:]' '[:upper:]' |
结果:
1 | HELLO VIM |
细目:
.!
在当前行执行filter命令。!tr '[:lower:]' '[:upper:]'
调用tr
命令将所有小写字符替换为大写字符。
必须传递范围以运行外部命令作为过滤器。如果您尝试在没有.
的情况下运行上述命令(:!tr '[:lower:]' '[:upper:]'
),则会看到错误。
假设您需要使用awk命令删除两行的第二列:
1 | :%!awk "{print $1}" |
结果:
1 | hello |
细目:
:%!
在所有行上执行filter命令(%
)。awk "{print $1}"
仅打印匹配项的第一列。在这种情况下,单词“你好”。
您可以使用链运算符(|
)链接多个命令,就像在终端中一样。假设您有一个包含这些美味早餐的文件:
1 | name price |
如果您需要根据价格对它们进行排序,并且仅以均匀的间距显示菜单,则可以运行:
1 | :%!awk 'NR > 1' | sort -nk 3 | column -t |
结果:
1 | buttermilk pancake 9 |
细目:
:%!
将过滤器应用于所有行(%)。awk 'NR > 1'
仅从第二行开始显示文本。|
链接下一个命令。sort -nk 3
使用列3(k 3
)中的值对数字进行排序(n
)。column -t
以均匀的间距组织文本。
普通模式命令
在正常模式下,Vim有一个过滤运算符(!
)。如果您有以下问候:
1 | hello vim |
要大写当前行和下面的行,可以运行:
1 | !jtr '[a-z]' '[A-Z]' |
细目:
!j
运行常规命令过滤器运算符(!
),目标是当前行及其下方的行。回想一下,因为它是普通模式运算符,所以适用语法规则“动词+名词”。tr '[a-z]' '[A-Z]'
将小写字母替换为大写字母。
filter normal命令仅适用于至少一行或更长的运动/文本对象。如果您尝试运行!iwtr'[az]''[AZ]'
(在内部单词上执行tr
),您会发现它在整个行上都应用了tr命令,而不是光标所在的单词开启。
聪明地学习外部命令
Vim不是IDE。它是一种轻量级的模式编辑器,通过设计可以高度扩展。由于这种可扩展性,您可以轻松访问系统中的任何外部命令。这样,Vim离成为IDE仅一步之遥。有人说Unix系统是有史以来的第一个IDE。
Bang 命令与您知道多少个外部命令一样有用。如果您的外部命令知识有限,请不要担心。我还有很多东西要学。以此作为持续学习的动力。每当您需要过滤文本时,请查看是否存在可以解决问题的外部命令。不必担心掌握特定命令的所有内容。只需学习完成当前任务所需的内容即可。
命令行模式
在前三章中,您已经学习了如何使用搜索命令(/
, ?
)、替换命令(:s
)、全局命令(:g
),以及外部命令(!
)。这些都是命令行模式命令的一些例子。
在本章中,您将学习命令行模式的更多技巧。
进入和退出命令行模式
命令行模式本身也是一种模式,就像普通模式、输入模式、可视模式一样。在这种模式中,光标将转到屏幕底部,此时您可以输入不同的命令。
有 4 种进入命令行模式的方式:
- 搜索命令 (
/
,?
) - 命令行指令 (
:
) - 外部命令 (
!
)
您可以从正常模式或可视模式进入命令行模式。
若要离开命令行模式,您可以使用 <esc>
、Ctrl-c
、Ctrl-[
。
有时其他资料可能会将“命令行指令”称为“Ex 命令”,将“外部命令”称为“过滤命令”或者“叹号运算符”。
重复上一个命令
您可以用 @:
来重复上一个命令行指令或外部命令。
如果您刚运行 :s/foo/bar/g
,执行 @:
将重复该替换。
如果您刚运行 :.!tr '[a-z]' '[A-Z]'
,执行 @:
将重复上一次外部命令转换过滤。
命令行模式快捷键
在命令行模式中,您可以使用 Left
或 Right
键,来左右移动一个字符。
如果需要移动一个单词,使用 Shift-Left
或 Shift-Right
(在某些操作系统中,您需要使用 Ctrl
而不是 Shift
)。
使用 Ctrl-b
移动到该行的开始,使用 Ctrl-e
移动到该行的结束。
和输入模式类似,在命令行模式中,有三种方法可以删除字符:
1 | Ctrl-h 删除一个字符 |
最后,如果您想像编辑文本文件一样来编辑命令,可以使用 Ctrl-f
。
这样还可以查看过往的命令,并在这种“命令行编辑的普通模式”中编辑它们,同时还能按下 Enter
来运行它们。
寄存器和自动补全
在编程中,只要能使用自动补全,就尽量不要重复输入。这种思想不仅能节省时间,还能减少打错字的可能。
您可以使用 Ctrl-r
来插入 Vim 寄存器中的文本(就和输入模式中的一样)。如果寄存器 “a 中存储着 “foo” 字符串,运行 Ctrl-r a
就可以插入它。输入模式中的寄存器能做到的一切,同样能在命令行模式中做到。
命令也能使用自动补全。例如,要在命令行模式中自动补全 echo
命令,首先输入 “ec”,接着按下 <Tab>
,此时您应该能在左下角看到一些 “ec” 开头的 Vim 命令(例如:echo echoerr echohl echomsg econ
)。按下 <Tab>
或 Ctrl-n
可以去到下一个选项。按下 <Shift-Tab>
或 Ctrl-p
可以回到上一个选项。
一些命令行指令接受文件名作为参数。edit
就是一个例子。输入 :e
后(不要忘记空格了),按下 <Tab>
,Vim 将列出所有相关的文件名。
历史记录窗口
您可以查看命令行指令和搜索项的历史记录(要确保在运行 vim --version
时,Vim 的编译选项中含有+cmdline_hist
)。
运行 :his :
来查看命令行指令的历史记录:
1 | ## cmd History |
Vim 列出了您运行的所有 :
命令。默认情况下,Vim 存储最后 50 个命令。运行 :set history=100
可以将 Vim 记住的条目总数更改为 100。
在命令行模式中,您可以按下 Up
和 Down
键来遍历此历史记录列表。假设您的命令行指令历史记录如下:
1 | 51 s/foo/bar/g |
按 :
后再按 Up
,您可以看到 :s/foo//g
。再按 Up
可以看到 :s/foo/baz/g
。Vim 向上遍历了历史记录。
类似地,运行 :his /
可以查看搜索记录。运行后,按下Up
或 Down
可以遍历此历史记录栈。
Vim 非常聪明,可以区分不同的历史记录。按下:
后再按Up
或 Down
,Vim 自动显示命令历史记录。按下/
后再按Up
或 Down
,Vim 自动显示搜索记录。
命令行窗口
历史记录窗口只能显示过往命令行指令,但无法运行它们。但在命令行窗口中,可以边浏览边执行。有三种命令行窗口:
1 | q: 命令行窗口 |
运行 q:
来打开命令行窗口。Vim 将在屏幕底部启动一个新窗口。 您可以使用 Up
或Ctrl-p
键向上遍历,使用 Down
或 Ctrl-n
键可以向下遍历。按下 <Return>
,Vim 将执行该命令。按下 Ctrl-c
、Ctrl-w c
、:quit
可以退出命令行窗口。
类似地,运行 q/
可以启动向前搜索命令行窗口,运行 q?
可以启动向后搜索命令行窗口。
聪明地学习命令行模式
对比其他三种模式,命令行模式就像是文本编辑中的瑞士军刀。寥举几例,您可以编辑文本、修改文件和执行命令。本章是命令行模式的零碎知识的集合。同时,Vim 模式的介绍也走向尾声。现在,您已经知道如何使用普通、输入、可视以及命令行模式,您可以比以往更快地使用 Vim 来编辑文本了。
是时候离开 Vim 模式,来了解如何使用 Vim 标记进行更快的导航了。
标签
快速转到任意定义处,是文本编辑中一个非常有用的特性。在本章中,您将学习如何使用 Vim 标签来做到这一点。
标签概述
假设有人给了您一个新的代码库:
1 | one = One.new |
One
?donut
?呃,对于当时编写代码的开发者而言,这些代码的含义可能显而易见。问题是当时的开发者已经不在了,现在要由您来理解这些费解的代码。而跟随有One
和 donut
定义的源代码,是帮助您理解的一个有效方法。
您可以使用fzf
或 grep
来搜索它们,但使用标签将更快。
把标签想象成地址簿:
1 | Name Address |
当然,标签可不是存储着“姓名-地址”对,而是“定义-地址”对。
假设您在一个目录中有两个 Ruby 文件:
1 | ## one.rb |
以及
1 | ## two.rb |
在普通模式下,您可以使用Ctrl-]
跳转到定义。在two.rb
中,转到one.donut
所在行,将光标移到donut
处,按下Ctrl-]
。
哦豁,Vim 找不到标签文件,您需要先生成它。
标签生成器
现代 Vim 不自带标签生成器,您需要额外下载它。有几个选项可供选择:
- ctags = 仅用于 C,基本随处可见。
- exuberant ctags = 最流行的标签生成器之一,支持许多语言。
- universal ctags = 和 exuberant ctags 类似,但比它更新。
- etags = 用于 Emacs,嗯……
- JTags = Java
- ptags.py = Python
- ptags = Perl
- gnatxref = Ada
如果您查看 Vim 在线教程,您会发现许多都会推荐 exuberant ctags,它支持 41 种编程语言,我用过它,挺不错的。但自2009年以来一直没有维护,因此 Universal ctags 更好些,它和 exuberant ctags 相似,并仍在维护。
我不打算详细介绍如何安装 Universal ctags,您可以在 universal ctags 仓库了解更多说明。在您安装 universal ctags 后,运行 ctags --version
,它会显示:
1 | Universal Ctags 0.0.0(b43eb39), Copyright (C) 2015 Universal Ctags Team |
请确保您看到了 “Universal Ctags
“。
接下来,生成一个基本的标签文件。运行:
1 | ctags -R . |
R
选项告诉 ctags
从当前位置 (.
) 递归扫描文件。稍后,您应该在当前文件夹看到一个tags
文件,里面您将看到类似这样的内容:
1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ |
根据 Vim 设置和 ctag 生成器的不同,您的tags
文件可能会有些不同。一个标签文件由两部分组成:标签元数据和标签列表。那些标签元数据 (!TAG_FILE...
) 通常由 ctags 生成器控制。这里我不打算介绍它们,您可以随意查阅文档。
现在回到 two.rb
,将光标移至 donut
,再输入Ctrl-]
,Vim 将带您转到 one.rb
文件里def donut
所在的行上。成功啦!但 Vim 怎么做到的呢?
解剖标签文件
来看看donut
标签项:
1 | donut one.rb /^ def donut$/;" f class:One |
上面的标签项由四个部分组成:一个tagname
、一个tagfile
、一个tagaddress
,以及标签选项。
donut
是tagname
。当光标在 “donut” 时,Vim 搜索标签文件里含有 “donut” 字符串的一行。one.rb
是tagfile
。Vim 会搜寻one.rb
文件。/^ def donut$/
是tagaddress
。/.../
是模式指示器。^
代表一行中第一个元素,后面跟着两个空格,然后是def donut
字符串,最后$
代表一行中最后一个元素。f class:One
是标签选项,它告诉 Vim,donut
是一种函数 (f
),并且是One
类的一部分。
再看看另一个标签项:
1 | One one.rb /^class One$/;" c |
这一行也是一样的:
One
是tagname
。注意,对于标签,第一次扫描区分大小写。如果列表中有One
和one
, Vim 会优先考虑One
而不是one
。one.rb
是tagfile
。Vim 会搜寻one.rb
文件。/^class One$/
是tagaddress
。Vim 会查找以class
开头 (^
) 、以One
结尾 ($
) 的行。c
是可用标签选项之一。由于One
是一个 ruby 类而不是过程,因此被标签为c
。
标签文件的内容可能不尽相同,根据您使用的标签生成器而定。但至少,标签文件必须具有以下格式之一:
1 | 1. {tagname} {TAB} {tagfile} {TAB} {tagaddress} |
标签文件
您知道,在运行 ctags -R .
后,一个新 tags
文件会被创建。但是,Vim 是如何知道在哪儿查找标签文件的呢?
如果运行 :set tags?
,您可能会看见 tags=./tags,tags
(根据您的 Vim 设置,内容可能有所不同)。对于 ./tags
,Vim 会在当前文件所在路径查找所有标签;对于 tags
,Vim 会在当前目录(您的项目根路径)中查找。
此外,对于 ./tags
,Vim 会在当前文件所在路径内查找一个标签文件,无论它被嵌套得有多深。接下来,Vim 会在当前目录(项目根路径)查找。Vim 在找到第一个匹配项后会停止搜索。
如果您的 'tags'
文件是 tags=./tags,tags,/user/iggy/mytags/tags
,那么 Vim 在搜索完 ./tags
和 tags
目录后,还会在 /user/iggy/mytags
目录内查找。所以您可以分开存放标签文件,不必将它们置于项目文件夹中。
要添加标签文件位置,只需要运行:
1 | :set tags+=path/to/my/tags/file |
为大型项目生成标签:
如果您尝试在大型项目中运行 ctag,则可能需要很长时间,因为 Vim 也会查看每个嵌套目录。如果您是 Javascript 开发者,您会知道 node_modules
非常大。假设您有五个子项目,每个都包含自己的 node_modules
目录。一旦运行 ctags -R .
,ctags 将尝试扫描这5个 node_modules
。但您可能不需要为 node_modules
运行 ctag。
如果要排除 node_modules
后执行 ctags,可以运行:
1 | ctags -R --exclude=node_modules . |
这次应该只需要不到一秒钟的时间。另外,您还可以多次使用 exclude
选项:
1 | ctags -R --exclude=.git --exclude=vendor --exclude=node_modules --exclude=db --exclude=log . |
标签导航
仅使用 Ctrl-]
也挺好,但我们还可以多学几个技巧。其实,标签跳转键 Ctrl-]
还有命令行模式::tag my-tag
。如果您运行:
1 | :tag donut |
Vim 就会跳转至 donut
方法,就像在 “donut” 字符串上按 Ctrl-]
一样。您还可以使用 <Tab>
来自动补全参数:
1 | :tag d<Tab> |
Vim 会列出所有以 “d” 开头的标签。对于上面的命令,结果则是 “donut”。
在实际项目中,您可能会遇到多个同名的方法。我们来更新下这两个文件。先是 one.rb
:
1 | ## one.rb |
然后 two.rb
:
1 | ## two.rb |
由于新添加了一些过程,因此编写完代码后,不要忘记运行 ctags -R .
。现在,您有了两个 pancake
过程。如果您在 two.rb
内按下 Ctrl-]
,会发生什么呢?
Vim 会跳转到 two.rb
内的 def pancake
,而不是 one.rb
的 def pancake
。这是因为 Vim 认为 two.rb
内部的 pancake
过程比其他的pancake
过程具有更高优先级。
标签优先级
并非所有的标签都有着相同的地位。一些标签有着更高的优先级。如果有重复的标签项,Vim 会检查关键词的优先级。顺序是:
- 当前文件中完全匹配的静态标签。
- 当前文件中完全匹配的全局标签。
- 其他文件中完全匹配的全局标签。
- 其他文件中完全匹配的静态标签。
- 当前文件中不区分大小写匹配的静态标签。
- 当前文件中不区分大小写匹配的全局标签。
- 其他文件中区分大小写匹配的全局标签。
- 当前文件中不区分大小写匹配的静态标签。
根据优先级列表,Vim 会对在同一个文件上找到的精确匹配项进行优先级排序。这就是为什么 Vim 会选择 two.rb
里的 pancake
过程而不是 one.rb
里的。但是,上述优先级列表有些例外,取决于您的'tagcase'
、'ignorecase'
、'smartcase'
设置。我不打算介绍它们,您可以自行查阅 :h tag-priority
。
选择性跳转标签
如果可以选择要跳转到哪个标签,而不是始终转到优先级最高的,那就太好了。因为您可能想跳转到 one.rb
里的 pancake
方法,而不是 two.rb
里的。现在您可以使用 :tselect
做到它!运行:
1 | :tselect pancake |
您可以在屏幕底部看到:
1 | ## pri kind tag file |
如果输入2
后再 <Return>
,Vim 将跳转到 one.rb
里的pancake
过程。如果输入1
后再 <Return>
,Vim 将跳转到 two.rb
里的。
注意pri
列,第一个匹配中该列是F C
,第二个匹配中则是F
。这就是 Vim 用来确定标签优先级的凭据。F C
表示在当前 (C
) 文件中完全匹配 (F
) 的全局标签。F
表示仅完全匹配 (F
) 的全局标签。F C
的优先级永远比 F
高。(译注:是,是)
如果运行:tselect donut
,即使只有一个标签可选,Vim 也会提示您选择跳转到哪一个。有没有什么方法可以让 Vim 仅在有多个匹配项时才提示标签列表,而只找到一个标签时就立即跳转呢?
当然!Vim 有一个 :tjump
方法。运行:
1 | :tjump donut |
Vim 将立即跳转到 one.rb
里的donut
过程,就像在运行 :tag donut
一样。现在试试:
1 | :tjump pancake |
Vim 将提示您从标签选项中选择一个,就像在运行:tselect pancake
。tjump
能两全其美。
tjump
在普通模式下有一个快捷键:g Ctrl-]
。我个人喜欢g Ctrl-]
胜过 Ctrl-]
。
标签的自动补全
标签能有助于自动补全。回想下第6章“插入模式”,您可以使用 Ctrl-x
子模式来进行各式自动补全。其中有一个我没有提到过的自动补全子模式便是 Ctrl-]
。如果您在插入模式中输入Ctrl-x Ctrl-]
,Vim 将使用标签文件来自动补全。
在插入模式下输入Ctrl-x Ctrl-]
,您会看到:
1 | One |
标签堆栈
Vim 维持着一个标签堆栈,上面记录着所有您从哪儿来、跳哪儿去的标签列表。使用 :tags
可以看到这个堆栈。如果您首先跳转到pancake
,紧接着是donut
,此时运行:tags
,您将看到:
1 | # TO tag FROM line in file/text |
注意上面的 >
符号,它代表着您当前在堆栈中的位置。要“弹出”堆栈,从而回到上一次的状态,您可以运行:pop
。试试它,再运行:tags
看看:
1 | # TO tag FROM line in file/text |
注意现在 >
符号位于 donut
所在的第二行了。再 pop
一次,然后运行:tags
:
1 | # TO tag FROM line in file/text |
在普通模式下,您可以按下 Ctrl-t
来达到和 :pop
一样的效果。
自动生成标签
Vim 标签最大的缺点之一是,每当进行重大改变时,您需要重新生成标签文件。如果您将pancake
过程重命名为 waffle
,标签文件不知道 pancake
被重命名了,标签列表仍旧存储着 pancake
过程。运行ctags -R .
可以创建更新的标签文件,但这可能会很缓慢。
幸运的是,有几种可以自动生成标签的方法。这一小节不打算介绍一个简单明了的过程,而是提出一些想法,以便您可以扩展它们。
在保存时生成标签
Vim 有一个自动命令 (autocmd
) 方法,可以在触发事件时执行任意命令。您可以使用这个方法,以便在每次保存时生成标签。运行:
1 | :autocmd BufWritePost *.rb silent !ctags -R . |
上面命令的分解如下:
autocmd
是 Vim 的自动命令方法,它接受一个事件名称、文件和一个命令。BufWritePost
是保存缓冲区时的一个事件。每次保存文件时将触发一次BufWritePost
事件。.rb
是 ruby (rb
) 文件的一种文件模式。silent
是您传递的命令的一部分。如果不输入它,每次触发自动命令时,Vim 都会提示press ENTER or type command to continue
。!ctags -R .
是要执行的命令。回想一下,!cmd
从 Vim 内部执行终端命令。
现在,每次您保存一个 ruby 文件时,Vim 都会运行ctags -R .
。
在 two.rb
中添加一个新过程:
1 | def waffle |
现在保存文件,接着检查一下标签文件,您会在里面看到 waffle
了。成功啦!
使用插件
有几种插件可以自动生成 ctags:
我使用 vim-gutentags。如果您使用了 Vim 插件管理器 (vim-plug, vundle, dein.vim, 等),只需要直接安装就能工作。
Ctags 以及 Git 钩子
Tim Pope 是一个写了很多非常棒的 Vim 插件的作者,他写了一篇博客,建议使用 git 钩子。可以看一看。
聪明地学习标签
只要配置得当,标签是非常有用的。如果您像我一样很容易地忘记事情,标签可以帮助您可视化一个项目。
假设在一个新的代码库中,您想要搞清楚 functionFood
干了什么,您可以通过跳转到它的定义来搞懂它们。在那儿可以看到,它又调用了 functionBreakfast
。继续跟踪,发现还调用了 functionPancake
。现在您明白了,函数调用路径图长这样:
1 | functionFood -> functionBreakfast -> functionPancake |
进一步可以知道,这段代码和早餐吃煎饼有关。
现在您已经知道如何使用标签,通过 :h tags
可以学习更多有关标签的知识。接下来让我们一起来探索另一个功能:折叠。
折叠
在阅读文件时,经常会有一些不相关的文本会妨碍您理解。使用 Vim 折叠可以隐藏这些不必要的信息。
本章中,您将学习如何使用不同的折叠方法。
手动折叠
想象您正在折叠一张纸来覆盖一些文本,实际的文本不会消失,它仍在那儿。Vim 折叠的工作方式与此相同,它_折叠_一段文本,在显示时会隐藏起来,但实际上并不会真的删除它。
折叠操作符是z
。折叠纸张时,它看起来也像字母 “z”。
假设有如下文本:
1 | Fold me |
输入 zfj
。Vim 将这两行折叠成一行,同时会看到类似消息:
1 | +-- 2 lines: Fold me ----- |
上面的命令分解如下:
zf
是折叠操作符。j
是用于折叠操作符的动作。
您可以使用 zo
打开/展开已折叠文本,使用 zc
关闭/收缩文本。
Vim 折叠遵循语法规则。您可以在折叠运算符后,加上一个动作或文本对象。例如,使用 zfap
可以折叠外部段落;使用 zfG
可以折叠至文件末尾;使用 zfa{
可以折叠 {
和 }
之间的文本。
您可以在可视模式下进行折叠。高亮您想要折叠的区域后 (v
, V
, 或 Ctrl-v
),再输入 zf
即可。
一个没有命令行模式版本的 Vim 操作符是不完整的。在命令行模式下,使用 :fold
命令可以执行一次折叠。若要折叠当前行及紧随其后的第二行,可以运行:
1 | :,+1fold |
,+1
是要折叠的范围。如果不传递范围参数,默认当前行。+1
是代表下一行的范围指示器。运行 :5,10fold
可以折叠第5至10行。运行 :,$fold
可以折叠当前行至文件末尾。
还有许多其他折叠和展开的命令。我发现他们实在太多,以至于在刚起步时很难记住。最有用的一些命令是:
zR
展开所有折叠。zM
收缩所有折叠。za
切换折叠状态。
zR
和 zM
可用于任意行上,但 za
仅能用于已折叠/未折叠的行上。输入 :h fold-commands
可查阅更多有关折叠的指令。
不同的折叠方法
以上部分涵盖了 Vim 手动折叠的内容。实际上,Vim 有六种不同的折叠方法:
- 手动折叠
- 缩进折叠
- 表达式折叠
- 语法折叠
- 差异折叠
- 标志折叠
运行 :set foldmethod?
可查看您当前正在使用哪一种折叠方式。默认情况下,Vim 使用手动方式。
在本章的剩余部分,您将学习其他五种折叠方法。让我们从缩进折叠开始。
缩进折叠
要使用缩进折叠,需要将 'foldmethod'
选项更改为缩进:
1 | :set foldmethod=indent |
假设有如下文本:
1 | One |
运行 :set foldmethod=indent
后将看到:
1 | One |
使用缩进折叠后,Vim 将会查看每行的开头有多少空格,并将它与 'shiftwidth'
选项进行比较,以此来决定该行可折叠性。'shiftwidth'
返回每次缩进所需的空格数。如果运行:
1 | :set shiftwidth? |
Vim 的默认 'shiftwidth'
值为2。对于上面的文本而言,”Two” 和 “Two again” 的开头都有两个空格。当 Vim 看到了空格数_且_'shiftwidth'
值为2时,Vim 认为该行的缩进折叠级别为1。
假设这次文本开头只有一个空格:
1 | One |
运行 :set foldmethod=indent
后,Vim 不再折叠已缩进的行了,因为这些行没有足够的空格。然而,当您改变 'shiftwidth'
的值为1后:
1 | :set shiftwidth=1 |
文本现在可以折叠了!现在,我们将 'shiftwidth'
以及文本开头的空格数都重新恢复为2后,另外添加一些内容:
1 | One |
运行折叠命令 (zM
) 后可以看到:
1 | One |
展开已折叠的行 (zR
),接着移动光标至 “Three”,然后切换文本的折叠状态 (za
):
1 | One |
这是啥?叠中叠?
是的,您可以嵌套折叠。文本 “Two” 和 “Two again” 的折叠级别都为1,文本 “Three” 和 “Three again” 的折叠级别都为2。如果在一段可折叠文本中,具有另一段折叠级别更高的可折叠文本,则可以具有多个折叠层。
标志折叠
要使用标志折叠,请运行:
1 | :set foldmethod=marker |
假设有如下文本:
1 | Hello |
输入 zM
后会看到:
1 | hello |
Vim 将 {{{` 和 `}}}
视为折叠指示器,并折叠其中的内容。使用标志折叠时,Vim 会寻找由 'foldmarker'
选项定义的特殊标志,并标记折叠区域。要查看 Vim 使用的标志,请运行:
1 | :set foldmarker? |
默认情况下,Vim 把 {{{` 和 `}}}
作为指示器。如果您想将指示器更改为其他诸如 “foo1” 或 “foo2” 的字符串,可以运行:
1 | :set foldmarker=foo1,foo2 |
假设有如下文本:
1 | hello |
现在,Vim 将使用 foo1
和 foo2
作为新折叠标志。注意,指示器必须是文本字符串,不能是正则表达式。
语法折叠
Vim 有一个能够自定义文本语法(高亮、字体粗细、颜色等)的语法系统。本章不会讨论语法系统的工作原理,但您可以使用它来指示要折叠的文本。要使用语法折叠,请运行:
1 | :set foldmethod=syntax |
假设您有如下文本,并且想折叠方括号里的所有内容:
1 | [ |
您需要设置正确的语法定义,来捕获方括号之间的字符:
1 | :syn region testFold start="\\[" end="\\]" transparent fold |
您应该能看到:
1 | +-- 5 lines: [ ----- |
上面的命令分解如下:
:syn
是语法命令。region
构造一个可以跨越几行的语法区域。查阅:h syntax.txt
可以获得更多信息。start="\\[" end="\\]"
定义区域的起始和结束。您需要转义 (\\
) 方括号,因为它们是特殊字符。transparent
防止高亮。fold
当语法匹配到起始字符和结束字符时,增加折叠级别。
表达式折叠
表达式折叠允许您定义要匹配折叠的表达式。定义折叠表达式后,Vim 会计算每行的 'foldexpr'
值。这是必须配置的变量,它要返回适当的值。如果返回 0,则不折叠行。如果它返回 1,则该行的折叠级别为 1。如果它返回 2,则该线的折叠级别为 2。除了整数外还有其他的值,但我不打算介绍它们。如果你好奇,可以查阅:h fold-expr
。
首先,更改折叠方法:
1 | :set foldmethod=expr |
假设您有一份早餐食品列表,并且想要折叠所有以 “p” 开头的早餐项:
1 | donut |
其次,更改 foldexpr
为捕获以 “p” 开头的表达式:
1 | :set foldexpr=getline(v:lnum)[0]==\\"p\\" |
这表达式看起来有点吓人。我们来分解下:
:set foldexpr
设置'foldexpr'
为自定义表达式。getline()
是 Vim 脚本的一个函数,它返回指定行的内容。如运行:echo getline(5)
可以获取第5行的内容。v:lnum
是 Vim'foldexpr'
表达式的特殊变量。Vim 在扫描每一行时,都会将行号存储至v:lnum
变量。[0]
处于getline(v:lnum)[0]
语境时,代表每一行的第一个字符。Vim 在扫描某一行时,getline(v:lnum)
返回该行的内容,而getline(v:lnum)[0]
则返回这一行的第一个字符。例如,我们早餐食品列表的第一行是 “donut”,则getline(v:lnum)[0]
返回 “d”;列表的第二行是 “pancake”,则getline(v:lnum)[0]
返回 “p”。==\\"s\\"
是等式表达式的后半部分,它检查刚才表达式的计算结果是否等于 “s”。如果是,则返回1,否则返回0。在 Vim 的世界里,1代表真,0代表假。所以,那些以 “s” 开头的行,表达式都会返回1。回想一下本节的开始,如果'foldexpr'
的值为1,则折叠级别为1。
在运行这个表达式后,您将看到:
1 | donut |
差异折叠
Vim 可以对多个文件进行差异比较。
如果您有 file1.txt
:
1 | vim is awesome |
以及 file2.txt
:
1 | vim is awesome |
运行 vimdiff file1.txt file2.txt
:
1 | +-- 3 lines: vim is awesome ----- |
Vim 会自动折叠一些相同的行。运行 vimdiff
命令时,Vim 会自动使用 foldmethod=diff
。此时如果运行 :set foldmethod?
,它将返回 diff
。
持久化折叠
当关闭 Vim 会话后,您将失去所有的折叠信息。假设您有 count.txt
文件:
1 | one |
手动从第三行开始往下折叠 (:3,$fold
):
1 | one |
当您退出 Vim 再重新打开 count.txt
后,这些折叠都不见了!
要在折叠后保留它们,可以运行:
1 | :mkview |
当打开 count.txt
后,运行:
1 | :loadview |
您的折叠信息都被保留下来了。然而,您需要手动运行 mkview
和 loadview
。我知道,终有一日,我会忘记运行 mkview
就关闭文件了,接着便会丢失所有折叠信息。能不能自动实现这个呢?
当然能!要在关闭 .txt
文件时自动运行 mkview
,以及在打开 .txt
文件后自动运行 loadview
,将下列内容添加至您的 vimrc:
1 | autocmd BufWinLeave *.txt mkview |
在上一章您已经见过 autocommand
了,它用于在事件触发时执行一条命令。现在有两个事件可以用于实现操作:
BufWinLeave
从窗口中删除缓冲时。BufWinEnter
在窗口中加载缓冲时。
现在,即使您在 .txt
文件内折叠内容后直接退出 Vim,下次再打开该文件时,您的折叠信息都能自动恢复。
默认情况下,在 Unix 系统的 ~/.vim/view
内运行 mkview
时,Vim 都会保存折叠信息。您可以查阅 :h 'viewdir'
来了解更多信息。
聪明地学习折叠
当我刚开始使用 Vim 时, 我会跳过学习 Vim 折叠,因为我觉得它不太实用。然而,随着我码龄的增长,我越发觉得折叠功能大有用处。得当地使用折叠功能,文本结构可以更加清晰,犹如一本书籍的目录。
当您学习折叠时,请从手动折叠开始,因为它可以随学随用。然后逐渐学习不同的技巧来使用缩进和标志折叠。最后,学习如何使用语法和表达式折叠。您甚至可以使用后两个来编写您自己的 Vim 插件。
现在,您已经知道如何进行折叠了。让我们来学习下一个:使用 git 进行版本控制。
Git
Vim 和 Git 是两种实现不同功能的伟大工具。Vim 用于文本编辑,Git 用于版本控制。在本章中,您将学习如何将 Vim 和 Git 集成在一起。
差异比较
在上一章中,您看到了如何运行 vimdiff
命令以显示多个文件之间的差异。
假设您有两个文件,file1.txt
和 file2.txt
。file1.txt
的内容如下:
1 | pancakes |
file2.txt
的内容如下:
1 | pancakes |
若要查看两个文件之间的差异,请运行:
1 | vimdiff file1.txt file2.txt |
或者也可以运行:
1 | vim -d file1.txt file2.txt |
vimdiff
并排显示两个缓冲区。左边是 file1.txt
,右边是 file2.txt
。不同的两行(apples 和 oranges)会被高亮显示。
假设您要使第二个缓冲区变成 apples,而不是 oranges。若想从 file1.txt
传输您当前位置的内容到 file2.txt
,首先使用 ]c
跳转到下一处差异(使用 [c
可跳回上一处),现在光标应该在 apples 上了。接着运行 :diffput
。此时,这两个文件都是 apples 了。
如果您想从另一个缓冲区(orange juice)传输文本来替代当前缓冲区(apple juice),首先使用 ]c
跳转至下一处差异,此时光标应该在 apple juice 上。接着运行 :diffget
获取另一个缓冲区的 orange juice 来替代当前缓冲区中的 apple juice。
:diffput
将文本从当前缓冲区_输出_到另一个缓冲区。:diffget
从另一个缓冲区_获取_文本到当前缓冲区。如果有多个缓冲区,可以运行 :diffput fileN.txt
和 :diffget fileN.txt
来指定缓冲区 fileN。
使用 Vim 作为合并工具
“我非常喜欢解决合并冲突。” ——佚名
我不知道有谁喜欢解决合并冲突,但总之,合并冲突是无法避免的。在本节中,您将学习如何利用 Vim 作为解决合并冲突的工具。
首先,运行下列命令来将默认合并工具更改为 vimdiff
:
1 | git config merge.tool vimdiff |
或者您也可以直接修改 ~/.gitconfig
(默认情况下,它应该处于根目录中,但您的可能在不同的位置)。配置您的 gitconfig
成如下内容,就可以开始了:
1 | [core] |
让我们创建一个假的合并冲突来测试一下。首先创建一个目录 /food
,并初始化 git 仓库:
1 | git init |
添加 breakfast.txt
文件,内容为:
1 | pancakes |
添加文件并提交它:
1 | git add . |
接着,创建一个新分支 apples:
1 | git checkout -b apples |
更改 breakfast.txt
文件为:
1 | pancakes |
保存文件,添加并提交更改:
1 | git add . |
真棒!现在 master 分支有 oranges,而 apples 分支有 apples。接着回到 master 分支:
1 | git checkout master |
在 breakfast.txt
文件中,您应该能看到原来的文本 oranges。接着将它改成 grapes,因为它是现在的应季水果:
1 | pancakes |
保存、添加、提交:
1 | git add . |
嚯!这么多步骤!现在准备要将 apples 分支合并进 master 分支了:
1 | git merge apples |
您应该会看到如下错误:
1 | Auto-merging breakfast.txt |
没错,一个冲突!现在一起来用一下新配置的 mergetool
来解决冲突吧!运行:
1 | git mergetool |
Vim 显示了四个窗口。注意一下顶部三个:
LOCAL
包含了grapes
。这是“本地”中的变化,也是您要合并的内容。BASE
包含了oranges
。这是LOCAL
和REMOTE
的共同祖先,用于比较它们之间的分歧。REMOTE
包含了apples
。这是要被合并的内容。
底部窗口(也即第四个窗口),您能看到:
1 | pancakes |
第四个窗口包含了合并冲突文本。有了这步设置,就能更轻松看到哪个环境发生了什么变化。您可以同时查看 LOCAL
、BASE
和 REMOTE
的内容。如果您在第四个窗口,将光标移至高亮处,再运行 :diffget LOCAL
,就可以_获取_来自 LOCAL
的改变(grapes
)。同样,运行 :diffget BASE
可以获取来自 BASE
的改变(oranges
),而运行 :diffget REMOTE
可以获取来自 REMOTE
的改变(apples
)。
在这个例子中,我们试着获取来自 LOCAL
的改变。运行 :diffget LO
(LOCAL
的简写),第四个窗口变成了 grapes。完成后,就可以保存并退出所有文件(:qall
)了。还不错吧?
稍加留意您会发现,现在多了一个 breakfast.txt.orig
文件。这是 Git 防止事与愿违而创建的备份文件。如果您不希望 Git 在合并期间创建备份文件,可以运行:
1 | git config --global mergetool.keepBackup false |
在 Vim 中使用 Git
Vim 本身没有集成 Git,但您仍然可以在 Vim 中执行 Git 命令。一种方法是在命令行模式中使用 !
叹号运算符。
使用 !
可以运行任何 Git 命令:
1 | :!git status |
您还可以使用 Vim 的特殊字符 %
(当前缓冲区) 或 #
(其他缓冲区):
1 | :!git add % " git add current file |
插件
如果要在 Vim 中集成 Git,您必须使用插件。以下是 Vim 中较流行的 Git 相关插件列表:
其中最流行的是 vim-fugitive。本章的剩余部分,我将使用此插件来介绍几个 git 工作流。
Vim-Fugitive
vim-fugitive 插件允许您在不离开 Vim 编辑器的情况下运行 git 命令行界面。您会发现,有些命令在 Vim 内部执行时会更好。
开始前,请先使用 Vim 插件管理器(vim-plug、vundle、dein.vim 等)安装 vim-fugitive。
Git Status
当您不带参数地运行 :Git
命令时,vim-fugitive 将显示一个 git 概要窗口,它显示了未跟踪、未暂存和已暂存的文件。在此 “git status
” 模式下,您可以做一些操作:
Ctrl-n
/Ctrl-p
转到下一个 / 上一个文件。-
暂存或取消暂存光标处的文件。s
暂存光标处的文件。u
取消暂存光标处的文件。>
/<
内联显示或隐藏光标处文件的差异变化。
查阅 :h fugitive-staging-maps
可获得更多信息。
Git Blame
在当前文件运行 :Git blame
命令,vim-fugitive 可以显示一个拆分的问责窗口。这有助于追踪那些 BUG 是谁写的,接着就可以冲他/她怒吼(虽然那个人可能是我)。
在 "git blame"
模式下您可以做:
q
关闭问责窗口。A
调整大小至作者列。C
调整大小至提交列。D
调整大小至日期/时间列。
查阅 :h :Git_blame
可获得更多信息。
Gdiffsplit
当您运行 :Gdiffsplit
命令后,vim-fugitive 会根据索引或工作树中的版本,与当前文件的最新更改执行 vimdiff
。如果运行 :Gdiffsplit <commit>
,vim-fugitive 则会根据 <commit>
中的版本来执行 vimdiff
。
由于您处于 vimdiff
模式中,因此您可以使用 :diffput
和 :diffget
来_获取_ 或 _输出_差异。
Gwrite 和 Gread
当您在更改文件后运行 :Gwrite
命令,vim-fugitive 将暂存更改,就像运行 git add <current-file>
一样。
当您在更改文件后运行 :Gread
命令,vim-fugitive 会将文件还原至更改前的状态,就像运行 git checkout <current-file>
一样。使用 :Gread
还有一个好处是操作可撤销。如果在运行 :Gread
后您改变主意,想要保留原来的更改,您只需要撤消(u
),Vim 将撤回 :Gread
操作。要换作是在命令行中运行 git checkout <current-file>
,就完成不了这种操作了。
Gclog
当您运行 :Gclog
命令时,vim-fugitive 将显示提交历史记录,就像运行 git log
命令一样。Vim-fugitive 使用 Vim 的快速修复来完成此任务,因此您可以使用 :cnext
和 :cprevious
来遍历下一个或上一个日志信息。您还可以使用 :copen
和 :cclose
打开或关闭日志列表。
在 "git log"
模式中,您可以做两件事:
- 查看树。
- 访问父级(上一个提交)。
您可以像 git log
命令一样,传递参数给 :Gclog
命令。如果您项目的提交历史记录很长,只想看最后三个提交,则可以运行 :Gclog -3
。如果需要根据提交日期来筛选记录,可以运行类似 :Gclog --after="January 1" --before="March 14"
的命令。
Vim-Fugitive 的更多功能
以上只是寥寥几个 vim-fugitive 功能的例子,您可以查阅 :h fugitive.txt
来了解更多有关 vim-fugitive 的信息。关键是,大多数甚至所有流行的 git 命令可能都有他们的 vim-fugitive 版本,您只需在文档中查找它们。
如果您处于 vim-fugitive 的“特殊模式”(如 :Git
或 :Git blame
模式)中,按下 g?
可以了解当前有哪些可用的快捷键,Vim-fugitive 将为您所处的模式显示相应的 :help
窗口。棒极了!
聪明地学习 Vim 和 Git
每个人都有不同的 git 工作流,可能 vim-fugitive 非常合适您的工作流(也可能不适合)。总之,我强烈建议您试试上面列出的所有插件。可能还有一些其他的我没有列出来,但适合您工作的就是最好的。
更多地了解 git 可以使您与 Vim-git 集成插件工作得更好。Git 本身是一个很庞大的主题,我只向您展示了它其中很小的一部分。好了,接下来谈谈如何使用 Vim 编译您的代码。
编译
编译是许多编程语言的重要主题。在本章中,您将学习如何在 Vim 中编译。此外,您将看到如何利用好 Vim 的 :make
命令。
从命令行编译
您可以使用叹号运算符(!
)进行编译。如果您需要使用 g++
来编译 .cpp
文件,可以运行:
1 | :!g++ hello.cpp -o hello |
但要每次手动指定文件名和输出文件名会非常繁琐和容易出错。而 makefile 是条可行之路。
Makefile
在本节中,我将简要介绍一些 makefile 的基础知识。如果您已经知道如何使用 makefile,可以直接跳转到下一部分。在当前目录中,创建一个 makefile
文件,内容是:
1 | all: |
在终端中运行 make
命令:
1 | make |
您将看到:
1 | echo "Hello all" |
终端输出了 echo 命令本身及其输出。您可以在 makefile 中编写多个“目标”。现在我们多添加几个:
1 | all: |
接着您可以用不同目标运行 make
命令:
1 | make foo |
除了输出之外,make
还输出了实际命令。要停止输出实际命令,可以在命令开头添加 @
:
1 | all: |
现在运行 make
,您将仅看到 “Hello all” 而没有 echo "Hello all"
了。
:make
Vim 有运行 makefile 的 :make
命令。当您运行它时,Vim 会在当前目录查找 makefile 并执行它。
您可以在当前目录创建 makefile
文件并添加如下内容来跟随教程:
1 | all: |
在 Vim 中运行:
1 | :make |
Vim 执行它的方式与从终端运行它的方式相同。:make
命令也接受终端中 make
命令的参数。运行:
1 | :make foo |
如果命令执行异常,:make
命令将使用 Vim 的 quickfix
来存储这些错误。现在试着运行一个不存在的目标:
1 | :make dontexist |
您应该会看到该命令执行错误。运行 quickfix
命令 :copen
可以打开 quickfix
窗口来查看该错误:
1 | || make: *** No rule to make target `dontexist'. Stop. |
使用 make
编译
让我们使用 makefile 来编译一个基本的 .cpp
程序。首先创建一个 hello.cpp
文件:
1 | #include <iostream> |
然后,更新 makefile
来编译和运行 .cpp
文件:
1 | all: |
现在运行:
1 | :make build |
g++
将编译 ./hello.cpp
并且输出 ./hello
。接着运行:
1 | :make run |
您应该会看到终端上打印出了 "Hello!"
。
makeprg
当您运行 :make
时,Vim 实际上会执行 makeprg
选项所设置的任何命令,您可以运行 :set makeprg?
来查看它:
1 | makeprg=make |
:make
的默认命令是外部的 make
命令。若要将 :make
命令更改为:每次运行它则执行 g++ <your-file-name>
,请运行:
1 | :set makeprg=g++\\ % |
\\
用于转义 g++
后的空格(转义本身也需要转义)。Vim 中 %
符号代表当前文件。因此,g++\\ %
命令等于运行 g++ hello.cpp
。
转到 ./hello.cpp
然后运行 :make
,Vim 将编译 hello.cpp
并输出 a.out
(因为您没有指定输出)。让我们重构一下,使用去掉扩展名的原始文件名来命名编译后的输出。运行:
1 | :set makeprg=g++\\ %\\ -o\\ %< |
上面的命令分解如下:
g++\\ %
如上所述,等同于运行g++ <your-file>
。-o
输出选项。%<
在 Vim 中代表了没有扩展名的当前文件名(如hello.cpp
变成hello
)。
当您在 ./hello.cpp
中运行 :make
时,它将编译为 ./hello
。要在 ./hello.cpp
中快速地执行 ./hello
,可以运行 :!./%<
。同样,它等同于运行 :!./<current-file-name-minus-the-extension>
。
查阅 :h :compiler
和 :h write-compiler-plugin
可以了解更多信息。
保存时自动编译
有了自动化编译,您可以让生活更加轻松。回想一下,您可以使用 Vim 的 autocommand
来根据某些事件自动执行操作。例如,要自动在每次保存后编译 .cpp
文件,您可以运行:
1 | :autocmd BufWritePost *.cpp make |
现在您每次保存 .cpp
文件后,Vim 都将自动执行 make
命令。
切换编译器
Vim 有一个 :compiler
命令可以快速切换编译器。您的 Vim 可能附带了一些预构建的编译配置。要检查您拥有哪些编译器,请运行:
1 | :e $VIMRUNTIME/compilers/<tab> |
您应该会看到一个不同编程语言的编译器列表。
若要使用 :compiler
命令,假设您有一个 ruby 文件 hello.rb
,内容是:
1 | puts "Hello ruby" |
回想一下,如果运行 :make
,Vim 将执行赋值给 makeprg
的任何命令(默认是 make
)。如果您运行:
1 | :compiler ruby |
Vim 执行 $VIMRUNTIME/compiler/ruby.vim
脚本,并将 makeprg
更改为使用 ruby
命令。现在如果您运行 :set makeprg?
,它会显示 makeprg=ruby
(这取决于您 $VIMRUNTIME/compiler/ruby.vim
里的内容,或者是否有其他自定义的 ruby 编译器,因此您的结果可能会有不同)。:compiler <your-lang>
命令允许您快速切换至其他编译器。如果您的项目使用多种语言,这会非常有用。
您不必使用 :compiler
或 makeprg
来编译程序。您可以运行测试脚本、分析文件、发送信号或任何您想要的内容。
创建自定义编译器
让我们来创建一个简单的 Typescript 编译器。先在您的设备上安装 Typescript(npm install -g typescript
),安装完后您将有 tsc
命令。如果您之前没有尝试过 typescript,tsc
将 Typescript 文件编译成 Javascript 文件。假设您有一个 hello.ts
文件:
1 | const hello = "hello"; |
运行 tsc hello.ts
后,它将被编译成 hello.js
。然而,如果 hello.ts
变成:
1 | const hello = "hello"; |
这会抛出错误,因为不能更改一个 const
变量。运行 tsc hello.ts
的错误如下:
1 | hello.ts:2:1 - error TS2588: Cannot assign to 'person' because it is a constant. |
要创建一个简单的 Typescript 编译器,请在您的 ~/.vim/
目录中新添加一个 compiler
目录(即 ~/.vim/compiler/
),接着创建 typescript.vim
文件(即 ~/.vim/compiler/typescript.vim
),并添加如下内容:
1 | CompilerSet makeprg=tsc |
第一行设置 makeprg
为运行 tsc
命令。第二行将错误格式设置为显示文件(%f
),后跟冒号(:
)和转义的空格(\
),最后是错误消息(%m
)。查阅 :h errorformat
可了解更多关于错误格式的信息。
您还可以阅读一些预制的编译器,看看它们是如何实现的。输入 :e $VIMRUNTIME/compiler/<some-language>.vim
查看。
有些插件可能会干扰 Typescript 文件,可以使用 --noplugin
标志以零插件的形式打开hello.ts
文件:
1 | vim --noplugin hello.ts |
检查 makeprg
:
1 | :set makeprg? |
它应该会显示默认的 make
程序。要使用新的 Typescript 编译器,请运行:
1 | :compiler typescript |
当您运行 :set makeprg?
时,它应该会显示 tsc
了。我们来测试一下:
1 | :make % |
回想一下,%
代表当前文件。看看您的 Typescript 编译器是否如预期一样工作。运行 :copen
可以查看错误列表。
异步编译器
有时编译可能需要很长时间。在等待编译时,您不会想眼睁睁盯着已冻结的 Vim 的。如果可以异步编译,就可以在编译期间继续使用 Vim 了,岂不美哉?
幸运的是,有插件来运行异步进程。有两个比较好的是:
在这一章中,我将介绍 vim-dispatch,但我强烈建议您尝试上述列表中所有插件。
Vim 和 NeoVim 实际上都支持异步作业,但它们超出了本章的范围。如果您好奇,可以查阅 。
插件:Vim-dispatch
Vim-dispatch 有几个命令,最主要的两个是 :Make
和 :Dispatch
。
:Make
Vim-dispatch 的 :Make
命令与 Vim 的 :make
相似,但它以异步方式运行。如果您正处于 Javascript 项目中,并且需要运行 npm t
,可以将 makeprg
设置为:
1 | :set makeprg=npm\\ t |
如果运行:
1 | :make |
Vim 将执行 npm t
。但同时,您只能盯着冻结了的屏幕。有了 vim-dispatch,您只需要运行:
1 | :Make |
Vim 将启用后台进程异步运行 npm t
,同时您还能在 Vim 中继续编辑您的文本。棒极了!
:Dispatch
:Dispatch
命令的工作方式和 :compiler
及 :!
类似。
假设您在 ruby spec 文件中,需要执行测试,可以运行:
1 | :Dispatch rspec % |
Vim 将对当前文件异步运行 rspec
命令。
自动调度
Vim-dispatch 有 b:dispatch
缓冲区变量,您可以配置它来执行特定命令,并利用上 autocmd
。如果在您的 vimrc 中添加如下内容:
1 | autocmd BufEnter *_spec.rb let b:dispatch = 'bundle exec rspec %' |
现在每当您进入一个以 _spec.rb
结尾的文件(BufEnter
),:Dispatch
将被自动运行以执行 bundle exec rspec <your-current-ruby-spec-file>
。
聪明地学习编译
在本章中,您了解到可以使用 make
和 compiler
命令从Vim内部异步运行_任何_进程,以完善您的编程工作流。Vim 拥有通过其他程序来扩展自身的能力,这使其变得强大。
视图、会话和 Viminfo
当您做了一段时间的项目后,您可能会发现这个项目逐渐形了成自己的设置、折叠、缓冲区、布局等,就像住了一段时间公寓后精心装饰了它一样。问题是,关闭 Vim 后,所有的这些更改都会丢失。如果能保留这些更改,等到下次打开 Vim 时,一切恢复如初,岂不美哉?
本章中,您将学习如何使用 视图、会话 和 Viminfo 来保存项目的“快照”。
视图
视图是这三个部分(视图、会话、Viminfo)中的最小子集,它是一个窗口的设置的集合。如果您长时间在一个窗口上工作,并且想要保留其映射和折叠,您可以使用视图。
我们来创建一个 foo.txt
文件:
1 | foo1 |
在这个文件中,做三次修改:
- 在第 1 行,创建一个自定义折叠
zf4j
(折叠接下来 4 行)。 - 更改
number
设置:setlocal nonumber norelativenumber
。这会移除窗口左侧的数字指示器。 - 创建本地映射,每当按一次
j
时,向下两行::nnoremap <buffer> j jj
。
您的文件看起来应该像:
1 | +-- 5 lines: foo1 ----- |
配置视图属性
运行:
1 | :set viewoptions? |
默认情况下会显示(根据您的 vimrc 可能会有所不同):
1 | viewoptions=folds,cursor,curdir |
我们来配置 viewoptions
。要保留的三个属性分别是折叠、映射和本地设置选项。如果您的设置和我的相似,那么您已经有了 folds
选项。运行下列命令使视图记住 localoptions
:
1 | :set viewoptions+=localoptions |
查阅 :h viewoptions
可了解 viewoptions
的其他可用选项。现在运行 :set viewoptions?
,您将看到:
1 | viewoptions=folds,cursor,curdir,localoptions |
保存视图
在 foo.txt
窗口经过适当折叠并设置了 nonumber norelativenumber
选项后,现在我们来保存视图。运行:
1 | :mkview |
Vim 创建了一个视图文件。
视图文件
您可能会想“Vim 将这个视图文件保存到哪儿了呢?”,运行下列命令就可以看到答案了:
1 | :set viewdir? |
默认情况下会显示 ~/.vim/view
(根据您的操作系统,可能会有不同的路径。查阅 :h viewdir
获得更多信息)。在您的 vimrc 中添加下列内容,可以更改为不同路径:
1 | set viewdir=$HOME/else/where |
加载视图文件
关闭并重新打开 foo.txt
,您会看到原来的文本,没有任何改变。这是预期行为。运行下列命令可以加载视图文件:
1 | :loadview |
现在您将看到:
1 | +-- 5 lines: foo1 ----- |
那些折叠、本地设置以及映射都恢复了。如果您细心还可以发现,光标位于上一次您运行 :mkview
时所处的行上。只要您有 cursor
选项,视图将记住光标位置。
多个视图
Vim 允许您保存 9 个编号的视图(1-9)。
假设您想用 :9,10 fold
来额外折叠最后两行,我们把这存为视图 1。运行:
1 | :mkview 1 |
如果您又想用 :6,7 fold
再折叠一次,并存为不同的视图,运行:
1 | :mkview 2 |
关闭并重新打开 foo.txt
文件,运行下列命令可以加载视图 1:
1 | :loadview 1 |
要加载视图 2,运行:
1 | :loadview 2 |
要加载原始视图,运行:
1 | :loadview |
自动创建视图
有一件可能会发生的很倒霉的事情是,您花了很长时间在一个大文件中进行折叠,一不小心关闭了窗口,接着丢失了所有折叠信息。您可以在 vimrc 中添加下列内容,使得在关闭缓冲区后 Vim 能自动创建视图,防止此类灾难发生:
1 | autocmd BufWinLeave *.txt mkview |
另外也能在打开缓冲区后自动加载视图:
1 | autocmd BufWinEnter *.txt silent loadview |
现在,当您编辑 txt
文件时,不用再担心创建和加载视图了。但也注意,随着时间的推移,视图文件会不断积累,记得每隔几个月清理一次。
会话
如果说视图保存了某个窗口的设置,那么会话则保存了所有窗口(包括布局)的信息。
创建新会话
假设您在 foobarbaz
工程中编辑着 3 个文件:
foo.txt
的内容:
1 | foo1 |
bar.txt
的内容:
1 | bar1 |
baz.txt
的内容:
1 | baz1 |
假设您的窗口布局如下所示(适当地使用 split
和 vsplit
来放置):
要保留这个外观,您需要保存会话。运行:
1 | :mksession |
与默认存储在 ~/.vim/view
的 mkview
不同,mksession
在当前目录存储会话文件(Session.vim
)。如果好奇,您可以看看文件。
如果您想将会话文件另存他处,可以将参数传递给 mksession
:
1 | :mksession ~/some/where/else.vim |
使用 !
来调用命令可以覆盖一个已存在的会话文件(:mksession! ~/some/where/else.vim
)。
加载会话
运行下列命令可以加载会话:
1 | :source Session.vim |
现在 Vim 看起来就像您离开它时的样子!或者,您也可以从终端加载会话文件:
1 | vim -S Session.vim |
配置会话属性
您可以配置会话要保存的属性。若要查看当前哪些属性正被保存,请运行:
1 | :set sessionoptions? |
我的显示:
1 | blank,buffers,curdir,folds,help,tabpages,winsize,terminal |
如果在保存会话时不想存储 terminal
,可以运行下列命令将其从会话选项中删除:
1 | :set sessionoptions-=terminal |
如果要在保存会话时存储 options
,请运行:
1 | :set sessionoptions+=options |
下面是一些 sessionoptions
可以存储的属性:
blank
存储空窗口buffers
存储缓冲区folds
存储折叠globals
存储全局变量(必须以大写字母开头,并且至少包含一个小写字母)options
存储选项和映射resize
存储窗口行列winpos
存储窗口位置winsize
存储窗口大小tabpages
存储选项卡unix
以 Unix 格式存储文件
查阅 :h 'sessionoptions'
来获取完整列表。
会话是保存项目外部属性的好工具。但是,一些内部属性不存储在会话中,如本地标记、寄存器、历史记录等。要保存它们,您需要使用 Viminfo!
Viminfo
如果您留意,在复制一个单词进寄存器 a,再退出并重新打开 Vim 后,您仍然可以看到存储在寄存器中的文本。这就是 Viminfo 的功劳。没有它,在您关闭 Vim 后,Vim 会忘记这些寄存器。
如果您使用 Vim 8 或更高版本,Vim 会默认启用 Viminfo。因此您可能一直在使用 Viminfo,而您对它毫不知情!
您可能会问:Viminfo 存储了什么?与会话有何不同?
要使用 Viminfo,您必须启用了 +viminfo
特性(:version
)。Viminfo 存储着:
- 命令行历史记录。
- 字符串搜索历史记录。
- 输入行历史记录。
- 非空寄存器的内容。
- 多个文件的标记。
- 文件标记,它指向文件中的位置。
- 上次搜索 / 替换模式(用于 “n” 和 “&”)。
- 缓冲区列表。
- 全局变量。
通常,会话存储“外部”属性,Viminfo 存储“内部”属性。
每个项目可以有一个会话文件,而 Viminfo 与会话不同,通常每台计算机只使用一个 Viminfo。Viminfo 是项目无关的。
对于 Unix,Viminfo 的默认位置是 $HOME/.viminfo
(~/.viminfo
)。根据您的操作系统,Viminfo 位置可能会有所不同。可以查阅 :h viminfo-file-name
。每一次您做出的“内部”更改,如将文本复制进一个寄存器,Vim 都会自动更新 Viminfo 文件。
请确保您设置了 选项(),否则您的 Viminfo 将不起作用。
读写 Viminfo
尽管只使用一个 Viminfo 文件,但您还是可以创建多个 Viminfo 文件。使用 :wviminfo
命令(缩写为 :wv
)来创建多个 Viminfo 文件。
1 | :wv ~/.viminfo_extra |
要覆盖现有的 Viminfo 文件,向 wv
命令多添加一个叹号:
1 | :wv! ~/.viminfo_extra |
Vim 默认情况下会读取 ~/.viminfo
文件。运行 :rviminfo
(缩写为 :rv
)可以读取不同的 Vimfile 文件:
1 | :rv ~/.viminfo_extra |
要在终端使用不同的 Viminfo 文件来启动 Vim,请使用 “i” 标志:
1 | vim -i viminfo_extra |
如果您要将 Vim 用于不同的任务,比如写代码和写作,您可以创建两个 Viminfo,一个针对写作优化,另一个为写代码优化。
1 | vim -i viminfo_writing |
不使用 Viminfo 启动 Vim
要不使用 Viminfo 启动 Vim,可以在终端运行:
1 | vim -i NONE |
要永不使用 Viminfo,可以在您的 vimrc 文件添加:
1 | set viminfo="NONE" |
配置 Viminfo 属性
和 viewoptions
以及 sessionoptions
类似,您可以用 viminfo
选项指定要存储的属性。请运行:
1 | :set viminfo? |
您会得到:
1 | !,'100,<50,s10,h |
看起来有点晦涩难懂。命令分解如下:
!
保存以大写字母开头、却不包含小写字母的全局变量。回想一下g:
代表了一个全局变量。例如,假设您写了赋值语句let g:FOO = "foo"
,Viminfo 将存储全局变量FOO
。然而如果您写了let g:Foo = "foo"
,Viminfo 将不存储它,因为它包含了小写字母。没有!
,Vim 不会存储这些全局变量。'100
代表标记。在这个例子中,Viminfo 将保存最近 100 个文件的本地标记(a-z)。注意,如果存储的文件过多,Vim 会变得很慢,1000 左右就可以了。<50
告诉 Viminfo 每个寄存器最多保存多少行(这个例子中是 50 行)。如果我复制 100 行文本进寄存器 a("ay99j
)后关闭 Vim,下次打开 Vim 并从寄存器 a("ap
)粘贴时,Vim 最多只粘贴 50 行;如果不指定最大行号,_所有_行都将被保存;如果指定 0,什么都不保存了。s10
为寄存器设置大小限制(kb)。在这个例子中,任何大于 10kb 的寄存器都会被排除。h
禁用高亮显示(hlsearch
时)。
可以查阅 :h 'viminfo'
来了解其他更多选项。
聪明地使用视图、会话和 Viminfo
Vim 能使用视图、会话和 Viminfo 来保存不同级别的 Vim 环境快照。对于微型项目,可以使用视图;对于大型项目,可以使用会话。您应该花些时间来查阅视图、会话和 Viminfo 提供的所有选项。
为您的编辑风格创建属于您自己的视图、会话和 Viminfo。如果您要换台计算机使用 Vim,只需加载您的设置,立刻就会感到宾至如归!
许可和版权
这些材料全部归 ©2020 Igor Irianto 所有。
这项作品已获得<<知识共享署名-非商业性-相同方式共享 4.0 版>>的许可。