06-基本编辑
第 6 章 基本编辑
掌握了你已经学到的导航快捷键,并且能够随意进入和离开插入模式,你的 Vim 编辑体验已经非常接近你在非模式编辑器中可能习惯的水平了。
**译者注:**大部分程序员对于IDE的熟练度很低,我想这是开发内容决定的。
然而,移动和插入文本只是软件开发者生活中的一小部分。更多时候,你需要编辑文本。删除代码、更改代码、重构代码、移动代码。这是我们所做的大部分工作。
是的,你可以通过导航到你想要的位置,然后进入插入模式来完成所有这些事情。删除键和退格键在插入模式下的作用与其他编辑器中相同。但是有更有效的工具可用。
最好的部分是你已经掌握了非常强大的编辑命令所需的大部分知识!
6.1. Vim 命令心智模型
你已经知道的导航命令,如 s
、f
、hjkl
和 web
,统称为动作(motion)命令。它们将光标从当前位置移动到新位置。
大多数动作命令可以前缀一个计数,所以导航模型总是 <计数><动作>
。计数通常将动作重复特定次数,尽管某些命令,如 Shift-G
(“转到行”),会使用计数作为绝对值。如果计数为空,则“默认”计数通常为 1
。即使是使用标签的 Seek 命令也允许前缀计数……尽管计数会被忽略!
译者注: Vim 命令通常遵循
[计数]动词[计数]动作
的结构。
- 动作 (Motion): 定义光标移动的范围或目标(如
w
到下一个词首,j
向下移动一行,$
到行尾,G
到某行/文件尾)。- 动词 (Verb/Operator): 定义要执行的操作(如
d
删除,c
更改,y
复制)。- 计数 (Count): 重复动词或动作的次数。
<计数><动作>
命令非常适合导航,这是我们到目前为止使用它们的全部目的,但它们也可以与一个动词(verb)结合,对光标和动作将把你移动到的位置之间的文本执行某些操作。
动词排在前面,所以结构总是 <动词><计数><动作>
。导航是“默认”动词,所以如果你省略动词(即跳过它),你的光标会移动到动作指示的位置。我们将在本章讨论各种重要的动词。
但这个模型还在不断扩展!事实证明,动词也可以被计数。语法变成 <计数><动词><计数><动作>
。然而,在我的一生中,我从未在一个命令中使用过所有这四个部分。通常你要么做 <计数><动词><动作>
或者 <动词><计数><动作>
。
这个模型很好,因为它允许你分治地学习,并在学习过程中重用知识。首先你学习了动作命令。然后你学习了计数。现在你将学习动词。如果你将来学习了新的动作命令或新的动词,你可以将它们与你已经知道的所有动词和动作混合使用,它们应该会以可预测的方式运行。
各种插件都在试图模仿这种策略,而且大多数是成功的。我对 explorer 的主要抱怨是它不遵循 <动词><动作>
的心智模型,而 mini.files 则遵循。类似地,有些人认为 Seek 模式违反了 Vim 的心智模型,因为计数对它没有意义。我的观点是 Seek 模式只是超越了计数,但它仍然能与动词清晰地结合,所以它是一个有效的 Vim 模型。
6.1.1. 关于插入模式的说明
像所有模型一样,这个模型也不是完美的。例如,你可以对 i
、I
、a
和 A
命令使用计数,但很明显“进入插入模式”既不是动作也不是动词。
例如,如果你输入 5ifoo<Escape>
,Neovim 将为你插入 foofoofoofoofoo
。这可能看起来不太有用,但如果你想要一个 80 个字符的 *
标尺来给标题加下划线,80i*<Escape>
就非常方便!
但是 <计数>i
这种“非动作”命令不能像你学过的导航命令那样与动词结合。所以了解模型的局限性很重要。
所以现在你明白了你已经知道的动作如何与动词结合以执行导航以外的操作,你只需要学习一些动词。
6.2. 删除文本
我已经透露过几次了,即使我没有,你大概也能猜到删除文本的动词是 d
(delete)。
因此,<动作>
会将你带到代码中的特定位置,而 d<动作>
则会删除光标与该位置之间的所有文本。以下是一些例子:
dh
:删除光标左侧的字符。(因为h
是向左移动的动作)d3w
:删除三个单词。(3w
是移动三个单词的动作)3dw
:删除一个单词,重复三次。(dw
是删除一个单词的操作,3
是重复动词的计数)d^
:删除从光标到行首(第一个非空白字符)的内容。(^
是移动到行首非空白字符的动作)d2fe
:删除光标位置与光标后第二个e
之间的所有文本,包括第二个e
。(2fe
是移动到第二个e
的动作)d2Ta
:删除光标与光标前第二个a
之间的所有文本,不包括第二个a
。(2Ta
是移动到第二个a
之前的动作)dsfoos
:删除当前光标位置与当你使用 Seek 模式寻找foo
时弹出的标签s
之间的文本。注意 Seek 模式总是跳转到你搜索词的开头。这意味着如果你跳转到的foo
在当前光标位置之后,oo
不会被删除,但f
会。但如果你跳转到的foo
在当前光标位置之前,foo
的所有三个字母都将被删除。
译者注: Seek 模式与
d
结合的行为需要注意:d<seek命令>
删除的是当前光标位置和seek最终落点位置之间的文本。由于 Seek 跳到目标词的开头,如果目标在后,删除范围不含目标词本身;如果目标在前,则包含目标词。看不懂就试试看。
如果以上任何一个例子让你惊讶,忽略 d
并回顾第 3 章以刷新你对动作的记忆。
所以 d
可以与你所知的所有动作命令一起工作,以及你还不知道的所有动作命令,并且还包括你尚未安装的插件定义的所有动作命令。
删除命令完成后,Neovim 仍将处于普通模式,你可以立即执行任何其他的 <动词><动作>
组合。
6.3. 更改文本
有时你只想删除文本,但另一个常见的任务是编辑文本。用另一个词替换一个词,更改拼写,删除段落的其余部分并用新的内容替换它,等等。
这可以很容易地通过将删除动词与插入模式结合来处理(例如 dwi
将删除一个单词并进入插入模式)。然而,你可以通过使用 c
动词来节省一次按键,它表示“change”(更改)。如果你把我上面列出的每个例子中的 d
替换为 c
,你将有效地得到“删除文本并立即进入插入模式”。
6.4. 操作到当前行尾
经常需要从光标位置删除或更改到当前行尾,而保持行首不变。这些操作在源代码编辑中发生的频率比你预期的要高,所以它们有一个快捷方式。
是的,你可以使用 d$
和 c$
来删除或更改到行尾,因为 $
是“跳转到行尾”的动作。这是心智模型的“正确”格式。然而,由于这是一个如此常见的操作,你可以用少一个按键来“作弊”,只需改用大写的 D
或 C
。
译者注:
D
: 等同于d$
,删除从光标到行尾的内容。C
: 等同于c$
,更改从光标到行尾的内容(即删除并进入插入模式)。
没有对应的快捷动词用于“删除到行首”,所以你必须使用 d^
或 d0
,其中 ^
是跳转到第一个非空白字符的动作,0
是跳转到第一列(无论是否为空白)的动作。
6.5. 操作整行
另一个常见的操作是更改或删除整行文本。事实上,这种情况非常普遍,以至于有专门针对“整行”的特殊动作。这些动作通过重复动词来访问。这是心智模型有点崩溃的另一个地方;动作的解释取决于动词。
实际上,这仅仅意味着 dd
删除整行,cc
删除整行并进入插入模式。这些都很容易输入,所以是很方便的简写。
译者注:
dd
和cc
(以及yy
复制整行)非常常用。它们可以看作是动词作用于一个隐含的“当前行”动作。
你可以将这些定制的动作与计数结合起来。d3d
将删除三行,而 3dd
将删除一行三次(输入更快,因为你的手指不必离开 d
键就能按两次)。是的,无论哪种方式结果都一样,但模型就是这样,你可以使用其中任何一种。请注意,在某些情况下,这两种格式可能会有细微不同的行为,尽管实际上我从未遇到过意外。
译者注:
d3d
逻辑上是 删除(d)+ 向下移动3行(3d) 的动作,但d
本身不是标准动作,所以这是特殊规则。3dd
是 重复3次 + 删除行(dd)的操作。别问,问就是祖宗之法不可变。
6.6. 修改单个字符的一些快捷方式
另一个常见的操作是对单个字符或特定数量的字符执行删除或更改操作。你可以使用 dl
删除光标下的字符,或 4dl
删除该字符及其后的三个字符来完成此操作。然而,因为你经常这样做,有一个没有动作的简写动词:x
。例如,如果你喜欢美式拼写,可以用 x
删除像“behaviour”这样的单词中多余的 u
。单个字母将被删除,然后你会回到普通模式,准备继续。
译者注:
x
(eXterminate?) 删除光标下的字符,相当于dl
。
x
命令可以与计数一起使用,所以如果你想删除从光标下开始的五个字符,只需使用 5x
。
如果你需要反向操作并删除光标之前的字符,请使用大写 X
。这个也可以带计数,它基本上会删除左侧那么多字符。我很少使用这个,因为 Shift 键反正使它需要两次按键,而 dh
或 d4h
并不更难。(译者注:原文是 hx
,但 hx
是先移动再删除,dh
是删除左边一个字符,d4h
删除左边4个字符)。
如果不是删除,而是需要将一个字符替换为另一个不同的字符,请使用 r
命令(replace)。这个命令会短暂进入插入模式让你输入一个字符,然后立即返回普通模式。对于一个常见的操作(拼写错误很常见,对吧?不只是我吧?)来说,这比类似 cl<新字符><Escape>
的操作少很多按键。可以对 r
使用计数,但行为有点没用:它会将光标下的字符以及该字符之后的 <计数>
个字符替换为同一个字母。我能想到的唯一有用之处是当你从别处复制粘贴密码提示符,需要将密码中的所有字符替换为 *
。
译者注:
r{char}
: 将光标下的字符替换为{char}
。5r*
: 将光标下及其后共 5 个字符都替换为*
。
另一个常见的操作是删除当前行末尾的换行符。在行内的任何位置使用 J
(“Join Lines”,连接行)命令。我经常用这个。如果你需要将多个连续行合并在一起,J
可以接受计数。它通常能正确处理空白(用单个空格替换缩进),但如果你需要在不修改空白的情况下进行连接,请使用双字符动词 gJ
。
译者注:
J
连接当前行和下一行,并在连接处插入一个空格(除非行尾是标点符号或连接处原来就有空格)。5J
连接当前行及其后 4 行。gJ
连接时不插入或删除任何空格。
6.7. 操作大小写
如果你需要将一个字符或一串字符转换为大写,请使用动词 gU
(第二个字符是 Shift 后的 U
),后跟任何标准的导航动作。我觉得这个特定的动词令人沮丧,因为 g
通常分配给 Go To
(转到)动作。在这种情况下(与上面的 gJ
一样),它是一个动词。
我猜你可以把它想成“Go To and Convert to Uppercase”(转到并转换为大写),其中 U
是 Uppercase
的缩写。
将当前光标位置和动作目标之间的所有文本转换为小写的反向函数是在动作前使用小写 gu
。有点难记,但它确实符合常见的 Vim 习惯用法,即 gu
表示一个动作,gU
表示相同的动作但范围更大(或相反效果,这里是大写)。
译者注:
gU<动作>
: 将动作范围内的文本转为大写。例如gUw
转大写到词尾,gU$
转大写到行尾。gu<动作>
: 将动作范围内的文本转为小写。例如guw
转小写到词尾。~
: 切换光标下字符的大小写,并将光标移到下一个字符。
我不觉得这些命令很有用。我更常用 ~
命令,它反转光标下字符的大小写。
重复的命令 gUU
和 guu
做的事情与其他重复动词相同,将大写/小写操作应用于整行。
如果你发现自己经常做大小写切换的工作,请参阅第 13 章关于 text-case
插件的讨论。
6.8. 重复命令
LazyVim 没有多光标模式。确实有支持多光标的插件,但根据我的经验,它们效果不是很好。Neovim 开发者已将多光标列入他们的路线图,所以我希望他们能提出一个能与 Vim 心智模型良好集成的范式。
与此同时,Neovim 提供了几种不同的工具来在你的代码中的多个地方执行操作。我们将在这里介绍基本的重复,并在后续章节中介绍其他有用的技术。
一旦你执行了任何动词操作,你可以导航到文档中的另一个地方,并通过一次按键重复该动词操作:.
(这是一个句点,尽管在这种上下文中你通常会听到它被称为“点重复”dot repeat)。
这突显了为什么 d
和 c
需要是独立的动词,而不是使用像 d<动作>i
这样的方式。当你使用 c
时,删除动作和你插入的文本都会被记住,所以你可以用 .
命令重复整个更改。例如,如果你想将所有名为 i
的变量实例替换为一个更好的名字 index
,你可以跳转到第一个 i
实例并输入 clindex<Escape>
来“将一个字符更改为 index”。然后你可以使用导航命令转到下一个 i
的使用处。现在只需输入 .
来重复更改,并继续到下一个实例。
像动作和动词一样,.
命令也可以带计数。然而,.
的计数有点微妙。它不会盲目地重复命令 <计数>
次,而是会替换被重复命令的计数。
这意味着,如果你使用动词 3dd
删除了三行,然后你执行的下一个操作是 2.
(“2 点”),那么第二个操作将删除两行,而不是六行。
译者注: 点
.
重复的是上一次的整个修改操作(包括动词、动作、计数以及在插入模式下输入的文本)。如果上一次操作是3dd
,.
会再次删除 3 行。但如果输入2.
,它不会执行3dd
两次,而是执行2dd
,即用新的计数2
替换掉旧的计数3
。
6.9. 录制命令(宏)
Vim 的命令录制和回放系统(通常称为宏,Macro)非常强大。你可以轻松地录制任意序列的导航、编辑和插入命令,然后在任何你需要的位置按需重复该序列。
要开始录制,请按 qq
。抱歉,我没有记住 q
的助记符。我感觉它只是键盘上最后一个可用的键!(译者注:q
可能代表 “record macro into register q”,因为你可以指定录制到哪个寄存器,例如 qa
录制到寄存器 a)。
译者注: Vim 宏录制的基本流程:
- 按
q
后跟你想将宏录制到的寄存器名称(一个小写字母,如a
、b
、q
等)。例如,qa
开始录制到寄存器a
。- 执行你想要录制的一系列命令(普通模式、插入模式、命令模式均可)。
- 按
q
停止录制。- 要回放宏,按
@
后跟你录制时使用的寄存器名称。例如,@a
回放寄存器a
中的宏。- 要重复上一次回放的宏,可以使用
@@
。
在那之后,输入你想要录制的任何导航、编辑和插入命令序列。删除单词、插入文本、更改文本、搜索单词(不要使用 Seek 模式,因为回放机制将不知道要跳转到哪个标签)。几乎任何你能在 Vim 中做的事情(甚至 :
命令)都可以被录制并在以后回放。
当你完成录制时,只需再次按 q
。录制内容将被存储起来,随时准备在你需要时回放。
译者注: 原文简化了宏录制,只提了
q
结束。这实际上是录制到q
寄存器的快捷方式。回放时应该用@q
。
6.9.1. 追加到录制
如果你部分完成了录制,然后意识到在完成录制之前需要更多信息或需要进行编辑,你可以像往常一样使用 q
停止录制,并做你需要做的事情。
当你准备好继续录制时,使用 qQ
来以追加模式录制。这里的主要提示是,你需要确保你的光标位于一个使得合并后的录制有意义的位置。这通常意味着与你停止录制时相同的位置,尽管这可能取决于你在此期间所做的更改。
译者注: 追加录制需要指定寄存器,使用大写字母。例如,要追加到寄存器
a
,应按qA
。原文的q
寄存器。
6.9.2. 回放录制
回放你最近保存的录制内容最简单快捷的方法是用大写 Q
。
译者注: 回放宏的标准方式是
@<寄存器>
。例如,如果用qa
录制,用@a
回放。如果用@q
回放。原文提到的Q
在 Vim 中默认进入 Ex 模式,除非被重新映射。LazyVim 可能将Q
映射为@q
(回放 q 寄存器)。
可以使用寄存器(一个让人联想到人类汇编编程黑暗时期的愚蠢名称,指存储位置)同时存储和替换多个录制内容。我们将在第 8 章更详细地讨论寄存器。
6.10. 撤销和重做
显然,这些是整本书中最重要的操作!使用 u
键撤销你最近的更改。注意“最近的更改”可能是一大块文本,特别是如果你有一段时间没有退出插入模式。例如,我是在一次插入会话中写完这整个段落的。如果我按 u
,整个段落都会丢失。
译者注:
u
(undo) 撤销上一次更改。一次插入模式会话算作一次更改。
但这没关系,因为我可以用 Control-r
重做。像大多数开发者一样,我广泛地使用这两个命令。(你知道吗,在打字机的旧时代,秘书们必须在打字测试中获得 100% 的准确率?你看,那时没有退格键或删除键)。
译者注:
Control-r
(redo) 重做上一次被撤销的更改。
Neovim 实际上在跟踪你的整个历史方面做得非常出色,而不仅仅是最近的一系列更改。所以,如果你做了一系列更改到达状态 B,然后撤销到状态 A,接着又做了一系列更改到达状态 C,仍然有可能回到状态 B(即:从 C 的更改退回到状态 A,然后沿着 B 的更改前进到状态 B)。
译者注: Vim/Neovim 维护一个撤销树 (undo tree),而不是线性的撤销历史。这意味着你可以撤销更改,然后进行新的更改(创建了新的分支),之后仍然可以回到之前的分支。
这有点像 git 分支的概念,只不过你的历史是为你做的每一次按键自动跟踪的。然而,使用原始 Neovim 命令处理撤销历史的分支可能会感觉相当笨拙(如果你胆子大,可以通读 :help undo-branches
)。我建议配置并安装 undotree 插件。
译者注:
undotree
插件提供了一个可视化界面来查看和导航撤销树,使得在不同历史状态间跳转更加容易。
大约 99.9% 的时间里,u
和 Control-r
就足够了,但剩下那 0.1% 的情况下,当你需要 undotree
时,它简直是天赐之物。
6.11. 总结
在本章中,我们扩展了对 Vim 心智模型的理解,然后介绍了几种可以与我们已经熟悉的导航动作结合使用的动词。
我们讨论了各种其他的编辑命令,然后介绍了如何使用 .
和命令录制来重复动作。最后,我们介绍了撤销和重做。
在下一章中,我们将学习文本对象以及 Vim 心智模型中操作符待决模式的一些额外细微之处。结合起来,这些使我们能够非常快速地对各种各样的代码结构执行操作。