在上一讲中,我们提到过在 hjkl 这四个用来移动的按键中,h 和 l 并不那么常用,因为左右移动有更方便的快捷键。事实上,你在完成上一讲的课后练习的时候应该也已经发现,使用 h 和 l 进行左右移动并没有那么直观,毕竟数字符数远比数行数要累得多。如果你在常用的现代编辑器中已经熟练掌握了一些键盘操作技巧,你应该知道 word、vscode 这种工具都是支持按照单词跳转的,比如说 Ctrl + → 可以跳转到单词的末尾。很显然,neovim 不可能没有提供类似的功能。
1 word 的跳转
在 neovim 中,关于一个 word 的定义是这样的:
A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs,
).
当然你也没必要太深究这个定义,你完全可以将形如一个变量名的东西视作单词,比如 foo
,i18n
,hello_world
这些内容都是一个 word(R 语言别来凑,var.name
这种形式的是 3 个单词,其中 .
属于上述定义中的“一串非空白字符”)。至于一些特殊情况,你会在使用过程中逐渐弄清楚,我没必要在这里过多强调。不过需要说明的是,中文里的一个词汇并不是一个 word。
关于 word 相关的跳转(我们称之为 word motion)有三种,它们分别是:
- w:跳转到下一个单词的开头
- e:跳转到下一个单词的结尾
- b:跳转到上一个单词的开头
以上操作都是在 normal mode 中进行的。这里,要特别特别注意上面这几句话的断句。以 b 为例,正确的断句是“跳转到上一个 单词的开头”而不是“跳转到 上一个单词 的开头”。例如,hello █orld
按 b 后会变成 █ello world
,但 hello wor█d
按 b 后会变成 hello █orld
,因为这里面有两个“单词的开头”,分别是 h
和 w
,在第二种情况下,光标前最后一个可以被称为“单词开头”的是 w,所以会跳转到 w 处。
e 同理,它会向后寻找最近的一个“单词的结尾”,然后将光标移动到那里。
和 hjkl 一样,我们也可以在 w / e / b 前面加上数字一次性跳转多个单词,例如 3w
会跳转 3 个单词。但是与 hjkl 不同的是,如果一行只有 20 个单词,那么 21w
会跳转到下一行,而不是像一行只有 20 个字符的时候 20l
只会停在行尾。
2 WORD 的跳转
看起来上述的功能已经足够强大了,但是有些情况下我们觉得还不够。例如,我们有这样一段内容:var.name <- 123
,现在光标位于一开始,即 █ar.name <- 123
。现在,如果我们想要把光标移动到 1
的位置,该怎么做呢?
我们需要先手动计数,v
后面单词开头有 .
/ n
/ <
/ 1
,所以应该按 4w
。这样数起来多少还是有点麻烦的,我们明明可以很清晰地看到整段文字被分成了 var.name
/ <-
/ 123
三个部分,如果可以直接选择跳转到这些组块的开头,至少我们计数起来会简单很多。neovim 为我们提供了这种功能,这些“组块”叫做 WORD:
A WORD consists of a sequence of non-blank characters, separated with white space.
只要没遇到空白字符,这一串内容都是一个 WORD。在 WORD 之间跳转非常简单,只要把 w / e / b 换成大写即可,即按下 Shift + w / e / b 就可以。此时,面对同样的需求,我们只需要 2W
就可以实现了。
neovim 中的 text object 还有很多,比如 s(句子)、p(段落)、t(标签)……这里我们不会对它们一一介绍,你只需要留心这一节内容并没有涵盖所有的 text object,如果以后现有知识不能满足你的需求,可以自行查找其余的 text object。
3 基于 text objects 的操作
注意,这是本篇教程第一个上难度的地方,可以反复阅读和实验。
3.1 行内操作
到目前为止,我们讲的所有内容都局限在移动光标上,至于文本的增删改,我们还是只会用我们熟悉的那一套流程,甚至一些我们在现代编辑器中必不可少的功能如复制、粘贴等在 neovim 中该怎么做,我们毫无头绪。
我们回想一下,在我们使用 VsCode 或其他编辑器进行编辑的时候,复制一段文字前的操作是什么?是选中这段文字。到目前为止,我并没有告诉你们如何选中一段文字(这是后面课程的内容),但我们确实提到了和“一定范围内的文本”相关的内容,那就是本节前面的 text objects。毕竟,“第 2 行第 1 列到第 20 列”和“第 2 行从第 1 列开始向后 3 个单词”确实都表示了一个范围。
不过在 neovim 中,操作的顺序有所不同,我们是先选定要进行的操作,再指定范围,即 <verb><range>
这个样子,其中 <verb>
是我们想要进行的复制 / 剪切等操作,<range>
是范围——准确来说,这个应该叫 motion,形如 3w
或 5l
,表示从光标当前所在为止开始到进行这一 motion 后光标所在位置这一范围。
你可能会觉得这挺好理解的,但事实上,这个所谓的“范围”还是很值得说道说道的。这个范围的起始位置,是光标的左侧;范围的结束位置,也是光标的左侧。所以:
- 如果是向右操作,那么这个范围包含光标起始位置的字符,但不包含光标最终停止位置的字符
- 如果是向左操作,那么这个范围不包含光标起始位置的字符,但包含光标最终停止位置的字符
我们假设有这样一段字符串 helloworld
,且光标位置为:hello█orld
,那么 <verb>3l
会对 wor
进行操作,而 <verb>3h
会对 llo
进行操作。
口说无凭,我们可以简单试验一下。这里就来介绍一些可以使用的文本操作。其中之一就是复制,在 neovim 中这个操作叫做 yank
,快捷键是 y。因此,按照上面所说的,如果我们需要向前复制到当前单词的结尾,可以使用 y1e
。特别地,这里的 1
可以省略,我们可以直接用 ye
来实现当前功能;但如果我们要复制的单词不止一个,则数字不可以省略。
在复制之后,我们就可以将这些内容进行粘贴(paste),快捷键是 p。在 normal mode 下,这个快捷键会将刚才复制的内容粘贴到光标的后面。此外,如果你想要将内容粘贴到光标的前面,可以使用 P。
好的,现在你可以尝试用复制和粘贴对刚才说的那个例子试验一下。
同理,除了复制以外,我们还可以使用删除(delete)操作——虽然叫做删除,但其实这更类似于剪切,快捷键是 d。这一操作会移除范围内的文字,且这一部分文字也可以用 p 和 P 进行粘贴。
3.2 行间操作
刚才我们讲的都还只是在一行内对文本进行操作。很多时候,我们想要对一整行或者是多行文本进行操作,这种时候再用上述方法就比较麻烦了。
不过,多行文本的操作和刚才所学的内容相比倒也不是什么很新鲜的内容,其形式仍旧是一致的,我们依然可以用诸如 dj
或者是 d3k
这样的形式进行操作。此外,我们上一讲所提到的 gg
和 G
也可以用在这里,例如 ygg
或 dG
。不过,区别在于,行间的操作会包含当前所在行和最终光标停止位置所在行,因而 dj
会删除 2 行内容而非 1 行。
基于此,我们这里抛出一个略具挑战性的问题:如何复制全文?
复制全文,就是从第一行复制到最后一行,所以我们可以先 gg
跳转到第一行,然后 yG
复制到最后一行,即可实现这一功能。
不过此时可能会有读者发现问题:我们提到行间操作可以对“一整行”内容进行操作,但如果行间操作会包含起始行和结尾行,那怎么复制 / 删除一行呢?用 jk 去进行操作确实解决不了这个需求。针对此需求,我们有一个特殊的用法:yy
和 dd
,二者分别会复制和删除当前行的内容。
不知道有没有人想到可以尝试一下
d0j
这种形式。当然,如果你已经尝试过了,你会发现这并不总是管用,因为0
在 neovim 中是一个特殊的 motion,它会跳转到当前行的开头,所以这一操作相当于从当前位置删除至行首然后向下移动一行。不必记忆这一知识点,我们后面还会再讲到的。
3.3 介词
现在讲到的东西还是不能解决一些问题。假设我们要删除一个单词该怎么办呢?dw
?如果你的光标位于单词中间,它只会删除半截单词。按照目前的解法,我们需要用 bdw
才可以。
Neovim 为我们提供了一个便捷的解法:diw
。其中,i
表示 inner,这个操作表示的是删除当前单词。
你也许会好奇,这种操作和 bdw
相比好像也没有简化多少呀?确实,但是介词操作并不局限于一个单词,你完全可以用这种方式操作括号里、引号里、标签里的内容,例如,(hello world)
/ [hello world]
/ {hello world}
/ "hello world"
/ 'hello world'
/ <p>hello world</p>
分别可以用 di(
或 di)
/ di[
/ di{
/ di"
/ di'
/ dit
(t 表示 tag)进行操作。
而除了 i
,我们还可以用 a
这个介词,这个介词会把外面的引号 / 括号 / 标签等一起选中,如对 (hello world)
进行 ya)
就会把小括号一起复制进去。
如上就是本讲的内容。现在,我们不妨再回到上一讲的练习,此时你会发现使用新学习的这几个快捷键,操作会方便很多。
至此,本系列教程中的 neovim 的基础编辑部分就结束了。如果你看过其他 neovim 教程,你会发现我讲的内容是很少的;现在用这些快捷键去进行编辑,体验也并没有非常好。这是因为我更推崇循序渐进的教学,没有必要一开始就讲很多你根本不可能记得住的快捷键。接下来的教程是 neovim 的配置部分,我会在这期间穿插讲解一些更高级的编辑技巧;鉴于我们配置 neovim 也是在 neovim 中进行的,我也会在配置的过程展示一些操作可以怎样更方便地进行。