开头叨叨

这篇文章围绕 Vim 展开。

很早前就想写一篇关于 Vim 的文章,但始终难以动笔,是因为担心自己的领悟还不够高。前几个月,得知 Vim 创始人和维护者 Bram Moolenaar 去世了,又重新翻阅了一些文章,就打算做一次 Vim 的传道师,给一些不了解这个软件和刚刚接触、或曾经接触过的朋友们做一些粗浅地介绍。

在知乎问答中,有很多对于 Vim 的讨论,也有一些非常不错的回答,作为一个爱好者和每天都离不开使用 Vim 的人,显然我阅读了其中大多数内容。本着不输出重复信息的原则,我在这篇文章中,会以另一种视角介绍 Vim,也就是从 Vim 的设计理念出发,介绍一些如何高效进行代码编辑的技巧。最后我也会谈谈我对一些话题的想法。

Vim 是 Vi 的增强版,诞生于那个计算机图形界面还未成熟的年代,所以当时鼠标这个外设也没有普及,文字录入工作还只依赖于键盘输入,所以很多同学讨论为什么 Vim 不支持丰富的光标操作,原因就在于此。虽然后来图形化已经替代了终端程序,成为消费级计算机主要的用户接口之一,但在一些特殊场合和应用场景下,非图形化的终端程序,依然充当着重要的功能。这其中就包括部分场景下的代码编辑工作。

大多数对 Vim 痴迷的计算机技术人员,据我了解,基本能囊括在以下几点,这也是 Vim 区别于其他文本编辑器的重要特点:

  • 快速,不依赖鼠标控制实现任何编辑操作(模态编辑)
  • 灵活,扩展性极强,可以自由定制(Vimscript,插件系统)
  • 启动快速,与系统软件契合较好(轻量级)
  • 工作环境受限,只能使用 Vim(Vi) 工作(Mac、Linux 系统自带程序)
  • 开源软件,社区维护,免费使用

其中最有特色的就是第一点,以至于很多其他编辑软件中,也会提供 Vim 键位和扩展(甚至包括一些非文本编辑类软件,比如控制浏览器)。

任何一点单独拿出来,都有其他匹配的软件有相似的特性,但结合在一起,可能只有 Vim 能做到了。

实践心得

在进入主题之前,先介绍一下我自己的学习和使用 Vim 的心得。

首先要摆正一个观念,Vim 是一个自定义程度很强的软件,所以达到一个目的,可能有很多种方法,有些方法快速,有些方法复杂,但其差别可能也就是一次点击和多次点击,所以并不是要求必须学会了所有的操作,才能开始使用它。就像我们刚开始接触电脑时,还不了解文件系统,而是将所有材料都放在桌面上,也一样可以工作。之后我们了解更多了,就可以不断地改善工作链路,让整个系统更高效。

学习和使用 Vim 的过程,是不断重复这样一个闭环的过程:

  1. 掌握一些用法,开始用它干活
  2. 发现一些操作很繁琐
  3. 思考这些操作,能否用更高效的操作替代
  4. 逐渐适应新的操作,取代旧的操作
  5. 新操作熟练掌握,形成肌肉记忆

我已经使用 Vim 十年了,依然还在践行这样一个迭代的过程,不断学习新的东西,并让自己对这款软件的使用更高效。

其中提到 ”肌肉记忆“,我应该也说一下。对于 Vim 新手来说,到网上查到一些用法,操作时总要回忆和记忆键位,所以并没有提高输入的效率。但其实这个阶段,还没有走完一次闭环(此时还在第 4 步)。只有完成了肌肉记忆,才是对这个新操作的完全掌握,就像弹钢琴,想发出什么音,到怎么发出,之间是没有 “这个音在哪个键上,应该用哪个手指点击” 这样一个脑回路的。

模态编辑

开始使用 Vim 时,首先一定要学习这个概念。Vim 只依赖键盘输入,包括输入文本内容和输入控制指令。为了能高效的输入控制指令,Vim 不同于其他程序采用 Ctrl 和 Alt 等键位连接字母区键位的组合键方式,而是重新利用键盘中易于触碰的部分按键实现大多数功能。组合键方式有一些键位比较难以点击,尤其是当需要多次点击时,这会打断输入流。

为了做到这一点,Vim 对键盘区做了多层的重新映射,在不同的模态(映射模式)下,点击相同的键,会有完全不同的行为。Vim 启动后,默认处于 Normal 模式,可以认为是 HOME 模式,依赖于一些按键,可以进入其他模式,在其他模式下,按 Esc 键,可以回到 Normal 模式。

Vim 支持几种不同的模态,常用的有:

  • Normal 模式:控制光标移动和对文本内容的修改(删除、复制等),并可以做到大量其他模式的操作
  • Insert 模式:文本内容输入,和其他编辑器一样
  • Visual 模式:选择内容,属于 Normal 模式的扩展,和 Command 模式配合,可以做到很多操作
  • Command 模式:控制软件的行为,它也可以实现其他几种模式的功能
  • Block Visual 模式:块选内容,和 Visual 模式类似,但按类似鼠标框选的方式选择文本
  • Replace 模式:替换一个字符或连续替换字符
  • Search 模式:搜索和跳转等

Vim 模态关系

对 Vim 的学习主要围绕着 Normal 模式展开。入门 Vim 的第一次闭环,就是将怎么切换模态(至少是在 Normal 和 Insert 之间切换)形成肌肉记忆的过程。

Normal 模式下常用的操作一定要熟悉,Command 命令可以在脚本里映射到 key,所以不用刻意记,熟悉了之后就随意了。后文会介绍一些常用的操作。

扩展性

Vim 提供了自己的一套配置脚本语言,我们可以利用这种编程范式,配置一些自定义的自动命令序列,比方说,我有一个自动命令,可以在每次保存时自动删除行末尾的空白字符。这种自定义的命令可以通过特定事件触发,也可以手动触发。另一个同样出色的编辑器,emacs,也因这种特性而同样被很多程序员们推崇。

在这种扩展性的基础上,衍生出了很多通用且优质的配置文件,进而发展成了插件系统。插件系统易于安装、扩展和分发,对于很多通用的扩展。很多朋友可能会说,其他现代编辑器,如 VSCode 也支持插件系统,没错,它们都是优秀的编辑器软件。

扩展性这个重要的特点,给予了使用者完全的自由,就像自己在设计属于自己的程序一样,这对于程序员这个群体来说,简直如鱼得水。

Vim 的一个 Fork 版,Neovim,放弃了维护 Vimscript 这个脚本语言,转而将一种更通用的语言,lua,作为另一种可以配置的脚本语言。由于 lua 相比于 Vimscript 来说,是一门更完善且易用的编程语言,更易于学习和阅读,所以得到大家的一致赞同,也成为了吸引新用户使用 Neovim 的重要亮点。目前,使用 lua 编写的配置文件和插件,如雨后春笋般涌现,Neovim 的开发社区也比 Vim 更活跃一些。

我并不想在本文介绍常用地插件和配置文件,如果有朋友感兴趣,我可以单独开一期,像其他介绍 Vim 的文章一样,分享一些我的 Vim 配置。

一些实践技巧

下边我会介绍一些使用 Vim 所特有的技巧,如果你是初学者,可能还没有掌握其中一些操作。这没有任何问题,我保证我介绍的都是比较基础的技巧,你可以利用上边介绍的实践心得,逐步接触、学习直到掌握这些更优秀的实践技巧。

如果我不特意说明,以下的操作都是在 Normal 模式中完成,部分操作也支持 Visual 模式。

光标移动

很多人都知道说,新手使用 Vim 时,很难脱离方向键移动光标的习惯,这很正常。所以他们被建议禁用方向键。我个人认为这种做法有点太极端。最早的键盘里是没有方向键的,这才使得 Vim 在设计光标移动时采用了 h j k l 的键位,在当时这种键位设计也不是 Vim 独有的。但这并不代表着方向键不如 h j k l ,否则也无法解释为什么后来的键盘添加了这样的按键了。

我倾向于说,h j k l 的学习并不应该成为接触 Vim 的一个门槛,如果习惯用方向键,依然可以用。当然,h j k l 的使用效率确实比方向键高,可以利用前边的实践心得来逐步地适应这套键位。

另外,hjkl 在 Vim 中也依然是最低效的一类光标移动操作。本质上它只避免了右手向右下方方向键区的移动,行为和方向键依然是一样的。Vim 的一个独特之处就在于,它天生适合代码编辑环境,所以光标在代码中的移动,其实有很多种,比如说:

  • 使用 Ctrl-dCtrl-uCtrl-fCtrl-b 快速翻页
  • e 到 下一个单词末尾,b 到上一个单词开头,w 到下一个单词开头等
  • ggG 到第一行或最后一行
  • - 来搜索特定的单词,nN 向下或向上跳转
  • / 进入搜索模式,快速搜索任何支持正则匹配的字段
  • % 跳转到匹配的括号
  • gd 跳转函数定义,Ctrl-o 返回

不再过多举例,这些也只是比较常见的快速移动操作,配合一些扩展,还能实现更复杂的快速移动。

不要重复操作

这一小节,我想探讨下使用各种更高效的方式替代通过按住一个键一段时间来完成目标的行为。

比如说,向下翻页,我们可能按住方向键下键一段时间,快要到达目的地时,多次短按下键,如果超出了目的行,还要短按上键。另一个例子是按住 backspace 键来删除一段文本。

这种操作就叫重复操作。不同于写文章,代码编辑中会更多的涉及到重复操作,比如复制粘贴,比如移动位置。

重复操作带来了更多的时间浪费,而且会占用我们宝贵的注意力,毕竟,脑子里都是代码逻辑的时候,抽出注意力去关注按住多久才要松手这种事,其实是潜在占用精力的。

Vim 的设计哲学中,非常在意避免重复操作,所以它原生支持大量的操作来替代重复操作,比如说:

  • dd 删除一行,d$ 删除从当前光标到行尾,df 加任何一个字符,就是删除从当前光标到接下来搜索该行中出现的第一个这个字符的位置,把 f 换成 F 就是向前删到指定字符,换成 tT 的行为类似,区别是保留搜索到的字符不删除(类似闭区间和开区间)
  • x 删除当前字符,替代 a + backspace + Esc
  • h j k l 加一个数字,就是向左下上右移动对应距离
  • c 是删除并插入, 替代 x + i,如果你经常使用 dwi 删除一个单词后进入插入模式,那么可以换成 cw

这种技巧其实非常多。另外值得提一下的是强大的 dot 操作和宏操作。

dot 操作,就是 . 键,这个键可以重复上一次原子操作,比如 cw 就是一次原子操作,如果需要重复这个操作多次,那么就不必要 cwcwcwcw 这样输入,而是 cw... ,是不是会快很多。

宏操作也很类似,它允许你定义一段操作作为一个集合,然后重复执行这个集合,就可以快速完成重复操作。

比如举我工作中常见的一个应用。我有时需要将一个特别长的行,按一些规律换行成多行,比如换行成每行 32 个字符,每隔 32 行中间插个空行。如果字符特别多,这种操作就会比较费时。我可以利用宏,先录制对前 32 行 32 列字符的操作,之后就可以播放这个宏,即可一键完成所有数据的处理。

宏操作只需要记住两个动作:

  • 录制:q 加一个字符,这个字符在 Vim 中被叫做寄存器,用来缓存内容的空间,字母区和一些特殊字符都对应一个专门的寄存器
  • 播放:@ 加这个字符,播放这个字符对应寄存器中的操作

dot 操作可以和宏操作结合起来用,实现更快速的操作。

学习如何避免重复操作,是使用 Vim 进阶必须要走的路。

在插入模式中避免重复的操作,就是 snippet 和自动补全机制,这些特性在其他代码编辑器中也存在,我就不展开了。

文本对象

很多进阶的 Vim 介绍文章和视频都会提到文本对象这个概念(text object),但其实不是什么很复杂的概念。

当我们拿到一个文本时,可以按不同性质将它切分,比如按段落,按行,按单词。Vim 中会增加更多的切分方式。比如,可以指定一个边界来切分,如 { } 中的内容,或者指定一个函数块或类块。这个东西就是文本对象。

文本对象是一种选择后执行逻辑,就是指定要选中哪些切分的内容,然后按指定方式执行。

为了区别要不要在选中内容时,同时也选中边界,Vim 提供了两个文本对象标记,iai 表示 inside,指不包含边界,a 表示 around,指包含边界。需要配合一个对文本对象操作的动作一起执行。比如:

  • 删除一个 { } 包含的内容,不包括边界就是 di{,包括边界就是 da{
  • 选中一个段落,使用 vipv 表示开始选中并进入 Visual 模式,p 表示段落

具体动作 dv,可以将它替换成其他类似的操作实现其他功能。

文本对象是一个很值得摸索和研究的话题。

程序思维

上边这几个小节中,其实已经体现出了在使用 Vim 过程中的程序思维。程序思维,就是可以非常自由的拼接一些操作,来实现更复杂的操作。d 表示删除,i 表示不包含边界,{ 表示边界字符是 { },连接起来就是删除不包含 { } 边界的中间的文本对象。y 表示复制,G 表示光标到文档结尾,连接起来就是复制从当前光标位置开始到结尾的内容。

Vim 的灵活性在这里体现的淋漓尽致,就像自动化编程一样,你可以按一定规律随意组合一些操作,快速完成一些复杂操作。对于一些开始复杂化的操作,可以将它编写到 keymap 中,或者 autocmd 中,实现更方便的快速完成。

当然,这里我想再重复一下本文开头的一个说明,即使你不熟悉这些看起来复杂的操作,也依然可以使用 Vim,这些只是可能对你有帮助的扩展,如果能掌握一定是更好的。大多数使用 Vim 的用户,可能也只掌握了不到一半的特性,这不妨碍他们完成工作任务。

肌肉记忆

我能想到的最后一个技巧,或者说不是技巧,而是概念,就是肌肉记忆。

就像开车一样,刚开始学习怎么开车时,我们需要思考哪里是油门刹车,怎么挂档位,但熟悉之后,这些操作就成为了一种习惯,不需要再刻意思考就可以完成,这就是肌肉记忆。

Vim 也同样如此,看似挺复杂的操作,比如 h j k l 这种不太符合常规的方向移动键,当你适应之后,就不会再有使用的困难了。我在将 h j k l 解释成 “左下上右” 时,还需要思考一下,但使用 Vim 中从来不用思考,这就是肌肉记忆优先于大脑记忆。

这种肌肉记忆需要练习,也就是我前边实践心得中的第 4-5 阶段,当完成第 5 阶段之后,这个操作技能就会被掌握,就会成为你使用 Vim 完成开发的得力助手。

当然还有个小话题,如果你还不会盲打,那么先练习盲打,肌肉记忆是手指的肌肉,而不是视觉和大脑的反馈。

不做 IDE

在网上越来越多的关于 Vim 的讨论都是关于 “怎么将 Vim 打造成一个强大的 IDE”,或者是拿 Vim 和 Visual Studio 这种 IDE 做比较。我的观点是,力气用错地方了。除非是享受 Vim 强大的扩展性,享受将它升级成各种强大软件的过程,除此之外,Vim 并不适合成为一个 IDE。

IDE 软件在 UI、稳定性、兼容性等方面做的都比较好,这是由于大多数成熟的 IDE 都有着强大的企业资源在支撑。而 Vim 只是一个开源软件,它依赖一种社区共识以及整体开发人员的水平和素质。Vim 确实是一个优秀软件,但将各种 IDE 的功能,如构建和调试、文件管理、发布和部署这些都集中起来,对 Vim 来说是有挑战性的,毕竟它最初的设计只是一个出色的代码编辑器。当然,Vim 及 Neovim 的配置和插件生态中,确实存在很多优秀的样本。

但这并不表示 Vim 相比 IDE 软件来说低人一等,Vim 有它独特的使用场景。比如说:

  • Linux 运维人员,服务器在云端,远程访问机器
  • 仅使用终端来开发和部署软件的工作

注意,我这里说的是 Vim 相比 IDE 独特的场景,而不是 Vim 可以使用的场景。

之前我写过一篇介绍 Tmux 的文章,通常这种环境下工作的工程师,都会配合一个终端管理器或窗口管理器,为了就是能灵活的使用 Vim 和其他系统软件,而不会有很大的割裂感。

换句话说,和 IDE 做比较的,不应该是 Vim,而是基于 Vim 作为代码编辑器的操作系统环境

总结

大概就说这些吧,如果看到有更好的 Vim 话题,我会继续更新。

以上内容仅代表我个人观点,我自己是非常喜欢倒腾 Vim 等工具的,Vim 和 Neovim 我自己搭建过类 IDE 环境,也用过很多种发行版(也就是别人搭好的环境),将它一步步改造成适合自己的软件,是很有成就感的。

但目前开始佛系,不再会向当初那样天天去社区看更新进展,茶余饭后去思考怎么在功能丰富性和性能方面去找平衡。可能是年纪大了吧,看到 Vim 的讨论依然这么热烈,Neovim 的生态也在蒸蒸日上,我是欣慰的。

工具终归是工具,就像争论什么是最好的编程语言一样,争论什么是最好的代码编辑工具,实际上都是没什么意义的,认清目标,适合自己的和适合项目的,就是最好的。


本文同步发布在知乎账号下:Vim 高效编辑技巧 - 知乎 (zhihu.com)