全书共123个技巧,每天更新两个,计划两个月更新完。
(目前3月19日更新完技巧24)[图片上传失败...(image-d27932-1616207157070)]
@TOC
前言
不使用插件、不使用自定义配置地打开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
第1章 Vim解决问题的方式
从本质上讲,我们的工作是重复性的。不论是在几个不同的地方做相同的小改动,还是在文档的相似结构间移动,我们都会重复很多操作。凡是可以简化重复性操作的方式,都会成倍地节省我们的时间。
Vim对重复性操作进行了优化。它之所以能高效地重复,是因为它会记录我们最近的操作,让我们用一次按键就能重复上次的修改。这听起来很强大,但是除非我们能够学会规划按键动作,使得在重复时能完成一项有用的工作,否则这没什么用。掌握这一理念是高效使用Vim的关键。
我们将以.
命令作为开始。这个看似简单的命令是Vim中的瑞士军刀,掌握它的用法是精通Vim的第一步。我们将运行一些可由.
命令快速完成的简单编辑任务,虽然每个任务彼此之间截然不同,但解决的方法却大同小异。我们将找到一种理想的编辑模式,即用一次按键移动,用另一次按键执行。
技巧1 认识 . 命令
.
命令可以让我们重复上次的修改
x
命令会删除光标下的字符,在这种情况下使用.
命令“重复上次修改”时,就会让Vim删除光标下的字符。
[图片上传失败...(image-a43ab6-1616207157070)]
dd
命令把整行一起删掉。
如果在 使用dd
后使用.
命令,那么“重复上次修改”会让Vim删除当前行。
[图片上传失败...(image-917de3-1616207157070)]
最后,
>G
命令会使得从当前行到末尾行统一增加缩进。如果在 此命令后使用.
命令,那么“重复上次修改”会让Vim增加从当前行到文档末尾的缩进。[图片上传失败...(image-1e7180-1616207157070)]
x
、dd
以及>
命令都是在 normal 模式中执行的命令。
不过,每次进入insert模式时,也会形成一次修改。从进入insert模式的那一刻起(例如,输入 i),直到返回 normal 模式时为止(输入 < Esc >)Vim会记录每一个按键操作。
做出这样一个修改后再用.
命令的话,它将会重新执行所有这些按键操作
操作前文本0_mechanics.txt
贴在下面,方便大家复制并练习。
Line one
Line two
Line three
Line four
.
命令是一个微型的宏
Vim可以录制任意数目的按键操作,然后在以后重复执行它们。这让我们可以把最常重复的工作流程录制下来,并用一个按键重放它们。可以把
.
命令当成一个很小的宏。
技巧2 不要自我重复
对于在行尾添加内容这样的常见操作,如添加分号,Vim提供了一个专门的命令,可以把两步操作合并为一步。
我们想在每行的结尾添加一个分号。要实现这一点,先得把光标移到行尾,然后切换到插入模式进行修改。$
命令可以完成移动动作,接着就可以执行a;< Esc >
完成修改了。
要完成全部修改,也可以对下面两行做完全相同的操作,不过那样做会错过这里将要提到的小窍门。由于.
命令可以重复上次的修改,因此不必重复之前的操作,而是执行两次j$.
。
[图片上传失败...(image-3af81-1616207157070)]
减少无关的移动
a
命令在当前光标之后添加内容,A
命令则在当前行的结尾添加内容。不管光标当前处于什么位置,输入A
都会进入插入模式,并把光标移到行尾。
A
把$a
封装成了一个按键操作。
用A
来代替$a
,大大提升了.
命令的效率。不必再把光标移到行尾,只需保证它位于该行内就行了(可在任意位置)。现在可以重复执行足够多次的j.
,完成对后续行的修改。
[图片上传失败...(image-f994bd-1616207157070)]
一键移动,另一键操作,真是太完美了!请留意这种应用模式,因为我们即将在更多的例子中看到它的身影。
操作前文本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命令。
你找出这些命令别的共同点了吗?它们全都会从普通模式切换到插入模式。仔细想想这一点,并想想这对.
命令可能产生怎 样的影响。
技巧3 以退为进
我们可以用一种常用的Vim操作习惯在一个字符前后各添加一个空格。
假设有一行代码看起来是这样的:
var foo = "method("+argument1+","+argument2+")";
在JavaScript
里把字符串连接到一起从来都不美观,但可以像下面这样在 + 号前后各添加一个空格,让肉眼更容易识别。
var foo = "method(" + argument1 + "," + argument2 + ")";
使修改可重复
[图片上传失败...(image-29033d-1616207157070)]
知识点:
f{字符}
在行内查找下一个指定的字符,使用;
与,
进行前进与退回
/{pattern}
在文档内查找下一处匹配的项; (用n
和N
进行前进与返回
*
表示向下查找光标处的单词
#
表示向上查找光标处的单词;
s
命令把两个操作合并为一个:它先删除光标下的字符,然后进入insert模式。在删除 + 号后,先输入␣ + ␣
,然后按键< ESC >
退出插入模式。
先后退一步,然后前进三步,这是个奇怪的小花招,看起来可能不够直接。但这样做最大的好处是:我们可以用.
命令重复这一修改。我们所要做的只是把光标移到下一个 + 号处,然后用.
命令重复这一操作即可。
使移动可重复
输入
f+
时,光标会直接移到下一个 + 号所在的位置。完成第一处修改后,可以重复按f+
命令跳到下一个 + 号所在的位置。
不过,;
命令会重复查找上次 f 命令所查找的字符,因此不用输入4次f+
,而是只输入一次,后面跟着再用3次;
命令。
合而为一
;
命令带我们到下一个目标字符上,.
命令则重复上次的修改。因 此,可以连续输入3次;.
来完成全部修改。
技巧4 执行、重复、回退
在面对重复性工作时,我们需要让移动动作和修改都能够重复,这样就可以达到最佳编辑模式。
如果我们知道如何重复之前的操作,而无需每次都输入整条命令,那么就会获得更高的效率。可以先执行一次,随后只需重复即可。
当一遍又一遍地连续按
j.j.j.
时,那种感觉就像是在敲鼓。可是,如果不小心在一行上敲了两次j
键,会发生什么?或是更糟,敲了两次.
键?
对
.
命令而言,我们永远可以按u
键撤销上次的修改。
在多数场景中,撤销(undo)都是我们想要使用的命令,难怪我键盘上的 u 键磨损得这么厉害!
技巧5 查找并手动替换
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
或者,可以简单地把光标移到这个单词上,然后按 *
键。以下面的操作为例。
[图片上传失败...(image-e9c3ee-1616207157070)]
使修改可重复
当光标位于“content”的开头时,就可以着手修改它。这包括两步操作:首先要删除单词“content”,然后输入替代的单词。
cw
命令会删除从光标位置到单词结尾的字符,并进入插入模式,然后就可以输入单词“copy”了。Vim会把我们离开插入模式之前的全部按键操作都记录下来,因此整个cwcopy< Esc >
会被当成一个修改。也就是说,执行.
命 令会删除从光标到当前单词结尾间的字符,并把它修改为“copy”。
合而为一
万事俱备!每次按 n 键时,光标就会跳到下一个“content”单词所在之处,而按
.
键时,它就会把光标下的单词改为“copy”。如果想替换所有地方,就可以不加思考地一直按n.n.n.
以完成所有的修改(但是,这种情况下也可以用:%s/content/copy/g
命令)。
技巧6 认识 . 范式
到目前为止,我们介绍了3个简单的编辑任务。尽管每个问题都不一样,不过我们都找到了用 .
命令解决该问题的方法。在本节,我们将比较这些方案,并找出它们共有的模式——一个我称之为“ . 范式
”的最佳编辑模式。
回顾前面3个 . 命令编辑任务
在技巧2中,我们想在一系列行的结尾添加分号。我们先用 A;< Esc >
修改了第一行,做完这步准备后,就可以使用 .
命令对后续行重复此修改。我们使用了 j
命令在行间移动,要完成剩余的修改,只需简单地按足够多次 j.
就可以了。
在技巧3中,我们想为每个 + 号的前后各添加一个空格。先用 f+
命令跳到目标字符上,然后用 s
命令把一个字符替换成3个,做完这步准备后,就可以按若干次 ;.
完成此任务。
在技巧5中,我们想把每处出现单词“content”的地方都替换成“copy”。使用 *
命令来查找目标单词,然后用 cw
命令修改第一处地方。做完这步准备后,就可以用 n
键跳到下一匹配项,然后用 .
键做相同的修改。要完成这项任务,只需简单地按足够多次 n.
就行了。
理想模式:用一键移动,另一键执行
所有这些例子都利用 .
命令重复上次的修改,不过这不是它们唯一的共同点,另外的共同点是它们都只需要按一次键就能把光标移到下一个目标上。
用一次按键移动,另一次按键执行,再没有比这更好的了,不是吗?这就是我们的理想解决方案。我们将会一次又一次地看到这一编辑模式,所以为了方便起见,把它叫做“. 范式
”。
第2章 普通模式
普通模式是Vim的自然放松状态,如果本章看起来出奇的短,那是因为几乎整本书都在讲如何利用普通模式,而本章只涉及其中的一些核心概念以及通用技巧。
其他文本编辑器大部分时间都处于类似Vim插入模式的状态中,因此对Vim新手来说,把普通模式(normal mode)当成默认状态看起来很奇怪。在技巧7中,我们将以一个画家的工作区作为类比,来解释其原因。
许多普通模式命令可以在执行时指定执行的次数,这样它们就可以被执行多次。在技巧10中,我们将结识一对用于加减数值的命令,并且会看到这两条命令如何与次数结合在一起,进行简单的算术运算。
指定执行的次数可以减少按键个数,但并不是说一定要为此目的而这样做。我们将会看到一些例子,在这些例子中,简单地重复执行一条命令,要比花时间去计算想要执行多少次更好。
普通模式命令的强大,很大程度上源于它可以把操作符与动作命令结合在一起。在本章的最后,我们将看到这种结合达到的效果。
技巧7 停顿时请移开画笔
对于不习惯Vim的人来说,普通模式看上去是一种奇怪的缺省状态,但有经验的Vim用户却很难想象还有其他任何方式。本节使用了一个比喻来说明为什么Vim要采用这一种方式。
你估计画家会花费多少时间用画笔在画布上作画?毫无疑问,这因人而异,但是,如果这占了画家全部工作时间的一半还要多的话,我会觉得非常诧异。
想一下除了画画外,画家还要做哪些事情。他们要研究主题,调整光线,把颜料混合成新的色彩,而且在把颜料往画布上画时,谁说他们必须要用画?画家也许会换用刻刀来实现不同的质地,或是用棉签来对已经画好的地方进行润色。
画家在休息时不会把画笔放在画布上。对Vim而言也是这样,普通模式就是Vim的自然放松状态,其名字已经寓示了这一点。
就像画家只花一小部分时间涂色一样,程序员也只花一小部分时间编写代码。绝大多数时间用来思考、阅读,以及在代码中穿梭浏览,而且当确实需要修改时,谁说一定要切换到插入模式才行?我们可以重新调整已有代码的格式,复制它们,移动其位置,或是删除它们。在普通模式中,我们有众多的工具可以利用。
技巧8 把撤销单元切成块
在其他编辑器中,输入一些词后使用撤销命令,可能会撤销最后输入的词或字符,然而在Vim中,我们自己可以控制撤销的力度。
u
键会触发撤销命令,它会撤销最新的修改。一次修改可以是改变文档内文本的任意操作,其中包括在普通模式、可视模式以及命令行模式中所触发的命令,而且一次修改也包括了在插入模式中输入(或删除)的文本,因此我们也可以说,i{insert some text} < Esc >
是一次修改。
在不区分模式的文本编辑器中,输入一些单词后使用撤销命令,有两种可能。一种是它可能会撤销最后输入的字符;另一种做得更好点,它可能会把字符分成块,使每次撤销操作删除一个单词而不是一个字符。
在Vim中,我们自己可以控制撤销命令的力度。从进入插入模式开始,直到返回普通模式为止,在此期间输入或删除的任何内容都被当成一次修改。因此,只要控制好对 < Esc >
键的使用,就可使撤销命令作用于单词、句子或段落。
如果我认为已经走错了方向,就会切换到普通模式,然后按 u
撤销。每次做撤销时,文字都按我最初书写时的思路,被切分成条理清晰的块,也就是说我可以很容易地试着写一两句话,如果感到不合适的话,随后按一两下键就可以将其舍弃。
当处于插入模式时,如果光标位于行尾的话,另起一行最快的方式是按 < CR >
。不过有时我更喜欢按 < Esc >o
,这是因为我有预感,也许在撤销时我想拥有更细的粒度。如果听起来这不太好理解,不必担心,当你对Vim越来越熟悉时,就会感到切换模式越来越轻松。
一般来讲,如果你停顿的时间长到足以问“我应该退出插入模式吗?”这个问题,就退出吧。
在插入模式中移动光标会重置修改状态
当我提到撤销命令会撤销从插入模式到退出此模式期间的输入(或删除)的全部字符时,我略过了一个小细节。
如果在插入模式中使用了< Up >
、< Down >
、< Left >
或< Right >
这些光标键,将会产生一个新的撤销块。你可以把这想象为先切换回普通模式,然后用h
、j
、k
或l
命令对光标进行了移动,唯一区别是我们并没有退出插入模式。这也会对.
命令的操作产生影响。
技巧9 构造可重复的修改
Vim对重复操作进行了优化,要利用这一点,必须考虑该如何构造修改。
在Vim中,要完成一件事,总是有不止一种方式。在评估哪种方式最好时,最显而易见的指标是效率,即哪种手段需要的按键次数最少(又名VimGolf)。然而,在平局时该如何选择获胜者呢?
在下例中,假设光标位于行尾处的字符“h”上,而我们想要删除单词“nigh”:
The end is nigh
反向删除
因为光标已经位于单词末尾,所以可以先反向删除该词。 [图片上传失败...(image-e54511-1616207157070)]
按db
命令删除从光标起始位置到单词开头的内容,但会原封未动 地留下最后一个字符 “h”,再按一下x
键就可以删除这个捣乱的字符。 这样,整个操作的 Vim高尔夫得分是3分。正向删除
[图片上传失败...(image-b3a3af-1616207157070)]
先用b
命令把光标移到单词的开头,移动好后,就可以用一个dw
命令删掉整个单词。这一次的Vim高尔夫得分也是3分。删除整个单词
到目前为止,已有的两种方式都要先做某种准备工作或清理工作。另外,也可以使用更为精准的
aw
文本对象(text object),而不是用动作命令(参见:h aw
)。
[图片上传失败...(image-9ebeb9-1616207157070)]
可以把daw
命令解读为“delete a word”,这样比较容易记忆。
决胜局:哪种方式最具重复性?
我们尝试了3种不同的方式来删除一个词:dbx
、bdw
以及 daw
。每种情况的Vim高尔夫得分都是3分。那么要怎么回答这个问题:“哪种方式最好?”
还记得吗,Vim对重复操作进行了优化。让我们再回顾一下这3种方式,这一次我们跟着用一次 .
命令,看看会发生什么。我建议你自己也亲自试一下。
反向删除方案包含两步操作:
db
命令删除至单词的开头,而后x
命令删除一个字符。如果我们跟着执行一次.
命令,它会重复删除一个字符(.
==x
)。我不觉得这有什么价值。
正向删除方案也包含两步。这一次,b
只是一次普通的移动,而dw
完成修改。此时用.
命令会重复
dw
,删除从光标位置到下个单词开头的内容。不过因为我们刚好已经在行尾了,并没有“下一个单词”,所以在这个场景里 .
命令没什么用。不过,至少它代表了一个更长点的操作(.
==dw
)。
最后的方案只调用一个操作:daw
。这个操作不仅仅删除了该单词,它还会删除一个空格,因此光标最终会停在单词“is”的最后一个字符上。如果此时使用.
命令,它会重复上次删除单词的命令。这一次,.
命令会做真正有用的事情(.
==daw
)。
结论
daw
可以发挥 .
命令的最大威力,因此我宣布它是本轮的获胜者。要想充分利用 .
命令,事先常常需要进行一番周详的考虑。如果你发现自己要在几个地方做同样的小修改,就可以尝试构造你的修改,让它们能够被 .
命令重复执行。要识别出这类机会需要进行一定的实践,不过一旦养成了使修改可重复的习惯,你就会从 Vim 这里得到“奖赏”。
有时,我并没有看到用 .
命令的机会,然而在做完一次修改后,我发现要做另一次同样的操作,这时候,我脑海里会浮现出 .
命令,而它也已经准备好为我效力了。每当遇到这种情况时,我都会开心地笑起来。
技巧10 用次数做简单的算术运算
大多数普通模式命令可以在执行时指定次数,可以利用这个 功能来做简单的算术运算。
很多普通模式命令都可以带一个次数前缀,这样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 >
则要快得多。由于光标不在要操作的数字上,所以该命令会正向跳到所找到的第一个数字上,从而省去了手动移光标的步骤。让我们看看整个操作过程。
[图片上传失败...(image-1e667c-1616207157070)]vim实用技巧(第二版)
在本例中,只复制了一行并做出改动。但是,假设要复制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)
- a word would mean
special
- whereas a WORD would mean
special/separated/words
- word 是字面意义上的单词,比如
go!to!school!
则go
和to
和school
分别是三个word。
而WORD是之间没有空格的一串字符go!to!school!
。
数字的格式
像在某些编程语言中的约定一样,
Vim把以0开头的数字解释为八进制值
,而不是十进制。在八进制体系中,007 + 0 01 = 010,看起来像是10,但实际上它是八进制中的8,糊涂了吗?
如果你经常使用八进制,Vim的缺省行为或许会适合你
。如果不是这样,那么你可能想把下面这行加入你的vimrc里:
set nrformats=
这会让Vim把所有数字都当成十进制,不管它们是不是以0开头的。
技巧11 能够重复,就别用次数
在处理某些特定工作时,使用次数可以使按键次数变得最少,不过并不是非得这样不可。我们需要认真考虑次数与重复各自的优缺点。
假设在缓冲区里有如下文字:
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....
)也有同样的争论,因此我的偏好看起来似乎不太一致。对此,你要总结自己的观点,这取决于你怎么看保留干净撤销历史记录的价值,以及你是否觉得用次数令人生厌。
技巧12 双剑合璧,天下无敌
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
就可以了。运行这条命令时,光标在哪儿并不重要,因为它总是作用于整个文件。
- 如果同时安装了commentary.vim和textobj-entire插件,就可以把它们放在一起使用。例如,执行
gcae
会切换整个文件的注释状态。 - 如果你对如何创建自定义动作命令感到好奇,可以由阅读
:h omap-info
开始.
结识操作符待决模式
普通、插入及可视模式很容易辨识,但是Vim还有另外一些很容易被忽视的模式,操作符待决模式(operator-pending mode)就是一个例子。每天我们无数次地使用它,但通常它只持续不到一秒时间。举个例子,在执行命令
dw
时,就会激活该模式。这一模式只在按 d 及 w 键之间的短暂时间间隔内存在,一眨眼工夫就不见了。
如果把Vim想象成有限状态机,那么操作符待决模式就是一个只接受动作命令的状态。这个状态在调用操作符时被激活,然后什么也不做,直到我们提供了一个动作命令,完成整个操作。当操作符待决模式被激活时,我们可以像平常一样按 < Esc > 中止该操作,返回到普通模式。
很多命令都由两个或更多的按键来调用(查阅
:h g
、:h z
、:h ctrl-w
,或者:h [
,可以看到一些例子),但在多数情况下,头一个按键只是第二个按键的前缀。这些命令不会激活操作符待决模式,相反,可以把它们当成命名空间(namespace),用来扩充可用命令的数目。只有操作符才会激活操作符待决模式。[俺理解为:不会激发这个模式的字符串,可以用作扩充命令的命名前缀]
你也许想知道,为什么要有一个完整的模式,专门用于操作符和动作命令之间的短暂瞬间,而命名空间命令则仅仅是普通模式的一个扩充?好问题!这是因为我们能够创建自定义映射项来激活或终结操作符待决模式。换句话说,它允许我们创建自定义的操作符及动作命令,从而让我们可以扩充Vim的词汇。
第3章 插入模式
大部分的Vim命令都在非插入模式中执行,不过有些功能在插入模式中会更好实现些,我们将在本章深入研究这些命令。尽管删除、复制 以及粘贴命令都是在普通模式中执行的,不过我们将会看到一种方便快捷的方式,让我们无需离开插入模式就能粘贴寄存器中的文本。另外我们也会学习Vim提供的两种简单方式,用来插入键盘上不存在的不常用字符。
替换模式是插入模式的一种特例,它会替换文档中已有的字符。我 们将学习如何使用它,并了解在哪些场景下它能够大显身手。我们也会结识插入-普通模式
,它是一个子模式,可以让我们执行一个普通模式命令,之后马上又回到插入模式。
自动补全是插入模式中才能使用的高级功能,我们将在第19章对其进行深入的研究。
技巧13 在插入模式中可即时更正错误
如果在插入模式下撰写文本时出了错,可以立刻对它进行更正,而无需切换模式。要迅速更正错误,除了用退格键外,还可以用插入模式中的其他一些命令。
盲打并不仅仅指输入时不看键盘,还意味着输入时要凭感觉。当盲打的人输入错误时,在眼睛看到屏幕上的错误之前,他们就已经察觉到 了,他们可以用手指感知到次序颠倒这类的错误。
在输入错误时,可以用退格键删除错误的文本,然后再输入正确的内容。如果出错的地方靠近单词结尾,这或许是最快的修正方式。但是,如果出错的位置在单词开头呢?
专业打字员会建议先删除整个单词,然后再重新输入一遍。如果你能以每分钟超过60个单词的速度输入,那么重新输入一个词只需要1秒钟的时间。即便你打不了这么快,最好也采用这种方式。我以前总是输错某些特定的词,但自从采纳这一建议后,我就更清楚地意识到哪些词会让我犯错,因此现在犯的错也少了很多。
另外,也可以切换到普通模式,然后跳到这个词的开头并更正错误,再按A
返回刚才的位置。不过完成这一套动作要花的时间可能不止1秒钟,并且它也无助于提高你的盲打技巧。虽然说我们可以切换模式,不过这并不意味着一定就得切换。
在插入模式下,退格键的作用如你所愿,它删除光标前的字符。另外,还可以用下面这些组合键。
按键操作 | 用途 |
---|---|
< C-h > | 删除前一个字符(同退格键) |
< C-u > | 删至行首 |
这些命令不是插入模式独有的,甚至也不是Vim独有的,在Vim的命令行
模式中,以及在 bash shell
中,也可以使用它们。
技巧14 返回普通模式
插入模式只专注于做一件事,那就是输入文字,而普通模式却是我们大部分时间所使用的模式(顾名思义),因此能快速在这两种模式间切换是很重要的。本节将介绍一些可以减少模式切换所带来的损耗的技巧。
切换回普通模式的经典方式是使用 < Esc >
键,但在许多键盘上这个键的距离似乎有点远。作为替代,也可以用
,它的效果与< Esc >
完全相同(参见 :h i_CTRL-[
)。
按键操作 | 用途 |
---|---|
< Esc > | 切换到普通模式 |
< C-[ > | 切换到普通模式 |
< C-o > | 切换到插入-普通模式 |
Vim新手经常会被不断地切换模式而搞得疲倦不堪,不过练习过一段时间以后,就会渐渐感觉到得心应手了。不过,Vim区分模式的特点在下面这种特定场景中却显得有点烦琐:
- 处于插入模式时,想运行一个普通模式命令,然后马上回到原来的位置继续输入。Vim为此提供了一种巧妙的方法,以减少模式切换所带来的不畅,这就是
插入-普通模式
。
结识插入-普通模式
插入-普通模式是普通模式的一个特例,它能让我们执行一次普通模式命令。在此模式中,可以执行一个普通模式命令,执行完后,马上又返回到插入模式。要从插入模式切换到插入-普通模式,可以按 < C-o >(参见
:h i_CTRL-O
)。
在当前行正好处于窗口顶部或底部时,有时我会滚动一下屏幕,以便看到更多的上下文。用zz
命令可以重绘屏幕,并把当前行显示在窗口正中,这样就能够阅读当前行之上及之下的半屏内容。我常常会键入< C-o >zz
,在插入-普通模式中触发这条命令。此操作完成后就会直接回到插入模式,因此可以不受中断地继续打字。
技巧15 不离开插入模式,粘贴寄存器中的文本
Vim的复制和粘贴操作一般都在普通模式中执行,不过有时我们也许想不离开插入模式,就能往文档里粘贴文本。
下面是一段尚未完成的文本。/practical-vim.txt
Practical Vim, by Drew Neil
Read Drew Neil's
我们想把本书的书名插到最后一行,以补全该行。由于书名在第一行的开头已经出现过了,所以将把它复制到一个寄存器中,然后在插入模式中把它添加到第二行结尾。
[图片上传失败...(image-ea93f0-1616207157070)]
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} 命令
在插入模式中,可以用
< C-r>{register}
命令很方便地粘贴几个单词。
可是如果寄存器中包含了大量的文本,你也许会发现屏幕的更新有些轻微的延时。这是因为Vim在插入寄存器内的文本时,其插入方式就如同这些文本是由键盘上一个个输进来的。因此,如果‘textwidth
’ 或者 ‘autoindent
’ 选项被激活了的话,最终就可能会出现不必要的换行或额外的缩进。
< C-r>< C-p>{register}
命令则会更智能一些,它会按原义插入寄存器内的文本,并修正任何不必要的缩进(参见 :h i_CTRL-R_CTRL-P
),不过这个命令有点不太好输入!因此,如果我想从一个寄存器里粘贴很多行文本的话,我更喜欢切换到普通模式,然后使用某个粘贴命令(参见技巧63)。
技巧16 随时随地做运算
表达式寄存器允许我们做一些运算,并把运算结果直接插入文档中。本节将看到一个运用此强大功能的实例。
大部分的Vim寄存器中保存的都是文本,要么是一个字符串,要么是若干行的文本。删除及复制命令允许我们把文本保存到寄存器中,粘贴命令则允许把寄存器中的内容插入文档里。
不过表达式寄存器则是个另类,它可以用来执行一段Vim脚本,并返回其结果。在本节,我们将把它当成计算器来用。传给它一个简单的算术表达式,如1 + 1,它就会给出结果2。对表达式寄存器所返回的文本,我们可以像用普通寄存器中的文本那样使用它。
可以用 =
符号指明使用表达式寄存器。在插入模式中,输入 < C-r >=
就可以访问这一寄存器。这条命令会在屏幕的下方显示一个提示符,可以在其后输入要执行的表达式。输入表达式后敲一下 < CR >
,Vim就会把执行的结果插入文档的当前位置了。
假设刚在back-of-envelope.txt
输入完下列内容:
6 chairs, each costing $35, totals $
我们想算一下总价,不过没必要找个信封在背面做演算,Vim可以
帮我们做这件事,我们甚至连插入模式都不用退出。做法如下。
A
[图片上传失败...(image-b0975a-1616207157070)]
表达式寄存器远不止能做简单算术运算。我们将在技巧71中看到一些更高级的应用。
技巧17 用字符编码插入非常用字符
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}表示的字符 |
技巧18 用二合字母插入非常用字符
虽然Vim允许我们用数字编码插入任意字符,不过这既难记忆也难输入。我们也可以用
二合字母(digraph)
来插入非常用字符,成对的字符更容易记忆一些。
二合字母用起来很方便。在插入模式中,只需输入 < C-k>{char1}{char2}
即可。
因此,如果想输入以二合字母
?I
表示的“¿
”字符,可以简单地输入< C-k>?I
。
Vim在选择组成二合字母的两个字符时,尽量使之具有描述性,这样就更容易记住它们,甚至也能猜出其含义。例如,左右书名号《
和》
分别以二合字母<<
及>>
表示,普通分数(或常用分数)½
、¼
和¾
则分别以二合字母12
、14
和34
来表示。Vim的缺省二合字母集依从一定的惯例, :h digraphs-default
文档对此进行了总结。
用命令 :digraphs
可以查看可用的二合字母列表,不过该命令的输出不太好阅读。也可以用 :h digraph-table
查看另一个更为有用的列表。
技巧19 用替换模式替换已有文本
在替换模式中输入会替换文档中的已有文本,除此之外,该模式与插入模式完全相同。
假设有如下一段文本。
Typing in Insert mode extends the line. But in Replace mode
the line length doesn't change.
我们想把这两个单独的句子合并成一句话,为此,需要把
句号改成逗号
,并将单词“But”中的“B”改为小写
。下例展示了如何用替换模式完成这项工作。
[图片上传失败...(image-acea64-1616207157070)]
用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
)。
第4章 可视模式
Vim的可视模式允许选中一块文本区域并在其上进行操作,从表面上看这应该很容易理解,因为大多数编辑软件都沿用此模式。然而,Vim的可视模式和其他软件的做法却截然不同,所以在本章的一开始,我们先深入了解可视模式(参见技巧20)。
Vim具有3种不同的可视模式,分别用于操作字符文本、行文本和块文本。我们将会探讨在这几种模式间切换的方法,并介绍一些更改选区边界的有用技巧(参见技巧21)。
也可以利用 .
命令来重复执行可视模式中的命令,然而只有在操作面向行的选区时,它才特别有用;而在操作面向字符的选区时,有时它无法达到我们的预期。我们将会看到,在这种情况下可能更适合用操作符命令。
列块可视模式非常特别,它允许在块状文本上进行操作。你会发现此功能有很多的用处,不过只着重介绍3个小技巧,展示其部分功能。
技巧20 深入理解可视模式
可视模式允许选中一个文本区域并在其上操作。尽管这看起来似乎很熟悉,但对于选中的文本,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 功能的插件,它会用选择模式来高亮当前的占位符。
技巧21 选择高亮选区
可视模式的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
,然后重新调整选区的边界即可。下面的操作对此功能进行了演示。
[图片上传失败...(image-f8e023-1616207157070)]
技巧22 重复执行面向行的可视命令
当使用 .
命令重复对高亮选区所做的修改时,此修改会重复作用于相同范围的文本。本节将修改一个面向行的高亮选区,然后使用 .
命令重复此修改。
在可视模式中执行完一条命令后,会返回到普通模式,并且在可视模式中选中的文本范围也不再高亮显示了。那么,如果想对相同范围的文本执行另外一条可视模式命令,该怎么办?
假设如下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中,已经用大量篇幅讨论过次数风格与重复风格之间的差异了。
在使用.
命令重复一条可视模式命令时,它操作的文本数量和上次被高亮选中的文本数量相同。对于面向行的高亮选区来说,这种做法往往符合我们的需要。但对于面向字符的高亮选区来说,这却会产生令人意外的结果。接下来将通过一个例子来说明这一点。[图片上传失败...(image-1ff5aa-1616207157070)]
技巧23 只要可能,最好用操作符命令,而不是可视命令
可视模式可能比Vim的普通模式操作起来更自然一些,但是它有一个缺点:在这个模式下,
.
命令有时会有一些异常的表现。可以用普通模式下的操作符命令来规避此缺点。
假设想把下面list-of-links.html
列表中的链接文字转换为大写格式。
one
two
three
可以用 vit
来选择标签里的内容。vit
可被解读为高亮选中标签内部的内容(v
isually select i
nside the t
ag),其中,it
命令是一种被称为文本对象(text object)的特殊动作命令。我们将在技巧52中对其进行详细讲解。
使用可视模式下的命令
在可视模式中,可以选定一个选区然后对其进行操作。本例中,可以使用
U
命令来把所选中的字符转换为大写(参见:h v_U
),具体操作如下图:
[图片上传失败...(image-131a2f-1616207157070)]
执行j.
命令,把光标移到下一行并重复上次的修改。此命令在第二行工作得很好,但如果再执行一次,最终就会得到这个看起来有点古怪的结果。
[图片上传失败...(image-910797-1616207157070)]
你看到发生什么了吗?当一条可视模式命令重复执行时,它会影响相同数量的文本
(参见:h visual-repeat
)。在本例中,最初的命令影响了一个由3个字母组成的单词。在第二行它依旧工作得很好,因为该行恰好也包含一个由3个字母组成的单词。但是,当我们想对一个由5个字母组成的单词重复此命令时,它只成功转换了其中的前3个字母,留下2个字母未被转换。
使用普通模式下的操作符命令
可视模式下的 U
命令有一个等效的普通模式命令:gU{motion}
(参见 :h gU
)。如果用此命令做第一处修改,就可以用点范式完成后续的修改。
[图片上传失败...(image-a53c74-1616207157070)]
结论
这两种方式都只需要4次按键操作:vitU
及 gUit
,但其背后的含义却大相径庭。在可视模式采用的方式中,这4次按键可以被当作两个独立的命令。vit 用来选中选区,而 U 用来对选区进行转换。与之相反的是,gUit 命令可以被当成一个单独的命令,它由一个操作符(gU)和一个动作命令(it)组成。
如果想使点命令
能够重复某些有用的工作,那么最好要远离可视模式。作为一般的原则,在做一系列可重复的修改时,最好首选操作符命令,而不是其对应的可视模式命令。
这并不是说可视模式出局了,它仍然占有一席之地。因为并非每个编辑任务都需要重复执行,对一次性的修改任务来说,可视模式完全够用,并且尽管Vim的动作命令允许进行精确的移动,但有时要修改的文本范围的结构很难用动作命令表达出来,而处理这种情形恰恰是可视模式擅长的。
技巧24 用面向列块的可视模式编辑表格数据
在任何编辑器中,我们都能够操作以行为单位的文本,但以列为单位进行文本操作就需要更为专业的工具了。Vim面向列块的可视模式就提供了这种能力,可以用它来对纯文本表格进行转换。
假设chapter-table.txt
有一个纯文本表格。
Chapter Page
Normal mode 15
Insert mode 31
Visual mode 44
我们想用管道符|
画一条竖线来隔开这两列文本,使之看起来更像一个表格。但是在此之前,要先减少两列之间的间隔,使它们不要分得这么开。用面向列块的可视模式可以完成这两处修改。[图片上传失败...(image-345d3e-1616207157070)]
使用
< C-v>
进入列块可视模式,然后向下移动几行光标3j
,选中一列文本。接下来,按x
键删除此列,并用.
命令重复删除相同范围的文本。此操作多重复几次直到距右边差不多有两列的距离。
也可以不用.
命令,而是把光标向右移动两三次,把列选区扩展为块选区,而后只需删除一次即可。不过,我更喜欢在删除时看到即时的视觉反馈,然后再多次重复此操作。
现在,我们已经把所需的两列文本排列到了合适的位置,接下来就可以在这两列文本间画一条竖线了。先用gv
命令重选上次的高亮选区
,然后输入r|
, 用管道符替换此选区内的字符。
到了这一步,我们或许也想画一条横线来分隔表头及其下的内容。先快速复制顶行并粘贴一份副本(yyp
),然后用连字符替换该行内的所有字符(Vr-
)。
Vim快捷键键位图(入门到进阶)