第04章 Vim 语法
刚接触Vim时很容易被Vim许多复杂的命令吓到,如果你看到一个Vim的用户使用gUfV
或1GdG
,你可能不能立刻想到这些命令是在做什么。这一章中,我将把Vim命令的结构拆分成一个简单的语法规则进行讲解。
这一章将是本书中最重要的一章,一旦你理解了Vim命令的语法结构,你将能够和Vim"说话"。注意,在这一章中当我讨论Vim语言时,我讨论并不是 Vimscript(Vim自带的插件编写和自定义设置的语言),这里我讨论的是Vim中normal模式的下的命令的通用规则。
如何学习一门语言
我并不是一个英语为母语的人,当我13岁移民到美国时我学习的英语,我会通过做三件事情建立我的语言能力:
- 学习语法规则
- 扩展我的词汇量
- 练习,练习,练习
同样的,为了说好Vim语言,你需要学习语法规则,增加词汇量,并且不断练习直到你可以把执行命令变成肌肉记忆。
语法规则
你只需要知道一个Vim语言的语法规则:
verb + noun # 动词 + 名词
这就类似与在英语中的祈使句:
- "Eat(verb) a donut(noun)"
- "Kick(verb) a ball(noun)"
- "Learn(verb) the Vim Editor(noun)"
现在你需要的就是用Vim中基本的动词和名字来建立你的词汇表
名词(动作 Motion)
我们这里将 动作 Motion 作为名词, 动作Motion用来在Vim中到处移动。下面列出了一些常见的动作的例子:
h 左
j 下
k 上
l 右
w 向前移动到下一个单词的开头
} 跳转到下一个段落
$ 跳转到当前行的末尾
在之后的章节你将学习更多的关于动作的内容,所以如果你不理解上面这些动作也不必担心。
动词(操作符 Operator)
根据:h operator
,Vim共有16个操作符,然而根据我的经验,学习这3个操作符在80%的情况下就已经够用了
y yank(复制)
d delete(删除)
c change 删除文本,将删除的文本存到寄存器中,进入插入模式
顺带说一句,当你yank一段文本后,您可以使用p
将它粘贴到光标后,或使用P
粘贴到光标前。
动词(操作符 Operator)和名词(动作 motions)的结合
现在你已经知道了基本的动词和名词,我们来用一下我们的语法规则,动词和名词的结合!假设你有下面这段文本:
const learn = "Vim";
- 复制当前位置到行尾的所有内容:
y$
- 删除当前位置到下一个单词的开头:
dw
- 修改当前位置到这个段落的结尾:
c}
动作 motions也接受数字作为参数(这个部分我将在下个章节展开),如果你需要向上移动3行,你可以用3k
代替按3次k
,数字可应用在Vim语法中。
- 向左拷贝2个字符:
y2h
- 删除后两个单词:
d2w
- 修改后面两行:
c2j
目前,你也许需要想很久才能完成一个简单的命令,不过我刚开始时也是这样,我也经历过类似的挣扎的阶段但是不久我的速度就快了起来,你也一样。唯一途径就是重复、重复再重复。
作为补充,行级的 操作符 operations (作用在整行中的操作符)在文本编辑中和其他的 操作符 一样,Vim允许你通过按两次 操作符使它执行行级的操作,例如dd
,yy
,cc
来执行删除,复制或修改整个行。您可以使用其他operations试一下(比如gUgU
)。
666!从这可以看出Vim命令的一种执行模式。但是到目前为止还没有结束,Vim有另一种类型的名词:文本对象(text object)
更多名词(文本对象 Text Objects)
想象一下你现在正在某个被括号包围的文本中例如(hello Vim)
,你现在想要删掉括号中的所有内容,你会怎样快速的完成它?是否有一种方法能够把括号中内容作为整体删除呢?
答案是有的。文本通常是结构化的,特别是代码中,文本经常被放置在小括号、中括号、大括号、引号等当中。Vim提供了一种处理这种结构的文本对象的方法。
文本对象可以被 操作符 operations 使用,这里有两类文本对象:
i + object 内部文本对象
a + object 外部文本对象
内部文本对象选中的部分不包含包围文本对象的空白或括号等,外部文本对象则包括了包围内容的空白或括号等对象。外部对象总是比内部对象选中的内容更多。如果你的光标位于一对括号内部,例如(hello Vim)
中:
- 删除括号内部的内容但保留括号:
di(
- 删除括号以及内部的内容:
da(
让我们看一些别的例子,假设你有这样一段Javascript的函数,你的光标停留在"Hello"中的"H"上:
const hello = function() {
console.log("Hello Vim");
return true;
}
- 删除整个"Hello Vim":
di(
- 删除整个函数(被{}包含):
di{
- 删除"Hello"这个词:
diw
文本对象很强大因为你可以在同一个位置指向不同的内容,可以删除一对小括号中的文本,也可以是当前大括号中的函数体,也可以是当前单词。这一点也很好记忆,当你看到di(
,di{
和diw
时,你也可以很好的意识到他们表示的是什么:小括号,大括号,单词。
让我们来看最后一个例子。假设你有这样一些html的标签的文本:
<div>
<h1>Header1</h1>
<p>Paragraph1</p>
<p>Paragraph2</p>
</div>
如果你的光标位于"Header1"文本上:
- 删除"Header1":
dit
- 删除
<h1>Header1</h1>
:dat
如果你的光标在"div"文本上:
- 删除
h1
和所有p
标签的行:dit
- 删除所有文本:
dat
- 删除"div":
di<
下面列举的一些通常见到的文本对象:
w 一个单词
p 一个段落
s 一个句子
(或) 一对()
{或} 一对{}
[或] 一对[]
<或> 一对<>
t XML标签
" 一对""
' 一对''
` 一对``
你可以通过:h text-objects
了解更多
结合性和语法
在学习Vim的语法之后,让我们来讨论一下Vim中的结合性以及为什么在文本编辑器中这是一个强大的功能。
结合性意味着你有很多可以组合起来完成更复杂命令的普通命令,就像你在编程中可以通过一些简单的抽象建立更复杂的抽象,在Vim中你可以通过简单的命令的组合执行更复杂的命令。Vim语法正是Vim中命令的可结合性的体现。
Vim的结合性最强大之处体现在它和外部程序结合时,Vim有一个 过滤操作符!
可以用外部程序过滤我们的文本。假设你有下面这段混乱的文本并且你想把它用tab格式化的更好看的一些:
Id|Name|Cuteness
01|Puppy|Very
02|Kitten|Ok
03|Bunny|Ok
这件事情通过Vim命令不太容易完成,但是你可以通过终端提供的命令column
很快的完成它,当你的光标位于"Id"上时,运行!}column -t -s "|"
,你的文本就变得整齐了许多:
Id Name Cuteness
01 Puppy Very
02 Kitten Ok
03 Bunny Ok
让我们分解一下上面那条命令,动词是!
(过滤操作符),名词是}
(到下一个段落)。过滤操作符!
接受终端命令作为另一个参数,因此我把column -t -s "|"
传给它。我不想详细描述column
是如何工作的,但是总之它格式化了文本。
假设你不止想格式化你的文本,还想只展示Ok
结尾的行,你知道awk
命令可以做这件事情,那么你可以这样做:
!}column -t -s "|" | awk 'NR > 1 && /Ok/{print $0}'
结果如下:
02 Kitten Ok
03 Bunny Ok
666!管道竟然在Vim中也能起作用。
这就是Vim的结合性的强大之处。你知道的动词 操作符,名词 动作,终端命令越多,你组建复杂操作的能力成倍增长。
换句话说,假设你只知道四个动作:w, $, }, G
和删除操作符(d
),你可以做8件事:按四种方式移动(w, $, }, G
)和删除4种文本对象(dw, d$, d}, dG
)。如果有一天你学习了小写变大写的操作符(gU
),你的Vim工具箱中多的不是1种工具,而是4种:gUw, gU$, gU}, gUG
。现在你的Vim工具箱中就有12种工具了。如果你知道10个动作和5个操作符,那么你就有60种工具(50个操作+10个移动)。另外,行号动作(nG
)给你了n
种动作,其中n
是你文件中的行数(例如前往第5行,5G
)。搜索动作(/
)实际上给你带来无限数量的动作因为你可以搜索任何内容。你知道多少终端命令,外部命令操作符(!
)就给你了多少种过滤工具。使用Vim这种能够组合的工具,所有你知道的东西都可以被串起来完成更复杂的操作。你知道的越多,你就越强大。
这种具有结合性的行为也正符合Unix的哲学:一个命令做好一件事。动作只需要做一件事:前往X。操作符只需要做一件事:完成Y。通过结合一个操作符和一个动作,你就获得了YX:在X上完成Y。
甚至,动作和操作符都是可拓展的,你可以自己创造动作和操作符去丰富你的Vim工具箱,Vim-textobj-user
插件允许你创建自己的文本对象,同时包含有一系列定义好的文本对象。
另外,如果你不知道我刚才使用的column
和awk
命令也没有关系,重要的是Vim可以和终端命令很好的结合起来。
聪明地学习语法
你刚刚学完Vim唯一的语法规则:
verb + noun
我学Vim中最大的"AHA moment"之一是当我刚学完大写命令(gU
)时,想要把一个单词变成大写,我本能的运行了gUiW
,它居然成功了,我光标所在的单词都大写了。我正是从那是开始理解Vim的。我希望你也会在不久之后有你自己的"AHA moment",如果之前没有的话。
这一章的目标是向你展现Vim中的verb+noun
模式,因此之后你就可以像学习一门新的语言一样渐进的学习Vim而不是死记每个命令的组合。
学习这种模式并且理解其中的含义,这是聪明的学习方式。