Vim终极指南:所思即所得

关于开发工具方面的书,有两本获得了超高的评价,一本是《Pro Git》,另一本就是本文的主要参考《Practical Vim》,副标题Edit Text at the Speed of Thought更为形象生动——以思考的速度去编辑。这本书中包含了一百多个非常实用的Vim使用技巧,不管你是已经使用Vim多少年的专家,相信都能从中获取新的养分。推荐大家在读完本文后进一步去读一下这本书进行深入学习。

多年以前对Intellij IDEA爱不释手,又苦于从Eclipse转型的困难,于是写了一篇《十大Intellij IDEA快捷键》,反响和收获都超乎想象。最近重度使用Vim,所以趁热打铁再来一篇Vim的使用手册,整理一下相信于人于己都会有益处的。关于本文的组织和内容:本文从一个编辑器的角度来审视Vim,按照文本编辑器应有的功能来组织文章结构。此外,字里行间也涵盖了一些非常流行的、对Vim基本功能增强的插件用法,具体安装配置请参考《Python+Vim:天作之合》


1.Vim之道

大家可能好奇:Vim还需要专门一本书来学吗?像本文最后一部分,整理或从网上找一个常用命令的Cheatsheet不就行了?不然。前言所述的技巧其实只是一个引子,会几个常用命令只是皮毛。当我们感慨Vim在小细节上的用心和鬼斧天工时,更重要地一点其实是Vim的内核和灵魂。其实不只是Vim这种开发工具,学习任何东西都一样,抓住其历史背景和设计初衷,体会设计者匠心独运的那一部分。之后再去看使用方法、技巧等细枝末节,就会发现一切都顺理成章。这一部分就是要重点说说Vim骨子里到底是什么样的,看看大浪淘沙后的Vim中包含的大智慧。


1.1 编辑的本质:重复!

文本编辑从本质上来说就是重复的。我们经常要在好几处做小改动,于是我们不断移动光标去修改,然后继续下一项改动。Vim是个神器,但其本质与其他工具无异,都是用自动化来对付重复。而自动化的核心就是:如何将我们的工作打造成一系列可以重复的小动作单元。如果不自己想清楚,那比Vim更高级的工具也救不了我们。

以小见大,我们日常生活工作中有很多其他事情本质上也都是重复的。作为天性“懒惰”程序员,我们当然要想法让自己以后更省事省时间。所以同样的道理,我们也需要梳理出整个工作流程,将它们变成一个个可重复的小逻辑单元,然后运用各种工具自动化。同样,关键在于梳理出可重复工作单元的思维过程。

1.2 Vim解药:dot配方

当我们想清楚了,Vim能帮助我们快速实现自动化,秘密就在Vim里一个最最基本的万能键:.(dot)。当我们按下它时,就像念了咒语、施了法术一样,会在当前光标位置重复上一次的操作。我们可以将dot想作一个微型的宏(Macro),我们之前做的一系列操作都被Vim自动录制并保存到某处,当按下dot时Vim帮我们自动播放之前录制的内容。

书中给了三个例子:在每行后加分号、一行中每个加号两边加空格、查找替换单词,来说明dot的万能。更重要地是为大家展示了“dot配方”的样子:一键移动(找到下一个目标)+一键操作(完成任务),简洁明了!如果我们的每个动作都能打造成这样理想的小单元,那我们就能很快速地完成所有任务。所以一个理想的dot配方就是:用Motion或其他搜索方法移动光标到下一个目标,然后用Operator+Motion/TextObject完成修改,最后不断用;和.重复这个配方。下面就要讲到这里提到的Operator、Motion、TextObject三个重要的概念。


2.组合的力量

Vim的常用操作可以用次数N+操作Operator+操作范围Motion/文本对象TextObject来表示,Vim的强大很大程度上来自于这种组合。当我们熟悉了这种组合方式,我们就能“研制”出自己的配方。那种摸着了门道,自己琢磨出新东西的窃喜大家可以自己体会。不仅如此,我们还能定制自己的Operator或Motion去扩充现有的词汇表。而且的确有人这样做了,比如注释/取消注释操作,比如根据不同语言扩展更多的TextObject。前面没有特意强调次数N,因为有时与其花时间去数要重复几遍,还不如操作一次,然后用dot去重复来得快。现在我们就来看看Vim的Operator、Motion、TextObject词汇表吧!


2.1 操作(Operator)

首先我们要了解一点,就是Vim超快速的移动能力得益于不同模式的区分,使得一副键盘上的按键在不同模式下表示不同的含义。这样我们只需用同样的单个按键就能完成看似非常高级的移动。

  • c (change):修改(删除并进入插入模式),非常实用,比先用d删除再进入插入模式要方便得多。更重要地是可以用dot来重复执行。
  • d (delete):删除,不用多说。
  • y (yank):拷贝到Vim的寄存器中,因为c已经被change占用了,所以只能换成另一个单词yank的首字母y。
  • g~ (swap case) gu (make lowercase) gU (make uppercase):这三个是大小写转换相关的。
  • > (shift right)、< (shift left)、= (autoindent):这三个在格式化代码时常用。

惯例:(一)操作重复写的话表示范围motion是当前行,例如dd是删除当前行,>>是增大一个缩进当前行,gUgU(或gUU)是当前行都变大写。(二)在当前行中可以使用f/F{char}向前/后查找字符,t/T{char}类似,区别是停留在char之“前”(向前搜索则停在前面,向后则停在后面),用;和,可以重复上一次的向前/后查找动作。因为这个区别,我们通常用f/F作快速查找移动,用t/T配合d/c做修改编辑。在后面介绍文本对象时,我们会看到类似的用法。


2.2 移动(Motion)

  • w/b:下一个/前一个word。还有W和B,表示下一个/前一个WORD。这里提一下word和WORD的区别,word是字母、数字、下划线组成的一个单词,WORD是空格分隔的单词。
  • e、ge:下一个/前一个word的末尾。同理也有E和gE。
  • f{char}:在当前行向后查找匹配的字符,F是向前查找。;和,可以重复上一次的f/t和F/T查找。
  • t{char}:与f类似,但光标会停在匹配的字符前,同理T向前查找停在字符后。
  • *:下一个匹配的word
  • %:在开闭括号间来回跳跃,支持()、{}、[]。
  • (:下一个句子,)是上一句。
  • {:下一段,}是上一段。
  • :非常有用的移动,可以跳回到上一次的位置。假如我们想修改一对小括号成花括号,则先移动到(,然后用%移动到末尾的),修改成},这时用%是无效的,因为此时括号已经不匹配,于是“就出场了,使我们跳回到(的位置。

2.3 文本对象(Text Object)

TextObject可以像Motion一样,用在Visual模式或与一个Operator结合,但不同的是文本对象不能在Normal模式下单独使用去移动光标,毕竟文本对象表示的是一个文本对象、一个范围。《Practical Vim》中做了精彩的比喻:如果说Motion是迎面飞踢的话,那TextObject就是剪刀踢。大家在Vim中,任意一个word中间按一下daw就能体会到这个比喻的妙处了。在word中间的某一个字符,我们彷佛腾空而起向两边踢出双腿,“干掉了”整个word。神奇吧?但如果你以为文本对象只有这么大点本事那就错了,下面就来看看我们有多少种对象吧!

文本对象由前缀a或i和下列后缀自由组合形成,a(around)表示包含下列标签以及中间的内容,i(inside)表示只包含中间的内容:

  • w W s p:与前面一致,表示word、WORD、句子和段落。
  • ) or b:一对小括号,(…)
  • } or B:一对花括号,{…}
  • ]:一对中括号,[…]
  • >:一对尖括号,<…>
  • :一对单引号
  • :一对双引号
  • `:一对`…`
  • t:一对html标签,例如tags

一切就绪,现在就来见识一下文本对象的威力吧。比如现在光标位于{hello}的e上,这时我们用ciw,就能直接删掉整个hello并且进入插入模式进行编辑,同理我们如果执行daw就能直接删除整个{hello}包括花括号。是不是想起了什么?没错,此处的用法与前面介绍的f和t的惯例类似,所以可以用一句简练的话来总结:Delete a(around)/f(inclusive). Change i(inside)/t(exclusive). 删除周围,修改其中。用好文本对象,甚至还能自己根据需求扩展的话,那真是威力无穷!


3.最优解:高尔夫记分法

很多时候我们会有多种方式去完成一个操作,如何进行选择呢?一个有趣的规则就是Vim高尔夫。我们知道高尔夫球是记每位选手的挥杆数,谁用的次数最少谁是胜者。Vim也是一样,谁需要的按键少谁赢,当次数一样时,还有更重要的一点:是否可重复,即是不是个有效的dot配方

书中给出了一个例子,当光标在一个单词的末尾最后一个字符上,如果删除这个单词?方案一:db往回删,但留下了当前光标下的最后一个字符,所以还要敲一下x。方案二:先b回到单词的第一个字符,再dw删除。方案三:diw,即delete+inside+word删掉当前单词。三种方案都是三次按键得三分,这时就看谁是有效的、可重复的“配方”了。胜者很明显,就是diw,因为dbx相当于两次操作,dot没法都重复。而bdw是先移动再操作,看似是我们前面提到的理想情况,但注意:一键移动是找到下一个目标,而这里的b是在目标上做了一次移动,所以这个方案还是很难重复在另一个单词上的!

大家感兴趣的话可以去VimGolf这个网站上玩一玩,上面有很多有趣的挑战。就像刷题一样,不过看起来似乎比Leetcode要更有乐趣一些,毕竟多了解一些技巧使我们能在日常工作中获益。


4.最后两招:Visual模式与宏录制

在一些不理想的情况下,我们没法非常完美的用一个dot配方就完成编辑,这时我们还可以求助于下面两个办法。限于时间和篇幅的关系,这一部分写的比较简略,实际上内容还是挺多的。感兴趣的同学,请直接去读原书和其他资料吧。

4.1 Visual模式

可视化模式可以细分为三种子模式:字符、行、块,分别在Normal模式下按v、V、Ctrl-v进入。如果一个编辑任务有简单的dot配方的话,我们倾向于不使用可视化选择修改的方法。书中提到了Visual模式的问题:不可重复!比如我们改了一个长度为3的单词,当我们想在另一行某个长度为4的单词上重复时就会发现问题,只有3个字符被修改了。所以这种方法只适用于:1)一次性的简单修改;2)修改范围不容易用dot配方写出来;3)两个Visual模式提供的很酷的功能:一就是V进入行模式,然后r-,则这一行都会被改为-,想想手写表格时这有多方便;二就是Ctrl-V进入块模式按列修改,方法为选好范围后按c,再输入想要的内容后Esc回到Normal模式,就会发现该列的每一行都变成了刚才我们输入的内容。

4.2 宏(Macro)

在Vim中进行宏录制非常简单,Normal模式下按q{register},比如qa就表示把这个宏保存到寄存器a,然后我们就可以输入命令了,最后按q退出。按@a或5@a就能任意重复刚才的宏了。看起来简单,但如果不遵循编写宏的三个黄金规则的话,写出来的宏就是一堆废代码,没有可重用的价值:

  1. 正则化(Normalize):第一步就是利用0、$、I、A正则化光标位置,因为每一行的长度、形状可能都不一样,如果不这样做的话再重复执行的时候就会在错误的位置上开始。
  2. 可重复的修改:利用f{char}等命令找到目标,然后修改。在这里,Vim的Motion还有起到了一个保护的作用。如果宏中的某一条Motion移动失败了,那宏就自动终止,不会执行后面的命令。
  3. 移动到下一目标:如果想不断在每行上执行的话,那在宏的最后,加上j。这样的话执行5@a重复5次时,宏就会自动在5行上运行了。

看到最后,是不是觉得其实本质上宏就是一个精心设计好的dot配方?其实一个最简单的宏就是qq;.q,然后执行@q。因为我们知道一个dot配方是由找目标和修改两步组成,所以我们可以写一个一次性的宏来减少我们敲击;.的次数。

关于宏还有个有趣的地方,就是宏可以并行或串行执行,前面的5@a其实是串行执行,如果中间一次执行失败了,就会立刻终止。那怎样才能并行执行呢?很简单,利用命令模式下执行:normal @a,这样其中某一条的失败不会影响其他的执行。


5.总结:Cheatsheet

前面主要介绍了Vim的核心价值——编辑,编辑也的确是最主要的工作,但我们每天的工作流还有很多其他部分组成:打开文件、打开包含特定关键字的文件、开始编辑、保存关闭。编辑部分也有一些前面没讲到的,例如下一页、复制粘贴、Undo等。所以,在本文的最后一部分做统一的整理。一些Vim插件能对这些周边工作有更强大的支持,所以这里也会介绍一下常用的插件。

操作 快捷键
文件管理 打开
(Ctrlp插件)
Ctrl-p:文件名模糊查找后:
Enter:当前窗口打开
Ctrl-v:水平分隔当前窗口
Ctrl-x:垂直分隔当前窗口
Ctrl-t:新建Tab打开文件
Tab gt:下一个Tab
gT:上一个Tab
1gt:第一个Tab
:tabonly:只保留当前Tab,关闭所有其他
编辑1
(Dot Formula)
操作符 c:修改
d:删除
y:复制
p:粘贴
gu gU g\~:小写、大写、大小写转换
< > =:左缩进、右缩进、自动缩进
cc dd yy gUU << ==:只当前行
移动 w b:下/上一个word
W B:下/上一个WORD
e ge:下/上一个word末尾
E gE:下/上一个WORD末尾
f:下一个匹配字符
t:下一个匹配字符前
\*:下一个匹配word
%:在() [] {}开闭括号间跳跃
(:下一个句子
{:下一个段落
` `:跳回到之前光标所在的位置
文本对象 w W:word和WORD
s:一个句子
p:一个段落
) or b:一对(…)
} or B:一对{…}
]:一对[…]
>:一对<…>
‘:一对’…’
“:一对”…”
`:一对`…`
t:一对<xml>…</xml>
编辑2 跳跃移动
(Easymotion插件)
/:查找后跳到指定字母索引的位置
\ j:向下跳到指定字母索引的行
\ k:向上跳到指定字母索引的行
更多移动 0 ^ _:行首
$ g_:行尾
gg:文件开头
G:文件末尾
ngg:第N行
H M L:屏幕上/中/下
zt zz zb:滚动当前行到屏幕的上/中/下
Ctrl-D Ctrl-F:下半页/下一页
Ctrl-U Ctrl-B:上半页/上一页
gj gk:下/上一显示行
开始编辑 i:光标前
a:光标后
I:行首
A:行尾
o:新加下一行
O:新加上一行
r:替换当前光标位置的字符
x:删除当前光标位置的字符
J:下一行追加到本行的末尾
复制粘贴 “1y:拷贝到寄存器1
“1p:从寄存器1复制
v{motion} p:可视化选中后粘贴,不影响匿名寄存器
查找替换 :s/foo/bar/g:只替换当前行的每一处
:%s/foo/bar/gc:整个文件进行确认和替换
:5,12s/foo/bar/g:替换5-12行(包括)
:.,$s/foo/bar/g:从当前行一直到文件末尾
:.,+2s/foo/bar/g:当前行以及后两行
:vimgrep /foo/ **/*:在整个项目中查找
撤销重做 u:Undo Ctrl-r:Redo
相关文章
相关标签/搜索
每日一句
    每一个你不满意的现在,都有一个你没有努力的曾经。
公众号推荐
   一个健康类的公众号,欢迎关注
小青桔健康