Vim 的哲学(四)

Vim 的哲学第四篇姗姗来迟,狗血的缘由我就很少说了,好消息是我将为这个系列带来一些动态演示。本来我打算录视频的,可是文章都写了那么些篇了,如今再录视频彷佛晚了些,因此我研究了一下如何录制高质量的 GIF 动画(第三方软件都很差用,最后我仍是用 QuickTime 和一段脚原本完成录制,挺酷的~)。接下来先奉上第一弹:程序员

查漏补缺

上一期的基础配置我遗漏了一个蛮重要的选项:shiftround,这个选项真的很贴心很好用,遗憾的是官方文档对此语焉不详。我特地 Google 了一下才发现挺少有人解释这个选项的。如今我也忘记了当初我是怎么知道它的,而后我发现用文字也还挺难解释清楚,因此仍是看动画吧:正则表达式

shiftround

round 在这里应该是取整的意思。当你的缩进不成倍时,开启这个选项将会让 Vim 自动帮你把周围的缩进化零为整,你就不须要手动去填/删空格了。顺便提早讲一下,缩进的指令是 <> 键,它们支持移动指令(立刻讲到),也支持数字前缀。对当前行执行缩进是 <<>>,也就是连按两次。咱们在之后会详细介绍关于缩进的知识。编程

OK,加上它在你的基础配置里,咱们开始新的旅程!vim

移动与编辑

如今咱们知道使用 Vim 的一个很重要的原则就是远离鼠标,远离可视化的定位装置(固然除了键盘之外),缘由我就再也不赘述了。在此原则之下,必然要有一些方式来帮助咱们选中目标而后作出咱们但愿的更改,也就是移动和编辑。segmentfault

在常规模式里有两个很重要的概念,一个叫作“动做”(Motions),另外一个叫作“操做”(Operators)。动做,是指你能让光标移动到哪里,而操做则须要结合动做来决定你能够对文本作什么。经过两者的结合,咱们能够逐渐体会 Vim 是如何贯彻保持简单这一原则的。编辑器

hjkl 虽然简单,可是在不少场合之下它们太没效率了,对么?从如今开始我但愿你记住:咱们一般不用 hjkl 作大量的光标移动。若是你发现你常常抽疯似的按这四个键在屏幕里来回移动,那你就已经错了!这四个键真正的主要用处是做为其余动做和操做的辅助键来使用的,随着咱们的深刻学习你会愈来愈理解这一点。工具

那么除此以外咱们还有什么选择呢?学习

横向移动 :help left-right-motions

当咱们以行/段为单位来审视咱们的文本时,Vim 为咱们提供了一些横向移动的快捷动做。动画

移动指令 移动效果
0 移动光标至行首
$ 移动光标至行尾
^ 移动光标至行首的第一个非空白符的字符
g_ 移动光标至行尾的最后一个非空白符的字符
f{char} F{char} 向前(右)或向后(左)移动光标至指定的字符({char})处,光标停留在该字符之上
t{char} T{char} 向前(右)或向后(左)移动光标至指定的字符({char})处,光标停留在该字符前/后面

经过上面这个表格,咱们能够获取到如下信息:ui

  1. Vim 的许多指令都是成对儿的(很是重要),学习各类指令时尝试成对儿去练习会事半功倍。
  2. 空白符(空格,Tab)在 Vim 里是很重要的,许多命令都有不一样的版原本应对有/没有空白符的情况。这主要是为了知足不一样的人群习惯,好比说码字儿的不是很在意多一两个空格,可是写代码的就不同了。
  3. 有些命令脱胎于正则表达式(相信你注意到了),因此结合正则来学习也能帮助你理解它们。
  4. 最后两行的那几个移动指令可能有一点很差理解,可是它们实在是太有用了,请你亲自试一下。之后我再深刻介绍它们。

这还没完,对于横向移动来讲,最麻烦的是当“回绕”(wrap)出现的时候。所谓回绕是指,当一行(段)的字符数目超过屏幕的可视宽度范围时,编辑器会将超出的部分自动转移到下一行来显示,可是这并不是换行,也就是说没有插入换行符,只是在显示上不让字符超出屏幕的最大宽度范围而已。

这个特性固然不是 Vim 特有的,几乎全部的编辑器都支持回绕,但是 Vim 对待回绕是和其余编辑器彻底不一样的,尤为是在编辑长文档时会让你以为有些古怪。我不会在本章详细介绍关于回绕的一切,由于本系列面向的读者主要是程序员。对于编写代码这样的工做,只要你遵循良好的编码规范(你应该这么作),那就不多会出现应对回绕的状况。因此目前为止你学会上述四个动做指令就足够了。将来的某一天我会专门写一篇如何打造专业的 Markdown 编辑功能,在那时候咱们再回过头来好好谈谈回绕。

如今我列出横向移动时若是遇到回绕行咱们能够作什么,这样你能够本身试一下:

移动指令 移动效果
gh gj gk gl 让光标在回绕行内作四方向移动
g0 移动光标至当前回绕行的行首
g$ 移动光标至当前回绕行的行尾
g^ 移动光标至当前回绕行的行首的第一个非空白符的字符
gm 移动光标至当前回绕行的中间位置(或尽量接近中间的位置)

关于 g

你或许已经注意到 g 键的屡次出现了,彷佛它和其余指令相互配合能够产生许多新指令。没错,在 Vim 中有那么几个“万能的”指令修饰键,g 是其中之一。若是你好奇还有多少指令是用 g 来修饰的,你能够键入 :help g 来查看一个列表。另外你应该知道这份列表实际上是索引文档的一部分,你能够时常打开索引(:help index.text)来考察下本身对 Vim 到底有多熟悉。

纵向移动 :help up-down-motions

纵向移动的指令比较多,我仍是先介绍几个简单而且最有用的:

移动指令 移动效果
gg 让光标跳转到文档的最开始处
G 让光标跳转到文档的最后一行
{count}G :{count} 让光标跳转到指定的行号,即 {count} 所表明的行号
{count}% 让光标跳转到指定的百分比位置,好比说第一行是 0%,最后一行是 100% 等
H 让光标移动到当前屏幕的顶部(High Position),不滚屏
M 让光标移动到当前屏幕的中部(Middle Position),不滚屏
L 让光标移动到当前屏幕的底部(Low Position),不滚屏

{count} 是一个前缀标记,意思是这个指令前面能够追加数字,好比说若是你键入 25G,那么光标就会移动到当前文档的第 25 行去。这个特性很是重要,Vim 的强大和灵活在很大程度上都仰仗相似的前缀特性。

G{count}G 实际上是同一个指令,只是带上数字前缀与否会产生不一样的效果,因此我分开写了。这是第一次,一旦你了解了这个特色,之后我就不必分开了。

ggG 是一对经典的搭档,许多很是有用的操做都是它们俩配合完成的。举个例子,《Vim 的哲学》全部的文字都是在Vim 里完成的,每一次到最后我都要把它们复制粘贴到 SegmentFault 博客的发表页面作最后的检查而且发布,如何所有复制过去?有不少办法,我通常选择以下两种:

  1. 若是我使用 MacVim(GUI 环境),那么我先 command + a,而后 "+y 或者 "*y
  2. 若是我使用 CLI Vim(命令行环境),就变成一串指令:gg"+yG,拆开看:gg + "+y + G

喂喂,command + a 是全选吧,这是 Vim 的指令吗?你别忽悠我喔~

我可没有说你只能用 Vim 的内置指令呀,了解你所处的环境,在不影响效率,不打乱节奏的前提下善于利用一切能够利用的工具,这不正是极客的特质之一吗?只不过我不是每次都有机会用到 MacVim 的,因此 gg + G 的经典组合仍是必需要掌握的。至于 "+y,这个涉及到寄存器(:help registers)的知识,咱们稍后就会讲到,别急。

以词为单位的移动 :help word-motions

hjkl 是以字符为单位来移动的,横向和纵向移动基本上都是以句/段为单位来使用的,然而有些状况下咱们须要的是介于两者之间的移动单位,也就是以词为单位。以词为单位使得咱们能够更精确(也是更具语义化)的移动光标,而且要比逐个字符的移动要快得多。

这里所说的词特指的是像英语那样的以空格(有时候也会是别的符号)为分隔符的词——很遗憾,中文分词是很大的挑战,Vim 没有内置这一功能。像这样的词均可以分出词头和词尾,好比 word 这个词,词头是 w,词尾是 d

因而在 Vim 中的词组移动也按照词分头尾的特性分红了两组:

移动指令 移动效果
w b 向前(右)或向后(左)移动光标至下一个词组的词头位置
e ge 向前(右)或向后(左)移动光标至下一个词组的词尾位置

一些朋友喜欢寻找每个指令对应的含义,这样有助于形象记忆。事实上,这些含义也不是我编造出来的,内置的文档里都有很详细的描述,好比说上面这几个分别是:

  1. w:Words forward
  2. b:words Backward
  3. e:forward to the End of word
  4. ge:backward to the End of word

你可能在想:我干吗要关心词头和词尾,好麻烦啊!这个问题其实无关于 Vim 的哲学,而是语言的哲学。

你看,我们的汉语和英语是彻底不一样的两个语种。在汉语里词与词的界限是靠意义来划分,书写形式则不过重要。用汉语写一句话,你能够在词组之间添加空格或者不添加空格,基本上不会对读者产生影响(除了个别会产生歧义的特例)。而英文及其余相似语言则否则,它们加或不加空格的差异大了去了!一段英文若是没有空格,那几乎就是没法阅读的。所以,对于母语为相似语种的人群来讲,空格所划分出的词头与词尾是天然而然,司空见惯的事情,他们一点也不会以为奇怪。不幸的是,做为程序员的咱们也(被迫)得使用英语做为咱们的主要书写语言,所以你必须习惯去辨识和使用词头与词尾。一旦你习惯了,你会发现它们很是有用。

为了让你看到正确使用词头词尾的效果,我放一张图给你对比一下差异。在这张图里,我分别演示了四种操做:

  1. 在句首,向右删除两个单词,使用词头做为动做指令(w
  2. 在句首,向右删除两个单词,使用词尾做为动做指令(e
  3. 在句尾,向左删除两个单词,使用词头做为动做指令(b
  4. 在句尾,向左删除两个单词,使用词尾做为动做指令(ge

每一步操做以后,注意观察光标停留的位置和最终的效果:

word_motions

这些结果或许是你指望的,也可能不是,但这没关系,没有哪种是绝对正确或错误的,重要的是你须要了解它们之间的差异,因而你能够在必要的时候选择正确的方式。

不过故事还没完,以上四个指令各自还有一个变体,分别是:WBEgE。要了解它们的做用,咱们得先聊一下词的定界符。

对于词和词之间,空格是惟一的区隔标准吗?很显然不是。像这样的词:i_am_a_word,Vim 会视为 1 个词,可是 i-am-a-word,Vim 则会视为 7 个词!这是由于 Vim 容许你为其指定能够被视做词组定界符的字符,因而当 Vim 遇到这些字符的时候,就会认为是一个词的结束。默认状况下,_ 不是定界符,因此它会被视做一个词的组成部分。

然而有些时候咱们但愿把这些定界符也看成词组的一部分,这样咱们能够移动的快速一点,这时大写版本的词组移动指令就派上用场了,它们永远都只把空白符(空格、TAB、EOL)视做词组的定界符。

你会以为本身定义定界符很酷吧?我会把它放在高级设置那一篇来说。

基础编辑 :help operator

Vim 内置了 15 个编辑指令(还有一些变体),可是通常来讲咱们用不到那么多。在本节咱们来学习其中的五种(共计 10 个):

删除 :help d

若是你把光标对准某个字符,而后按下 d(delete),你会发现什么都没有发生?不要惊讶,编辑操做是要配合移动指令来干活的,我以前花大力气介绍一堆移动指令不是漫无目的的不是?

OK,精彩的来了。当你按下 d,Vim 会说:“好的伙计,你想要删除对吧?接下来请告诉我你要删什么?”

若是你要删一个词,按下 dw,也就是 delete word

若是你要删除两个词,按下 d2w 或者 2dw,它们的效果是同样的,可是它们表明的含义略有差异:

  • d2w 意思是:删除 -> 2 个 -> 词
  • 2dw 意思是:2 次 -> 删除 -> 1 个词

在这个例子里,两种操做的结果不会产生歧义,因此你能获得同样的效果。可是之后你会发如今某些特定的条件下,数字前缀在不同的地方会产生不同的效果(不仅局限于删除操做)。因此,正确的理解操做的含义是有必要的,请记住:

理解操做的含义,而不是背诵操做的顺序。就好像你说话说的是你想要表达的意思,而不是字词的某种排列组合。有些时候你颠倒字词的顺序不会影响你要表达的意思,由于不存在歧义,但有些时候则正好相反,切记切记!

若是我要删除一整行怎么办?简单:dd

那若是我想要从光标的位置开始一直删除到行结束呢?那还用我教你?d$!不过 Vim 还有另一个版本等价于 d$,它是:D

哦,那这么说若是我使用 0d$ 或者 0D,就是和 dd 等价的咯?

啊哈~聪明的童鞋,很抱歉你错了!可是不怪你,这是一个很重要的区别,咱们来单独看一下示范:

dd_0D_0d$

看明白了吗?其实差别是很是明显的,dd 是连同行尾的行结束符(EOL)一块儿删除的,因此粘贴的时候也会连着行结束符一块儿粘贴;而 0D0d$ 则不会包含行结束符。

你可能还纳闷呢,不是演示删除的吗?为何删掉的东西还能再粘贴回来呢?嗯,可能删除这个词不太恰当,若是改叫剪切是否是突然就以为贴切起来了?

Vim 的删除操做和咱们常见的剪切很是类似,事实上 Vim 的删除指的是从你的眼前把目标文字移除到寄存器中,以后你还能够从寄存器里把删除的部分再粘贴回来。Vim 拥有一堆各式各样的寄存器,擅于使用寄存器可让你的编辑工做变得异常轻松。从此咱们会单独介绍寄存器的进阶使用。

让我来问你一个问题:若是要删除一个字符该怎么办?你或许已经了解到 x 能够删除光标所在的那个字符,X 能够删除光标左边的那个字符,可是你是否知道这两个功能也是从 d 演化出来的呢?试着找找答案吧。

改写 :help c

在你执行完删除以后,紧接着按下 i(insert),你就等于在改写以前删除的内容了。如何把这两步简化成一步?答案就是 c(change)了。对于 c,真的没什么好讲的,你学会了 d 就等于学会了 c,由于 c 就等于 d 完了紧接着 i 而已。

并且其余相关联的操做也是相似的,好比说:

  • c2w:改写两个词
  • cl:改写光标所在位置的字符,而且 s 等价于 cl
  • cc:改写光标所在的一整行,而且 S 等价于 cc
  • c^c$:从光标所在位置开始一直改写到行头/行尾,而且 C 等价于 c$

s 比较少用,由于一般改一个字符咱们会使用 r,也就是替换(replace),可是 s 在编辑中文的时候有妙用,容我在这里卖个关子,等到打造专业 Markdown 编辑器的时候再说(实在是不能都说了,要否则这篇结束不了了)。

Scc 多节省一次按键,并且也比较好按(在标准键位上),因此推荐用 S 来代替 cc

复制 :help y

复制是几个基础编辑操做里怪癖略多的一个。首先是它的命名,yyank 的首字母,可是 yank 又是什么?它和拷贝(复制)有什么关系呢?

这也和寄存器有关。你看,Vim 的复制/剪切(删除)/粘贴操做都是基于它底层的寄存器的,因为 c 已经被改写(change)占用了,而 Vim 的复制实质上是把目标文本拉拽(yank)到寄存器中备用,因此……好了你知道了就是了,咱不解释那么多,反正 y 就是复制了,爱咋咋地~

另一个怪癖出在 Y 身上,按照以前删除和改写的经验,你必定会认为 Y 就等同于 y$ 呗,(Vim 乱入:“呵呵,图样图森破!你觉得我会让你这么轻易就掌握诀窍吗,少年?”)但是很不幸你又错了。这一回,Y 又和 yy 等价了……

我一直都没闹清楚为何到了复制这里就和删除/改写不同了,就连官方的帮助文档都是这么说的:

若是你但愿 Y 是从光标处复制到行尾(这样更合乎逻辑,不过不兼容 Vi),你可使用 :map Y y$

看起来惟一的缘由就是为了和老 Vi 兼容,可是咱们彻底不在意这一点!后面的 :map Y y$ 是键位映射,虽然咱们还没讲到,不过这一句你已经能够把它放到你的 .vimrc 里了,重启 Vim 以后你会发现 Y 的表现和 C D 它们保持一致了,谢天谢地!

粘贴 :help p

粘贴就单纯多了,只有两个指令:

操做指令 移动效果
p 自光标所在位置向右粘贴默认寄存器里的内容
P 自光标所在位置向左粘贴默认寄存器里的内容

默认寄存器里的内容取决于你在粘贴前最后的编辑动做,有多是删除或复制的一段文本,也有多是其余的。因为咱们尚未详细介绍强大的寄存器功能,你或许会偶尔感到有些不便,在这里我先介绍一个最经常使用的技巧:

有时候,咱们须要完成以下操做:

  1. 在某处复制或删除(剪切)了一些文本
  2. 在另一处删除一些文本
  3. 把以前复制或删除(剪切)的文本粘贴到这里

你看,这其实是要用 A 处的文原本替换 B 处的文本,但因为默认寄存器只保留了最后一次的复制/删除(剪切)内容,因此当你完成第 2 步的时候,你在第 1 步准备好的文本已经没了……大多数人是这么作的:

  1. 在某处复制或删除(剪切)了一些文本
  2. 来到另一处,先把这些文本粘贴到空白的地方
  3. 把须要替换的文本删除
  4. 清理多余的空白(若是须要的话)

实际上,咱们可让 Vim 不把指定的内容放入默认寄存器,这样就不会覆盖预先准备好的内容了,这等同于完全删除而不是剪切。Vim 的默认寄存器是 ""(也叫匿名寄存器,:h quotequote),它保存常规的复制/删除等操做的内容,Vim 还有一个名字很酷的寄存器叫作:黑洞寄存器(Blackhole Register,:h quote_),它的按键是 "_。若是你在键入任何操做以前先输入 "_,操做的结果将不会被任何寄存器保留下来,就好像丢入了一个深渊黑洞,再也回不来了……(好伤感 T_T)

所以,咱们能够这么玩:

  1. 在某处复制或删除(剪切)了一些文本
  2. 使用黑洞寄存器删除须要替换的内容,例如删除一行:"_dd
  3. 直接粘贴,搞定!

我把这个过程也录了下来,你能够对照看看:

blackhole_register

我真是爱死这玩意儿了!不过你要知道,就上例而言黑洞寄存器不是惟一的办法,说不定你更喜欢别的操做组合,好比下面这个:

without_blackhole

这一套“组合拳”没有用黑洞寄存器,它的好处是若是我反悔了,我还能够撤销以前的操做把被替换的内容找回来。整个过程的按键顺序是这样的:y$ -> gt -> gP -> D

请容许我用更加具备语义的方式来重复一遍上面的操做:

  1. y$:从光标所在位置(行首)复制到行尾(不包括换行符)
  2. gt:切换至下一个标签页
  3. gP:自光标位置向左粘贴刚才复制的内容,结束以后把光标向右移动一个字符(这就是 g 的做用,为了把末尾的 . 保留住。你也可使用 Pl 实现同样的目标)
  4. D :自光标位置删除到行尾

我但愿你理解我这样重复一遍的缘由,它包含了体现 Vim 哲学的三个侧面:

  1. 每一步都保持简单的颗粒操做
  2. 从不死记硬背,而是去表达你的意图,用你本身的方式
  3. 条条大路通 Vim,何须死撞一棵树?

好吧,第三点纯粹是我在胡扯,哈哈。

大小写转换

大小写转换其实不算什么大事,原本我也犹豫还要不要介绍一下,可是考虑到这个在编程的时候还挺有用的,因而索性一并说了吧,反正也很少……

操做指令 移动效果
~ 转换光标所在字符的大小写(严格来讲,这不是一个操做指令)
g~ 转换字符的大小写(这个才真的是)
gu 强制转换成小写
gU 强制转换成大写

解释一下头两个,~ 不是操做指令,是由于它没办法和移动指令结合,它就只会转换当前光标所在位置的那个字符。若是你有多个字符须要转换,你就只能一个一个按过去。g~ 才是转换大小写的正式版,它能够结合移动指令。比方说按下 g~3j 会把往下 3 行的字符大小写都转换了(小写变大写,大写变小写)。不过 Vim 有一个选项叫作 tildeop(:h tildeop),它默认是关闭的,若是你开启它,~ 就会变成和 g~ 同样了。这选项我记得很熟,由于我常常在团队里作重构工做,这种改写命名的活儿一再重复,我索性就把 ~ 变成真正的操做指令了。

另外,毫无心外的,它们几个都有直接操做当前一整行的快捷版本,分别是:g~~ guu gUU

趣味知识:你知道 ROT13 加密编码吗?这多是世界上最简单的加密手段了,有意思的是 Vim 也内置了 ROT13 编码/解码功能。闲来无事的时候能够拿来逗别人玩哦!切换 ROT13 编码/解码的操做指令是:g?(:help g?)

缩进与排版

操做指令 移动效果
gq 自动应用排版规则
gw 自动应用排版规则(光标位置不变)
= 自动应用缩进规则
< > 手动应用缩进规则(左右两个方向)

前面两个在编写代码时不太经常使用,却是在编写文档时能发挥做用,所以它们不是重点,请自行查阅文档并尝试。

后面两个就比较经常使用了,所谓“自动应用缩进规则”,前提是你得有可用的缩进规则。Vim 内置了很是多种语言的缩进规则,那些没有内置的也基本上均可以在网上找到合适的缩进规则插件。此前咱们也在基础设置里打开了 filetype indent on,因此此时若是你打开一份源码文件,而后按下 gg=G,“唰”的一下——整个世界清静了。

还记得吧?gg 是去文件的最开始处,G 则是去文件的最后一行,因此这条命令的含义是:“从文件的开始处应用自动缩进规则直到文件的最后一行”。固然你能够没必要老是对整个文件进行自动缩进,以前咱们提到过的移动指令均可以搭配使用,以后咱们还要介绍更增强大灵活的文本对象选择指令,搭配上自动缩进那叫一个如虎添翼~

至于 <> 就没什么新鲜的了,手动缩进呗!缩进的宽度是由 shiftwidth 指定的,我们上次已经设置过了的有木有?另外它们也有针对当前行的快捷版本,你已经知道了,是吧?

想知道你的 Vim 内置了那些语言的缩进规则?键入这条命令::e $VIMRUNTIM/indent

Bonus

你如今能够尝试把这些最经常使用的指令应用在你的平常工做里了,若是你能坚持去寻找最有效率的操做方式,你终将会明白其实根本用不着装太多的插件。我很乐意进一步帮助你,因此你若是在使用中有任何疑问请不要客气尽管询问我,我也喜欢看看有什么新的挑战,因此来吧~


其实,第四篇原本想直接讲文本对象的,可是我担忧新手会看不太懂,因而把文本对象一再日后挤。挤到如今才发现,天啊!这篇太长了,实在是不能再继续下去了。因而,咱们只好对文本对象说拜拜了~我们下期再见!

相关文章
相关标签/搜索