Featured image of post Neovim 入门教程 03——text object

Neovim 入门教程 03——text object

使用 text object 进行更快的移动和文本操作

在上一讲中,我们提到过在 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, ).

当然你也没必要太深究这个定义,你完全可以将形如一个变量名的东西视作单词,比如 fooi18nhello_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,因为这里面有两个“单词的开头”,分别是 hw,在第二种情况下,光标前最后一个可以被称为“单词开头”的是 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,形如 3w5l,表示从光标当前所在为止开始到进行这一 motion 后光标所在位置这一范围。

你可能会觉得这挺好理解的,但事实上,这个所谓的“范围”还是很值得说道说道的。这个范围的起始位置,是光标的左侧;范围的结束位置,也是光标的左侧。所以:

  • 如果是向右操作,那么这个范围包含光标起始位置的字符,但不包含光标最终停止位置的字符
  • 如果是向左操作,那么这个范围不包含光标起始位置的字符,但包含光标最终停止位置的字符

我们假设有这样一段字符串 helloworld,且光标位置为:hello█orld,那么 <verb>3l 会对 wor 进行操作,而 <verb>3h 会对 llo 进行操作。

口说无凭,我们可以简单试验一下。这里就来介绍一些可以使用的文本操作。其中之一就是复制,在 neovim 中这个操作叫做 yank,快捷键是 y。因此,按照上面所说的,如果我们需要向前复制到当前单词的结尾,可以使用 y1e。特别地,这里的 1 可以省略,我们可以直接用 ye 来实现当前功能;但如果我们要复制的单词不止一个,则数字不可以省略。

在复制之后,我们就可以将这些内容进行粘贴(paste),快捷键是 p。在 normal mode 下,这个快捷键会将刚才复制的内容粘贴到光标的后面。此外,如果你想要将内容粘贴到光标的前面,可以使用 P

好的,现在你可以尝试用复制和粘贴对刚才说的那个例子试验一下。

同理,除了复制以外,我们还可以使用删除(delete)操作——虽然叫做删除,但其实这更类似于剪切,快捷键是 d。这一操作会移除范围内的文字,且这一部分文字也可以用 pP 进行粘贴。

3.2 行间操作

刚才我们讲的都还只是在一行内对文本进行操作。很多时候,我们想要对一整行或者是多行文本进行操作,这种时候再用上述方法就比较麻烦了。

不过,多行文本的操作和刚才所学的内容相比倒也不是什么很新鲜的内容,其形式仍旧是一致的,我们依然可以用诸如 dj 或者是 d3k 这样的形式进行操作。此外,我们上一讲所提到的 ggG 也可以用在这里,例如 yggdG。不过,区别在于,行间的操作会包含当前所在行和最终光标停止位置所在行,因而 dj 会删除 2 行内容而非 1 行。

基于此,我们这里抛出一个略具挑战性的问题:如何复制全文?

复制全文,就是从第一行复制到最后一行,所以我们可以先 gg 跳转到第一行,然后 yG 复制到最后一行,即可实现这一功能。

不过此时可能会有读者发现问题:我们提到行间操作可以对“一整行”内容进行操作,但如果行间操作会包含起始行和结尾行,那怎么复制 / 删除一行呢?用 jk 去进行操作确实解决不了这个需求。针对此需求,我们有一个特殊的用法:yydd,二者分别会复制和删除当前行的内容。

不知道有没有人想到可以尝试一下 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 中进行的,我也会在配置的过程展示一些操作可以怎样更方便地进行。

使用 Hugo + Stack 主题构建