第1章 Vim解决问题的方式
本质上讲,我们的工作是重复性的。不论是在几个不同的地方做相同的小改动,还是在文档的相似结构间移动,我们都会重复很多操作。凡是可以简化重复性操作的方式,都会成倍地节省我们的时间。
Vim对重复性操作进行了优化。它之所以能高效地重复,是因为它会记录我们最近的操作,让我们用一次按键就能重复上次的修改。这听起来很强大,但是除非我们能够学会规划按键动作,使得在重复时能完成一项有用的工作,否则这没什么用。掌握这一理念是高效使用Vim的关键。
我们将以 . 命令作为开始。这个看似简单的命令是 Vim 中的瑞士军刀,掌握它的用法是精通Vim的第一步。我们将运行一些可由 . 命令快速完成的简单编辑任务,虽然每个任务彼此之间截然不同,但解决的方法却大同小异。我们将找到一种理想的编辑模式,即用一次按键移动,用另一次按键执行。
技巧1 结识.命令
. 命令可以让我们重复上次的修改,它是Vim中最为强大的多面手。
Vim文档只是简单地提到.命令会“重复上次修改”(参见:h .),这听起来没什么特别,但在这个简单的说明里,我们会发现让Vim区分模式的编辑模型如此高效的核心原因。首先我们要问:“究竟什么是修改?”
要理解 . 命令的强大,我们需要意识到这一点:“上次修改”可以指很多东西,一次修改的单位可以是字符、整行,甚至是整个文件。
我们将使用下面这段文本进行说明:
the_vim_way/0_mechanics.txt
Line one
Line two
Line three
Line four
x 命令会删除光标下的字符,在这种情况下使用 . 命令“重复上次修改”时,就会让Vim删除光标下的字符:
我们可以输入几次 u 撤销上述修改,使文档恢复到初始状态。
dd 命令也做删除操作,但它会把整行一起删掉。如果在 dd后使用 . 命令,那么“重复上次修改”会让Vim删除当前行:
最后,>G 命令会增加从当前行到文档末尾处的缩进层级。如果我们在此命令后使用 . 命令,那么“重复上次修改”会让 Vim 增加从当前行到文档末尾的缩进层级。在下例中,我们让光标从第二行开始,以便一目了然地看出差别。
x、dd 以及 > 命令都是在普通模式中执行的命令,不过,每次我们进入插入模式时,也会形成一次修改。从进入插入模式的那一刻起(例如,输入 i),直到返回普通模式时为止(输入<Esc>),Vim会记录每一个按键操作。做出这样一个修改后再用 . 命令的话,它将会重新执行所有这些按键操作(参见技巧8中的“在插入模式中移动光标会重置修改状态”部分中的补充说明)。
. 命令是一个微型的宏
在后面的第11 章“宏”中,我们将看到Vim可以录制任意数目的按键操作,然后在以后重复执行它们。这让我们可以把最常重复的工作流程录制下来,并用一个按键重放它们。我们可以把 . 命令当成一个很小的宏(macro)。
我们将在本章看到一些关于 . 命令的应用,另外我们还将在技巧9 及技巧23 中学到 . 命令的一些最佳应用技巧。
技巧2 不要自我重复
对于在行尾添加内容这样的常见操作,如添加分号,Vim提供了一个专门的命令,可以把两步操作合并为一步。
假设有如下的JavaScript程序片段:
the_vim_way/2_foo_bar.js
var foo = 1
var bar = 'a'
var foobar = foo + bar
我们想在每行的结尾添加一个分号。要实现这一点,先得把光标移到行尾,然后切换到插入模式进行修改。$ 命令可以完成移动动作,接着就可以执行 a;<Esc> 完成修改了。
要完成全部修改,我们也可以对下面两行做完全相同的操作,不过那样做会错过这里将要提到的小窍门。由于 . 命令可以重复上次的修改,因此我们不必重复之前的操作,而是执行两次 j$.。一个键(.)顶 3 个(a;<Esc>),虽然每次省的并不多,不过在重复操作时,累积效应可不小。
不过让我们再仔细审视一下这个操作模式:j$.。j 命令使光标下移一行,而 $ 命令把光标移到行尾。我们用了两下按键,仅仅是为了把光标移到指定位置,以便可以用 . 命令。你觉得还有改进的余地吗?
减少无关的移动
a 命令在当前光标之后添加内容,而 A 命令则在当前行的结尾添加内容。不管光标当前处于什么位置,输入 A 都会进入插入模式,并把光标移到行尾。换句话说,它把 $a 封装成了一个按键操作。在本技巧后的一箭双雕部分中,我们将会看到Vim提供了不少这样的复合命令。
下面是对之前的例子的改进:
我们通过用 A 来代替 $a,大大提升了 . 命令的效率。我们不必再把光标移到行尾,只需保证它位于该行内就行了(可在任意位置)。现在我们可以重复执行足够多次的 j. ,完成对后续行的修改。
一键移动,另一键操作,真是太完美了!请留意这种应用模式,因为我们即将在更多的例子中看到它的身影。
虽然这一模式对这个简短的例子来说很好用,但它不是万能的。试想一下,如果我们不得不给连续50 行添加分号,即便每个修改输一次 j. ,看起来也是一项很繁重的工作。跳到技巧30可以看到另外一种解决方法。
一箭双雕
我们可以这样说,A 命令把两个动作($a)合并成了一次按键。不过它不是唯一一个这样的命令,很多Vim的单键命令都可以被看成两个或多个其他命令的组合。下表列出了类似的一些例子,你能找出它们之间别的共同点吗?
如果你发觉自己正在输入 ko(或更糟糕,在用 k$a<CR>),马上打住!想想你在干什么,然后你就会意识到可以把它换成O 命令。
你找出这些命令别的共同点了吗?它们全都会从普通模式切换到插入模式。仔细想想这一点,并想想这对 . 命令可能产生怎样的影响。
技巧3 以退为进
我们可以用一种常用的Vim操作习惯在一个字符前后各添加一个空格。乍一看,这种方法有点古怪,不过其好处是可重复,这将使我们可以事半功倍地完成工作。
假设有一行代码看起来是这样的:
the_vim_way/3_concat.js
var foo = "method("+argument1+","+argument2+")";
在 JavaScript 里把字符串连接到一起从来都不美观,但我们可以像下面这样在 +号前后各添加一个空格,让肉眼更容易识别:
var foo = "method(" + argument1 + "," + argument2 + ")";
使修改可重复
下面的惯用方法可以解决这个问题:
s 命令把两个操作合并为一个:它先删除光标下的字符,然后进入插入模式。在删除 + 号后,我们先输入+,然后退出插入模式。
先后退一步,然后前进三步,这是个奇怪的小花招,看起来可能不够直接。但这样做最大的好处是:我们可以用 . 命令重复这一修改。我们所要做的只是把光标移到下一个 + 号处,然后用 . 命令重复这一操作即可。
使移动可重复
本例中还有另外一个小窍门。f{char} 命令让Vim查找下一处指定字符出现的位置,如果找到了,就直接把光标移到那里(参见:h f)。因此,当我们输入f+时,光标会直接移到下一个 + 号所在的位置。我们将会在技巧49里学到更多关于 f{char}命令的知识。
完成第一处修改后,我们可以重复按 f+ 命令跳到下一个 + 号所在的位置。不过,还有一种更好的方法可以用。; 命令会重复查找上次 f 命令所查找的字符,因此我们用不着输入4次 f+,而是只用输入一次,后面跟着再用3次 ; 命令。
合而为一
; 命令带我们到下一个目标字符上,而 . 命令则重复上次的修改。因此,我们可以连续输入3 次 ;. 来完成全部修改。看起来是不是很熟悉?
与其和Vim区分模式的编辑模型做斗争,倒不如与它一起协同工作。然后,你就会发现它能把特定任务变得多么的容易。
技巧4 执行、重复、回退
在面对重复性工作时,我们需要让移动动作和修改都能够重复,这样就可以达到一个最佳编辑模式。Vim 对此的支持是:它会记住我们的操作,并使最常用的操作触手可及,所以我们可以很方便地重复执行它们。本节将介绍 Vim 可以重复执行的每个操作,并学习如何回退这些命令。
我们已经看到 . 命令会重复上次修改。由于很多操作都被当成一次修改,因此 . 命令已经证明了它的神通广大。但有些命令能以其他的方式重复。例如,@: 可以用来重复任意Ex命令(在技巧31 中讨论),或者我们也可以输入 & (参见技巧92)来重复上次的:substitute命令(它本身也是一条E x命令)。
如果我们知道如何重复之前的操作,而无需每次都输入整条命令,那么我们就会获得更高的效率。我们可以先执行一次,随后只需重复即可。
然而,这么少的按键就可以完成这么多的事情,这也可能会带来麻烦。我们需要很小心地操作才行,不然就很容易出错。当一遍又一遍地连续按 j.j.j. 时,那种感觉就像是在敲鼓。可是,如果不小心在一行上敲了两次 j 键,会发生什么?或是更糟,敲了两次 . 键?
当Vim让一个操作或移动可以很方便地重复时,它总是会提供某种方式,让我们在不小心做过头时能回退回来。对 . 命令而言,我们永远可以按 u 键撤销上次的修改。如果在使用 f{char} 命令后,不小心按了太多次 ; 键,就会偏离我们的目标。不过我们可以再按 , 键跳回去,这个命令会反方向查找上次 f{char}所查找的字符(参见技巧49)。
在你不小心做过头时,知道怎么回退会很有帮助。表1-1总结了Vim中可重复执行的命令,以及相应的回退方式。在多数场景中,撤销(undo)都是我们想要使用的命令,难怪我键盘上的 u 键磨损得这么厉害!
表1-1 可重复的操作及如何回退
技巧5 查找并手动替换
Vim提供了一个:substitute 命令专门用于查找替换任务,不过用上面介绍的技术,我们也可以手动修改第一处地方,然后再一个个地查找替换其他匹配项。. 命令可以把我们从繁重的工作中解放出来,而即将登场的另一个有用的单键命令,则能够让我们方便地在匹配项间跳转。
在下面这段文本中,每一行都出现了单词“content”:
the_vim_way/1_copy_content.txt
...We're waiting for content before the site can go live...
...If you are content with this, let's go ahead with it...
...We'll launch as soon as we have the content...
假设我们想用单词“copy”(意义同“copywriting”)来替代“content”。也许你会想,这太简单了,只要用替换命令就行了,像下面这样:
➾:%s/content/copy/g
但是,且慢!如果我们运行上面这条命令,就会出现“If you are ‘copy’ with this,”这样的句子,这很荒唐!
之所以会有这种问题,是因为“content”一词有两种含义,一个是“copy”的同义词(发音为'kon'tεnt),另一个是“happy”的同义词(发音为kən'tent)。用专业的话说,我们是在处理拼写相同,但含义和发音都不同的词。不过这不是我想说的重点,重点是我们一定要小心每一步操作。
我们不能想当然地用“copy”替换每一个“content”,而是要时刻留神,对每个地方都要问“这里要修改吗?”,然后回答“修改”或者“不改”。substitute 命令也能胜任这项工作,我们将在技巧89中学到该怎么做。不过现在,我们将寻求符合本章主题的另一种解决办法。
偷懒的办法:无需输入就可以进行查找
现在你可能已经猜到了,. 命令是我最喜爱的Vim单键命令,而排在第二位的是* 命令,此命令可以查找当前光标下的单词(参见:h *)。
我们可以调出查找提示符,并输入完整的单词来查找 “content”:
➾/content
或者,我们可以简单地把光标移到这个单词上,然后按 * 键。以下面的操作为例:
刚开始,我们把光标移到单词“content”上,然后使用 * 命令对它进行查找,你也可以自己试一下。这会产生两个结果:一是光标会跳到下一个匹配项上,二是所有出现这个词的地方都会被高亮显示出来。如果你并没有看到高亮,试着运行一下 :set hls。要了解更多这方面的内容,请参见技巧80。
执行过一次查找“content”的命令后,现在我们只需按 n 键就可以跳到下一个匹配项。在本例中,按 *nn 会遍历完所有的匹配项,从而跳回到本次查找的起点。
使修改可重复
当光标位于“content”的开头时,我们就可以着手修改它。这包括两步操作:首先要删除单词“content”,然后输入替代的单词。cw 命令会删除从光标位置到单词结尾间的字符,并进入插入模式,接下来我们就可以输入单词“copy”了。Vim 会把我们离开插入模式之前的全部按键操作都记录下来,因此整个 cwcopy<Esc> 会被当成一个修改。也就是说,执行 . 命令会删除从光标到当前单词结尾间的字符,并把它修改为“copy”。
合而为一
万事俱备!每次我们按 n 键时,光标就会跳到下一个“content”单词所在之处,而当我们按 . 键时,它就会把光标下的单词改为“copy”。
如果我们想替换所有地方,就可以不加思考地一直按 n.n.n. 以完成所有的修改(但是,这种情况下也可以用 :%s/content/copy/g 命令)。然而,由于我们需要留意不符合要求的匹配项,所以在按了 n 之后,我们要审视一下当前的匹配项,然后决定是否把它改为“copy”。如果需要修改的话,就按 . 命令,反之则不用。无论决定是什么,我们都可以再次按 n 移到下一个地方,如此循环往复,直到完成全部的修改。
技巧6 结识.范式
到目前为止,我们介绍了3个简单的编辑任务。尽管每个问题都不一样,不过我们都找到了用 . 命令解决该问题的方法。在本节,我们将对这些方案进行比较,并找出它们所共有的模式——一个我称之为“ . 范式”的最佳编辑模式。
回顾前面3个 . 命令编辑任务
在技巧 2 中,我们想在一系列行的结尾添加分号。我们先用 A;<Esc> 修改了第一行,做完这步准备后,就可以使用 . 命令对后续行重复此修改。我们使用了 j 命令在行间移动,要完成剩余的修改,只需简单地按足够多次j. 就可以了。
在技巧 3 中,我们想为每个 + 号的前后各添加一个空格。我们先用 f+ 命令跳到目标字符上,然后用 s 命令把一个字符替换成3 个,做完这步准备后,我们就可以按若干次 ;. 完成此任务。
在技巧5中,我们想把每处出现单词“content”的地方都替换成“copy”。我们使用 * 命令来查找目标单词,然后用 cw 命令修改第一处地方。做完这步准备后,就可以用 n 键跳到下一匹配项,然后用 . 键做相同的修改。要完成这项任务,只需简单地按足够多次 n. 就行了。
理想模式:用一键移动,另一键执行
所有这些例子都利用 . 命令重复上次的修改,不过这不是它们唯一的共同点,另外的共同点是它们都只需要按一次键就能把光标移到下一个目标上。
用一次按键移动,另一次按键执行,没有再比这更好的了,不是吗?这就是我们的理想解决方案。我们将会一次又一次地看到这一编辑模式,所以为了方便起见,我们把它叫做“. 范式”。