全书共123个技巧,每天更新两个,计划两个月更新完。
(目前更新完技巧39)
不使用插件、不使用自定义配置地打开vim.
$ vim -u NONE -N //-u NONE使得 Vim 在启动时不加载你的vimrc;
//-N 标志则会激活 ‘nocompatible’ 选项,防止进入 vi 兼容模式。
有些 Vim的内置功能是由 Vim 脚本实现,只有在激活插件时, 它们才会工作。
则启动 Vim 时,可以执行如下命令,用essential.vim
文件取代你的 vimrc
。
$ vim -u code/essential.vim
essential.vim
的内容如下:
set nocompatible
filetype plugin on
从本质上讲,我们的工作是重复性的。不论是在几个不同的地方做相同的小改动,还是在文档的相似结构间移动,我们都会重复很多操作。凡是可以简化重复性操作的方式,都会成倍地节省我们的时间。
Vim对重复性操作进行了优化。它之所以能高效地重复,是因为它会记录我们最近的操作,让我们用一次按键就能重复上次的修改。这听起来很强大,但是除非我们能够学会规划按键动作,使得在重复时能完成一项有用的工作,否则这没什么用。掌握这一理念是高效使用Vim的关键。
我们将以.
命令作为开始。这个看似简单的命令是Vim中的瑞士军刀,掌握它的用法是精通Vim的第一步。我们将运行一些可由.
命令快速完成的简单编辑任务,虽然每个任务彼此之间截然不同,但解决的方法却大同小异。我们将找到一种理想的编辑模式,即用一次按键移动,用另一次按键执行。
.
命令可以让我们重复上次的修改
最后,
>G
命令会使得从当前行到末尾行统一增加缩进。如果在 此命令后使用.
命令,那么“重复上次修改”会让Vim增加从当前行到文档末尾的缩进。
x
、dd
以及>
命令都是在 normal 模式中执行的命令。
不过,每次进入insert模式时,也会形成一次修改。从进入insert模式的那一刻起(例如,输入 i),直到返回 normal 模式时为止(输入 < Esc >)Vim会记录每一个按键操作。
做出这样一个修改后再用.
命令的话,它将会重新执行所有这些按键操作
操作前文本0_mechanics.txt
贴在下面,方便大家复制并练习。
Line one
Line two
Line three
Line four
.
命令是一个微型的宏
Vim可以录制任意数目的按键操作,然后在以后重复执行它们。这让我们可以把最常重复的工作流程录制下来,并用一个按键重放它们。可以把
.
命令当成一个很小的宏。
对于在行尾添加内容这样的常见操作,如添加分号,Vim提供了一个专门的命令,可以把两步操作合并为一步。
我们想在每行的结尾添加一个分号。要实现这一点,先得把光标移到行尾,然后切换到插入模式进行修改。$
命令可以完成移动动作,接着就可以执行a;< Esc >
完成修改了。
要完成全部修改,也可以对下面两行做完全相同的操作,不过那样做会错过这里将要提到的小窍门。由于.
命令可以重复上次的修改,因此不必重复之前的操作,而是执行两次j$.
。
a
命令在当前光标之后添加内容,A
命令则在当前行的结尾添加内容。不管光标当前处于什么位置,输入A
都会进入插入模式,并把光标移到行尾。
A
把$a
封装成了一个按键操作。
用A
来代替$a
,大大提升了.
命令的效率。不必再把光标移到行尾,只需保证它位于该行内就行了(可在任意位置)。现在可以重复执行足够多次的j.
,完成对后续行的修改。
一键移动,另一键操作,真是太完美了!请留意这种应用模式,因为我们即将在更多的例子中看到它的身影。
操作前文本2_foo_bar.js
贴在下面,方便大家复制并练习。
var foo = 1
var bar = 'a'
var foobar = foo + bar
复合命令 | 等效的长命令 |
---|---|
C | c$ //删空光标后面的所有东西,并进入插入模式 |
s | cl //往后删一个字符,并进入插入模式 |
S | ^C //删空这行,并进入插入模式 |
I | ^i //到达行首,并进入插入模式 |
A | $a //到达行尾,并进入插入模式 |
o | A< CR > //下面插入一个空行,并进入插入模式 |
O | ko //在上面新建一个空行,并进入插入模式 |
cc和S是同义词,完全一样,代表修改一行。用于.
命令也一样。
如果你发觉自己正在输入
ko
(或更糟糕,在用k$a
),马上 打住!想想你在干什么,然后你就会意识到可以把它换成O命令。
你找出这些命令别的共同点了吗?它们全都会从普通模式切换到插入模式。仔细想想这一点,并想想这对.
命令可能产生怎 样的影响。
我们可以用一种常用的Vim操作习惯在一个字符前后各添加一个空格。
假设有一行代码看起来是这样的:
var foo = "method("+argument1+","+argument2+")";
在JavaScript
里把字符串连接到一起从来都不美观,但可以像下面这样在 + 号前后各添加一个空格,让肉眼更容易识别。
var foo = "method(" + argument1 + "," + argument2 + ")";
f{字符}
在行内查找下一个指定的字符,使用;
与,
进行前进与退回
/{pattern}
在文档内查找下一处匹配的项; (用n
和N
进行前进与返回
*
表示向下查找光标处的单词
#
表示向上查找光标处的单词;
s
命令把两个操作合并为一个:它先删除光标下的字符,然后进入insert模式。在删除 + 号后,先输入␣ + ␣
,然后按键< ESC >
退出插入模式。
先后退一步,然后前进三步,这是个奇怪的小花招,看起来可能不够直接。但这样做最大的好处是:我们可以用.
命令重复这一修改。我们所要做的只是把光标移到下一个 + 号处,然后用.
命令重复这一操作即可。
输入
f+
时,光标会直接移到下一个 + 号所在的位置。完成第一处修改后,可以重复按f+
命令跳到下一个 + 号所在的位置。
不过,;
命令会重复查找上次 f 命令所查找的字符,因此不用输入4次f+
,而是只输入一次,后面跟着再用3次;
命令。
;
命令带我们到下一个目标字符上,.
命令则重复上次的修改。因 此,可以连续输入3次;.
来完成全部修改。
在面对重复性工作时,我们需要让移动动作和修改都能够重复,这样就可以达到最佳编辑模式。
如果我们知道如何重复之前的操作,而无需每次都输入整条命令,那么就会获得更高的效率。可以先执行一次,随后只需重复即可。
当一遍又一遍地连续按
j.j.j.
时,那种感觉就像是在敲鼓。可是,如果不小心在一行上敲了两次j
键,会发生什么?或是更糟,敲了两次.
键?
对
.
命令而言,我们永远可以按u
键撤销上次的修改。
在多数场景中,撤销(undo)都是我们想要使用的命令,难怪我键盘上的 u 键磨损得这么厉害!
Vim提供了一个
:substitute
命令专门用于查找替换任务, 不过用上面介绍的技术,也可以手动修改第一处地方,然后再一 个个地查找替换其他匹配项。.
命令可以把我们从繁重的工作中解放出来,而即将登场的另一个有用的单键命令则能够让我们方便地在匹配项间跳转。
在下面这段文本中,每一行都出现了单词“content”。
...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,”这样的句子,这很荒唐!
我们不能想当然地用“copy”替换每一个“content”,而是要时刻留神,对每个地方都要问“这里要修改吗?”,然后回答“修改”或者“不改”。
.
命令是我最喜爱的Vim单键命令,而排在第二位的是*
命令
我们可以调出查找提示符,并输入完整的单词来查找“content”。
➾ /content
或者,可以简单地把光标移到这个单词上,然后按 *
键。以下面的操作为例。
当光标位于“content”的开头时,就可以着手修改它。这包括两步操作:首先要删除单词“content”,然后输入替代的单词。
cw
命令会删除从光标位置到单词结尾的字符,并进入插入模式,然后就可以输入单词“copy”了。Vim会把我们离开插入模式之前的全部按键操作都记录下来,因此整个cwcopy< Esc >
会被当成一个修改。也就是说,执行.
命 令会删除从光标到当前单词结尾间的字符,并把它修改为“copy”。
万事俱备!每次按 n 键时,光标就会跳到下一个“content”单词所在之处,而按
.
键时,它就会把光标下的单词改为“copy”。如果想替换所有地方,就可以不加思考地一直按n.n.n.
以完成所有的修改(但是,这种情况下也可以用:%s/content/copy/g
命令)。
到目前为止,我们介绍了3个简单的编辑任务。尽管每个问题都不一样,不过我们都找到了用 .
命令解决该问题的方法。在本节,我们将比较这些方案,并找出它们共有的模式——一个我称之为“ . 范式
”的最佳编辑模式。
在技巧2中,我们想在一系列行的结尾添加分号。我们先用 A;< Esc >
修改了第一行,做完这步准备后,就可以使用 .
命令对后续行重复此修改。我们使用了 j
命令在行间移动,要完成剩余的修改,只需简单地按足够多次 j.
就可以了。
在技巧3中,我们想为每个 + 号的前后各添加一个空格。先用 f+
命令跳到目标字符上,然后用 s
命令把一个字符替换成3个,做完这步准备后,就可以按若干次 ;.
完成此任务。
在技巧5中,我们想把每处出现单词“content”的地方都替换成“copy”。使用 *
命令来查找目标单词,然后用 cw
命令修改第一处地方。做完这步准备后,就可以用 n
键跳到下一匹配项,然后用 .
键做相同的修改。要完成这项任务,只需简单地按足够多次 n.
就行了。
所有这些例子都利用 .
命令重复上次的修改,不过这不是它们唯一的共同点,另外的共同点是它们都只需要按一次键就能把光标移到下一个目标上。
用一次按键移动,另一次按键执行,再没有比这更好的了,不是吗?这就是我们的理想解决方案。我们将会一次又一次地看到这一编辑模式,所以为了方便起见,把它叫做“. 范式
”。
Vim提供一个区分模式的用户界面,就是说在Vim中按键盘上的任意键所产生的结果可能会不一样,而这取决于当前处于哪种模式(mode)。知道当前正处于哪种模式,以及如何在各模式间切换,是极其重要的。在本书的这一部分,我们将学习每种模式的工作方式及其用途.
普通模式是Vim的自然放松状态,如果本章看起来出奇的短,那是因为几乎整本书都在讲如何利用普通模式,而本章只涉及其中的一些核心概念以及通用技巧。
其他文本编辑器大部分时间都处于类似Vim插入模式的状态中,因此对Vim新手来说,把普通模式(normal mode)当成默认状态看起来很奇怪。在技巧7中,我们将以一个画家的工作区作为类比,来解释其原因。
许多普通模式命令可以在执行时指定执行的次数,这样它们就可以被执行多次。在技巧10中,我们将结识一对用于加减数值的命令,并且会看到这两条命令如何与次数结合在一起,进行简单的算术运算。
指定执行的次数可以减少按键个数,但并不是说一定要为此目的而这样做。我们将会看到一些例子,在这些例子中,简单地重复执行一条命令,要比花时间去计算想要执行多少次更好。
普通模式命令的强大,很大程度上源于它可以把操作符与动作命令结合在一起。在本章的最后,我们将看到这种结合达到的效果。
对于不习惯Vim的人来说,普通模式看上去是一种奇怪的缺省状态,但有经验的Vim用户却很难想象还有其他任何方式。本节使用了一个比喻来说明为什么Vim要采用这一种方式。
你估计画家会花费多少时间用画笔在画布上作画?毫无疑问,这因人而异,但是,如果这占了画家全部工作时间的一半还要多的话,我会觉得非常诧异。
想一下除了画画外,画家还要做哪些事情。他们要研究主题,调整光线,把颜料混合成新的色彩,而且在把颜料往画布上画时,谁说他们必须要用画?画家也许会换用刻刀来实现不同的质地,或是用棉签来对已经画好的地方进行润色。
画家在休息时不会把画笔放在画布上。对Vim而言也是这样,普通模式就是Vim的自然放松状态,其名字已经寓示了这一点。
就像画家只花一小部分时间涂色一样,程序员也只花一小部分时间编写代码。绝大多数时间用来思考、阅读,以及在代码中穿梭浏览,而且当确实需要修改时,谁说一定要切换到插入模式才行?我们可以重新调整已有代码的格式,复制它们,移动其位置,或是删除它们。在普通模式中,我们有众多的工具可以利用。
在其他编辑器中,输入一些词后使用撤销命令,可能会撤销最后输入的词或字符,然而在Vim中,我们自己可以控制撤销的力度。
u
键会触发撤销命令,它会撤销最新的修改。一次修改可以是改变文档内文本的任意操作,其中包括在普通模式、可视模式以及命令行模式中所触发的命令,而且一次修改也包括了在插入模式中输入(或删除)的文本,因此我们也可以说,i{insert some text} < Esc >
是一次修改。
在不区分模式的文本编辑器中,输入一些单词后使用撤销命令,有两种可能。一种是它可能会撤销最后输入的字符;另一种做得更好点,它可能会把字符分成块,使每次撤销操作删除一个单词而不是一个字符。
在Vim中,我们自己可以控制撤销命令的力度。从进入插入模式开始,直到返回普通模式为止,在此期间输入或删除的任何内容都被当成一次修改。因此,只要控制好对 < Esc >
键的使用,就可使撤销命令作用于单词、句子或段落。
如果我认为已经走错了方向,就会切换到普通模式,然后按 u
撤销。每次做撤销时,文字都按我最初书写时的思路,被切分成条理清晰的块,也就是说我可以很容易地试着写一两句话,如果感到不合适的话,随后按一两下键就可以将其舍弃。
当处于插入模式时,如果光标位于行尾的话,另起一行最快的方式是按 < CR >
。不过有时我更喜欢按 < Esc >o
,这是因为我有预感,也许在撤销时我想拥有更细的粒度。如果听起来这不太好理解,不必担心,当你对Vim越来越熟悉时,就会感到切换模式越来越轻松。
一般来讲,如果你停顿的时间长到足以问“我应该退出插入模式吗?”这个问题,就退出吧。
在插入模式中移动光标会重置修改状态
当我提到撤销命令会撤销从插入模式到退出此模式期间的输入(或删除)的全部字符时,我略过了一个小细节。
如果在插入模式中使用了< Up >
、< Down >
、< Left >
或< Right >
这些光标键,将会产生一个新的撤销块。你可以把这想象为先切换回普通模式,然后用h
、j
、k
或l
命令对光标进行了移动,唯一区别是我们并没有退出插入模式。这也会对.
命令的操作产生影响。
Vim对重复操作进行了优化,要利用这一点,必须考虑该如何构造修改。
在Vim中,要完成一件事,总是有不止一种方式。在评估哪种方式最好时,最显而易见的指标是效率,即哪种手段需要的按键次数最少(又名VimGolf)。然而,在平局时该如何选择获胜者呢?
在下例中,假设光标位于行尾处的字符“h”上,而我们想要删除单词“nigh”:
The end is nigh
反向删除
因为光标已经位于单词末尾,所以可以先反向删除该词。
按db
命令删除从光标起始位置到单词开头的内容,但会原封未动 地留下最后一个字符 “h”,再按一下x
键就可以删除这个捣乱的字符。 这样,整个操作的 Vim高尔夫得分是3分。正向删除
先用b
命令把光标移到单词的开头,移动好后,就可以用一个dw
命令删掉整个单词。这一次的Vim高尔夫得分也是3分。删除整个单词
到目前为止,已有的两种方式都要先做某种准备工作或清理工作。另外,也可以使用更为精准的
aw
文本对象(text object),而不是用动作命令(参见:h aw
)。
可以把daw
命令解读为“delete a word”,这样比较容易记忆。
我们尝试了3种不同的方式来删除一个词:dbx
、bdw
以及 daw
。每种情况的Vim高尔夫得分都是3分。那么要怎么回答这个问题:“哪种方式最好?”
还记得吗,Vim对重复操作进行了优化。让我们再回顾一下这3种方式,这一次我们跟着用一次 .
命令,看看会发生什么。我建议你自己也亲自试一下。
反向删除方案包含两步操作:
db
命令删除至单词的开头,而后x
命令删除一个字符。如果我们跟着执行一次.
命令,它会重复删除一个字符(.
==x
)。我不觉得这有什么价值。
正向删除方案也包含两步。这一次,b
只是一次普通的移动,而dw
完成修改。此时用.
命令会重复
dw
,删除从光标位置到下个单词开头的内容。不过因为我们刚好已经在行尾了,并没有“下一个单词”,所以在这个场景里 .
命令没什么用。不过,至少它代表了一个更长点的操作(.
==dw
)。
最后的方案只调用一个操作:daw
。这个操作不仅仅删除了该单词,它还会删除一个空格,因此光标最终会停在单词“is”的最后一个字符上。如果此时使用.
命令,它会重复上次删除单词的命令。这一次,.
命令会做真正有用的事情(.
==daw
)。
daw
可以发挥 .
命令的最大威力,因此我宣布它是本轮的获胜者。要想充分利用 .
命令,事先常常需要进行一番周详的考虑。如果你发现自己要在几个地方做同样的小修改,就可以尝试构造你的修改,让它们能够被 .
命令重复执行。要识别出这类机会需要进行一定的实践,不过一旦养成了使修改可重复的习惯,你就会从 Vim 这里得到“奖赏”。
有时,我并没有看到用 .
命令的机会,然而在做完一次修改后,我发现要做另一次同样的操作,这时候,我脑海里会浮现出 .
命令,而它也已经准备好为我效力了。每当遇到这种情况时,我都会开心地笑起来。
大多数普通模式命令可以在执行时指定次数,可以利用这个 功能来做简单的算术运算。
很多普通模式命令都可以带一个次数前缀,这样Vim就会尝试把该
命令执行指定的次数,而不是只执行一次(参见 :h count
)
< C-a >
和 < C-x >
命令分别对数字执行加和减操作。在不带次数执行时,它们会逐个加减,但如果带一个次数前缀,那么可以用它们加减任意整数。例如,如果把光标移到字符5上,执行 10< C-a >
就会把它变成15。
但是如果光标不在数字上会发生什么?文档里说, < C-a >
命令会“把当前光标之上或之后的数值加上 [count]”(参见 :h ctrl-a
)。因此,如果光标不在数字上,那么 < C-a >
命令将在当前行正向查找一个数字,如果找到了,它就径直跳到那里。我们可以利用这一点简化操作。
.blog, .news { background-image: url(/sprite.png); }
.blog { background-position: 0px 0px }
我们要复制最后一行并且对其做两个小改动,即用“news”替换单词“blog”,以及把“0px”改为“-180px”。可以运行
yyp
来复制此行,然后用cW
来修改第一个单词。但该怎么处理那个数值呢?
一种做法是用 f0 跳到此数字,然后进入插入模式手动修改它的值,即i-18< Esc >
。不过,运行180< C-x >
则要快得多。由于光标不在要操作的数字上,所以该命令会正向跳到所找到的第一个数字上,从而省去了手动移光标的步骤。让我们看看整个操作过程。
在本例中,只复制了一行并做出改动。但是,假设要复制10份,并对后续数字依次减180。如果要切换到插入模式去修改每个数字,每次都得输入不同的内容(-180,然后-360,以此类推)。但是如果用180< C-x >
命令的话,对后续行也可以采用相同的操作过程。甚至还可以把这组按键操作录制成一个宏(参见第11章),然后根据需要执行多次。
本例知识点的补充():
cw
和cW
不一样,
According to Vim documentation ( :h 03.1 )A word ends at a non-word character, such as a “
.
”, “-
” or “)
”.A WORD ends strictly with a white-space. This may not be a word in
normal sense, 因此大写.例如:
ge b w e <- <- ---> ---> This is-a line, with special/separated/words (and some more). ~ <----- <----- --------------------> -----> gE B W E
If your cursor is at
m
(of more above)
a word would mean
more
(i.e delimited by ‘)’ non-word character)whereas a WORD would mean
more).
(i.e. delimited by white-space
only)similarly, If your cursor is at
p
(of special)
数字的格式
像在某些编程语言中的约定一样,
Vim把以0开头的数字解释为八进制值
,而不是十进制。在八进制体系中,007 + 0 01 = 010,看起来像是10,但实际上它是八进制中的8,糊涂了吗?
如果你经常使用八进制,Vim的缺省行为或许会适合你
。如果不是这样,那么你可能想把下面这行加入你的vimrc里:
set nrformats=
这会让Vim把所有数字都当成十进制,不管它们是不是以0开头的。
在处理某些特定工作时,使用次数可以使按键次数变得最少,不过并不是非得这样不可。我们需要认真考虑次数与重复各自的优缺点。
假设在缓冲区里有如下文字:
Delete more than one word
想把这段文字改为“Delete one word”,也就是说,要像这段文字里所讲的那样删除两个单词。 有几种方式可以达到这一目的,
d2w
和2dw
都可以。使用d2w
,先调用删除命令,然后以2w
作为动作命令,可以把它解读为“删除两个单词”;然而2dw
做的相反,这一次,次数作用于删除命令,而动作命令只跨越一个单词,可以把这解读为“做两次删除单词的操作”。抛开语义不讲,无论哪种方法,结果都是相同的。
现在,让我们考虑另外一种方式,即dw.
。这可以解读为“删除一个单词,然后重复上次的操作”。 概括一下,我们的3种选择d2w
、2dw
或者dw.
都是3次按键,不过哪一种最好呢? 根据我们的讨论,d2w
和2dw
是相同的,在执行完两者中的任一个后,可以按u
键撤销,这样两个被删除的单词又会回来。或者,我们不是用撤销,而是用.
命令重复执行它,这就会删除后面的两个单词。 对于dw.
的情形,按u
或.
的结果会有细微的差别。这里的修改是dw
,即删除一个单词。因此,如果想恢复这两个被删除的单词,必须撤销两次,按uu
(或者,如果你愿意,也可以按2u
)。按.
则只删除后面的一个单词,而不是两个。
现在假设我们原本是想删除3个单词,而不是2个。由于判断出了点差错,我们执行了d2w
而不是d3w
,那接下来怎么做?我们不能使用.
命令,因为那会总共删除4个单词。因此,我们或是先撤销而后修正次数(ud3w
),或是继续删除下一个单词(dw
)。
现在考虑另一种方案,如果我们在第一处地方用的是dw.
命令,那么只要再多重复一次.
命令就行了。因为我们最初的修改只是简单的dw
,因此u
命令和.
命令都具有更细的粒度,每次只作用于一个单词。 现在假设我们想删除7个单词,我们可以运行d7w
,或是dw......
(即dw
后面跟6次.
命令)。计算一下按键的次数,哪个命令胜出是很显而易见的。不过你真地确信自己数对了次数吗?
计算次数很是讨厌,因此我宁愿按6次.
命令,也不愿意只为减少按键的次数,而浪费同样的时间去统计次数。如果我多按了一次.
命令怎么办?没关系,只要按一次u
键就可以回退回来。 还记得吗,我们的口诀是(参见技巧4):执行、重复、回退。这里就是在把它付诸行动。
假设我们想把文字“I have a couple of questions”改为“I have some more questions”,可以用下面的方式做。
c3wsome more< Esc >
在此场景中,使用 .
命令的意义不大,我们可以删除一个单词,然后再用 . 命令删除另一个,但随后我们还得切换到插入模式(例如,使用 i
或 cw
)。对我来说这么做很不顺手,我反而更愿意用次数。
使用次数的另一个好处是:它保留了一个干净、连贯的撤销历史记录。完成这次修改后,按一下 u
键就可以撤销整个修改,这和技巧8中的讨论是一致的。
对于是用次数风格(d5w
)还是用重复风格(dw....
)也有同样的争论,因此我的偏好看起来似乎不太一致。对此,你要总结自己的观点,这取决于你怎么看保留干净撤销历史记录的价值,以及你是否觉得用次数令人生厌。
Vim的强大很大程度上源自操作符与动作命令相结合。在本节,我们将看到它是如何工作的,并考虑其寓义。
d{motion} 命令可以对一个字符(dl
)、一个完整单词(daw
)或一整个段落(dap
)进行操作,它作用的范围由动作命令决定。c{motion}、y{motion} 以及其他一些命令也类似,它们被统称为操作符(operator)。可以用 :h operator
来查阅完整的列表,表2-1总结了一些比较常见的操作符。
g~
、gu
和 gU
命令要用两次按键来调用,我们可以把上述命令中的g
当作一个前缀字符,用以改变其后面的按键行为,进一步的讨论请参见本技巧最后的“结识操作符待决模式”部分。
操作符与动作命令的结合形成了一种语法。这种语法的第一条规则很简单,即一个操作由一个操作符,后面跟一个动作命令组成。学习新的动作命令及操作符,就像是在学习Vim的词汇一样。如果掌握了这一简单的语法规则,在词汇量增长时,就能表达更多的想法。
假如我们已经知道如何用
daw
删除一个单词,然后又学到gU
命令(参见:h gU
)。它也是个操作符,所以可以用gUaw
把当前单词转换成大写形式。
如果我们的词汇进一步扩充,学会了作用于段落的ap
动作命令,就会发现我们可以进行两个新的操作:用dap
删除整个段落,或者用gUap
把整段文字转换为大写。
Vim的语法只有一条额外规则,即当一个操作符命令被连续调用两次时,它会作用于当前行。所以 dd
删除当前行,而 >>
缩进当前
行。gU
命令是一种特殊情况,我们既可以用 gUgU
,也可以用简化版的gUU
来使它作用于当前行。
表2-1 Vim的操作符命令
命令 | 用途 |
---|---|
c | 修改 |
d | 删除 |
y | 复制到寄存器 |
g~ | 反转大小写 |
gu | 转换为小写 |
gU | 转换为大写 |
> | 增加缩进 |
< | 减小缩进 |
= | 自动缩进 |
! | 使用外部程序过滤{motion}所跨越的行 |
使用Vim缺省的操作符和动作命令,我们能够执行的操作的数目是巨大的,然而,我们还可以通过自定义动作命令及操作符来进一步扩充其数目。让我们想想这寓示着什么。
随同Vim发布的标准操作符集合相对比较少,但可以定义新的操作符。Tim Pope的 commentary.vim 插件提供了一个很好的例子,此插件为Vim支持的编程语言增添了注释及取消注释的命令。
注释命令以 gc{motion}
触发,它会切换指定行的注释状态。它是一个操作符命令,因此可以把它和所有动作命令结合在一起。gcap
将切换当前段落的注释状态, gcG
会把从当前行到文件结尾间的所有内容注释掉,gcc
则注释当前行(gcgc
的缩写)。
如果你对如何创建自定义操作符感到好奇,可以先阅读一下文档 : h :map-operator
。
Vim缺省的动作命令集已经相当全面了,但是我们还是可以定义新的动作命令及文本对象来进一步增强它。 Kana Natsuno的 textobj-entire 插件是一个很好的例子,它为Vim增加了两种新的文本对象
ie
和ae
,它们作用于整个文件。
如果想用=
命令自动缩进整个文件,可以执行gg=G
(就是说,先用gg
跳到文件开头,然后用=G
自动缩进从光标位置到文件结尾的所有内容)。
但是如果安装了textobj-entire 插件的话,简单地执行=ae
就可以了。运行这条命令时,光标在哪儿并不重要,因为它总是作用于整个文件。
gcae
会切换整个文件的注释状态。:h omap-info
开始.普通、插入及可视模式很容易辨识,但是Vim还有另外一些很容易被忽视的模式,操作符待决模式(operator-pending mode)就是一个例子。每天我们无数次地使用它,但通常它只持续不到一秒时间。举个例子,在执行命令
dw
时,就会激活该模式。这一模式只在按 d 及 w 键之间的短暂时间间隔内存在,一眨眼工夫就不见了。
如果把Vim想象成有限状态机,那么操作符待决模式就是一个只接受动作命令的状态。这个状态在调用操作符时被激活,然后什么也不做,直到我们提供了一个动作命令,完成整个操作。当操作符待决模式被激活时,我们可以像平常一样按 < Esc > 中止该操作,返回到普通模式。
很多命令都由两个或更多的按键来调用(查阅
:h g
、:h z
、:h ctrl-w
,或者:h [
,可以看到一些例子),但在多数情况下,头一个按键只是第二个按键的前缀。这些命令不会激活操作符待决模式,相反,可以把它们当成命名空间(namespace),用来扩充可用命令的数目。只有操作符才会激活操作符待决模式。[俺理解为:不会激发这个模式的字符串,可以用作扩充命令的命名前缀]
你也许想知道,为什么要有一个完整的模式,专门用于操作符和动作命令之间的短暂瞬间,而命名空间命令则仅仅是普通模式的一个扩充?好问题!这是因为我们能够创建自定义映射项来激活或终结操作符待决模式。换句话说,它允许我们创建自定义的操作符及动作命令,从而让我们可以扩充Vim的词汇。
大部分的Vim命令都在非插入模式中执行,不过有些功能在插入模式中会更好实现些,我们将在本章深入研究这些命令。尽管删除、复制 以及粘贴命令都是在普通模式中执行的,不过我们将会看到一种方便快捷的方式,让我们无需离开插入模式就能粘贴寄存器中的文本。另外我们也会学习Vim提供的两种简单方式,用来插入键盘上不存在的不常用字符。
替换模式是插入模式的一种特例,它会替换文档中已有的字符。我 们将学习如何使用它,并了解在哪些场景下它能够大显身手。我们也会结识插入-普通模式
,它是一个子模式,可以让我们执行一个普通模式命令,之后马上又回到插入模式。
自动补全是插入模式中才能使用的高级功能,我们将在第19章对其进行深入的研究。
如果在插入模式下撰写文本时出了错,可以立刻对它进行更正,而无需切换模式。要迅速更正错误,除了用退格键外,还可以用插入模式中的其他一些命令。
盲打并不仅仅指输入时不看键盘,还意味着输入时要凭感觉。当盲打的人输入错误时,在眼睛看到屏幕上的错误之前,他们就已经察觉到 了,他们可以用手指感知到次序颠倒这类的错误。
在输入错误时,可以用退格键删除错误的文本,然后再输入正确的内容。如果出错的地方靠近单词结尾,这或许是最快的修正方式。但是,如果出错的位置在单词开头呢?
专业打字员会建议先删除整个单词,然后再重新输入一遍。如果你能以每分钟超过60个单词的速度输入,那么重新输入一个词只需要1秒钟的时间。即便你打不了这么快,最好也采用这种方式。我以前总是输错某些特定的词,但自从采纳这一建议后,我就更清楚地意识到哪些词会让我犯错,因此现在犯的错也少了很多。
另外,也可以切换到普通模式,然后跳到这个词的开头并更正错误,再按A
返回刚才的位置。不过完成这一套动作要花的时间可能不止1秒钟,并且它也无助于提高你的盲打技巧。虽然说我们可以切换模式,不过这并不意味着一定就得切换。
在插入模式下,退格键的作用如你所愿,它删除光标前的字符。另外,还可以用下面这些组合键。
按键操作 | 用途 |
---|---|
< C-h > | 删除前一个字符(同退格键) 删除前一个单词 |
< C-u > | 删至行首 |
这些命令不是插入模式独有的,甚至也不是Vim独有的,在Vim的命令行
模式中,以及在 bash shell
中,也可以使用它们。
插入模式只专注于做一件事,那就是输入文字,而普通模式却是我们大部分时间所使用的模式(顾名思义),因此能快速在这两种模式间切换是很重要的。本节将介绍一些可以减少模式切换所带来的损耗的技巧。
切换回普通模式的经典方式是使用 < Esc >
键,但在许多键盘上这个键的距离似乎有点远。作为替代,也可以用
,它的效果与< Esc >
完全相同(参见 :h i_CTRL-[
)。
按键操作 | 用途 |
---|---|
< Esc > | 切换到普通模式 |
< C-[ > | 切换到普通模式 |
< C-o > | 切换到插入-普通模式 |
Vim新手经常会被不断地切换模式而搞得疲倦不堪,不过练习过一段时间以后,就会渐渐感觉到得心应手了。不过,Vim区分模式的特点在下面这种特定场景中却显得有点烦琐:
插入-普通模式
。插入-普通模式是普通模式的一个特例,它能让我们执行一次普通模式命令。在此模式中,可以执行一个普通模式命令,执行完后,马上又返回到插入模式。要从插入模式切换到插入-普通模式,可以按 < C-o >(参见
:h i_CTRL-O
)。
在当前行正好处于窗口顶部或底部时,有时我会滚动一下屏幕,以便看到更多的上下文。用zz
命令可以重绘屏幕,并把当前行显示在窗口正中,这样就能够阅读当前行之上及之下的半屏内容。我常常会键入< C-o >zz
,在插入-普通模式中触发这条命令。此操作完成后就会直接回到插入模式,因此可以不受中断地继续打字。
Vim的复制和粘贴操作一般都在普通模式中执行,不过有时我们也许想不离开插入模式,就能往文档里粘贴文本。
下面是一段尚未完成的文本。/practical-vim.txt
Practical Vim, by Drew Neil
Read Drew Neil's
我们想把本书的书名插到最后一行,以补全该行。由于书名在第一行的开头已经出现过了,所以将把它复制到一个寄存器中,然后在插入模式中把它添加到第二行结尾。
yt,
命令把“Practical Vim”复制到复制专用寄存器中(将在技巧50中结识t{char}
动作命令),然后在插入模式中,按< C-r >0
把刚才复制的文本粘贴到光标所在位置(将在第10章以大量的篇幅介绍寄存器以及复制操作)。
这个命令的一般格式是< C-r>{register}
,其中{register}
是想要插入的寄存器的名字(参见 :h i_CTRL-R
)。
知识点:t,
的光标移动过程,可借助下面的图片来理解记忆。(光标停在,
前的m
处,则yt,
复制时不包括逗号,
)
重新映射大小写转换键(Caps Lock)
对Vim用户而言,Caps Lock键是一个威胁。如果大小写转换键处于大写模式,而你尝试用k
或j
去移动光标,那么你触发的将会是K
和J
命令。简单地讲,K
命令用于查看处于光标之下的那个单词的手册页(参见:h K
),J
命令则用来把当前行和下一行连接在一起(参见:h J
)。也就是说,如果你不小心切换到了大写模式,你将会惊讶地发现,缓冲区的内容怎么这么快就乱掉了!
很多Vim用户都会重新映射大小写转换键,把它当成另外一个键用,如 < Esc >
或 < Ctrl >
。在现代键盘上,< Esc >
键很难够得到,而大小写转换键却很方便。把大小写转换键映射成 < Esc >
键可以省很多力气,尤其是Vim对 < Esc >
键用得这么频繁。
不过我更喜欢把大小写转换键映射为 < Ctrl >
键。
的功用和< Esc >
键相同,如果 < Ctrl >
键触手可及,那么这一组合键输入起来也会很容易。另外,不管是在 Vim中还是在其他程序中,很多快捷键也都会用到 < Ctrl >
。
要重新映射大小写转换键,最简单的方法是在操作系统级别进行映射。不过对于OS X、Linux及Windows来说,其映射方法各不相同。因此,我不会在这儿重复每种系统的映射方法,而是建议你用Google搜索一下。注意,这一定制不仅会影响Vim,还会作用于整个系统。不过,如果你照我的建议做,你将会永远忘掉大小写转换键,我保证你不会怀念它。
在插入模式中,可以用
< C-r>{register}
命令很方便地粘贴几个单词。
可是如果寄存器中包含了大量的文本,你也许会发现屏幕的更新有些轻微的延时。这是因为Vim在插入寄存器内的文本时,其插入方式就如同这些文本是由键盘上一个个输进来的。因此,如果‘textwidth
’ 或者 ‘autoindent
’ 选项被激活了的话,最终就可能会出现不必要的换行或额外的缩进。
< C-r>< C-p>{register}
命令则会更智能一些,它会按原义插入寄存器内的文本,并修正任何不必要的缩进(参见 :h i_CTRL-R_CTRL-P
),不过这个命令有点不太好输入!因此,如果我想从一个寄存器里粘贴很多行文本的话,我更喜欢切换到普通模式,然后使用某个粘贴命令(参见技巧63)。
表达式寄存器允许我们做一些运算,并把运算结果直接插入文档中。本节将看到一个运用此强大功能的实例。
大部分的Vim寄存器中保存的都是文本,要么是一个字符串,要么是若干行的文本。删除及复制命令允许我们把文本保存到寄存器中,粘贴命令则允许把寄存器中的内容插入文档里。
不过表达式寄存器则是个另类,它可以用来执行一段Vim脚本,并返回其结果。在本节,我们将把它当成计算器来用。传给它一个简单的算术表达式,如1 + 1,它就会给出结果2。对表达式寄存器所返回的文本,我们可以像用普通寄存器中的文本那样使用它。
可以用 =
符号指明使用表达式寄存器。在插入模式中,输入 < C-r >=
就可以访问这一寄存器。这条命令会在屏幕的下方显示一个提示符,可以在其后输入要执行的表达式。输入表达式后敲一下 < CR >
,Vim就会把执行的结果插入文档的当前位置了。
假设刚在back-of-envelope.txt
输入完下列内容:
6 chairs, each costing $35, totals $
我们想算一下总价,不过没必要找个信封在背面做演算,Vim可以
帮我们做这件事,我们甚至连插入模式都不用退出。做法如下。
A
表达式寄存器远不止能做简单算术运算。我们将在技巧71中看到一些更高级的应用。
Vim可以用字符编码(Character Code)插入任意字符。使用此功能可以很方便地输入键盘上找不到的符号。
只要知道某个字符的编码,就可以让Vim插入该字符,我们可以用这种方式插入任意字符。要根据字符编码插入字符,只需在插入模式中输入 < C-v>{code}
即可,其中 {code}
是要插入字符的编码。
Vim接受的字符编码共包含3位数字。例如,假设想插入大写字母“A”,它的字符编码是65,因此需要输入 < C-v>065
。
然而,如果想插入一个编码超过3位数的字符该怎么办?例如,Unicode基本多文种平面(Unicode Basic Multilingual Plane)的地址空间最大会有65 535个字符。解决方法是用4位十六进制编码来输入这些字符,即输入 < C-v>u{1234}
(注意数字前的 u
)。假设想插入字符编码为00bf
的反转问号(“¿”),只需在插入模式中输入 < C-v>u00bf
即可。
更多详细内容可参见 :h i_CTRL-V_digit
。
如果你想知道文档中任意字符的编码,只需把光标移到它上面并按ga
命令,然后屏幕下方会显示出一条消息,分别以十进制和十六进制的形式显示出其字符编码(参见 :h ga
)。当然,如果你想知道文档中不存在的字符的编码,该命令就无能为力了。在这种情况下,你或许得去查一下unicode表。
另外,如果 < C-v>
命令后面跟一个非数字键,它会插入这个按键本身代表的字符。例如,如果启用了 ‘expandtab
’ 选项,那么按< Tab>
键将会插入空格而不是制表符。然而,按 < C-v>< Tab>
则会一直插入制表符,不管 ‘expandtab
’ 选项激活与否。
按键操作 | 用途 |
---|---|
< C-v>{123} | 以十进制字符编码插入字符 |
< C-v>u{1234} | 以十六进制字符编码插入字符 |
< C-v>{nondigit} | 按原义插入非数字字符 |
< C-k>{char1}{char2} | 插入以二合字母{char1}{char2}表示的字符 |
虽然Vim允许我们用数字编码插入任意字符,不过这既难记忆也难输入。我们也可以用
二合字母(digraph)
来插入非常用字符,成对的字符更容易记忆一些。
二合字母用起来很方便。在插入模式中,只需输入 < C-k>{char1}{char2}
即可。
因此,如果想输入以二合字母
?I
表示的“¿
”字符,可以简单地输入< C-k>?I
。
Vim在选择组成二合字母的两个字符时,尽量使之具有描述性,这样就更容易记住它们,甚至也能猜出其含义。例如,左右书名号《
和》
分别以二合字母<<
及>>
表示,普通分数(或常用分数)½
、¼
和¾
则分别以二合字母12
、14
和34
来表示。Vim的缺省二合字母集依从一定的惯例, :h digraphs-default
文档对此进行了总结。
用命令 :digraphs
可以查看可用的二合字母列表,不过该命令的输出不太好阅读。也可以用 :h digraph-table
查看另一个更为有用的列表。
在替换模式中输入会替换文档中的已有文本,除此之外,该模式与插入模式完全相同。
假设有如下一段文本。
Typing in Insert mode extends the line. But in Replace mode
the line length doesn't change.
我们想把这两个单独的句子合并成一句话,为此,需要把
句号改成逗号
,并将单词“But”中的“B”改为小写
。下例展示了如何用替换模式完成这项工作。
用R
命令可以由普通模式进入替换模式,然后就如例中所示,输入“,␣b
”替换原有的“.␣B
”字符。完成替换后,可以按< Esc>
键返回普通模式。如果你的键盘上有< Insert>
键,那么也可以用该键在插入模式和替换模式间切换
,不过并非所有的键盘都有这个键。
某些字符会使替换模式变得复杂化。以制表符为例,在文件中它以单个字符表示,但在屏幕上它却会占据若干列的宽度,此宽度由‘tabstop
’ 设置决定(参见 :h 'tabstop'
)。如果把光标移到制表符上,然后进入替换模式,那么输入的下一个字符将会替换制表符
。假设 ‘tabstop’ 选项设置为8(这是缺省值),那么该操作的结果就是把8个字符替换成了一个字符,这将大幅缩短当前行的长度。
不过Vim还有另外一种替换模式,称为虚拟替换
模式(virtualreplace mode)。该模式可由 gR
命令触发,它会把制表符当成一组空格进行处理
。假设把光标移到
一个占屏幕8列宽的制表符
上,然后切换到虚拟替换
模式,在输入前7个字符时,每个字符都会被插入制表符之前;最后,当输入第8个字符时,该字符将会替换制表符。
在虚拟替换模式中,我们是按屏幕上实际显示的宽度来替换字符的,而不是按文件中所保存的字符进行替换。这会减少意外情况的发生,因此建议在可能的情况下
尽量使用虚拟替换模式
。
Vim也提供了单次版本的替换模式及虚拟替换模式。r{char}
和gr{char}
命令允许覆盖一个字符,之后马上又回到普通模式(参见 :h r
)。
Vim的可视模式允许选中一块文本区域并在其上进行操作,从表面上看这应该很容易理解,因为大多数编辑软件都沿用此模式。然而,Vim的可视模式和其他软件的做法却截然不同,所以在本章的一开始,我们先深入了解可视模式(参见技巧20)。
Vim具有3种不同的可视模式,分别用于操作字符文本、行文本和块文本。我们将会探讨在这几种模式间切换的方法,并介绍一些更改选区边界的有用技巧(参见技巧21)。
也可以利用 .
命令来重复执行可视模式中的命令,然而只有在操作面向行的选区时,它才特别有用;而在操作面向字符的选区时,有时它无法达到我们的预期。我们将会看到,在这种情况下可能更适合用操作符命令。
列块可视模式非常特别,它允许在块状文本上进行操作。你会发现此功能有很多的用处,不过只着重介绍3个小技巧,展示其部分功能。
可视模式允许选中一个文本区域并在其上操作。尽管这看起来似乎很熟悉,但对于选中的文本,Vim的视角却有异于其他文本编辑器。
假设,我们暂时没有在Vim中工作,而是在网页里的文本框中输入了单词“March”,但发现应该输入的是“April”。因此,先用鼠标双击此单词,把它高亮选中后,按退格键删除它,然后再输入正确的月份。
可能你已经知道了,其实在本例中并没有必要按退格键。当单词“March”处于选中状态时,我们只需敲字母“A”,就会替换掉所选内容,清出地方来输入
“April”的剩余部分。尽管节省的按键次数有限,但毕竟积少成多。
如果你期待Vim的可视模式也能以这种方式工作,那你就会觉得意外了。顾名思义,可视模式仅仅是另外一种模式,也就是说在此模式中,每个按键都完成一种不同的功能。
你已经熟识的很多普通模式命令,它们在可视模式中也能完成相同的功能。我们仍可以把 h
、j
、k
及 l
当成光标键使用;也可以用f{char}
跳到当前行的某个字符上,然后用 ;
和 ,
命令相应地正向或反向重复此跳转;甚至还可以用查找命令
(以及 n / N
命令)跳转到匹配指定模式的地方。每次在可视模式中移动光标,都会改变高亮选区的边界
。
某些可视模式命令执行的基本功能与普通模式相同,但操作上有些细微的变化。例如,在这两种模式中, c
命令的功能是一样的,都是删除指定的文本并切换到插入模式。不过,要指定其操作的范围,二者的方式却不甚相同。在普通模式中,先触发修改命令,然后使用动作命令指定其作用范围。如果你还记得技巧12中讲过的内容,就会知道这个命令被称为操作符命令。然而,在可视模式中,要先选中选区,然后再触发修改命令。这种次序颠倒的方式对所有的操作符命令都适用(参见表2-1 Vim的操作符命令)。对大多数人来说,可视模式的做法感觉起来更自然。
让我们回顾一下前面遇到的那个简单例子,即把单词“March”修改为“April”。这一次,假设我们不是在网页上的文本框里,而是回到了舒适的Vim中。我们先把光标移到单词“March”的某个位置,然后执行
viw
来高亮选择这个词。现在不能直接输入单词“April”,因为这会触发 A 命令并把文本“pril”添加到行尾。
我们要换种做法,先用c
命令修改所选内容,把这个单词删掉并进入插入模式,然后就可以输入完整的“April”了。这种做法和最初在网页中所用的方式类似,只不过用的是c
键而不是退格键。结识
选择模式
在一个典型的文本编辑器环境中,当选中一段文本后,再输入任意可见字符时,这些选中的文本将会被删除。虽然Vim的可视模式未遵从此惯例
,但是其选择模式
(Select Mode)却按此方式工作。根据Vim的内置文档所述,选择模式“类似于Microsoft Windows的选择模式”(参见:h Select-mode
)。在此模式下,输入的可见字符会使选中的文本被删除,同时Vim会进入插入模式,并插入这个可见字符。
按< C-g>
可以在可视模式
及选择模式
间切换。切换后看到的唯一不同是屏幕下方的提示信息会在 “-- 可视 --”(– VISUAL –)及“–选择–”(–SELECT–)间转换。但是,如果在选择模式中输入任意可见字符,此字符会替换所选内容并切换到插入模式。当然,如果是在可视模式中,仍可以像往常一样用 c 键来修改所选内容。
如果你乐于接受 Vim 区分模式的特性,那么你应该很少会用到选择模式
。这一模式的存在,只是为了迎合那些想让Vim更像其他文本编辑器的用户。我只知道有一处地方会用到选择模式。有一个模拟 TextMate 的 snippet 功能的插件,它会用选择模式来高亮当前的占位符。
可视模式的3个子模式用于处理不同类型的文本。我们将在本节看到如何激活每种子模式,以及如何在它们之间切换。
Vim有3种可视模式。在面向字符的可视模式中,我们能够选择任意的字符范围,不论它是单个字符,还是位于一行内,或是跨若干行的指定字符范围,都没问题。该模式适用于操作单词或短语**。如果我们想对整行进行操作,可以改用面向行的可视模式**。而面向列块的可视模式则允许对文档中的列块进行操作。列块可视模式非常特别,所以会在技巧24、技巧25和技巧26中花大量篇幅对其进行介绍。
v
键是通往可视模式
的大门。在普通模式下,按 v
可激活面向字符的可视模式,按 V
(v和Shift键一起按)可激活面向行的可视模式
,而按 < C-v>
(v和Ctrl键一起按)则可激活面向列块的可视
模式,请参见下表中的汇总。
gv
命令是个有用的快捷键,它用来重选上一次由可视模式选择的文本范围。不管上个选区是面向字符的、面向行的,还是面向列块的,gv
命令都能够正确地工作。不过如果上次的选区被删除了,它也许会工作得不太正常。
命令 | 用途 |
---|---|
v | 激活面向字符的可视模式 |
V | 激活面向行的可视模式 |
< C-v> | 激活面向列块的可视模式 |
gv |
重选上次的高亮选区 |
可以在不同风格的可视模式间切换,方式与在普通模式下激活可视模式的方式相同。如果当前处于面向字符的可视模式,可以按 V 来切换到面向行的可视模式,或是用 < C-v>
来切换到面向列块的可视模式。然而,如果在面向字符的可视模式中再次按 v
,就会回到普通模式。所以,可以把 v
键当成在普通模式及面向字符的可视模式间转换的开关,V
及 < C-v>
键也一样可以在普通模式及其对应的可视模式间切换。当然了,你总是可以按 < Esc> 或
按键操作 | 用途 |
---|---|
< Esc> | 回到普通模式 |
v / V /< C-v> | 切换到普通模式(在对应的面向字符可视模式、面向行的可视模式和面向列块的可视模式中使用时) |
v | 切换到面向字符的可视模式 |
V | 切换到面向行的可视模式 |
< C-v> | 切换到面向列块的可视模式 |
O | 切换高亮选区的活动端 |
高亮选区的范围由其两个端点界定。其中一端固定,另一端可以随光标自由移动,可以用 o键
来切换其活动的端点。
在定义选区时,如果定义到一半,才发现选区开始的位置不对,此时用这个键会很方便,不用退出可视模式再从头开始,只需按一下
o
,然后重新调整选区的边界即可。下面的操作对此功能进行了演示。
当使用 .
命令重复对高亮选区所做的修改时,此修改会重复作用于相同范围的文本。本节将修改一个面向行的高亮选区,然后使用 .
命令重复此修改。
在可视模式中执行完一条命令后,会返回到普通模式,并且在可视模式中选中的文本范围也不再高亮显示了。那么,如果想对相同范围的文本执行另外一条可视模式命令,该怎么办?
假设如下Python代码fibonacci-malformed.py
的缩进有些问题。
def fib(n):
a, b = 0, 1
while a < n:
print a,
a, b = b, a+b
fib(42)
这段代码的每级缩进使用4个空格,首先对Vim进行配置,使之符合此缩进风格。
要想让 <
和 >
命令正常工作,需要把 ‘shiftwidth’
及‘softtabstop’
的值设为4,并启用 ‘expandtab’
选项。如果想了解这些配置是如何协同工作的,请查阅Vimcasts.org上的“Tabs and Spaces”主题。下面一行命令会完成上述设置。
➾ :set shiftwidth=4 softtabstop=4 expandtab
在这段缩进错误的Python代码中,while关键字下面的两行应该多缩进两级。可以高亮选择这两行,然后用 >
命令对它进行缩进,以修正其缩进错误。但此操作只增加一级缩进就返回普通模式了。
要解决此问题,一个办法是使用 gv
命令重选相同的文本,然后再次调用缩进命令。然而,如果你已经对Vim解决问题的方式有所领悟的话,脑海里应该会响起警钟。
当需要执行重复操作时,.
命令是最佳的解决方案。与其手动重选相同范围的文本并执行相同的命令,倒不如直接在普通模式里按 .
键。下面是具体的操作。
如果你善于计算的话,也许更乐意在可视模式中执行
2>
以便一步到位。不过我更喜欢用.
命令,因为它可以给我即时的视觉反馈。如果需要再次缩进的话,只需再按一次.
键即可;或者如果按的次数太多,导致缩进过深,按u
键就可以撤销多余的缩进。在技巧11中,已经用大量篇幅讨论过次数风格与重复风格之间的差异了。
在使用.
命令重复一条可视模式命令时,它操作的文本数量和上次被高亮选中的文本数量相同。对于面向行的高亮选区来说,这种做法往往符合我们的需要。但对于面向字符的高亮选区来说,这却会产生令人意外的结果。接下来将通过一个例子来说明这一点。
可视模式可能比Vim的普通模式操作起来更自然一些,但是它有一个缺点:在这个模式下,
.
命令有时会有一些异常的表现。可以用普通模式下的操作符命令来规避此缺点。
假设想把下面list-of-links.html
列表中的链接文字转换为大写格式。
<a href="#">onea>
<a href="#">twoa>
<a href="#">threea>
可以用 vit
来选择标签里的内容。vit
可被解读为高亮选中标签内部的内容(v
isually select i
nside the t
ag),其中,it
命令是一种被称为文本对象(text object)的特殊动作命令。我们将在技巧52中对其进行详细讲解。
在可视模式中,可以选定一个选区然后对其进行操作。本例中,可以使用
U
命令来把所选中的字符转换为大写(参见:h v_U
),具体操作如下图:
执行j.
命令,把光标移到下一行并重复上次的修改。此命令在第二行工作得很好,但如果再执行一次,最终就会得到这个看起来有点古怪的结果。
你看到发生什么了吗?当一条可视模式命令重复执行时,它会影响相同数量的文本
(参见:h visual-repeat
)。在本例中,最初的命令影响了一个由3个字母组成的单词。在第二行它依旧工作得很好,因为该行恰好也包含一个由3个字母组成的单词。但是,当我们想对一个由5个字母组成的单词重复此命令时,它只成功转换了其中的前3个字母,留下2个字母未被转换。
可视模式下的 U
命令有一个等效的普通模式命令:gU{motion}
(参见 :h gU
)。如果用此命令做第一处修改,就可以用点范式完成后续的修改。
这两种方式都只需要4次按键操作:vitU
及 gUit
,但其背后的含义却大相径庭。在可视模式采用的方式中,这4次按键可以被当作两个独立的命令。vit 用来选中选区,而 U 用来对选区进行转换。与之相反的是,gUit 命令可以被当成一个单独的命令,它由一个操作符(gU)和一个动作命令(it)组成。
如果想使点命令
能够重复某些有用的工作,那么最好要远离可视模式。作为一般的原则,在做一系列可重复的修改时,最好首选操作符命令,而不是其对应的可视模式命令。
这并不是说可视模式出局了,它仍然占有一席之地。因为并非每个编辑任务都需要重复执行,对一次性的修改任务来说,可视模式完全够用,并且尽管Vim的动作命令允许进行精确的移动,但有时要修改的文本范围的结构很难用动作命令表达出来,而处理这种情形恰恰是可视模式擅长的。
在任何编辑器中,我们都能够操作以行为单位的文本,但以列为单位进行文本操作就需要更为专业的工具了。Vim面向列块的可视模式就提供了这种能力,可以用它来对纯文本表格进行转换。
假设chapter-table.txt
有一个纯文本表格。
Chapter Page
Normal mode 15
Insert mode 31
Visual mode 44
我们想用管道符|
画一条竖线来隔开这两列文本,使之看起来更像一个表格。但是在此之前,要先减少两列之间的间隔,使它们不要分得这么开。用面向列块的可视模式可以完成这两处修改。
使用
< C-v>
进入列块可视模式,然后向下移动几行光标3j
,选中一列文本。接下来,按x
键删除此列,并用.
命令重复删除相同范围的文本。此操作多重复几次直到距右边差不多有两列的距离。
也可以不用.
命令,而是把光标向右移动两三次,把列选区扩展为块选区,而后只需删除一次即可。不过,我更喜欢在删除时看到即时的视觉反馈,然后再多次重复此操作。
现在,我们已经把所需的两列文本排列到了合适的位置,接下来就可以在这两列文本间画一条竖线了。先用gv
命令重选上次的高亮选区
,然后输入r|
, 用管道符替换此选区内的字符。
到了这一步,我们或许也想画一条横线来分隔表头及其下的内容。先快速复制顶行并粘贴一份副本(yyp
),然后用连字符替换该行内的所有字符(Vr-
)。
用列块可视模式可以同时往若干行中插入文本。列块可视模式不仅仅对表格数据有用,在编程时我们也时常受惠于此功能。
例如,对于以下CSS片段。sprite.css
li.one a{ background-image: url('/images/sprite.png'); }
li.two a{ background-image: url('/images/sprite.png'); }
li.three a{ background-image: url('/images/sprite.png'); }
假设已经把文件
sprite.png
从images/
目录移到了components/
目录,就需要修改每一行的内容,使其指向该文件的新位置。可以使用列块可视模式完成此工作。
整个过程看起来非常熟悉。先指定想要操作的选区,本例中的高亮选区恰好为方形。按c
键时,所有被选中的文本都消失了,同时进入插入模式。
在插入模式中输入单词“components”时,此单词只出现在顶行,下面的两行没什么变化。只有在按< Esc>
返回到普通模式后,才看到刚才输入的文本出现在下面这两行里。
在Vim列块可视模式
中,修改命令的表现或许有点怪,它看上去有点不一致。删除操作会同时影响所有被选中的行,但插入操作只影响顶行(至少在处于插入模式的期间)。其他文本编辑器也提供了类似的功能,但是它们会同时更新所有被选中的行,如果你已经习惯了这样的表现(就像我以前一样),那么你会发现Vim的实现不太完美。
不过在实践中,最终的结果没什么区别。因为处于插入模式的时间很短,所以没必要太过惊讶。
列块可视模式在操作由行列组成的方形代码块时表现得很好,然而,它并不仅限于操作方形的文本区域。
我们已经见过2_foo_bar.js
的JavaScript代码片段。
var foo = 1
var bar = 'a'
var foobar = foo + bar
这段代码有连续3行,每行的长度各不相同,而我们想在每行结尾添加一个分号。在技巧2中,使用
.
命令解决了此问题,不过,用列块可视模式也可以完成该任务。
在进入列块可视模式后,按$
键把选区扩大到每行的行尾。乍一看,人们也许会觉得这很难,因为每一行的长度都是不同的。然而在这个场景中,Vim知道我们是想把选区扩大到选中的这些行的结尾,它会让我们打破方形的限制,创建出一个右边界长短不一的文本选区。
确定好选区后,用A
命令就可以在每行的结尾添加内容(参见Vim对“i
”及“a
”键的约定)。此命令让我们进入插入模式,且使光标停留在顶行。处于插入模式期间,任何输入的内容只出现在顶行,然而一旦返回到普通模式,这些修改就会被扩散到其余选中的行上。
Vim 对“i”及“a”键的约定
Vim对于从普通模式切换到插入模式的命令有几个约定,i
命令和a
命令都完成此切换,并分别把光标置于当前字符之前或之后,I
命令和A
命令的表现类似,只是它们分别把光标置于当前行的开头和结尾。
Vim 对于从列块可视模式切换到插入模式的命令也遵从类似的约定。I
命令和A
命令都完成此切换,并分别把光标置于选区的开头和结尾。那i
和a
命令呢,它们在可视模式里干什么?
在可视模式及操作符待决模式
中,i
和a
键沿用一个不同的约定
。它们会被当作一个文本对象的组成部分,我们将在技巧52中深入探讨文本对象。如果你在列块可视模式里选中了一块区域,并且很奇怪为什么按i
键没进入插入模式,那么换用I
键试一下。
初时,先有 ed,
ed 为 ex 之父,
ex 为 vi 之父,
而 vi为 Vim 之父。
➤ The Old Testament of Unix
Vim的先祖是vi,正是vi开创了区分模式编辑的范例。相应的,vi奉一个名为ex的行编辑器为先祖,这就是为什么会有 Ex 命令。这些早期UNIX文本编辑器的血脉依旧流淌在现代Vim中,对某些基于行的编辑任务来说,Ex 命令仍然是最佳工具。在本章中,我们将学习如何使用命令行模式,这将为我们揭示 ex 编辑器的余风遗韵。
命令行模式会提示我们输入一条
Ex
命令、一个查找模式,或一个表达式。在本节,我们将结识一些操作缓冲区中的文本的Ex
命令,并学习一些可在此模式中使用的特殊按键映射项。
在按下
:
键时,Vim会切换到命令行模式。这个模式和shell下的命令行有些类似,可以输入一条命令,然后按< CR>
执行它。在任意时刻,都可以按< Esc>
键从命令行模式切换回普通模式。
出于历史原因,在命令行模式中执行的命令又被称为Ex
命令,参见Vim(及其家族)的词源。在按/
调出查找提示符或用访问表达式寄存器(参见技巧16)时,命令行模式也会被激活。本节介绍的一些技巧在这些不同的提示符下都适用,不过本节内容主要侧重于
= Ex
命令。
可以用Ex
命令读写文件(:edit
和:write
)、创建新标签页(:tabnew
)、分割窗口(:split
)、操作参数列表(:prev/:next
)及缓冲区列表(:bprev/:bnext
)。事实上,Vim为几乎所有功能都提供了相应的Ex命令(参见:h ex-cmd-index
可获得完整列表)。
本节主要关注那些用来编辑文本的 Ex
命令,5-1列出了其中最有用的一些命令。
在这些命令中,绝大部分都可指定操作的范围,将在技巧28中了解这意味着什么。:copy
命令对快速复制一行非常好用,这将在用:t
命令复制行中介绍。:normal
命令提供了一种便捷的方式来对指定范围内的行做相同的修改,这将在技巧30中介绍。
我们将在第10章学到更多关于 :delete
、:yank
及 :put
命令的知识。:substitute
命令和 :global
命令非常强大,所以每个命令都用单独的一章来介绍,详细内容请看第14章和第15章。
表5-1 操作缓冲区文本的 Ex 命令
命令 | 用途 |
---|---|
:[range]delete [x] | 删除指定范围内的行[到寄存器x中] |
:[range]yank [x] | 复制指定范围的行[到寄存器x中] |
:[line]put [x] | 在指定行后粘贴寄存器x中的内容 |
:[range]copy {address} | 把指定范围内的行拷贝到 {address} 指定的行之下 |
:[range]move {address} | 把指定范围内的行移动到 {address} 指定的行之下 |
:[range]join | 连接指定范围内的行 |
:[range]normal {commands} | 对指定范围内的每一行执行普通模式命令 {commands} |
:[range]substitute/{pattern}/{string}/[flags] | 把指定范围内出现{pattern}的地方替换为{string} |
:[range]global/{pattern}/[cmd] | 对指定范围内匹配{pattern}的所有行执行Ex 命令{cmd} |
在命令行模式中,键盘上的大部分按键都只是简单输入一个字符,这点与插入模式类似。只不过在插入模式中,文本被输入缓冲区里,而在命令行模式中,文本出现在命令行上。另外,在这两种模式中都可以用组合键触发命令。有些命令在插入模式和命令行模式中可以通用。例如,可以用 < C-w>
和 < C-u>
分别删除至上个单词的开头及行首,也可以用
或
来插入键盘上找不到的字符,还可以用 `{register} 命令把任意寄存器的内容插入命令行,就像在技巧15中见过的那样。然而,有些命令行模式中的组合键在插入模式中不存在,我们将在技巧33中结识几个这样的命令。
在命令行提示符下,可以使用的动作命令数量很有限。
和
光标键可以一次把光标向左或右移动一个字符,与我们已经习以为常的普通模式下的大量动作命令相比,这让人感觉极度受限。然而,正如我们即将在技巧34中看到的那样,Vim的命令行窗口提供了构造复杂命令所需的编辑能力。
有时使用
Ex
命令,能比用普通模式命令更快地完成同样的工作。举个例子,普通模式命令一般操作当前字符或当前行,而Ex
命令却可以在任意位置执行,这意味着无需移动光标就可以使用Ex
命令做出修改。但使Ex
命令脱颖而出的最让人赞叹的功能,是它们拥有能够在多行上同时执行的能力。
一般地说,Ex
命令操作范围更大,并且能够在一次执行中修改多行。或者可以概括为,Ex
命令影响的范围较广并且距离较远。
很多 Ex 命令可以用 [range]
指定要操作的范围。可以用行号、位置标记或是查找模式来指定范围的开始位置及结束位置。
Ex 命令的优点之一是它可以在某一范围内的所有行上执行。以下面这个简短的HTML文本practical-vim.html
作为示例。
Line 1
2 <html>
3 <head><title>Practical Vimtitle>head>
4 <body><h1>Practical Vimh1>body>
5 html>
我们将使用 :print
命令作为演示。这条命令只是简单地在Vim命令行下方回显指定行的内容,它不产生什么实际影响,不过可以用它来说明一个范围由哪些行构成。当然,可以试着把以下示例中的 :print
换成诸如 :delete
、:join
、:substitute
或 :normal
这样的命令,这样就能真切地感受到 Ex 命令是多么有用。
如果输入一条只包含数字的Ex 命令,那么 Vim 会把这个数字解析成一个地址,并把光标移动到该数字指定的行上。例如,运行下面的命令将跳到文件的首行。
➾ :1
➾ :print
如图:
此文件只包含5行内容,如果要跳到文件的末尾,既可以输入:5
,也可以用:$
我们在这里使用的是:p
,它是:print 命令的简写
。实际上,用不
着分开执行这两条命令,可以像下面这样把这两条命令合成一条
。
此命令会把光标移到第3行,然后显示该行的内容。记住,这里用:p
命令的目的只是进行讲解。如果执行的是:3d
命令,只需一条命令就可以跳到第3行并删除此行;而与之等效的普通模式命令,则要先执行3G
,再跟着执行dd
。因此,从这个例子可以看出,Ex 命令执行得要比普通模式命令更快。
迄今为止,地址只是被当成一个单独的行号,不过也可以用它来指定一个范围,如下例所示。
此例会打印第2行到第5行之间的每一行的内容(含第2行及第5行)。注意,运行完这条命令后,光标将停留在第5行。通常,一个范围具有如下的形式。
:{start},{end}
需注意的是 {start} 和 {end} 都是地址。到目前为止,我们已经看到过用行号
作为地址,然而很快就会看到也能用查找模式
或是位置标记
作为地址。
符号
.
代表当前行
的地址。因此,可以很容易地写出一个范围,用以代表从当前位置到文件末尾间的所有行。
符号%
也有特殊含义,它代表当前文件中的所有行。
这和运行:1, $p
是等效的。这种简写形式在和:substitute
命令一起使用时非常普遍。如下图:
上述命令让 Vim 把每行内的第一个“Practical”替换为“Pragmatic”,
我们将在第14章学习关于此命令的更多内容。
也可以用高亮选区选定一个范围,而不是用数字指定。如果先执行
2G
,再跟着执行VG
,就会选中如下一个高亮选区。
<html>
<head><title>Practical Vimtitle>head>
<body><h1>Practical Vimh1>body>
html>
如果现在按下
:
键,命令行上会预先填充一个范围:'<,'>
。这个范围看起来有点晦涩难懂,不过可以简单地把它理解为一个代表高亮选区的范围
。接下来可以输入一条 Ex 命令,使它在每个被选中的行上执行。
如果只是想对文件的部分内容执行:substitute
命令,用这种方式定义范围会很方便。
符号'<
是代表高亮选区首行的位置标记,'>
则代表高亮选区的最后一行(更多关于位置标记的内容,请参见技巧54),这些位置标记即使在退出可视模式后仍然存在。如果尝试在普通模式下直接运行:'<,'>p
,它会始终回显上一次高亮选区选中的内容。
Vim 也接受以模式pattern
作为一条 Ex 命令的地址,如下所示。
:/<html>/,/<\/html>/p
这个范围看起来比较复杂,但实际上它符合范围的一般形式 :{start},{end}
。在本例中,{start}
地址是模式 //
,而{end}
地址是 /<\/html>/
。换句话说,这个范围由 开标签所在的行开始,到对应闭标签所在的行结束。
在此例中,用地址 :2,5
也可以获得同样样的结果,并且这种表示方式更简洁,不过它也更不可靠。用模式指定范围的话,命令总是对整个范围进行操作,无论这个范围包含多少行都没问题。
假设想对位于
之间的每一行都运行一条 Ex 命令,但是不想包括
及
标签所在的行,那么可以为之加上偏移。:/<html>/+1,/<\/html>/-1p
➾ :{address}+n
如果 n 被省略,那么缺省偏移量为1。
{address}
可以是一个行号、一个位置标记,或是一个查找模式。
假设想对由当前行开始的特定几行执行一条命令,那么可以使用相对于当前行的偏移。➾ :2 ➾ :.,.+3p
符号
.
代表当前行,所以上例中的:.,.+3
相当于:2,5
。
定义范围的语法非常灵活,既可以混合搭配行号
、位置标记
以及查找模式
,也可以对它们加以偏移
。下表对用来构建 Ex 命令的地址及范围的符号进行了总结。
符号 | 地址 |
---|---|
1 | 文件的第一行 |
$ | 文件的最后一行 |
0 | 虚拟行,位于文件第一行上方 |
. | 光标所在行 |
'm | 包含位置标记m的行 |
'< | 高亮选区的起始行 |
'> | 高亮选区的结束行 |
% | 整个文件(:1,$ 的简写形式) |
第0行在文件中并不真实存在,但它作为一个地址,在某些特定场景下会很有用处。特别是在把指定范围内的行复制或移动到文件开头时,可以用它做 :copy {address}
及 :move {address}
命令的最后一个参数。将在接下来的两个技巧中看到这两条命令的应用实例。
在定义一个 [range]时,它总是代表一系列连续行,不过 :global
命令也可以在一系列非连续行上执行Ex 命令,我们将在第15章学习这方面的更多知识。
:copy
命令(及其简写形式:t
)让我们可以把一行或多行从文档的一部分复制
到另一部分,:move
命令则可以让我们把一行或多行移
到文档的其他地方。
使用购物清单shopping-list.todo
作为演示
Shopping list
Hardware Store
Buy new hammer
Beauty Parlor
Buy nail polish remover
Buy nails
这个购物清单还没完成,我们也要在五金商店(hardware store)买些钉子(nails)。为完成这个清单,将重用文件的最后一行,即在“Hardware Store”下面为之创建一份副本。可以用 Ex 命令 :copy
轻松地完成这项工作。
copy命令的格式如下(参见 :h :copy
)。
:[range]copy {address}
在此例中,[range] 是第6行,而 {address} 用的是符号 .
,它代表当前行。因此,可以把 :6copy.
命令解读为“为第6行创建一份副本,并放到当前行下方”。
:copy
命令可以简写为两个字母 :co
,也可以用更加简练的:t
命令,它是 :copy
命令的同义词。
为了更好地记忆,可以把该命令想成“复制到(copy TO
)”。下表展示了 :t
命令的一些应用实例。
命令 | 用途 |
---|---|
:6t. | 把第6行复制到当前行下方 |
:t6 | 把当前行复制到第6行下方 |
:t. | 为当前行创建一个副本(类似于普通模式下的 yyp) |
:t$ | 把当前行复制到文本结尾 |
:’<,’>t0 | 把高亮选中的行复制到文件开头 |
:t.
命令会创建一个当前行副本,另外一种做法则是用普通模式的
复制和粘贴命令(yyp
)来达到同样的效果。这两种复制当前行的技术有个需要关注的差别:yyp
会使用寄存器,:t.
则不会。
因此,当我不想覆盖默认寄存器中的当前内容时,有时会使用:t.
来复制行。
在上表中,也可以将
yyp
变化一下来复制想要的行,但不管怎样,这都需要一些额外的移动动作。先跳到想复制的行上(6G
),复制该行(yy
),快速跳回原先的位置(),然后再用粘贴命令(
p
)创建一个副本。由此可见,在复制距离较远的行时,:t
命令通常更加高效。
在 Ex 命令影响范围广且距离远 中,我们已经了解了这个一般规律,即普通模式命令适合在本地进行操作,Ex 命令则可以远距离操作。本节以实例印证了这一规律。
:move
命令看上去和 :copy
命令很相似(参见 :h :move
)。
:[range]move {address}
可以把它简写为一个字母 :m
。假设想把Hardware Store一节移到Beauty Parlor一节的下方,用 :move
就可以实现这一点。
在选中高亮选区后,只需简单地执行命令:'<,'>m$
即可。另外还有种做法,可以执行dGp
,此命令可以分解为:d
删除高亮选区,G
跳转到文件结尾,p
则粘贴刚刚删除的文本。
记住,'<,'>
代表了高亮选区
。因此可以很容易地选中另外一个高亮选区,然后重复执行:'<,'>m$
命令把选中的文本移到文件结尾。重复上次的 Ex 命令非常简单,只需按@:
即可(技巧31给出了另一个例子),所以这里采取的方式与使用普通模式命令相比,在重复执行时会更方便。
如果想在一系列连续行上执行一条普通模式命令,可以用
:normal
命令。此命令在与.
命令或宏结合使用时,只需花费很少的努力就能完成大量重复性任务。
想一下在技巧2中遇到过的例子,我们想在一系列行后添加一个分号。使用点范式让我们迅速完成了这项工作。但是在那个例子里,只需对连续的3行做此修改。如果不得不做50次同样的修改会怎么样呢?如果还用点范式的话,得按50次 j.
,总共得100次按键动作!
这里有一种更好的方法。将在下面文件的每行后都添加一个分号,以此作为演示。为节省空间,此处只列出了5行内容,然而你可以想象这里有50行,那么这种方法看起来就颇具诱惑了。
foobar.js
var foo = 1
var bar = 'a'
var baz = 'z'
var foobar = foo + bar
var foobarbaz = foo + bar + baz
接下来,不用一行一行地执行 .
命令,而是使用Ex 命令 :normal.
对整个范围内的所有行同时执行 .
命令。
:'<,'>normal .
命令可以解读为“对高亮选区中的每一行,对其执行普通模式下的 .
命令”。无论是操作5行还是50行文本,这种方法都能出色地完成任务,更棒的是我们甚至都不需要计算行数,在可视模式中选中这些行使我们摆脱了计数的负担。
这个例子使用 :normal
执行 .
命令,但是也可以用这种方式执行任意其他的普通模式命令。例如,可以用如下命令解决上面的问题。
➾ :%normal A;
符号
%
代表整个文件范围
,因此:%normal A;
告诉 Vim 在文件每行的结尾都添加一个分号。在做此修改时会切换到插入模式,但是在修改完后,Vim会自动返回到普通模式。
在执行指定的普通模式命令之前,Vim会先把光标移到该行的起始处
。因此在执行时,用不着担心光标的位置。例如,下面这条命令可以把整个JavaScript文件注释掉
。b ➾ :%normal i//
虽然用 :normal
命令可以执行任意的普通模式命令,但是我发现当它和 Vim 的重复命令结合在一起时,最为强大,既可以用 :normal.
应对简单的重复性工作,也可以用 :normal @q
应对较复杂的任务。
具体的实例参见技巧68和技巧70。
在 Ex 命令影响范围广且距离远 中,我们说过 Ex 命令可以一次修改若干行。 :normal
命令则让我们可以把具有强大表现力的 Vim 普通模式命令与具有大范围影响力的 Ex 命令结合在一起,这种结合真的是珠联璧合!
对本节所涉及问题的另外一种解决方案,请参见技巧26。
.
命令可以重复上次的普通模式命令。然而,如果想重复上次的 Ex 命令
,得使用@:
才行。知道如何回退上次的命令永远是有价值的,因此本节也会讨论这一点。
在第1章中,我们见识过如何用 .
命令重复上次的修改。但是,.
命令不会重复由 Vim 命令行中做出的修改。作为替代,可以用 @:
来重复上次的 Ex 命令(参见 :h @:
)。
例如,下面两条命令在遍历缓冲区列表的条目时非常有用,用:bn[ext]
可以在列表中逐项正向移动,而 :bp[revious]
命令进行反向移动(技巧37详细讨论了缓冲区列表)。假设缓冲区列表中有大约十几个条目,而我们打算逐个查看每个缓冲区,因此可以输入一次下面的命令。
➾ :bnext
然后再用
@:
重复执行此命令。留意一下这和运行宏的相似之处(参见通过执行宏来回放命令序列),另外也需注意,:
寄存器总是保存着最后执行的命令行命令(参见:h quote_:
)。在运行过一次@:
后,后面就可以用@@
命令来重复它。
假设我们按得忘乎所以,执行了太多次 @:
命令以致于错过了目标。那要怎样才能改变方向往回跳呢
?当然,可以执行 :bp
revious 命令,但是想想如果以后再次执行 @:
命令会发生什么?没错,它会反向遍历缓冲区列表,恰恰与最初的方向相反。这会把人搞糊涂的。
在这种情况下,更好的选择是使用
命令(参见技巧56)。每次运行 :bn
ext 命令(或用 @:
命令重复执行它)时,它都会在跳转列表中添加一条记录,而
命令会回到跳转列表的上条记录
可以执行一次 :bnext
,然后用 @:
重复任意多次;如果想往回跳,就用
命令。这样一来,如果接下来还想继续正向遍历
缓冲区列表,就可以继续用 @:
命令。请牢记技巧4中提到的口诀:执行、重复、回退。
Vim 为几乎所有功能都提供了相应的Ex 命令。虽然用 @:
总是可以重复上一条 Ex 命令,但如果想回退其影响,却没有这种直截了当的方式。用本节提到的
命令,也能够回退:next
、:cnext
、:tnext
等命令的执行结果;然而对于5-1中列出的Ex 命令,则要用 u
键才能撤销其影响。
如同在shell中一样,在命令行上也可以用
键自动补全命令。
Vim 在选取 Tab 补全的补全项时非常智能,它会检查命令行上已经输入的上下文,然后再构建合适的补全列表。例如,可以这样输入:
命令会让Vim 显示可用的补全列表(参见
:h c_CTRL-D
)。另外,如果多次按> 键,命令行上会依次显示
colder
、colorscheme
,然后再回到最初的col
,如此循环往复。
要想反向遍历补全列表,可以按。
假设想改配色方案,但是不太记得要用的配色方案的名称,可以用
命令列出所有的可用选项。
这一次,
基于可用的配色方案显示一个补全列表。如果想激活solarized
方案,只需输入字母“so”,然后按 Tab
键即可补全此命令。
在很多场景中,Vim的 Tab 补全都能做出正确的选择。如果输入了一个以文件路径作为参数的命令(如 :edit
或 :write
),那么
会用当前工作目录中的目录或文件名补全。在 :tag
命令中,它会自动补全标签名;而在 :set
及 :help
命令中,它可以补全 Vim 的每一个设置选项。
甚至在创建自定义 Ex 命令时,也能够定义该命令的 Tab
键补全行
为。要想了解更多,请查阅 :h :command-complete
。
当 Vim 只找到一个Tab 补全项时,它会直接使用整个补全项。但是如果 Vim 找到了多个补全项,那么会有几种做法。缺省情况下,首次按下 Tab 键时,Vim 会用第一个补全项补全,以后每按一下 Tab 键,就会依次遍历剩余的补全项。
调整 ‘wildmode’
选项可以自定义补全行为(参见 :h 'wildmode'
)。如果习惯用bash shell的方式工作,那么下面的设置会满足你的需要。
set wildmode=longest,list
如果习惯于 zsh 提供的自动补全菜单,或许会想试试这个。
set wildmenu
set wildmode=full
当 ‘wildmenu’
选项被启用时,Vim 会提供一个补全导航列表。可以按
、
或
正向遍历其列表项,也可以用
、
或
对其进行反向遍历。
即使是在命令行模式下,Vim 也始终知道光标位于何处以及哪个分割窗口处于活动状态。为节省时间,可以把活动窗口中的当前单词(或字串)插入命令行中。
在 Vim 的命令行下,
映射项会复制光标下的单词并把它插入命令行中。可以利用这一功能减少击键的次数。
假设想把下面这段代码
loop.js
中的变量 tally重命名为
counter。
当按下*
键时,光标会正向跳到下一处匹配项,不过光标始终停留在相同的单词上。接下来,可以输入cwcounter
对其进行修改。然后用 :substitute 命令完成其余的修改。由于光标已经在单词“counter”上了,因此无需再次输入它,而是直接用
映射项把它插入替换域。
:%s//
/g
这条命令看起来没省多少事,但是用两次按键就能插入一个单词不算太糟。此处也用不着输入查找模式,而这要感谢 *
命令。要知道为什么可以像上面这样将查找域留空,请参考技巧91。
用于插入光标下的单词,而如果想插入光标下的字串(参见技巧 49的说明),可以用
,更多细节请参见 :h c_CTRL-R_CTRL-W
。虽然本例是以 :substitute
命令作为示例的,但实际上这些映射项可用于任意 Ex
命令。
这里介绍另一种应用场景。试着打开你的 vimrc 文件,把光标移到其中的一项设置上,然后输入 :help
,就可以查阅该设置的文档了。
Vim 会记录命令行模式中执行过的命令,并提供了两种方式回溯这些命令,用光标键回滚之前的命令或调出命令行窗口查看先前的命令。
Vim 会记录命令行模式下的命令历史,并且可以很容易地回溯之前的命令,因此对于比较长的 Ex
命令来说,不用在命令行中多次输入它。
先按
:
键切换到命令行模式,在保持提示符为空的情况下按键,此时最后执行的那条Ex 命令就会被填充到命令行上。再接着按
键,就可以回到更早的 Ex 历史命令;按
键,则会沿相反方向滚动。
现在,尝试先输入 :help
,然后按
键遍历之前的 Ex
命令。这一次,Vim不会显示所有的历史命令,而是会对列表进行过滤
,只有以单词“help”开头的 Ex 命令才会被包含在列表中。
Vim 缺省会记录最后20条命令,对内存越发便宜的现代计算机来说,保存更多历史命令只是小菜一碟,因此可以修改 ‘history
’选项,以提高其保存的上限。可以试着把下面这行内容加入 vimrc 文件。
set history=200
命令历史不仅是为当前编辑会话记录的,这些历史即使在退出Vim再重启之后仍然存在 (参见
:h viminfo
),因此提高历史记录的数目非常有价值。
Vim 不仅会记录 Ex
命令的历史,还会为查找命令单独保存一份历史记录。在按 /
调出查找提示符后,用
和
键可以正向或反向遍历之前的查找记录。从本质上讲,查找提示符只是命令行模式的另一种形式。
像插入模式一样,命令行模式适合从头开始构建命令,但它却不是一个编辑文本的好地方。
假设我们正在写一个简单的Ruby脚本,然后发现每做出一个修改时,都会执行下面两条命令。
➾ :write
➾ :!ruby %
在接连执行了几次这两条命令后,我们意识到可以简化工作过程,把这两条命令合为一条。这样,以后就可以从历史中选择该完整命令并再次执行。
➾ :write | !ruby %
这些命令都已经在历史中了,所以不必从头输入整条命令。但要怎样才能把历史中的两条记录合并成一条呢?请输入 q:
,先结识一下命令行窗口
(参见 :h cmdwin
)。
命令行窗口就像是一个常规的 Vim 缓冲区,只不过它的每行内容都对应着命令历史中的一个条目。可以用
k
及j
键在历史中向前或向后移动,也可以用 Vim 的查找功能
查找某一行。
在按下> 键时(不管在窗口里哪个模式,按键
立即执行该行命令),
会把当前行的内容当成Ex命令加以执行
。
命令行窗口的好处在于它允许使用 Vim 完整的、区分模式的编辑能力来修改历史命令。可以用任何习以为常的动作命令进行移动,也可以在高亮选区上操作,或是切换到插入模式
中,甚至还能对命令行窗口中的内容执行 Ex 命令。
在按
q:
调出命令行窗口
后,可以像下面这样解决问题。
修改完后,按 就会执行 :update | !ruby% 命令,就好像在
命令行输入了这条命令一样。
下图使用了
q:
进入命令行窗口,以便于修改命令行命令,从而实验:2s/counter/update
和:2s/counter/update/g
的区别。
当命令行窗口处于打开状态
时,它会始终拥有焦点。这意味着,除非关闭命令行窗口,否则无法切换到其他窗口
。要想关闭命令行窗口,可以执行 :q
命令(就像关闭普通 Vim 窗口那样),或是按
。
在命令行窗口内按
时,该命令在活动窗口的文中执行。活动窗口是指在
调出命令窗口前,处于活动状态的那个窗口
。当命令行窗口处于打开状态时,Vim 并不会提示哪个窗口是活动窗口,因此如果使用了分割窗口,就需要特别留意。
假设正在命令行上构建一条 Ex
命令,做到一半时,才意识到需要更强大的编辑能力,这时该怎么办呢?当处于命令行模式下
时,可以用
映射项切换到命令行窗口
中,此前已经输入命令行上的内容仍然会得以保留。下表总结了打开命令行窗口的几种方式。
命令 | 动作 |
---|---|
q/ | 打开查找命令历史的命令行窗口 |
q: | 打开 Ex 命令历史的命令行窗口 |
|
从命令行 模式切换到命令行窗 口 |
q:
命令和 :q
命令很容易混淆。我敢肯定我们都曾经不小心打开过命令行窗口,而实际上我们只是想退出 Vim。这的确让人羞赧,因为这个功能是如此的有用,但是很多人在他们第一次(意外)遭遇它时却感觉很沮丧。要看命令行窗口的另一个应用实例,请跳到技巧85。
不用离开Vim就能方便地
调用外部程序
。更棒的是,还可以把缓冲区的内容
作为标准输入发送给一个外部命令
,或是把外部命令
的标准输出导入缓冲区
里。
本节讨论的命令在终端 Vim 中
工作得最好。如果在运行 GVim(或
MacVim),那么命令运行得也许没那么顺畅。这没什么好奇怪的,如果 Vim 自身在 shell 里运行,那把工作委派给 shell 也会容易得多。
GVim 在某些其他方面做得更好一些,但是终端 Vim在这件事上则更有优势。
在 Vim 的命令行模式中,给命令加一个叹号前缀(参见 :h :!
)就可以调用外部程序。例如,如果想查看当前目录
的内容,可以运行下面的命令。
➾ :!ls
《 duplicate.todo loop.js
emails.csv practical-vim.html
foobar.js shopping-list.todo
history-scrollers.vim
Press ENTER or type command to continue
注意区分 :!ls
和 :ls
的不同之处。前者调用的是 shell 中的 ls
命令,而 :ls
调用的是 Vim的内置命令
,用来显示缓冲区列表的内容
。
在 Vim 的命令行中,符号 %
代表当前文件名(参见 :h cmdline-special
)。在运行那些操作当前文件
的外部命令时,可以使用它。例如,如果正在编辑某个Ruby 文件,那么可以用下面的方式执行此文件。
➾ :!ruby %
Vim 也提供了一组文件名修饰符,让我们可以从当前文件名中提取出诸如文件路径或扩展名之类的信息(参见 :h filename-modifiers
),技巧45中有一个使用这些修饰符的例子。
:!{cmd}
这种语法适用于执行一次性命令,但是如果想在 shell 中执行几条命令要怎么做?对于这种情况,可以执行 Vim 的 :shell 命令来启动一个交互的 shell 会话
(参见 :h :shell
)
➾ :shell
➾ $ pwd
《 /Users/drew/books/PracticalVim/code/cmdline_mode
➾ $ ls
《 duplicate.todo loop.js
emails.csv practical-vim.html
foobar.js shopping-list.todo
history-scrollers.vim
➾ $ exit
`用 exit 命令可以退出此 shell 并返回 Vim`
把Vim置于后台
:shell
命令是 Vim 提供的一个功能,它可以切换到一个交互 shell中。但是,如果Vim自身是在终端中运行的,那么也能直接访问终端内置的 shell 命令。例如,bash
shell支持作业控制,让我们可以暂停一个作业,把它放到后台,然后在稍后某个时间再把它调回前台继续运行。
假设正在 bash shell 中运行 Vim,然后需要执行一些 shell 命令。可以先按Ctrl-z
挂起Vim 所属的进程,并把控制权交还给bash。此时 Vim 进程在后台处于挂起状态,让我们可以像往常一样与 bash 会话进行交互。运行下面这条命令可以查看当前的作业列表。➾ $ jobs 《[1]+ Stopped vim
在 bash 中,可以用
fg
命令唤醒一个被挂起的作业
,把它移到前台。这会让 Vim 恢复成挂起前的状态。Ctrl-z
和fg
命令比Vim 提供的:shell
和exit
命令更加方便快捷。要想了解更多信息,请运行man bash
,然后阅读作业控制(job control
)一节。
在用 :!{cmd}
时,Vim 会回显 {cmd} 命令的输出
。如果命令的输出很少或没有输出,这工作得很好;但如果命令会产生大量输出,这样回显用处不大。另外一种做法是可以用 :read !{cmd}
命令,把{cmd} 命令的输出读入当前缓冲区中(参见 :h :read!`` )。
:read !{cmd}
命令让我们把命令的标准输出重定向到缓冲区
。正如你所期望的一样,:write !{cmd}
做相反的事。它把缓冲区内容作为指定 {cmd} 的标准输入
(参见 :h :write_c
),跳到技巧46可以看到此功能的一个应用实例。
根据叹号在命令行上的位置不同,它的含义也不大相同。比较以下命令。
➾ :write !sh
➾ :write ! sh
➾ :write! sh
前两个命令都会把缓冲区的内容传给外部的
sh
命令作为标准输入, 而最后一条命令调用:write!
命令把缓冲区内容写到一个名为sh
的文件.
这里的叹号会让Vim 覆盖任何已存的 sh 文件。正如你看到的那样,叹号放的位置不同,命令的作用也大相径庭。因此,在构建这类命令时要多加小心。
:write !sh
命令的作用是在shell中执行
当前缓冲区中的每行内容
,查阅:h rename-files
可看到该命令的一个绝佳示例。
当给定一个范围时,:!{cmd} 命令就具有了不同的含义。由[range] 指定的行会传给 {cmd} 作为标准输入,然后又会用 {cmd}的输出覆盖 [range]内原本的内容。换一种说法就是 [range] 内的文本会被指定的 {cmd} 过滤(参见 :h :range!
)。Vim 把过滤器定义为“一个由标准输入读取文本,并对其进行某种形式的修改后输出到标准输出的程序”。
emails.csv
first name,last name,email
john,smith,[email protected]
drew,neil,[email protected]
jane,doe,[email protected]
作为演示,将用外部的 sort 命令对下列 CSV 文件中的记录进行排序。
我们想基于第二个字段“姓氏”来重排这些记录。可以用 -t','
参数告诉 sort 命令,这些记录以逗号分隔,然后再用 -k2
参数指定按第二个字段进行排序。
:2,$!sort -t',' -k2
此行命令在gvim我运行失败,终端vim运行成功:
现在 CSV 文件中的内容就是按姓氏排序的了。
Vim 提供了一种方便的快捷方式来设置 :[range]!{filter}
命令中的范围。可以用 !{motion}
操作符切换到命令行模式,并把指定{motion} 涵盖的范围预置在命令行上(参见 :h !
)。例如,如果把光标移到第2行,然后执行 !G
,Vim 就会打开命令行并把范围 :.,$!
预置在命令行上。虽然此后仍需输入剩下的 {filter}
命令,但这毕竟节省了部分工作。
在 Vim中操作时,可以很方便地调用shell 命令。下表选取了最有用的一些调用外部命令的方式。
命令 | 用途 |
---|---|
:shell | 启动一个shell (输入exit返回 Vim) |
:!{cmd} | 在shell 中执行 {cmd} |
:read !{cmd} | 在shell 中执行 {cmd} ,并把其标准输出插入光标下方 |
:[range]write !{cmd} | 在 shell 中执行 {cmd} ,以 [range] 作为其标准输入 |
:[range]!{filter} | 使用外部程序 {filter} 过滤指定的 [range] |
Vim 对某些外部命令会另眼相待。例如,make
及 grep
在 Vim 中都有包装命令,这些命令不仅执行起来更方便,而且Vim会将它们的输出解析、导入 quickfix
列表中。将在第17章和第18章用很大篇幅介绍这两条命令。
如果要执行一连串Ex命令,可以把它们置于脚本
之中,从而节省工作量。当再想执行那一组命令时,只需加载脚本文件即可,而无需逐条输入这些命令。
以下内容来源于Vimcasts.org归档网页中前两部主题的链接。
vimcasts/episodes-1.html
<ol>
<li>
<a href="/episodes/show-invisibles/">
Show invisibles
a>
<li>
<li>
<a href="/episodes/tabs-and-spaces/">
Tabs and Spaces
a>
li>
ol>
我们想把其内容转成纯文本格式,标题在前,URL在后。
vimcasts-episodes-1.txt
Show invisibles: http://vimcasts.org/episodes/show-invisibles/
Tabs and Spaces: http://vimcasts.org/episodes/tabs-and-spaces/
假设需要对一组格式相似的文件进行这种转换,来看一下几种不同的操作方法。
其实用一条 :substitue
命令就可以实现这种格式转换,不过我更倾向于用几条小命令来完成。以下Ex命令序列就是一种可行的方案。
➾ :g/href/j
➾ :v/href/d
《 8 fewer lines
➾ :%norm A: http://vimcasts.org
➾ :%norm yi"$p
➾ :%s/\v^[^\>]+\>\s//g
不理解这些命令也没关系,这不会影响你对本技巧的学习。不过如果你感兴趣的话,下面是对这些命令的简要介绍。 :global
命令和:vglobal
命令结合在一起使用,用于把此文件缩减成两行,其中包含了我们所需要的内容,只不过前后次序是颠倒的(技巧 99);而:normal
命令会在行尾加上 Vimcast 网站的根链接(技巧30);最后的:substitute
命令会删除 标签。就像我常说的那样,理解命令的最佳途径就是自己实践一下。
除了可以逐条执行命令,还可以把它们存成一个文件,比如存为batch.vim(使用扩展名 .vim可以使 Vim显示正确的语法高亮)。文件中的每一行都对应前文中的一条 Ex 命令。在这种情况下,
不必
为每一行加上前缀字符 :
。
在把 Ex 命令保存到文件时,我个人更倾向于使用命令的全名,因为此时更关注脚本的易读性,而不是节省按键次数。
batch.vim
global/href/join
vglobal/href/delete
%normal A: http://vimcasts.org
%normal yi"$p
%substitute/\v^[^\>]+\>\s//g
可以用 :source
来执行batch.vim
脚本(参见 :h source
)。脚本中的每一行都会被当成一条 Ex 命令执行,就像在 Vim 的命令行中输入这些命令一样。在之前的场景中,你也许已经见识过 :source
命令了:它常用于在运行时加载vimrc文件
(更多信息,请参见将配置信息存至vimrc文件)。
我建议你亲自试一下。这些代码可以从位于Pragmatic Bookshelf网
站的Practical Vim主页下载。进入cmdline_mode 目录,就可以看到
batch.vim 以及episodes-1.html,然后再打开 Vim。
➾ $ pwd
《 ~/dnvim2/code/cmdline_mode
➾ $ ls *.vim
《 batch.vim history-scrollers.vim
➾ $ vim vimcasts/episodes-1.html
现在就可以执行此脚本了:
:source batch.vim
仅用这一条命令,就可以执行batch.vim
中的所有Ex命令了。如果你
改变了主意,只需按下 u
键即可让文档完好如初。
如果脚本只执行一次,那么把 Ex 命令存成文件没有多大意义。只有想多次运行一组 Ex 命令,这一技巧才彰显其价值。
随书提供的代码例库包含了一些格式与episodes-1.html相同的文件。请确保在启动Vim之前切换到cmdline_mode目录。
➾ $ pwd
《 ~/dnvim2/code/cmdline_mode
➾ $ ls vimcasts
《 episodes-1.html episodes-2.html episodes-3.html
➾ $ vim vimcasts/*.html
使用通配符启动Vim时,匹配该通配符的所有文件会被加入 Vim 的参数列表里。可以一个个地遍历这些文件,逐一执行batch.vim。
➾ :args
《 [vimcasts/episodes-1.html] vimcasts/episodes-2.html vimcasts/episodes-3.html
➾ :first
➾ :source batch.vim
➾ :next
➾ :source batch.vim
《 etc.
不过更棒的方法是使用 :argdo
命令
➾ :argdo source batch.vim
只需这一条命令,就可以对参数列表里的每个文件执行 batch.vim
中的Ex 命令了。
VIM中arg和argdo的使用介绍
VIM中arg和argdo的使用介绍
我之所以用几种不同的 Ex 命令来展示这一技术,只是为了说明这一技巧适用的可能性。在实践中,如果发现要一遍又一遍地执行某几条:substitute
命令时,我常常会用脚本完成。在执行完batch.vim
后,我通常会把它删掉;但如果我认为将来可能会再用到的话
,也会将其纳入版本控制
。
在本书的这一部分,我们将学习如何使用文件及缓冲区。Vim允许
在一个编辑会话中编辑多个文件
,既可以每次显示一个文件,也可以把工作区分成若干分割窗口或标签页,每个窗口或标签页包含一个独立的缓冲区。另外,还会看到在Vim中打开文件的几种不同方式,并掌握一些方法来解决无法把缓冲区保存到文件
的问题。
Vim允许同时在多个文件上工作。缓冲区列表记录了一次编辑会话中打开的所有文件。在技巧37中将学习如何操作此列表,并了解文件
与缓冲区
的区别。
参数列表
是缓冲区列表的强力补充。在技巧38中,将看到如何使用:args
命令把缓冲区列表中的文件分组,此后,就可以遍历这个参数列表,或是用:argdo
命令在列表中的每个文件上执行 Ex 命令。
Vim允许把工作区划分成窗口,技巧40介绍了具体的做法。在接下
来的技巧41中,还将看到如何利用 Vim 的标签页来把分割窗口组织到
一起。
在一次编辑会话中,可以打开多个文件。用Vim的缓冲区列表可以对这些文件进行管理。
就像其他任一文本编辑器一样,Vim允许读取、编辑文件,并保存修改。在工作过程中,我们通常会说“我们正在编辑一个文件”,但真实情况并不是这样,我们编辑的只是文件在内存中的映像,也就是Vim术语中的“缓冲区”。
文件是存储在磁盘上的,而缓冲区存在于内存中
。当Vim打开一个文件时,该文件的内容被读入一个具有相同名字的缓冲区。
刚开始,缓冲区的内容和文件的内容完全相同,
但当对缓冲区做出修改时,二者的内容就会出现差别。如果决定保留这些修改,就可以再把缓冲区的内容写回到文件里。
绝大多数Vim命令都用来操作缓冲区,不过也有一些命令针对文件进行操作,这当中包括:write
、:update
及:saveas
命令。
Vim允许同时在多个缓冲区上工作。先在shell里用下面的命令打开
几个文件。
➾ $ cd code/files
➾ $ vim *.txt
《 2 files to edit
*.txt
通配符会匹配当前目录下的两个文件 a.txt 和 b.txt,因此上面的命令会让 Vim 打开这两个文件。当Vim启动时,它会显示一个窗口,窗口内的缓冲区对应第一个文件。虽然另一个文件当前不可见,但其内容已经被载入一个后台的缓冲区了
,通过下面的命令可以看到这一点。
➾ :ls
《 1 %a "a.txt" line 1
2 "b.txt" line 0
:ls 命令会列出所有被载入内存中的缓冲区的列表(参见 :h :ls
)。用 :bnext
命令可以切换到列表中的下一个缓冲区。
➾ :bnext
➾ :ls
《 1 # "a.txt" line 1
2 %a "b.txt" line 1
%
符号指明哪个缓冲区在当前窗口中可见
,#
符号则代表轮换文件。按可以在
当前文件
和轮换文件
间快速切换,在本例中,按一次会切换到a.txt,再按一次,就又回到 b.txt 了。
可以用4条命令来遍历缓冲区列表。
:bprev
和:bnext
在列表中反向或正向移动,每次移动一项;:bfirst
和:blast
则分别跳到列表的开头和结尾。
我使用了Tim Pope在 unimpaired.vim 插件中定义的下列按键映射。
nnoremap <silent> [b :bprevious<CR>
nnoremap <silent> ]b :bnext<CR>
nnoremap <silent> [B :bfirst<CR>
nnoremap <silent> ]B :blast<CR>
Vim 已经使用 [
和 ]
键作为一系列相关命令的前缀了(参见 :h [
),因此上面这些按键映射的风格与其一致。除了上面这些外,unimpaired.vim
还定义了其他一些类似的映射项,分别用来遍历参数列表([a
和 ]a
)、quickfix
列表([q
和 ]q
)、位置列表([l
和 ]l
)以及标签列表([t
和 ]t
)。你自己去看看吧。
:ls
列表的开头有一个数字,它是在缓冲区创建时由 Vim 自动分配的编号。
可以用
:buffer N
命令直接凭编号跳转到一个缓冲区(参见:h :b
),或是用更直观的:buffer {bufname}
格式实现同样的功能。{bufname}
只需包含文件路径中足以唯一标识此缓冲区的字符即可。
如果输入的字符串匹配了不止一个缓冲区列表中的条目,就可以用Tab 补全的方式在这些条目中选择(参见技巧32)。
:bufdo
命令允许在:ls
列出的所有缓冲区上执行Ex 命令(参见:h:bufdo
)。不过在实际应用中,我发现:argdo 更加实用
,将在技巧38中结识这条命令。
每次打开一个文件时,Vim 就会创建一个新的缓冲区。在第7章中,将学到一些打开文件的方法。如果想删除缓冲区,可以用:bdelete
命令,命令格式如下。
:bdelete N1 N2 N3
:N,M bdelete
删除一个缓冲区并不会影响缓冲区关联的文件,而只是简单地把该文件在内存中的映像 删掉。如果想删除编号5~10(包含5和10)的缓冲区,可以执行
:5,10bd
;然而,如果想要 保留编号为8的缓冲区,就只能用:bd 5 6 7 9 10
了。
缓冲区的编号由 Vim 自动分配,没有办法手动改变此编号。因此,如果想删除一个或多个缓冲区,先得进行一番查找以便找出它们的编号,而这一过程会比较耗时。因此,除非有充足的理由要删除某个缓冲区,否则我才不会自找麻烦。这样一来,:ls
列表中的文件就是我在此编辑会话中打开的所有文件。
Vim 内置的
缓冲区管理功能缺乏灵活性
。如果想对缓冲区进行组织,使其满足工作过程的需要,使用缓冲区列表并不是最佳选择。相反,最好是把工作区划分成多个分割窗口、标签页,或是使用参数列表
。接下来的几个技巧将会介绍这些内容。
参数列表
易于管理,适用于对一批文件进行分组,使其更容易访问。用:argdo
命令可以在参数列表中的每个文件上执行一条 Ex 命令
。
首先用Vim打开一些文件:
➾ $ cd code/files/letters
➾ $ vim *.txt
《 5 files to edit
在技巧37中,我们已经看到过 :ls
命令会列出缓冲区列表。现在,再让我们看看参数列表。
➾ :args
《 [a.txt] b.txt c.txt. d.txt e.txt
参数列表记录了在启动时作为参数传递给Vim的文件列表。在本例中,只用了一个参数
*.txt
。然而,shell会对*
通配符进行扩展,使其匹配5个文件,这5个文件已经在参数列表中看到了。输出中的“[]
”字符则指明了参数列表中的哪个文件是活动文件
。
与 :ls
命令显示的列表相比,:args
命令的输出比较简陋。如果你知道参数列表是 vi 的一个功能,而缓冲区列表是 Vim 引入的增强功能,就不会觉得奇怪了。但是,请给参数列表一个表现的机会,你会发现它是缓冲区列表的一个强力补充。
就像其他许多功能一样,Vim 的参数列表功能也被增强了,只是名字还沿用原来的而已。实际上,可以在任意时刻改变参数列表的内容
,就是说 :args
列表并不一定反映启动Vim时所传的参数。千万别被表面的名字给唬住了!(同样的情况请见“‘:compiler
’与‘:make
’不仅限于编译型语言”。)
填充参数列表
当不带参数运行 :args
命令时,它会打印当前参数列表的内容。另外,也可以用下列格式来设置参数列表的内容(参见 :h :args_f
)。
:args {arglist}
{arglist}
可以包括文件名、通配符,甚至是一条 shell 命令的输出结果。我们将使用 files/mvc
目录作为演示,可以在随本书发布的源文件中找到此目录。如果你打算照着做的话,请先切换到此目录,然后再启动 Vim。
➾ $ cd code/files/mvc
➾ $ vim
要了解此目录树的结构,请参见技巧42。
填充参数列表最简单的方式是逐一指定文件的名字。
➾ :args index.html app.js
➾ :args
《 [index.html] app.js
如果只是想在列表里增加几个文件,用这种方式就行了。它的好处是可以指定文件的次序,但它也有一个缺点,那就是手动增加文件的工作量比较大。如果想往参数列表中加入大量文件,那么使用通配符会快得多。
通配符是一个占位标记,它代表了可用于文件或目录名称的字符。*
符号用于匹配0个或多个字符,但它的范围仅局限于指定的目录,而不会递归其子目录
(参见 :h wildcard
);**
通配符也匹配0个或多个字符,但它可以递归进入指定目录的子目录(参见 :h starstar-wildcard
)。
可以把这两种通配符结合起来用,并加上部分文件名或目录名,以此构造一个模式(即所谓的 glob模式
),然后用它来匹配我们感兴趣的文件集合。下表总结了在 files/mvc 目录中满足指定glob模式
的一些有代表性的文件(并未列出全部)。
Glob模式 | 所匹配的文件 |
---|---|
:args *.* |
index.html app.js |
:args **/*.js | app.js lib/framework.js app/controllers/Mailer.js …etc |
:args **/*.* |
app.js index.html lib/framework.js lib/theme.css app/controllers/Mailer.js …etc |
就像可在 {arglist}
中使用多个文件名一样,也可以使用不止一个 glob
模式。如果想构造一个只包含 .js
和 .css
文件,但不包含其他文件类型的参数列表,可以采用以下glob模式。
➾ :args **/*.js **/*.css
在写这本书时,有时我想按照目录顺序把每一章的文件名加入参数列表中。为达到这一目的,我维护了一个文本文件,每行保存一个文件名。下面的内容就节选自此文本文件。.chapters
the_vim_way.pml
normal_mode.pml
insert_mode.pml
visual_mode.pml
然后,就可以执行下面的命令,用该文件的内容填充参数列表。
:args `cat .chapters`
Vim 会在 shell 中执行反撇号(
'
)括起来的命令,然后把 cat 命令的输出作为 :args命令的参数
。虽然本例是用cat
命令获取.chapters 文件
的内容,
但实际上可以用这种方式执行任意可用的shell命令。然而,此功能并不是所有系统都可用,更多细节请查阅:h backtick-expansion
。
参数列表比缓冲区列表更容易管理,这使其成为对缓冲区进行分组的理想方式。使用
:args {arglist}
命令,一下就可清空并重新设置参数列表,接着可以用:next 及 :prev 命令
遍历参数列表中的文件,或是用:argdo
命令在列表中的每个缓冲区上执行同一条命令
。
我的感觉是:缓冲区列表就像是我的计算机桌面(desktop),它永远是乱七八糟的;参数列表则像一个整洁的独立工作区(workspace),只有在需要扩展空间时才会用到它。我们将会看到其他一些使用参数列表
的例子,请参见技巧36和技巧70。
Vim 对被修改过的缓冲区会给予特殊对待,以防未加保存就意外退出。本节将介绍如何隐藏一个被修改过的缓冲区,以及如何在退出 Vim 时处理隐藏缓冲区。
在 shell 中,运行如下命令启动 Vim。
➾ $ cd code/files
➾ $ ls
《 a.txt b.txt
➾ $ vim *.txt
《 2 files to edit
首先,对 a.txt 做些修改,按 Go
在缓冲区的结尾增加一个空行。先不要保存修改,查看当前的缓冲区列表。
➾ :ls
《 1 %a + "a.txt" line 1
2 "b.txt" line 0
缓冲区 a.txt
前有一个 + 号
,表示这个缓冲区被修改过
了。如果现在保存文件,缓冲区的内容会被写入磁盘
里,而 + 号也会消失
了。但是我们先不急着保存,而是试着切换一下缓冲区。
➾ :bnext
《 E37: No write since last change (add ! to override)
此时,Vim 会弹出一条错误信息,说当前缓冲区中有未保存的改动。让我们试一下括号中的建议,在上述命令的结尾加一个叹号
:
➾ :bnext!
➾ :ls
《 1 #h + "a.txt" line 1
2 %a "b.txt" line 1
叹号会强制 Vim 切换缓冲区,即使当前缓冲区中有未保存的修改,也会继续切换。如果现在再运行 :ls 命令,就会发现 b.txt 被标记为 a,表示它当前是
活动缓冲区(active)
,a.txt 则被标记为 h
,表示它是一个隐藏缓冲区
(hidden)。
当一个缓冲区被隐藏后,Vim允许我们像往常一样工作。可以打开其他缓冲区,对其进行修改、保存等,没有任何不同。也就是说,一直到尝试退出编辑会话前,一切如常。然而,当想关闭编辑会话时,Vim就会提醒某个缓冲区中有未保存的修改。
➾ :quit
《 E37: No write since last change (add ! to override)
E162: No write since last change for buffer "a.txt"
Vim 会把第一个有改动的隐藏缓冲区载入当前窗口,这样就可以决定如何处理它。如果要保留修改,可以执行
:write
命令把缓冲区保存到文件;如果想摒弃此修改,可以执行:edit!
,重新从磁盘读取此文件
,这会用文件的内容覆盖缓冲区中的内容
。当缓冲区内容与磁盘文件一致后,可以再次尝试执行 :quit 命令了。
如果会话里有不止一个被修改过的隐藏缓冲区,那么每次执行:quit
命令时, Vim 都会激活下一个未保存的缓冲区。同样的,可以用 :write
及:edit!
来保存或摒弃此修改。当没有其他窗口和隐藏缓冲区时,:q
命令会关闭 Vim。
如果想退出 Vim 而
不想检查未保存的修改
,可以执行:qall!命令
;如果想保存所有有改动的缓冲区
而无需逐个检查,可以用
:wall
命令。
表6-1对所有处理隐藏缓冲区的方式进行了总结。
命令 | 用途:w[rite] 把缓冲区内容写入磁盘 |
---|---|
:e[ dit] ! |
把磁盘文件内容读入缓冲区(即回滚所做修改) |
:qa[ ll] ! |
关闭所有窗口,摒弃修改而无需警告 |
:wa[ ll] ! |
把所有改变的缓冲区写入磁盘 |
默认情况下,Vim 不会让我们
从一个改动过的缓冲区切换到其他缓冲区。不管是用 :next!
、:bnext!
、:cnext!
,还是其他类似的命令,如果省略了末尾的叹号
,Vim 就会弹出一条错误信息“已修改但尚未保存”。在多数情况下,这条消息都是一个有用的提醒,但在下面这种场景里它却会带来麻烦。让我们考虑一下:argdo
、:bufdo
以及:cfdo
命令的执行过程。:argdo{cmd}
命令像下面这样工作。
➾ :first
➾ :{cmd}
➾ :next
➾ :{cmd}
《 etc.
如果选择的 {cmd} 修改了第一个缓冲区,那么 :next> 命令将会失败,因为
除非保存了第一项中的修改,否则Vim是不会让我们跳到参数列表中的第二项的
。这用起来很不方便!
如果启用了 ‘hidden’ 选项(参见
:h hidden
),就可以不带末尾的叹号来执行 :next、:bnext及 :cnext 等命令了
。
如果活动缓冲区的内容发生了变化,Vim 会在离开该缓冲区时自动将其设为隐藏。‘hidden’ 设置让我们用一条:argdo
、:bufdo
或:cfdo
命令就可以修改一组缓冲区。
运行完 :argdo {cmd}
后,需要保存对每个文件做出的修改。可以先执行 :first
,然后再用 :wn
逐个保存,用这种方式可以逐一检查每个文件;或者如果我们相信一切正常,就可以运行 :argdo write
(或:wall
)来保存所有的缓冲区。
Vim快捷键键位图(入门到进阶)