Vim everywhere

前言

自从用上 vscode 以后,vim 逐渐成为了一个插件,似乎消失了,我也不知道我还算不算 vim 用户了。最近重新关注起来,那个被我丢在角落的 vim 社区已经有了远超我想象的进步了。 当初因为插件性能问题,我开始转向了 vscode 。后来 neovim 诞生了,支持多窗口,内置了 lsp 支持,这使得 neovim 的 LSP 性能并不再是问题了。而且 neovim 内置了 Lua 插件支持,不仅性能大大提升,而且语言能力强大非常多。另外,还有了 LazyVim 这样开箱即用的解决方案,内置了非常多优秀的插件,也节省了配置的时间。 研究了一段时间,终端的 neovim 功能已经非常强大了,文本搜索、LSP、文件搜索,session 功能,完全不输 VSCode,并且有了新的 which key 插件,对应新手,已经不需要记忆快捷键了。总体感觉 vscode 不再香了,vim 依旧是最酷的编辑器。 这里重新整理一下 vim 的一些我感觉好用之处,对我自己来说也是一次温习过程。

基本思想

在 vim 的世界里,文本编辑最核心的是修改,而不是输入。这一点和大部分编辑器都不一样,vim 默认的是 normal 模式,这个模型下,用户可以快速找到文本,进行修改、选中、移动光标、删除。在 normal 模式下,我们所有的键都是快捷键,这也是 vim 能够摆脱鼠标的原因。 在 normal 模式下,除了 ctrl + key 和普通 key 作为快捷键之外,vim 还扩展了一个 leader key ,一般是 space 空格键,这个键可以多一个组合的方式。结合 which key ,我们也不需要记忆绑定了。这是 vim 高效的基础,可以有无法的操作组合。

几个阶段

首先,简单介绍一下我个人使用 vim 的几个阶段。

初识

最开始,是大学刚毕业之前,买了一个上网本,那种性能非常差但非常便宜的电脑,这种电脑能用的编辑器也就 vim 了。作为初学者,学习 vim 最好的方式就是使用 vim 自带的工具,vimtutor ,命令行输入一下 vimtutor 就可以开始练习了。 此外,vim 自带的文档,也是非常好的,:help就可以了。

狂热

熟悉以后,vim 成为了我的主要编辑器,有一段时间喜欢参与网上的编辑器争论,还专门写了两篇文章介绍 vim 。那时候试图用 vim 做一切,包括写日记、文章。 但 vim 要跟上 IDE 的功能还是很难的,搞一堆插件,最终 vim 配置越来越复杂,需要记的命令也越来越多,vim 本身也越来越慢。

vim mode

再后来,我开始使用上了 vscode ,加上 vim 插件(这个插件是实现了 vim 的基本功能)。**从此以后,很少用 vim **,只在偶尔处理大批量文本的情况用用。 到了最后这个阶段,我慢慢的适应了 vim 作为插件的形式运行在 vscode 中。vim 我也只留下最简单,最原始的用法。折腾 vim 插件太繁琐了,很多功能现代编辑器要强大得多。但通过 vim 插件,可以把 vim 的编辑的优势保留(不依赖鼠标操作),两者结合是最完美的了。 vim 我虽然不用了,但 vim 的模式会永远存在于各种环境中。

快速移动

vim 的 normal 模式下,你只能输入命令,可以非常方便的用来移动光标位置,最著名的就是h/j/k/l 移动了。当然这种也只适合小规模移动,但这种方法成了一种标准,可以应用与各种地方。比如,ctrl + w + (hjkl) 表示移动到不同的窗口。通过 vimium ,我们可以通过 H L 在 chrome 窗口左右切换,这样浏览器访问也可以大大减少鼠标的使用。 在 normal 模式下,所有的键都可以是快捷键,所以,vim 也逐渐发展出来一套快速移动的模式。除了上下左右,还有一些常用的移动

操作

功能

<ctrl> + u / d

向上、下滚动半页屏幕

w / e / b

向前移动一个 word, e 移动到 word end 位置,b 回退一个 word

$ / 0

移动到行头尾为止

gg / G

移动到最开头结尾

H / M / L

光标移动到屏幕的 top / middle / bottom 位置

这些快捷键是有点多,但都可以通过 vim 内置的 :help M 这类的指令找到相关的说明。 虽然记住这些很麻烦,但其实也不算多,每天用用自然熟悉了。这些快捷键逐渐成为了一种标准,比如我们使用终端工具,less more 之类的,也是这些快捷键操作来移动光标。

窗口移动

有一组命令,可以在不同窗口切换,都是 ctrl + w 开头

  • ctrl + w + (hjkl) : 上下左右切换

  • ctrl + w + (sv) : split window (horizontally or vertically), 开启一个分屏

  • ctrl + w + q: 退出当前窗口

这些命令可以看似组合非常多,但是有规律的,hjkl 是一种位置的标准,q 也是退出的常用命令。

搜索模式

前面的快速移动,都是一些通用的移动,但有时候我们是有目标的移动,这时候就需要使用到了搜索模式,这也是很多鼠标需要用到的场景。 搜索模式需要两个操作步骤,第一步选择一个关键词,第二步移动。当然,vim 的搜索优势只是在同一个文件范围,如果跨文件,vscode 的搜索更方便。 我用的比较多的是,搜索光标下的 word ,通过 * 进入搜索模式,然后通过 n/N往后、前移动。这种方式实际上,大部分是查询一个变量的引用,我们可以通过 gd/gr 这种方式更快捷,gd 意思就是 goto declaration , gr 查找引用。 另外一种方式,就是通过 /,然后输入正则表达式进行搜索。还有一个叫 easymotion 的移动,简单说来就是通过一个快捷键进入搜索模式,但是高亮的所有 word 的开头,举个例子,一开始代码这样的

image.png

image.png

输入 ,,w,然后变成了下图,接下来我就可以在任意位置跳动,比如输入 a 就到了this.renderMattermost位置。最新的 neovim 插件 flit.vim ,直接通过 f F 激活进入 easymotion 搜索模式,这更简单了。
image.png

image.png

类似操作的还有 vimium ,通过 vimium ,页面输入 f ,所有可点击的链接都有一个字母标识,输入字母,即可跳转 url ,这实际上并不比鼠标点击慢,而且少了切换过程。
image.png

image.png

mac 操作系统也可以如此操作,hammerspoon 有一个插件,可以给每个窗口一个字母标识,然后通过一个快捷键可以跳转到任意窗口。

text object

text object 是 vim 里面最有特色的功能了。text object 就是定义一段文本对象的,比如 aw 表示 word ,( 表示括号包裹的一个对象。在 vim 里面移动或者修改,有点类似于描述一件事情,做一件事情,就有一个操作的对象,这个对象就是 text object 。 比如,我想删除一个单词,就是 delete word daw ,也可以加数字 d3aw ,删除三个 word 。组合行为一般分为两段

  • d v y 这是几个基础的操作, d 删除,v 选中,v 表示进入选择模式,y 复制

  • a i 这是范围的修饰,a 英文的 a ,i 表示 inner ,aw 和 iw 的区别就是 aw 会删除包括单词后面的空格,i 只是单词本身。a" 和 i" 的区别类似,a" 删除引号里面的内容,同时包括引号本身,i" 只是 inner ,只删除引号里面的内容

  • 最后就是文本对象符号了,有很多,下面有一个 table 列出来了 | | a/i + w / p / s | a /i + ( " ' [ { < | a / i + t | | --- | --- | --- | --- | | d | 删除一个 word, paragraph, sentence | 删除 ' [ 之类符号 里面的内容, i 是 inner ,不删除符号,a 删除符号本身 | dat / dit 删除一个 html tag | | v | 选中一个 word | vi( 选择符号包裹的内容 | 选择一个 html tag | | ds (delete surround) | 无 | 删除符号 | 删除一个 tag | | cs | | 修改符号,"hello" + csa"' = 'hello' | <div>hello</div> + cst" => "hello" 也可以 cstt 修改 tag 标签 |

在 neovim 里面,b 表示最近的一个 block,包括( [ {三个了。还有了 q (quote)表示 `" ' ``,和 LSP 结合,有了 F(Function) 、c(Class) 这类的文本对象了。

vim-surround

s(urround) 这个是一个插件提供的 object ,叫做 vim-surround ,这个在写代码的过程中使用非常多。这个使用的场景非常多,比如

  • 快速添加一个 tag,一个函数返回一个元素<Comp1>,这时候上面加一个组件<Comp2>,这时候代码变成了 <Comp1><Comp2>,通过 surround 可以快速添加一个 <></> 之类的包裹(选中组件,输入 S ,输入标签)

  • 标签修改,cstt 然后输入新的标签名称,或者把一个字符串转换为一个组件,cs"<p> 这样就可以了

  • 删除标签,dst

除了可以操作一个文本对象之外,还可以对象之间移动。这些移动包括以下操作 {} 往前往后移动一个 paragraph 。[] ][ 找到最近的一个 { 。% 在 ( " { < 符号收尾移动。这些都是比较常见的移动方式。

其他

复制

通常我们通过 y 执行复制操作,p 粘贴。但 vim 其实还有一个 register 的概念,我们可以的复制的东西,会依次写入到 0-9 标记的 register 中。然后通 "0p可以粘贴指定 register 的内容,这比系统一个剪切板稍微好点。通过 :di可以查看 register 里面的内容,粘贴一次以后,可以通过 .重放,这也是比较常用的。

redo and undo

u / <ctrl> + r:u(ndo) 撤销操作,ctrl + r redo 撤销 u 操作。vim 还支持 Undo Persistence ,关闭文件后还能撤回,有点类似 git 功能了。 类似的通过 ctrl + o / i 两个操作符,可以在历史访问位置之间快速调整,ctrl + o 回到上次位置, i 反之。

输入模式

本文中,所有的快捷键都是正常模式命令,这里单独说一下输入模式下的快捷键。 在输入模型下,也有一些通用的快捷键。因为 vim 的 normal 比较强大,所以我在输入模型下最多的操作是退出 insert 模式。一般是 esc ,此外还有 ctrl + [ / c / o ,退出有稍微的不同,我一般用 ctrl + [ 比较多,esc 太远了。

快捷键

输入模型下的快捷键,很多都是系统级别通用的,和 vim 关系并不大。 ctrl + f / b , 光标向前向后移动,ctrl + h 删除一个字母,ctrl a / e 移动到行头尾位置。这些是 emac 快捷键,在 vim 中也部分可用,但一般没必要,vim 移动用正常模式更快,这些我一般在其他输入场景使用。 此外,还有一组自动补全相关的快捷键,ctrl + n / p ,应用非常广泛(浏览器地址栏、终端、vscode 默认快捷键,Jetbrains),如果有输入提示,表示在提示选项之间前后切换,否则表示行 next 和 previous 。这一组是 vim 的快捷键和 emac 的组合,搜索提示是 vim 的,上下换行是 emac 的。 vim 特有的是 ctrl + w 删除一个 word 。

marco 宏

在 vim 中,我们可以通过 q + 任意一个符号(mark)开始录制一个宏,然后后面的操作会被录制,完成以后通过 q 完成录制,这样就完成了录制过程,通过 @ + mark 来重放。这种场景在一些数据处理操作非常方便,比如把一个 csv 转换成一个 js 对象,我们完成第一行,然后通过 100@a 就可以完成后续 100 行的替换了。

总结

这里所说的,大部分是 vim 基础功能,其中有两个插件,vim-surround 和 easymotion ,这些都是加强 vim 基础功能的插件,在 Vscode 的 vim 插件默认支持的。 虽然 vim 可以很快,但是也还是有代价的,需要记住很多快捷键。neovim 加 which key ,可以减少记忆的负担,但这些适合组合的快捷键,很多基础的操作是不需要组合的,还有需要记忆的。 现在越来越多的 ai 辅助工具,比如 copilot ,要比 vim 快得多,也不许要太过执着于 vim 。vim 最大的价值在于他提供了一套快捷的文本操作模式,并且这种模式,可以应用于任意场景。因为 vim 如此受欢迎,所以这套快捷操作模式,对于任意编辑器来说都是必须品。对于开发者来说,快捷键使用,认准 vim 就够了。