跳到主要内容

第04章 Vim 语法

刚接触Vim时很容易被Vim许多复杂的命令吓到,如果你看到一个Vim的用户使用gUfV1GdG,你可能不能立刻想到这些命令是在做什么。这一章中,我将把Vim命令的结构拆分成一个简单的语法规则进行讲解。

这一章将是本书中最重要的一章,一旦你理解了Vim命令的语法结构,你将能够和Vim"说话"。注意,在这一章中当我讨论Vim语言时,我讨论并不是 Vimscript(Vim自带的插件编写和自定义设置的语言),这里我讨论的是Vim中normal模式的下的命令的通用规则。

如何学习一门语言

我并不是一个英语为母语的人,当我13岁移民到美国时我学习的英语,我会通过做三件事情建立我的语言能力:

  1. 学习语法规则
  2. 扩展我的词汇量
  3. 练习,练习,练习

同样的,为了说好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允许你通过按两次 操作符使它执行行级的操作,例如ddyycc来执行删除,复制或修改整个行。您可以使用其他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插件允许你创建自己的文本对象,同时包含有一系列定义好的文本对象

另外,如果你不知道我刚才使用的columnawk命令也没有关系,重要的是Vim可以和终端命令很好的结合起来。

聪明地学习语法

你刚刚学完Vim唯一的语法规则:

verb + noun

我学Vim中最大的"AHA moment"之一是当我刚学完大写命令(gU)时,想要把一个单词变成大写,我本能的运行了gUiW,它居然成功了,我光标所在的单词都大写了。我正是从那是开始理解Vim的。我希望你也会在不久之后有你自己的"AHA moment",如果之前没有的话。

这一章的目标是向你展现Vim中的verb+noun模式,因此之后你就可以像学习一门新的语言一样渐进的学习Vim而不是死记每个命令的组合。

学习这种模式并且理解其中的含义,这是聪明的学习方式。