Vim 快速移动

2021-01-09

很多人听说Vim就想试试。可一看到罗列出来的快捷键,就打退堂鼓🥁。这也是人之常情。错不在于退缩的人,而在于写Vim教程的人。

Vim的快捷键虽多,但有内在的规律。我们记快捷键是为了什么?不就是为了提高编缉的效率嘛。那Vim针对效率提供了哪些功能呢?本质上我们要记住的是这些功能。那些快捷键不过是这些功能的一种表达方式罢了。

这就像我们小时候背英语作文。我们要背一篇作文,不能只关注一句话里的语法和单词,更要关注文章的结构跟内容。我之前都是先用中文理解内容,在脑子里有个中文版的大纲;再一句一句地翻成英文;最后反复背诵,熟能生巧。所以要理解着背,不能死记硬背。

本文就试着给大家带来一种通过理解来背诵快捷键的思路。今天只讲光标移动部分。

写程序的同学应该都清楚,典型的线上系统基本都是读多写少。代码也是一样。对于一段代码,跟它的整个生命周期相比,编码的时间可能连百分之一都不到。我们部分时间都是阅读或者修改已经写好的代码。Vim的作者有鉴于这种事实,引入了所谓的 Normal 模式。在 Normal 模式下,你不能输入内容,但是可以高效地移动和修改。这是 Vim 跟常见的编缉器最大的不同点。这也是 Vim 最令初学者难以理解,也是最让初学者望而却步的特性。

那怎么移动呢?无外乎上下左右。我们现在键盘都有这四个键。有人说Vim在诞生的年代,那个时候的键盘没有像现在这样的方向键,所以选用了hjkl四个键来移动光标。我不认同这个观点。

首先,上下左右是最最基础的移动操作,自然也是最最常用的移动操作。既然常用,我们就应该着力优化这几个操作。怎么优化呢?一般人的右手比左手要零活,所以这个活得让右手干。另外,对于标准键盘,右手的姆指之外的四个指头默认依次放到jkl;键上。因为上下左右使用最频繁,我们肯定不希望频繁移动手指,所以最简单的做法就是使用jkl;来控制方向。因为你的右手默认就压在这四个键上,不用移动手指就能按到。但是,分号要用小姆指才能按到。小姆指跟其他三个指头相比又不那么灵活,所以最终选取了hjkl来控制方向,分别表示左下上右四个方向。因为食指非常灵活,同时控制h和j,所以使用起来比小指要方便一点。

至于为什么用jk表示上下用hl表示左右,我就不清楚了。我隐约觉着上下移动要比左右移动更常用,所以把最方便的jk用来控制上下了。

好了,我们可以控制光标上下左右随便移动了。如果 Vim 只支持hjkl,你一定会发狂。因为hjkl一次只能移动一个字符或者一行。如果要在几十上百行的文件来回移动,指头都按断了。

显然,我们需要效率更高的移动方式。最简单的方法就是不要重复。为此 Vim 引入了数字+命令的方式来加快移动速度。比如,j表示向下移动一行,3j表示向下移动三行,100j表示向下移动一百行。hjkl每个命令都可以跟数字搭配。其实,Vim下很多命令都可以跟数字搭配。先按好你想要的数字(右下角会显示),再按具体的命令,Vim会自动重复执行。

有了数字+命令这一法宝,我们基本可以畅通无阻。此法快则快矣,却有一个致命的问题。如果你要移动的行数和列数很大,你应该怎么确定这个数字呢?不管是猜测还是数数,都不会提高移动的效率。我们需要继续优化。

Vim针对左右移动和上下移动分别作了若干优化。我们先说左右移动。

假设我们有这么一段文本,我们的光标在字world的d这个字母,我们想稳动到右边的a上。

hello world this is a demo.
          ^

最简单的做法就是不断按l向右移,一直到a为止。如果你火眼金眼,一下字就看出来d跟a之间隔着十一个字母,你可以按11l一步到位。显然,大家都是凡人。那能不能让Vim帮我们数呢? 显然是可以的。我们可以通过按fa来告诉 Vim 一直向右数数,直到发现字母a为止。然后执行11l(这个 11 是 Vim 自己数的,不用我们数了)。是不省事多了。我猜这里的f对应find这个单词。应该一下就记住了吧。

有的时候,我们需要光标停在字母a的前面,而非字母a上(也就是说,在移动的时候对数字减一),我们可以按ta。这里的t跟前面的f几乎是一样的,只是会少稳一个字符。我猜这个t对应till,也就until的意思。

有向右查,就肯定支持向左查。保持对称,这是 Vim 命令的另一内在规律。向左也很容易记忆,分别是F和T。一看就明白,我就不做缀述了。但有一点,Vim 有好多命命都有大小写形式,大家在学习的时候要注意总结。

让我们回到原来的例子。

hello world this is a demo.
          ^

如果我们想让光标移到单词is的第一个字母,可以按fi。你会发现光标只会停在this的i上。这个时候你得重复按fi才能跳到is上。什么,重复按怎么能有效率呢?优化!为此,Vim提供了重复上次查找的命令。你在按了fi之后发现还没到,可以直接按;,相当于再按了一次fi。重复一次觉不出来,如果要重复好多次,就会显著加快移动。同样的,Vim保持对称,为我们提供了,命令,功能跟;相同,但方向相反。;和,对F和T同样有效,大家不妨练习一下。

熟悉了f相关的命令,我在在左右方向上的移动效率就会有一个巨大的提升。我们可以通过查找诸如空格、括号、逗号、分号等特征字符完成快速移动。但是,我们还有提升的空间。

让我们再一次回到前面的例子。

hello world this is a demo.
          ^

为了能进一步加速,我们必须从文本内容的结构入手了。我们知道,西方拼音文字都是以单词为单位书写的,单词之间以空格隔开。我们自然就想到能不能一个单词一个单词地移动。

如果使用f,稳动到this需要按ft,再移动到is需要按fi,再移动到a需要按fa。每次都要找到单词的第一个字母,然后配合f移动。每个单词的首字母又不尽相同,效率肯定上不去。那能不能让 Vim 帮我们找单词的首字母呢?答案是肯定的。我们需要用w。每按一次w,Vim 就会跳到右边单词的首字母。所以,如果要逐个单词移动,我们可以不断按w。记住,Vim 的对称原则。如果想跳到左边的单词,则需要按b。w可以理解成word,b呢可以理解成back。使用w和b可以不用考虑单词具体的首字母,效率自然比使用f命令族要高。

跟移动到右边单词开头对应的则是稳动到尾(这也是一种对称),需要按e, 可以理解为 end。如果是想向左跳,则需要按ge。可能是跳到左边单词结尾这种用法不太常用,所以就用了g修饰e来表示。

wbege这几个命令也可以配合数字使用,请大家自行练习。

最后需要说的左右移动命令就是跳转到一行的开始和结尾了。理论上,如果想跳到结尾,你可以一直按l, 也可以一直按e,但都要按很多次,而且次数不定。这显然没有效率可言。于是,Vim 用$表示跳到一行的结尾。如果你学过正则,一定不会陌生。同样的,为了对称,跳到行首用^,也是正则的元字符。

Vim还支持直接跳转到指定列。如果你们相移动到第五列,我们可以执行5|。一般应于根据报错信息准确定位的场景,不是特别常用。

好了,基本上左右方向上的移动都说完了。现在我们再说上下方向。

左右是列移动动,对应上下是行移动。左右有单词移动,一个单词可以看成多列的集合。上下也有对应的一屏。一屏可以看成多行的集合,一屏近似跟一个单词对应。左右移动对应上下移动。一行的首尾对应的文件的第一行和最后一行。你会发现上下移动跟左右移动惊人的相似。

总结一个表格就是这样子,有一种内在的对称。

左右移动 键位映射 上下移动 键位映射
向右查找 f/t 向下查找 /
向左查找 F/T 向上查找 ?
重复向右查找 ; 重复向下查找 n
重复向左查找 , 重复向上查找 N
跳到右单词 w 跳到下一屏 ctrl+f
跳到左单词 b 跳到上一屏 ctrl+b
跳到单词开头 b 跳到屏幕第一行 H
跳到单词结尾 e 跳到屏幕最末行 L
一行的开头 ^ 第一行 gg
一行的结束 $ 最末行 G
向右 l 向下 j
向左 h 向上 k
跳转到指定列 数字+| 跳转到指定行 数字+g

以上就是常用的移动命令。因为上下跟左右是对称的,只要稍加练习应该很快就能记住。

从l到w到f再到$,单次移动的距离依次增大。但是,我们要注意,移动距离越大的命令,停下的位置就越固定,相对而言也就越不灵活。最灵活的是hjkl,因为一次只能移动一行或一列。我们在移动的时候应该尽量先使用移动距离大的命令,再使用移动距离小的命令,最后使用hjkl进行微调。如果你在日常中需要频繁地重复多次按某个键来移动光标,你一定要想想 Vim 有没有提供相关的优化键位。 记住,不要重复!

「taoshu」微信公众号