0%

Vim 实用技巧(01):Vim 解决问题的方式 & 普通模式

《Vim 实用技巧》一书旨在想人们传授如何像 Vim 高手一样思考问题。对于 Vim 高手来说,Vim 能以思考的速度编辑文本。本系列文章从 Vim 实际使用出发,介绍大量的 Vim 实用技巧,每一篇文章都是某一主题的技巧集合。

Vim 对重复性操作进行了优化。它之所以能高效重复,是因为它会记录我们最近的的操作,让我们用一次按键就能重复上次修改。但是前提是我们能够学会规划按键动作。掌握这一理念是高效使用 Vim 的关键。

普通模式是 Vim 自然放松的状态,这篇文章同时会介绍普通模式下一些通用技巧。

基础技巧

在介绍具体的技巧之前,首先介绍一些关于 Vim 帮助信息的按键命令:

  • :h vimtutor:获取vim向导;
  • :h key-notation:获取vim按键帮助文档;
  • :h feature-list:浏览vim完整的功能列表(vim的功能集包括tiny,small,normal,big,huge)。
  • :version:查看vim版本及所有可用功能

有时候,我们不希望 vim 的配置文件对我们的操作产生干扰,此时,可以用 vim -u NONE -N 启动 vim。 -u NONE 标志让 vim 在启动时不加载你的 vimrc 文件,当用不加载 vimrc 文件的方式启动时,vim 会切换到 vi 兼容模式,而 -N 标志能防止进入vi兼容模式。

Vim 解决问题的方式

技巧 1:结识 . 命令

. 命令可以让我们重复上次的修改,它是 Vim 中的瑞士军刀。

上次修改 可以指很多东西,一次修改的单位可以是字符、整行,甚至是整个文件。除了普通模式下的修改命令之外,每次我们进入插入模式时,也会形成一次修改:从进入插入模式的那一刻起,直到返回普通模式时为止(输入ESC),Vim 会记录每一个按键操作。此时使用 . 命令,它将会重新执行所有这些按键操作。

. 命令是一个微型的宏。

技巧 2:不要自我重复

如果想在如下内容的行尾添加分号:

1
2
3
4
5
this is line 1
this is line 2
this is line 3
this is line 4
this is line 5
  • 第一种方法:$a;<ESC>,如果接下来有多行要做相同的修改,可以使用 `j$.命令进行重复。
  • 第二种方法:A;<ESC>,如果接下来有多行要做相同的修改,可以使用 j. 命令进行重复。

第二种方法更加简单,它通过一个按键移动(j)移动到目标行,另一个按键执行修改(.)。一键移动、一键修改 的应用模式也是 Vim 中高效操作的常见技巧。

第二种方法中的 A 命令把两个按键 $a 合并成了一次按键,下面是类似的命令:

  • Cc$
  • scl
  • S^c$
  • I^i
  • A$a
  • oA<CR>
  • Oko

这些命令的共同点是:它们都会从普通模式切换到插入模式。

技巧 3:以退为进

如下所示,要在运算符“+”两边各输入一个空格:输入如下的命令序列:

1
1+2+3+4+5
  • f+:将光标移动到当前行中下一个出现 + 符号的地方
  • s + <ESC>:删除+字符,输入 + 三个字符,回到普通模式

这里 s 命令删除当前光标所在的单词,然后输入 + 并回到普通模式。先删除一个字符,再添加回三个字符,这是一个小技巧,这样做的好处是我们可以使用 . 命令重复这一修改:如果要继续修改,输入 ;. 即可重复修改:

  • ; 可以重复查找上一个 f, F, t, T 命令所查找的字符,
  • `.`` 命令重复上次修改

; 命令带我们到下一个目标字符上,而 . 命令则重复上次修改,这也体现了 一键移动,另一键操作。所以与其和 Vim 区分模式的编辑模型作斗争,倒不如与它一起协同工作。

技巧 4:执行、重复、回退

在面对重复性工作时,我们需要让移动命令和修改都能够重复,这样就可以达到一个最佳编辑模式。

除了 . 命令可以重复上一次修改之外,有些命令也支持以其他方式重复。而且当 Vim 让一个操作或移动可以方便地重复时,它总是会提供某种方式,让我们在不小心做过头时能回退回来。

下表列出了vim中可重复执行的命令,以及相应的回退方式:

目的 操作 重复 回退
做出一个修改 edit comand . u
在行内查找下一指定字符 f{char}/t{char} ; ,
在行内查找上一指定字符 F{char}/T{char} ; ,
在文档内查找下一匹配 /pattern n N
在文档内查找上一匹配 ?pattern n N
执行替换 :s/target/replacement & u
执行一系列修改 qx{changes}q @x u

技巧 5:查找并手动替换

在替换时我们一定要小心每一步操作。所以我们可能会输入查找字符串,对每个匹配手动确认是否需要进行替换。也有一种偷懒的办法,无需输入就可以进行查找:

  • *:该命令可以查到当前光标下的单词

所以可以先通过移动命令将光标移动到某个单词后,在输入 * 进行查找。

技巧 6:结识 . 范式

以上编辑技巧,都体现了一个共有的模式,即 . 范式。在这个范式中,通过 . 命令重复上次修改,再通过一次按键将光标移动到下一个目标上。

. 范式(一键移动、另一键执行)是 Vim 中的理想编辑模式。

普通模式

技巧 7:停顿时请移开画笔

画家在休息时不会把画笔放在画布上,对 Vim 而言也是这样:程序员只花费很小的时间编写代码,绝大多数时间用来思考、阅读、浏览代码。普通模式就是 Vim 的自然放松状态,在普通模式中,我们也有众多的工具可以利用。

技巧 8:把撤销单元切成块

u 键会触发撤销命令,它会撤销最新的修改。一次修改是可以改变文档内文本的任意操作,其中包括在普通模式、可视模式以及命令行模式所触发的命令,而且一次修改也包括了插入模式中输入(或删除)的文本。

在 Vim 中,我们可以自己控制撤销命令的粒度:从进入插入模式开始,直到返回普通模式为止,在此期间输入或删除的任何内容都被当成一次修改。因此只要控制ESC键的使用,就可以控制撤销的粒度。

多久离开一次插入模式?这是个人喜好问题。最好让每个 可撤销块 对应一次思考过程。

当处于插入模式时,如果光标处于行尾,另起一行最快的方法是使用 enter 键,但是有时也可以尝试 <ESC>o 键,因为在撤销时它会让你有更细的粒度。

有一个小细节需要注意:如果在插入模式中使用 <Up><Down><Left><Right> 这些光标键,将会产生一个新的撤销块。你可以把这想象为先切换回普通模式,然后使用相应的 h,j,k,l 命令对光标进行移动。

技巧 9:构造可重复的修改

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

在vim中,要完成一件事,总是有不止一种方式。在评估哪种方式最好时,最显而易见的指标是效率。即哪种手段需要的按键次数最少(又名 VimGolf)。

如下所示,假设光标位于行尾的字符 h 处,如果想删除单词 nigh,有三种方法:

1
The end is nigh
  • 反向删除:dbx
  • 正向删除:bdw
  • 删除整个单词:daw

第三种方式这里使用更为精准的 aw 文本对象,aw 代表 a word,即一个单词。可以把 daw 命令解读为 delete a word

虽然三种方式都需要三次按键,即每种方式的高尔夫得分都是3分。但是 daw 可以发挥 . 命令的最大威力。

  • 如果使用反向删除,. 命令会重复删除一个字符(. == x)
  • 如果使用正向删除,. 命令会重复删除从光标位置到下个单词开头的内容(. == dw),但是由于已经在行尾了,并没有下一个单词,所以这个场景里,. 命令没有什么用处。
  • 如果使用 daw 命令,. 命令会重复删除一个单词(. == daw),而且由于第一次指定daw后,不仅删除了单词 nigh,还删除了一个空格,因此光标直接在单词 is 的最后一个字符。此时可以直接使用 . 命令重复。

技巧 10:用次数做简单的算术运算

很多普通模式命令都可以带一个次数前缀(:h count),这样 Vim 就会尝试把该命令执行指定的次数,而不是只执行一次。我们可以利用这个功能来做简单的算术运算。

Ctrl+aCtrl+x 命令分别对数字执行加减操作。在不带次数执行时,它们会逐个加减。但是如果带一个次数前缀,那么就可以用它们加减任意整数。如果当前光标不在数字上,这两个命令会在当前光标之前或之后的数值上加减整数。即这两个命令会在当前行中查找数字,然后执行相应的加减运算。

Vim 能够识别数字的格式(例如 Vim 把 0 开头的数字解释为八进制值,而不是十进制值)。可以通过 set nrformats= 来关闭这项功能,这样vim把所有数字都当成十进制。

技巧 11:能够重复,就别用次数

如下所示:

1
Delete more than one word

要删除 more than 这两个单词(假设光标在 m 上),可以使用如下命令:

  • d2w:先调用删除命令,然后以 2w 作为动作命令,即删除 2 个单词
  • 2dw:首先次数作用于删除命令,而动作命令只跨越一个单词,即做 2 次删除单词的操作

再来考虑 dw.:该命令先删除一个单词,然后使用 . 命令重复,因此此时 u 命令和 . 命令提供了更细的粒度。而且计算次数是很讨厌的,尽管使用次数减少了按键数,但浪费了同样的时间去统计次数。口诀是 执行、重复、回退

只在必要时使用次数,例如如下所示,需要将 a couple of 修改为 some more(假设光标在 a 上):

1
I have a couple of questions.

此时直接使用 c3w 比较合适。而且使用次数还有另外一个好处:它保留了一个干净的、连贯的撤销历史记录。

对于是用次数风格还是用重复风格,这本身就存在着争论。这需要总结你自己的观点。

技巧 12:双剑合璧,天下无敌

Vim 的强大很大程度上源自操作符与动作命令(有的书称为移动命令)相结合,操作符 + 动作命令 = 操作

vim 常见的操作符命令如下(:h operator):

  • c:修改
  • d:删除
  • y:复制到寄存器
  • g~:反转大小写
  • gu:转换为小写
  • gU:转换为大写
  • :增加缩进

  • <:减少缩进
  • =:自动缩进

操作符与动作命令的结合形成一种语法。这种语法的第一个规则很简单:即一个操作由一个操作符,后面跟一个动作命令组成。Vim 的语法还有一条额外规则:当一个操作符命令被连续调用两次时,它会作用于当前行,所以:

  • cc:修改当前行
  • dd:删除当前行
  • :缩进当前行

  • gugu:将当前行转换为小写,这种情况也可以简化为 guu
  • ……

使用 Vim 内置的操作符和动作命令,我们可以执行的操作数目是非常巨大的。除此之外,我们还可以通过自定义动作命令及操作符来进一步扩充操作的数目:

  • 可以使用 :h :map-operator 来查看自定义操作符的相关文档
  • 可以使用 :h :omap-info 来查看自定义动作命令的相关文档。例如 textobj-entire 插件新增了两种新的文本对象 ieae,作用于整个文件

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

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

操作符待决模式也是我们创建自定义操作符及动作命令的基础。