Vim实用技巧(第2版)

978-7-115-42786-1
作者: 【英】Drew Neil(尼尔)
译者: 杨源车文隆
编辑: 陈冀康

图书目录:

详情

本书介绍了Vim编辑器的100多种实用技巧,旨在帮助读者快速掌握Vim编辑器,提高编程和开发效率。本书实用性强,内容精炼简洁,第1版的译本也获得了国内读者的一直认可。本书的第2版也得到国外程序开发社区和专家的一致好评,在Amazon上获得5星级的评论和很好的销售排名。

图书摘要

版权信息

书名:Vim实用技巧(第2版)

ISBN:978-7-115-42786-1

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [英] Drew Neil

  译    杨 源  车文隆

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Simplified Chinese-language edition Copyright © 2016 by Posts & Telecom Press. All rights reserved.

Copyright © 2015 The Pragmatic Programmers, LLC. Original English language edition,entitled Practical Vim, second Edition.

本书中文简体字版由The Pragmatic Programmers, LLC授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


Vim是一款功能丰富而强大的文本编辑器,其代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中得到非常广泛的使用。Vim能够大大提高程序员的工作效率。对于Vim高手来说,Vim能以与思考同步的速度编辑文本。同时,学习和熟练使用Vim又有一定的难度。

本书为那些想要提升自己的程序员编写,阅读本书是熟练掌握高超的Vim技巧的必由之路。全书共21章,包括123个技巧。每一章都是关于某一相关主题的技巧集合。每一个技巧都有针对性地解决一个或一类问题,帮助读者提升Vim的使用技能。本书示例丰富,讲解清晰,采用一种简单的标记方法,表示交互式的编辑效果,可以帮助读者快速掌握和精通Vim。

本书适合想要学习和掌握Vim工具的读者阅读,有一定Vim使用经验的程序员,也可以参考查阅以解决特定的问题。


我通过本书学到的Vim知识比从其他渠道获得的要多得多。

➤ Robert Evans 软件工程师,编码狂人

读完本书的几章后,我意识到自己有多么孤陋寡闻,在30分钟时间里一下子被从中级用户打回到初学者。

➤ Henrik Nyh 软件工程师

本书不断地改变我对一个编辑器能做什么的信仰。

➤ John P. Daigle 开发人员,ThoughtWorks 公司

Drew 在本书中继续了他在Vimcasts 网站上的杰出工作。对任何关注 Vim 的人来说,这都是一本不可错过的书。

➤ Anders Janmyr 开发人员,Jayway

本书在官方文档和如何真正使用 Vim 之间架设了一座跨越鸿沟的桥梁。读完几章以后,我就把默认编辑器换成了 Vim,从此再未换过。

➤ Javier Collado 自动化QA工程师,Canonical公司

Drew Neil 远不止为我们展示了工作所用的正确工具。他于穿插叙述之中,揭示了每个决定背后的哲学。本书教你让Vim 用手指思考,而不是期待你记住所有东西。

➤ Mislav Marohnic 顾问

我将 Vim 用于做服务器维护已经超过15年了,但只在最近才把它用于软件开发。我以为我了解 Vim,但本书极大地提升了我的编码效率。

➤ Graeme Mathieson 软件工程师,Rubaidh公司

本书让我意识到对于 Vim 我还有多少东西要学。每个技巧都可以很容易地马上应用到工作过程当中,从多方面提升你的工作效率。

➤ Mathias Meyer 《Riak 手册》的作者

在Vim 知识方面,本书是一个无尽的宝藏。现在我用 Vim 处理日常工作已经超过两年了,这本书给了我无穷的启发。

➤ Felix Geisendörfer 联合创始人,Transloadit


传统观点认为,Vim的学习曲线很陡,但我相信绝大多数Vim用户对此不以为然。在学习Vim的初期,人们的确需要经历一段驼峰似的阻力,然而一旦完成了vimtutor的训练,并了解如何为vimrc配置一些基本选项后,就会达到一个新的高度,能用Vim完成实际工作了—尽管步履蹒跚,但终有回报。

接下来该做什么呢?来自互联网的答案是所谓的“技巧”—一种解决特定问题的灵丹妙药。当你觉得解决某个问题的方法不是最佳时,没准儿就要去搜索专门解决它的技巧了,或者你可能会主动看一些更受追捧的技巧。根据我的学习经验,这种策略的确奏效,不过这样学得很慢。“用 * 查找光标下的单词”这一招固然会让你受益匪浅,但却难以帮助你像Vim高手一样思考问题。

当我发现本书正是以这种“技巧”的方式组织章节时,你一定能理解我所持的怀疑态度。这区区上百条技巧怎么能让我举一反三呢?但当我翻了几页本书之后,我才意识到自己对“技巧”的理解太片面了。本书介绍的技巧与我认为的“问题 / 解决方法”方式有所不同,它旨在向人们传授如何像Vim高手一样思考问题。从某种意义上讲,这些技巧更像是寓言故事而非医师处方。书中的前几条技巧向人们介绍了应用范围很广的 . 命令,这是Vim高手们最重要的看家法宝,因为当时没人指点,我自己过了多年才意识到这一点。

正是由于这个原因,我才对本书的出版感到如此兴奋。如果现在再有Vim新手问我“下一步该学什么”,我知道该告诉他们什么了。不管怎么说,本书甚至还教会了我不少东西呢。

Tim Pope

Vim 核心贡献者

2012 年 4 月


本书是为那些想提升自己的程序员写的。你一定听说过,对于Vim高手来说,Vim能以思考的速度编辑文本。阅读本书则是通往此途的必经之路。

本书是精通Vim的捷径。尽管它不会手把手教你,不过初学者可以先运行随Vim发布的交互式课程——Vim向导[1]来了解必备的知识。本书则在这一基础之上着重介绍核心概念,并为你讲解地道的用法。

Vim是高度可配置的,然而定制是一件很个性化的事情,因此我试图避免建议什么应该放进你的vimrc里,什么不应该。相反,本书关注的是Vim编辑器的核心功能。不管你是通过SSH登录远端服务器工作,还是在用本地安装了插件而增添了额外功能的GVim,这些功能都永远在那儿。精通了Vim的核心功能,你就获得了一个可移植的、强大的文本编辑工具。

这是一本按技巧组织的书,它被设计成不必从头读到尾(没错!在下一章开头,我会建议你直接跳到正文)。每一章都是关于某一相关主题的技巧集合,而每个技巧都讲解一个特定的实用功能。有些技巧自成一体,而有些技巧则依赖本书中其他地方的内容,这些有依赖关系的技巧会以交叉引用的形式呈现给大家,因此你可以轻松找到所有内容。

虽然整本书的进度安排不是先从入门开始,然后再到高级,但是每个独立章节中的内容都是按循序渐进的方式来组织的。缺乏经验的Vim用户可能更愿意先浏览全书,只阅读每章的前几个技巧;而高级用户可能会重点看每章中比较靠后的技巧,或是根据需要查阅本书。

在Vim中,对一件给定的任务,总能找到不止一种解决办法。例如,第1章里的所有问题都围绕 . 命令进行设计,以便讲解 . 命令的应用,不过这些问题也都可以用 :substitute命令解决。

在阅读我的解决方法时,你自己也许会想:“难道用这种方法做不是更快吗?”可能你是对的!我的解决方法只是在讲解一种特定的技术,试图透过它们简单的外表,找出它与你日常所面临问题的相似之处,而这一点才是这些技巧可以帮你节省时间的地方。

如果你要低头看着键盘打字,那学习Vim的好处不会立竿见影地显现出来。要高效地使用Vim,必须学会盲打。

Vim的祖先要追溯到经典的Unix编辑器vi和ed,参见技巧27中的“Vim(及其家族)的词源”部分,它们比鼠标及所有点击界面出现得都早,因此根本没有这类接口,所有操作都通过键盘完成。Vim也是一样,Vim中的所有操作也都可以通过键盘完成。对盲打人员来说,这意味着用Vim做任何事都能更快些。

[1] http://vimhelp.appspot.com/usr_01.txt.html#vimtutor


感谢Bram Moolenaar 创造了 Vim,也感谢所有对 Vim 的开发做出贡献的人。这是一个永恒的软件,我期盼自己能与它一同成长。

感谢Pragmatic Bookshelf 公司的每个人,正是你们的齐心协力才使这本书变得更好。特别感谢本书的项目编辑Kay Keppler,感谢他教导我成为一名作者,并促使本书成形,而不在乎经历了多少的成长之痛以及我偶尔使性子。同样要感谢本修订版的项目编辑Katharine Dvorak。我也想感谢David Kelly,感谢他对我与众不同的排版要求所做出的巧妙设计。

本书刚开始并没有打算按照技巧的方式组织章节,但Susannah Pfalzer 发现采用这种格式会更好。重写这么多内容的确很痛苦,但做完之后,我头一次写出了让自己满意的初稿。Susannah 知道什么是最好的,感谢她分享了这一见解。

感谢Dave Thomas 和 Andy Hunt 创办了Pragmatic Bookshelf 公司。我没想过让其他出版商出版本书,并且我很荣幸本书能和其他书一起列在他们的书目中。

如果没有技术审阅人员,本书不可能出版。每一位技术审阅人都为之贡献了力量,并帮助本书成形。在此我想感谢Adam McCrea、Alan Gardner、Alex Kahn、Ali Alwasity、Anders Janmyr、Andrew Donaldson、Angus Neil、Charlie Tanksley、Ches Martin、Daniel Bretoi、David Morris、Denis Gorin、Elyézer Mendes Rezende、Erik St. Martin、Federico Galassi、Felix Geisendörfer、Florian Vallen、Graeme Mathieson、Hans Hassel- berg、Henrik Nyh、Javier Collado、Jeff Holland、Josh Sullivan、Joshua Flanagan、Kana Natsuno、Kent Frazier、Luis Merino、Mathias Meyer、Matt Southerden、Mislav Marohnic、Mitch Guthrie、Morgan Prior、Paul Barry、Peter Aronoff、Peter Rihn、Philip Roberts、Robert Evans、Ryan Stenhouse、Steven、Ragnarök、Tibor Simic、Tim Chase、Tim Pope、Tim Tyrrell以及Tobias Sailer。

即便是有了技术审阅人员的审查,一些错误依然隐藏在书中。在此我想感谢为本书提交错误报告的所有人,是你们帮助我找到错误并修正它们。

Vim的内置文档是非常了不起的资源,本书中到处都在引用它。我想感谢Carlo Teubner把Vim文档在线发布到vimhelp.appspot.com,并持续更新。

本书第一版中有些技巧很糟糕,但我依然把它们加入书中,因为我觉得它们很重要。在本修订版中,我很高兴能够重写这几个糟糕的技巧。感谢Christian Brabandt实现了“改变玩法”的gn命令,让我能够重写技巧84。感谢Yeggapan Lakshmanan实现了cfdo命令(及其相关命令),让我能够重写技巧97。我也想感谢David Bürgin所提交的Vim补丁7.3.850,这一补丁终结了vimgrep命令带给我的小头疼。

另外,我想感谢整个 Vim 社区,感谢他们通过互联网分享见解。通过阅读StackOverflow 上 Vim 标签中的内容和订阅 vim_use 邮件列表,我学到了本书中的很多技巧。

Tim Pope的rails.vim插件对于说服我皈依 Vim 起了很大的作用,并且他开发的许多其他插件也都成为我的Vim设置中必不可少的组成部分。我也从Kana Natsuno的插件中领悟到很多东西,在我印象里,他的自定义文本对象是对Vim核心功能最好的扩展。感谢你们两位把Vim这把“锯子”磨得更加锋利,使我们大家从中获益。

感谢Joe Rozner 提供的 wakeup 源码,我使用它来介绍 :make 命令;感谢Oleg Efimov对nodeline缺陷的快速反馈,也感谢Ben Cormack对robots以及ninjas的解释。

2012 年 1 月,我们搬到了柏林,在这里的技术社区的启发下,我完成了本书。我想感谢Gregor Schmidt 成立了 Vim 柏林用户组,也感谢Schulz-Hofen 举办的招待我们的聚会。与 Vim 用户交谈的机会,真正帮我理清了思路,因此我感激每个参加 Vim 柏林会议的人。也感谢Daniel 及 Nina Holle 把自己的房子转租给我们,它既是居住的绝佳场所,也是工作的高效环境。

2011 年 3 月我住在埃及时,需要动手术清除肠道粘连。不幸的是,我离家很远;幸运的是,我妻子陪伴在身边。Hannah把我送到半岛南部医院,在那里我受到了很好的照顾。我想对那里的所有医务人员表示感谢,感谢他们对我的悉心照料,也感谢 Shawket Gerges 医生为我成功地进行了手术。

当我母亲知道我要动手术时,她抛下一切乘坐最早航班飞到埃及。要知道当时这个国家正在发生革命,她老人家需要多大的勇气才能做到这一切啊。我无法想象没有我母亲的支持与经验,我和Hannah 该如何度过这段困难时期。我为一生中拥有两个如此伟大的女人而感到幸福。


在本书中,我会通过图例进行演示,而不是描述它们,因为用书面语不太容易做到这一点。为了显示在交互式编辑会话中所采取的步骤,我采用了一种简单的标记方法,把按键操作及 Vim 缓冲区的内容排在一起进行说明。

如果你急于动手操作的话,现在可以直接跳过这一部分。这一部分主要描述本书沿用的体例,你会发现其中很多都不需要解释。或许在某个地方你会偶然发现一个符号,想搞清楚它究竟代表什么意思。当这种情况发生时,你可以回到这一部分来寻求答案。

了解Vim文档的最好方式是花点时间阅读。我在书中给出了Vim文档入口的“超链接”,以方便读者找到相关文档。例如,这里有一个通往Vim向导的“超链接”::h vimtutor

这个图标具有两个作用。第一,它起到指示牌的作用,把目光吸引到这些有用的参考信息上;第二,如果你在联网的电子设备上阅读本书的话,那么你可以单击这些图标,它会把你带到 Vim 在线文档的相应入口。从这个意义上讲,它的确是超链接。

但是,如果你正在阅读纸版书,那该怎么做?别担心,如果在你手边有可访问的 Vim程序,简单地输入图标前的命令即可。

例如,你可以输入 :h vimtutor:h:help命令的简写)。你可以把它想成 vimtutor文档的唯一地址,即某种形式的URL。从这个意义上讲,此 help 引用也是一种指向 Vim 内置文档的超链接。

Vim 区分模式的界面把它同其他文本编辑器区别开来。以音乐作个比喻,让我们拿Qwerty 键盘与钢琴键盘进行比较。一个钢琴家可以每次只弹一个琴键来演奏主旋律,他也可以一次弹多个键来演奏和弦。对于多数文本编辑器,要触发一个键盘快捷键,需要先按住一个或多个修饰键,如控制键或命令键,然后再按另外一个键,在Qwerty键盘上的这种操作方式,等同于在钢琴键盘上演奏和弦。

某些Vim命令也由演奏和弦的方式触发。不过普通模式命令则被设计成输入一串按键。在Qwerty键盘上的这种操作方式,则等同于在钢琴键盘上演奏主旋律。

Ctrl-s 是用来表示组合键命令的惯用约定,意为“同时按控制键及 s 键”,但这种约定方式并不适合用来描述Vim区分模式的命令集。在本节,我们将结识贯穿于全书的标记,在讲解Vim的用法时会用到它们。

在普通模式中,我们按次序输入一个或多个键组成一条命令。这些命令看起来像下面这样:

标记

含义

x

按一次 x

dw

依次按 dw

dap

依次按 dap

这些序列大多数包含两个或3个按键,但有的命令会更长。解读 Vim 普通模式命令序列的含义可能颇具挑战性,不过经过练习后你会做得更好。

当你看到诸如 <C-p> 这样的键时,它的意思不是“先按 <,然后按 C,再按 -,等等”。<C-p> 标记等同于Ctrl-p,意为“同时按 <Ctrl>p”。

我不会无缘无故地选择这种标记方式的。首先,在Vim 的文档中使用了这种标记(:h key-notation ),我们也用它定义自定义按键映射项。另外,某些 Vim 命令由组合键及其他键以一定的次序组合在一起,这种标记也可以很好地表达这些命令。请看下面这些例子。

标记

含义

<C-n>

同时按 <Ctrl>n

g<C-]>

g,然后同时按 <Ctrl>]

<C-r>0

同时按 <Ctrl>r,然后按 0

<C-w><C-=>

同时按 <Ctrl>w,然后同时按 <Ctrl>=

很多Vim命令需要以一定的次序按两个或多个按键。有些命令后面必须跟某种特定类型的按键,而其他命令后面则可以跟键盘上的任意键。我使用花括号表示一条命令后可以跟有效按键集合。下面是一些例子。

标记

含义

f{char}

f,后面跟任意字符

`{a-z}

`,后面跟任意小写字母

m{a-zA-Z}

m,后面跟任意小写或大写字母

d{motion}

d,后面跟任意动作命令

<C-r>{register}

同时按 <Ctrl>r,后面跟一个寄存器地址

有些特殊按键以其名字表示,下表节选了其中的一些。

标记

含义

<Esc>

按退出键

<CR>

按回车键,也写作 <Enter>

<Ctrl>

按控制键

<Tab>

按制表键

<Shift>

按切换键

<S-Tab>

同时按 <Shift><Tab>

<Up>

按上光标键

<Down>

按下光标键

按空格键

 

注意,空格由 表示。它和 f{char} 命令组合在一起时记为 f␣

在操作Vim时,经常会从普通模式切换到插入模式,然后再切换回普通模式。Vim中的每个键都可能具有不同的含义,这取决于当前哪个模式生效。我用了另一种样式表示在插入模式中输入的键,这可以让人很容易地把它们与普通模式下的按键区分开来。

看看这个例子 cwreplacement<Esc>。普通模式命令 cw 会删除从光标位置到当前词结尾处的文本,并切换到插入模式。然后我们在插入模式中输入单词“replacement”,并按 <Esc> 键再切换回普通模式。

普通模式所用的样式也用于可视模式,而插入模式的样式也用来表示命令行模式及替换模式下输入的按键。你可以通过上下文清楚地知道当前处于哪个模式。

在有些技巧中,我们会在 shell或 Vim中执行一条命令行命令。下面是在 shell 中执行 grep 命令的格式。

 $ grep -n Waldo *

下面是执行 Vim 内置的 :grep 命令的格式。

➾ :grep Waldo *

在全书中,$符号表示在外部 shell 中执行一条命令行命令,: 提示符则表示这条命令在内部的命令行模式中执行。有时我们也会看到其他的提示符,包括:

提示符

含义

$

在外部 shell 中执行命令行命令

:

用命令行模式执行一条 Ex 命令

/

用命令行模式执行正向查找

?

用命令行模式执行反向查找

=

用命令行模式对一个 Vim 脚本表达式求值

无论你何时在文中见到一条 Ex 命令,比如 :write,都可以假设我们按了 <CR> 键来执行该命令,否则该命令什么也不会做,因此可以认为 <CR> 在 Ex 命令中是隐含的。

与之相反,Vim 的查找命令允许在按 <CR> 前预览第一个匹配项(参见技巧82)。当你在文中见到一条查找命令时,比如 /pattern<CR>,你会看到 <CR> 键被显式地标出来了;如果 <CR> 被省略了,那是有意为之,也就是说你现在还不要按回车。

在显示缓冲区内容时,如果能指示当前光标位于何处,那会很有用。在下面的例子里,你可以看到光标位于单词“One”的第一个字母上。

One two three

当我们执行一项包含若干步的修改时,缓冲区的内容会经历一些中间状态。为了讲解这一过程,我使用了一个表格,在其左栏中显示所执行的命令,在右栏中显示缓冲区的内容。下面是个简单的例子。

按键操作

缓冲区内容

{start}

One two three

dw

two three

在第2行,我们运行dw命令删除了光标下的单词。通过查看位于同一行的缓冲区内容,我们可以立刻看到这条命令执行完后缓冲区的状态。

在讲解 Vim 的查找命令时,如果能把缓冲区内出现的每个匹配项都高亮显示出来,那会很有帮助。在下例中,查找字符串“the”会让出现该模式的4处地方被高亮显示出来:

按键操作

缓冲区内容

{start}

the problem with these new recruits is that they don't keep their boots clean.

/the<CR>

the problem with these new recruits is that they don't keep their boots clean.

你可以跳到技巧81,了解如何激活 Vim 的查找高亮功能。

可视模式允许在缓冲区内选择文本,然后在其上操作。在下例中,用it 文本对象选中 <a> 标签内的文本。

按键操作

缓冲区内容

{start}

<a href="http://pragprog.com/dnvim/">Practical Vim</a>

vit

<a href="http://pragprog.com/dnvim/">Practical Vim</a>

注意,高亮显示可视选区的样式与高亮显示查找匹配项的样式相同。当你看到这种样式时,根据上下文就可以知道它究竟是代表一处查找匹配项,还是一个高亮选区。

本书中的例子通常都先显示修改前的文件内容,并在示例文本中给出该文件所在的路径,如下所示:

macros/incremental.txt

partridge in a pear tree
turtle doves
French hens
calling birds
golden rings

每当你看到以这种方式列出的文件路径时,都表示该例可被下载。我建议你在 Vim 中打开此文件,然后亲自试试这个例子。这是学习Vim的最好方式。

要照着书中的例子操作,你可以从Pragmatic Bookshelf的网站上下载本书所有的示例和源代码[1]。如果你在联网电子设备上阅读本书,也可以单击文件名来逐一获取每个文件。你可以用上面的例子试验一下。

Vim 是高度可配置的,如果你不喜欢其默认的行为,可以改变它们。这本是好事,但是,如果你用自定义的 Vim 跟着做本书中的例子,可能会感到迷惑,你也许会发现有些东西并不像书中描述的那样工作。如果你怀疑是自定义配置造成了干扰,那么你可以做一个快速的测试。试着先退出 Vim,然后再用下列选项启动它。

 $ vim -u NONE -N

-u NONE 标志让 Vim 在启动时不加载你的vimrc,这样,你的定制项就不会生效,插件也会被禁用。当用不加载 vimrc 文件的方式启动时,Vim会切换到 vi 兼容模式,这将导致很多有用的功能被禁用,-N 标志则会使能 ‘nocompatible’ 选项,防止进入 vi 兼容模式。

对于本书中的大多数例子来说,用 vim -u NONE –N启动 Vim应该可以确保你获得与书中的描述相符的体验,不过也有几处例外。有些Vim的内置功能是由 Vim 脚本实现的,也就是说,只有在激活插件时,它们才会工作。下面的文件中包含了激活 Vim 内置插件的最小配置。

essential.vim

set nocompatible
filetype plugin on

在启动 Vim 时,可以执行如下命令,用该文件取代你的 vimrc

 $ vim -u code/essential.vim

在执行时,需要相应地调整 code/essential.vim 文件所在的路径。激活Vim内置的插件功能后,可以使用诸如 netrw(参见技巧44)、omni-completion(参见技巧119),以及很多其他的功能。我在本书中所说的Vim的出厂配置,指的就是激活了内置的插件功能,并且禁用了 vi 兼容模式时的配置。

需要留意技巧开头的名为“准备工作”的小节,要想跟着技巧中的步骤做,需要对Vim进行相应的配置。如果你由Vim的出厂配置开始,然后再动态应用这些定制项,就应该能重现技巧中的结果,不会遇到任何问题。

如果你仍遇到问题,请看后面的“关于Vim的版本”部分。

Vim脚本让我们可以给Vim添加新的功能,或是改变其已有的功能。它是一种完整的脚本语言,并且这个主题本身就可以写一整本书。不过本书并不是这样一本书。

但我们不会完全避开此话题,Vim脚本一直隐身在幕后,时刻准备响应我们的召唤。在技巧16、技巧71、技巧95及技巧96中,我们将看到一些如何使用它们完成日常工作的例子。

本书展示了如何使用Vim的核心功能。换句话说,它假设我们不使用任何第三方插件。不过技巧87是个例外,visual-star.vim 插件添加的功能我认为是不可或缺的,并且它只需很少的代码——不超过10行。同时它也展示了扩充 Vim 的功能是多么容易。文中给出了 visual-star.vim的实现,但没有讲解。这应该能给你一些印象,了解Vim脚本是什么样的,以及你能用它干什么。如果它激起了你的兴趣,那就更好了。

本书中的所有例子都在最新的 Vim版本中测试过,在写本书时是版本 7.4。就是说,大多数例子在任意 7.x 版本中都能够很好地工作,并且所讨论的很多功能在 6.x 中也同样适用。

有些Vim功能可以在编译期间被禁用。例如,在配置编译选项时,可以传入--with-features=tiny参数,这会禁用除最基本的功能外的其他所有功能(Vim的功能集还包括 smallnormalbighuge)。可以查阅 :h +feature-list ,浏览完整的功能列表。

如果你发现自己的Vim缺少本书所讨论的某个功能,那么你也许正在使用一个最小功能集的 Vim 发行版。可以用 :version命令检查此功能是否可用。

➾ :version

《  VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Oct 14 2015 18:41:08)
    Huge version without GUI. Features included (+) or not (-):
    +arabic +autocmd +balloon_eval +browse +builtin_terms +byte_offset
    +cindent +clientserver +clipboard +cmdline_compl +cmdline_hist
    +cmdline_info +comments
     ...

在现代计算机上,没理由不用 Vim 的 huge 功能集!

传统上,Vim在终端内运行,没有图形用户界面(GUI)。我们也可以说Vim具有TUI,即文本用户界面。如果你每天有大量时间花在命令行上,你会感觉这很自然。

如果你通常使用基于图形用户界面的文本编辑器,那么GVim(或OSX上的MacVim)可以给你提供一个通往Vim世界的有用桥梁(参见 :h gui)。GVim支持更多的字体以及更多的语法高亮颜色,也可以使用鼠标。它也遵从某些操作系统的约定,例如,在MacVim中,可以用Cmd-XCmd-V与系统剪切板交互,也可以用 Cmd-S 保存文件,用 Cmd-W关闭一个窗口。如果你能接受的话,可以用这些命令,不过你应该已意识到,还有更好的方法完成这些。

对本书的目的而言,运行终端Vim还是GVim关系不大。我们将着重于介绍Vim的核心命令,这些功能在两者中都能很好地运行。我们要学习的重点是如何用Vim的方式来工作。

[1] http://pragprog.com/titles/dnvim/source_code


从本质上讲,我们的工作是重复性的。不论是在几个不同的地方做相同的小改动,还是在文档的相似结构间移动,我们都会重复很多操作。凡是可以简化重复性操作的方式,都会成倍地节省我们的时间。

Vim对重复性操作进行了优化。它之所以能高效地重复,是因为它会记录我们最近的操作,让我们用一次按键就能重复上次的修改。这听起来很强大,但是除非我们能够学会规划按键动作,使得在重复时能完成一项有用的工作,否则这没什么用。掌握这一理念是高效使用Vim的关键。

我们将以 . 命令作为开始。这个看似简单的命令是Vim中的瑞士军刀,掌握它的用法是精通Vim的第一步。我们将运行一些可由 . 命令快速完成的简单编辑任务,虽然每个任务彼此之间截然不同,但解决的方法却大同小异。我们将找到一种理想的编辑模式,即用一次按键移动,用另一次按键执行。

. 命令可以让我们重复上次的修改,它是Vim中最为强大的多面手。

Vim文档只是简单地提到 . 命令会“重复上次修改”(参见 :h . ),这听起来没什么特别,但在这个简单的说明里,我们会发现让Vim区分模式的编辑模型如此高效的核心原因。首先我们要问:“究竟什么是修改?”

要理解 . 命令的强大,我们需要意识到这一点:“上次修改”可以指很多东西,一次修改的单位可以是字符、整行,甚至是整个文件。

我们将使用下面这段文本进行说明。

the_vim_way/0_mechanics.txt

Line one
Line two
Line three
Line four

x 命令会删除光标下的字符,在这种情况下使用 . 命令“重复上次修改”时,就会让Vim删除光标下的字符。

按键操作

缓冲区内容

{start}

Line one

 

Line two

 

Line three

 

Line four

×

ine one

 

Line two

 

Line three

 

Line four

.

ne one

 

Line two

 

Line three

 

Line four

..

one

 

Line two

 

Line three

 

Line four

我们可以输入几次 u 撤销上述修改,使文档恢复到初始状态。

dd 命令也做删除操作,但它会把整行一起删掉。如果在 dd 后使用 . 命令,那么“重复上次修改”会让Vim删除当前行。

按键操作

缓冲区内容

{start}

Line one

 

Line two

 

Line three

 

Line four

dd

Line two

 

Line three

 

Line four

.

Line three

 

Line four

最后,>G 命令会增加从当前行到文档末尾处的缩进层级。如果在此命令后使用 . 命令,那么“重复上次修改”会让Vim增加从当前行到文档末尾的缩进层级。在下例中,让光标从第二行开始,以便一目了然地看出差别。

按键操作

缓冲区内容

{start}

Line one

 

Line two

 

Line three

 

Line four

>G

Line one

 

 Line two

 

 Line three

 

 Line four

j

Line one

 

 Line two

 

 Line three

 

 Line four

.

Line one

 

 Line two

 

  Line three

 

   Line four

j.

Line one

 

 Line two

 

  Line three

 

   Line four

xdd以及 > 命令都是在普通模式中执行的命令,不过,每次进入插入模式时,也会形成一次修改。从进入插入模式的那一刻起(例如,输入 i),直到返回普通模式时为止(输入 <Esc>),Vim会记录每一个按键操作。做出这样一个修改后再用 . 命令的话,它将会重新执行所有这些按键操作(参见技巧8中的在插入模式中移动光标会重置修改状态部分中的补充说明)。

在第11章“宏”中,我们将看到Vim可以录制任意数目的按键操作,然后在以后重复执行它们。这让我们可以把最常重复的工作流程录制下来,并用一个按键重放它们。可以把 . 命令当成一个很小的宏(macro)。

我们将在本章看到一些关于 . 命令的应用,另外还将在技巧9及技巧23中学到 . 命令的一些最佳应用技巧。

对于在行尾添加内容这样的常见操作,如添加分号,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)合并成了一次按键。不过它不是唯一一个这样的命令,很多Vim的单键命令都可以被看成两个或多个其他命令的组合。下表列出了类似的一些例子,你能找出它们之间别的共同点吗

如果你发觉自己正在输入 ko(或更糟糕,在用 k$a),马上打住!想想你在干什么,然后你就会意识到可以把它换成O 命令。

你找出这些命令别的共同点了吗?它们全都会从普通模式切换到插入模式。仔细想想这一点,并想想这对 . 命令可能产生怎样的影响。

a 命令在当前光标之后添加内容, A 命令则在当前行的结尾添加内容。不管光标当前处于什么位置,输入 A 都会进入插入模式,并把光标移到行尾。换句话说,它把 $a 封装成了一个按键操作。在本技巧后的一箭双雕部分中,我们将会看到Vim提供了不少这样的复合命令。

下面是对之前例子的改进。

复合命令

等效的长命令

C

c$

s

cl

S

^C

I

^i

A

$a

o

A

O

ko

按键操作

缓冲区内容

{start}

var foo = 1

 

var bar = 'a'

 

var foobar = foo + bar

A;

var foo = 1;

 

var bar = 'a'

 

var foobar = foo + bar

j

var foo = 1;

 

var bar = 'a'

 

var foobar = foo + bar

.

var foo = 1;

 

var bar = 'a';

 

var foobar = foo + bar

j.

var foo = 1;

 

var bar = 'a';

 

var foobar = foo + bar;

A 来代替 $a,大大提升了 . 命令的效率。不必再把光标移到行尾,只需保证它位于该行内就行了(可在任意位置)。现在可以重复执行足够多次的 j. ,完成对后续行的修改。

一键移动,另一键操作,真是太完美了!请留意这种应用模式,因为我们即将在更多的例子中看到它的身影。

虽然这一模式对这个简短的例子来说很好用,但它不是万能的。试想一下,如果我们不得不给连续50行添加分号,即便每个修改输一次 j. ,看起来也是一项很繁重的工作。跳到技巧30可以看到另外一种解决方法。

我们可以用一种常用的Vim操作习惯在一个字符前后各添加一个空格。乍一看,这种方法有点古怪,不过其好处是可重复,这将使我们可以事半功倍地完成工作。

假设有一行代码看起来是这样的:

the_vim_way/3_concat.js

var foo = "method("+argument1+","+argument2+")";

在JavaScript里把字符串连接到一起从来都不美观,但可以像下面这样在 + 号前后各添加一个空格,让肉眼更容易识别。

var foo = "method(" + argument1 + "," + argument2 + ")";

下面的惯用方法可以解决这个问题。

按键操作

缓冲区内容

{start}

var foo = "method("+argument1+","+argument2+")";

f+

var foo = "method("+argument1+","+argument2+")";

s␣+␣

var foo = "method(" + argument1+","+argument2+")";

;

var foo = "method(" + argument1+","+argument2+")";

.

var foo = "method(" + argument1 + ","+argument2+")";

;.

var foo = "method(" + argument1 + "," + argument2+")";

;.

var foo = "method(" + argument1 + "," + argument2 + ")";

s 命令把两个操作合并为一个:它先删除光标下的字符,然后进入插入模式。在删除 + 号后,先输入␣+␣,然后退出插入模式。

先后退一步,然后前进三步,这是个奇怪的小花招,看起来可能不够直接。但这样做最大的好处是:我们可以用 . 命令重复这一修改。我们所要做的只是把光标移到下一个 + 号处,然后用 . 命令重复这一操作即可。

本例中还有另外一个小窍门。f{char} 命令让Vim查找下一处指定字符出现的位置,如果找到了,就直接把光标移到那里(参见 :h f)。因此,输入 f+ 时,光标会直接移到下一个 + 号所在的位置。我们将会在技巧50里学到更多关于 f{char} 命令的知识。

完成第一处修改后,可以重复按 f+ 命令跳到下一个 + 号所在的位置。不过,还有一种更好的方法可以用。; 命令会重复查找上次 f 命令所查找的字符,因此不用输入4次 f+,而是只输入一次,后面跟着再用3次 ; 命令。

; 命令带我们到下一个目标字符上, . 命令则重复上次的修改。因此,可以连续输入3次 ;. 来完成全部修改。看起来是不是很熟悉?

与其和Vim区分模式的编辑模型做斗争,倒不如与它一起协同工作。然后,你就会发现它能把特定任务变得多么的容易。

在面对重复性工作时,我们需要让移动动作和修改都能够重复,这样就可以达到最佳编辑模式。Vim对此的支持是:它会记住我们的操作,并使最常用的操作触手可及,所以可以很方便地重复执行它们。本节将介绍 Vim 可以重复执行的每个操作,并学习如何回退这些命令。

我们已经看到 . 命令会重复上次修改。由于很多操作都被当成一次修改,因此 . 命令已经证明了它的神通广大。但有些命令能以其他的方式重复。例如,@: 可以用来重复任意Ex命令(在技巧31中讨论),或者也可以输入 & (参见技巧93)来重复上次的 :substitute命令(它本身也是一条Ex命令)。

如果我们知道如何重复之前的操作,而无需每次都输入整条命令,那么就会获得更高的效率。可以先执行一次,随后只需重复即可。

然而,这么少的按键就可以完成这么多的事情,这也可能会带来麻烦。我们需要很小心地操作才行,不然就很容易出错。当一遍又一遍地连续按 j.j.j. 时,那种感觉就像是在敲鼓。可是,如果不小心在一行上敲了两次 j 键,会发生什么?或是更糟,敲了两次 . 键?

当Vim让一个操作或移动可以很方便地重复时,它总是会提供某种方式,让我们在不小心做过头时能回退回来。对 . 命令而言,我们永远可以按 u 键撤销上次的修改。如果在使用 f{char} 命令后,不小心按了太多次 ; 键,就会偏离我们的目标。不过可以再按 , 键跳回去,这个命令会反方向查找上次f{char}所查找的字符(参见技巧50)。

当不小心做过头时,知道怎么回退会很有帮助。表1-1总结了Vim中可重复执行的命令,以及相应的回退方式。在多数场景中,撤销(undo)都是我们想要使用的命令,难怪我键盘上的 u 键磨损得这么厉害!

表1-1 可重复的操作及如何回退

目的

操作

重复

回退

做出一个修改

{edit}

.

u

在行内查找下一指定字符

f{char}/t{char}

;

,

在行内查找上一指定字符

F{char}/T{char}

;

,

在文档中查找下一处匹配项

/pattern

n

N

在文档中查找上一处匹配项

?pattern

n

N

执行替换

:s/target/replacement

&

u

执行一系列修改

qx{changes}q

@x

u

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 命令也能胜任这项工作,我们将在技巧90中学到该怎么做。不过现在,我们将寻求符合本章主题的另一种解决办法。

现在你可能已经猜到了,. 命令是我最喜爱的Vim单键命令,而排在第二位的是 命令,此命令可以查找当前光标下的单词(参见 `:h `)。

我们可以调出查找提示符,并输入完整的单词来查找“content”。

➾ /content

或者,可以简单地把光标移到这个单词上,然后按 * 键。以下面的操作为例。

按键操作

缓冲区内容

{start}

...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...

*

...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...

cwcopy<Esc>

...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 copy...

n

...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 copy...

.

...We're waiting for copy 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 copy...

刚开始,把光标移到单词“content”上,然后使用 * 命令对它进行查找,你也可以自己试一下。这会产生两个结果:一是光标跳到下一个匹配项上,二是所有出现这个词的地方都被高亮显示出来。如果你并没有看到高亮,试着运行一下 :set hls。要了解更多这方面的内容,请参见技巧81。

执行过一次查找“content”的命令后,现在只需按 n 键就可以跳到下一个匹配项。在本例中,按 *nn 会遍历完所有的匹配项,从而跳回到本次查找的起点。

当光标位于“content”的开头时,就可以着手修改它。这包括两步操作:首先要删除单词“content”,然后输入替代的单词。cw 命令会删除从光标位置到单词结尾间的字符,并进入插入模式,接下来就可以输入单词“copy”了。Vim会把我们离开插入模式之前的全部按键操作都记录下来,因此整个 cwcopy<Esc> 会被当成一个修改。也就是说,执行 . 命令会删除从光标到当前单词结尾间的字符,并把它修改为“copy”。

万事俱备!每次按 n 键时,光标就会跳到下一个“content”单词所在之处,而按 . 键时,它就会把光标下的单词改为“copy”。

如果想替换所有地方,就可以不加思考地一直按 n.n.n. 以完成所有的修改(但是,这种情况下也可以用 :%s/content/copy/g 命令)。然而,由于我们需要留意不符合要求的匹配项,所以在按了 n 之后,要审视一下当前的匹配项,然后决定是否把它改为“copy”。如果需要修改的话,就按 . 命令,反之则不用。无论决定是什么,都可以再次按 n 移到下一个地方,如此循环往复,直到完成全部的修改。

到目前为止,我们介绍了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> 键的使用,就可使撤销命令作用于单词、句子或段落。

那么,应该多久离开一次插入模式呢?这是个人喜好的问题,不过我喜欢让每个“可撤销块”对应一次思考过程。在写这段文字时(当然是在Vim中写的),我经常在每句话的结尾停顿一下,想一想接下来该写什么。不管停顿的时间有多短,每次停顿都是一个自然的中断点,提示我该退出插入模式了。当我准备好继续写时,按 A 命令就可以回到原来的地方继续写作。

如果我认为已经走错了方向,就会切换到普通模式,然后按 u 撤销。每次做撤销时,文字都按我最初书写时的思路,被切分成条理清晰的块,也就是说我可以很容易地试着写一两句话,如果感到不合适的话,随后按一两下键就可以将其舍弃。

当处于插入模式时,如果光标位于行尾的话,另起一行最快的方式是按 <CR>。不过有时我更喜欢按 <Esc>o,这是因为我有预感,也许在撤销时我想拥有更细的粒度。如果听起来这不太好理解,不必担心,当你对Vim越来越熟悉时,就会感到切换模式越来越轻松。

一般来讲,如果你停顿的时间长到足以问“我应该退出插入模式吗?”这个问题,就退出吧。

在插入模式中移动光标会重置修改状态

当我提到撤销命令会回退从进入插入模式到退出此模式期间输入(或删除)的全部字符时,我略过了一个小细节。如果在插入模式中使用了 <Up><Down><Left><Right> 这些光标键,将会产生一个新的撤销块。你可以把这想象为先切换回普通模式,然后用 hjkl 命令对光标进行了移动,唯一区别是我们并没有退出插入模式。这也会对 . 命令的操作产生影响。

Vim对重复操作进行了优化,要利用这一点,必须考虑该如何构造修改。

在Vim中,要完成一件事,总是有不止一种方式。在评估哪种方式最好时,最显而易见的指标是效率,即哪种手段需要的按键次数最少(又名VimGolf [1])。然而,在平局时该如何选择获胜者呢?

在下例中,假设光标位于行尾处的字符“h”上,而我们想要删除单词“nigh”:

normal_mode/the_end.txt

The end is nigh

因为光标已经位于单词末尾,所以可以先反向删除该词。

按键操作

缓冲区内容

{start}

The end is nigh

db

The end is h

x

The end is

db 命令删除从光标起始位置到单词开头的内容,但会原封未动地留下最后一个字符 “h”,再按一下 x 键就可以删除这个捣乱的字符。这样,整个操作的 Vim高尔夫得分是3分。

这一次,让我们尝试一下正向删除。

按键操作

缓冲区内容

{start}

The end is nigh

b

The end is nigh

dw

The end is

先用 b 命令把光标移到单词的开头,移动好后,就可以用一个 dw 命令删掉整个单词。这一次的Vim高尔夫得分也是3分。

到目前为止,已有的两种方式都要先做某种准备工作或清理工作。另外,也可以使用更为精准的 aw 文本对象(text object),而不是用动作命令(参见 :h aw )。

按键操作

缓冲区内容

{start}

The end is nigh

daw

The end is

可以把 daw 命令解读为“delete a word”,这样比较容易记忆。在技巧52和技巧53中将介绍更多关于文本对象的细节。

我们尝试了3种不同的方式来删除一个词:dbxbdw 以及 daw。每种情况的Vim高尔夫得分都是3分。那么要怎么回答这个问题:“哪种方式最好?”

还记得吗,Vim对重复操作进行了优化。让我们再回顾一下这3种方式,这一次我们跟着用一次 . 命令,看看会发生什么。我建议你自己也亲自试一下。

反向删除方案包含两步操作:db 命令删除至单词的开头,而后x 命令删除一个字符。如果我们跟着执行一次 . 命令,它会重复删除一个字符( . = = x )。我不觉得这有什么价值。

正向删除方案也包含两步。这一次,b 只是一次普通的移动,而 dw 完成修改。此时用 . 命令会重复 dw,删除从光标位置到下个单词开头的内容。不过因为我们刚好已经在行尾了,并没有“下一个单词”,所以在这个场景里 . 命令没什么用。不过,至少它代表了一个更长点的操作(. = = dw)。

最后的方案只调用一个操作:daw。这个操作不仅仅删除了该单词,它还会删除一个空格,因此光标最终会停在单词“is”的最后一个字符上。如果此时使用 . 命令,它会重复上次删除单词的命令。这一次, . 命令会做真正有用的事情(. = = daw)。

daw 可以发挥 . 命令的最大威力,因此我宣布它是本轮的获胜者。

要想充分利用 . 命令,事先常常需要进行一番周详的考虑。如果你发现自己要在几个地方做同样的小修改,就可以尝试构造你的修改,让它们能够被 . 命令重复执行。要识别出这类机会需要进行一定的实践,不过一旦养成了使修改可重复的习惯,你就会从 Vim 这里得到“奖赏”。

有时,我并没有看到用 . 命令的机会,然而在做完一次修改后,我发现要做另一次同样的操作,这时候,我脑海里会浮现出 . 命令,而它也已经准备好为我效力了。每当遇到这种情况时,我都会开心地笑起来。

数字的格式

007的后面是什么?不,这不是詹姆斯·邦德的恶作剧,我是在问:如果对007加1,你觉得会得到什么结果。

如果你的答案是008,那么当你尝试对任意以0开头的数字使用 <C-a> 命令时,也许会感到诧异。像在某些编程语言中的约定一样,Vim把以0开头的数字解释为八进制值,而不是十进制。在八进制体系中,007 + 001 = 010,看起来像是十进制中的10,但实际上它是八进制中的8,糊涂了吗?

如果你经常使用八进制,Vim的缺省行为或许会适合你。如果不是这样,那么你可能想把下面这行加入你的vimrc里:

这会让Vim把所有数字都当成十进制,不管它们是不是以0开头的。

大多数普通模式命令可以在执行时指定次数,可以利用这个功能来做简单的算术运算。

很多普通模式命令都可以带一个次数前缀,这样Vim就会尝试把该命令执行指定的次数,而不是只执行一次(参见 :h count )。

<C-a><C-x> 命令分别对数字执行加和减操作。在不带次数执行时,它们会逐个加减,但如果带一个次数前缀,那么可以用它们加减任意整数。例如,如果把光标移到字符5上,执行 10<C-a> 就会把它变成15。

但是如果光标不在数字上会发生什么?文档里说, <C-a> 命令会“把当前光标之上或之后的数值加上 [count]”(参见 :h ctrl-a )。因此,如果光标不在数字上,那么 <C-a> 命令将在当前行正向查找一个数字,如果找到了,它就径直跳到那里。我们可以利用这一点简化操作。

set nrformats=

下面是一段CSS片段。

normal_mode/sprite.css

.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> 则要快得多。由于光标不在要操作的数字上,所以该命令会正向跳到所找到的第一个数字上,从而省去了手动移光标的步骤。让我们看看整个操作过程。

按键操作

缓冲区内容

{start}

.blog, .news { background-image: url(/sprite.png); }

 

.blog { background-position: 0px 0px }

yyp

.blog, .news { background-image: url(/sprite.png); }

 

.blog { background-position: 0px 0px }

 

.blog { background-position: 0px 0px }

cW.news<Esc>

.blog, .news { background-image: url(/sprite.png); }

 

.blog { background-position: 0px 0px }

 

.news { background-position: 0px 0px }

180<C-x>

.blog, .news { background-image: url(/sprite.png); }

 

.blog { background-position: 0px 0px }

 

.news { background-position: -180px 0px }

在本例中,只复制了一行并做出改动。但是,假设要复制10份,并对后续数字依次减180。如果要切换到插入模式去修改每个数字,每次都得输入不同的内容(-180,然后-360,以此类推)。但是如果用 180<C-x> 命令的话,对后续行也可以采用相同的操作过程。甚至还可以把这组按键操作录制成一个宏(参见第11章),然后根据需要执行多次。

在处理某些特定工作时,使用次数可以使按键次数变得最少,不过并不是非得这样不可。我们需要认真考虑次数与重复各自的优缺点。

假设在缓冲区里有如下文字。

Delete more than one word

想把这段文字改为“Delete one word”,也就是说,要像这段文字里所讲的那样删除两个单词。

有几种方式可以达到这一目的,d2w2dw 都可以。使用 d2w,先调用删除命令,然后以 2w 作为动作命令,可以把它解读为“删除两个单词”;然而 2dw 做的相反,这一次,次数作用于删除命令,而动作命令只跨越一个单词,可以把这解读为“做两次删除单词的操作”。抛开语义不讲,无论哪种方法,结果都是相同的。

现在,让我们考虑另外一种方式,即dw.。这可以解读为“删除一个单词,然后重复上次的操作”。

概括一下,我们的3种选择 d2w2dw 或者 dw. 都是3次按键,不过哪一种最好呢?

根据我们的讨论,d2w2dw 是相同的,在执行完两者中的任一个后,可以按 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”,可以用下面的方式做。

按键操作

缓冲区内容

{start}

I have a couple of questions.

c3wsome more<Esc>

I have some more questions.

在此场景中,使用 . 命令的意义不大,我们可以删除一个单词,然后再用 . 命令删除另一个,但随后我们还得切换到插入模式(例如,使用 icw)。对我来说这么做很不顺手,我反而更愿意用次数。

使用次数的另一个好处是:它保留了一个干净、连贯的撤销历史记录。完成这次修改后,按一下 u 键就可以撤销整个修改,这和技巧8中的讨论是一致的。

对于是用次数风格(d5w)还是用重复风格(dw....)也有同样的争论,因此我的偏好看起来似乎不太一致。对此,你要总结自己的观点,这取决于你怎么看保留干净撤销历史记录的价值,以及你是否觉得用次数令人生厌。

Vim的强大很大程度上源自操作符与动作命令相结合。在本节,我们将看到它是如何工作的,并考虑其寓义。

d{motion} 命令可以对一个字符(dl)、一个完整单词(daw)或一整个段落(dap)进行操作,它作用的范围由动作命令决定。c{motion}y{motion} 以及其他一些命令也类似,它们被统称为操作符(operator)。可以用 :h operator 来查阅完整的列表,表2-1总结了一些比较常见的操作符。

g~gugU 命令要用两次按键来调用,我们可以把上述命令中的 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插件提供了一个很好的例子[2],此插件为Vim支持的编程语言增添了注释及取消注释的命令。

注释命令以 gc{motion} 触发,它会切换指定行的注释状态。它是一个操作符命令,因此可以把它和所有动作命令结合在一起。gcap 将切换当前段落的注释状态, gcG 会把从当前行到文件结尾间的所有内容注释掉,gcc 则注释当前行。

如果你对如何创建自定义操作符感到好奇,可以先阅读一下文档 : h :map-operator

Vim缺省的动作命令集已经相当全面了,但是我们还是可以定义新的动作命令及文本对象来进一步增强它。

Kana Natsuno的textobj-entire插件是一个很好的例子[3],它为Vim增加了两种新的文本对象 ieae,它们作用于整个文件。

如果想用 = 命令自动缩进整个文件,可以执行 gg=G (就是说,先用 gg 跳到文件开头,然后用 =G 自动缩进从光标位置到文件结尾的所有内容)。但是如果安装了textobj-entire插件的话,简单地执行 =ae 就可以了。运行这条命令时光标在哪儿并不重要,因为它总是作用于整个文件。

注意:

 

如果同时安装了commentary和textobj-entire插件,就可以把它们放在一起使用。例如,执行 gcae 会切换整个文件的注释状态。

如果你对如何创建自定义动作命令感到好奇,可以由阅读 :h omap-info 开始。

结识操作符待决模式

普通、插入及可视模式很容易辨识,但是Vim还有另外一些很容易被忽视的模式,操作符待决模式(operator-pending mode)就是一个例子。每天我们无数次地使用它,但通常它只持续不到一秒时间。举个例子,在执行命令 dw 时,就会激活该模式。这一模式只在按 dw 键之间的短暂时间间隔内存在,一眨眼工夫就不见了。

如果把Vim想象成有限状态机,那么操作符待决模式就是一个只接受动作命令的状态。这个状态在调用操作符时被激活,然后什么也不做,直到我们提供了一个动作命令,完成整个操作。当操作符待决模式被激活时,我们可以像平常一样按 <Esc> 中止该操作,返回到普通模式。

很多命令都由两个或更多的按键来调用(查阅 :h g :h z :h ctrl-w ,或者 :h [ ,可以看到一些例子),但在多数情况下,头一个按键只是第二个按键的前缀。这些命令不会激活操作符待决模式,相反,可以把它们当成命名空间(namespace),用来扩充可用命令的数目。只有操作符才会激活操作符待决模式。

你也许想知道,为什么要有一个完整的模式,专门用于操作符和动作命令之间的短暂瞬间,而命名空间命令则仅仅是普通模式的一个扩充?好问题!这是因为我们能够创建自定义映射项来激活或终结操作符待决模式。换句话说,它允许我们创建自定义的操作符及动作命令,从而让我们可以扩充Vim的词汇。

[1]  http://vimgolf.com/

[2]  https://github.com/tpope/vim-commentary

[3]  https://github.com/kana/vim-textobj-entire


相关图书

云原生测试实战
云原生测试实战
Kubernetes快速入门(第2版)
Kubernetes快速入门(第2版)
Kubernetes零基础实战
Kubernetes零基础实战
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:编程基础篇
深入浅出Windows API程序设计:编程基础篇
云原生技术中台:从分布式到云平台设计
云原生技术中台:从分布式到云平台设计

相关文章

相关课程