08-剪贴板、寄存器与选择 - Some-soda

08-剪贴板、寄存器与选择

第 8 章 剪贴板、寄存器与选择

Vim 有一套强大的复制粘贴体验,这比你在其他编辑器中习惯的操作系统剪贴板还要早。令人高兴的是,LazyVim 配置将 Neovim 剪贴板系统设置为自动与操作系统剪贴板协同工作。

实际上,你已经知道如何将文本剪切到系统剪贴板:只需要删除它。

没错。每当你使用 dc 动词时,你删除的文本会自动被剪切到剪贴板。这通常非常方便,但偶尔也会有点烦人,所以我将在本章后面展示一个避免保存已删除文本的变通方法。

8.1. 粘贴文本

粘贴(在 Vim 中通常称为“放置”put)文本使用我在第 1 章简要提到的 p 命令。在普通模式下,单个命令 p 会将系统剪贴板中的任何内容放置在当前光标位置。这通常是你最近删除的文本,但也可能是你从浏览器复制的 URL 或从电子邮件或其他任何系统剪贴板对象复制的文本。

译者注: p (put) 将寄存器内容粘贴到光标之后

插入文本的位置可能有点令人惊讶,但它通常能满足你的需求。通常,如果你删除了一些单词或不是整行的字符串,它会紧跟在当前光标位置之后。然而,如果你使用了操作整行的命令,例如 ddcc,文本将被放置在下一行。这在你移动代码行(代码编辑中的常见任务)时可以节省几次按键。

p 命令可以与计数一起使用,所以在你不太可能想要连续粘贴剪贴板中内容的 5 份副本的情况下,你可以使用 5p

当你用 p 粘贴时,你的光标会停留在原地,文本插入在光标之后。如果你想将文本粘贴到当前光标位置之前,请使用大写 P,其中 Shift 操作被解释为“反方向执行 p”。与 p 一样,除非是像 dd 这样的行级编辑,否则文本将直接插入到光标位置之前,如果是行级编辑,则会放置在前一行。

译者注: P (Put) 将寄存器内容粘贴到光标之前

如果你已经在插入模式并且需要粘贴一些东西并继续输入,你可以使用 Control-r 命令,后跟 + 键。r 可能很难记住,但它代表“register”(寄存器)。我们稍后会更详细地讨论寄存器到底是什么。

译者注: 在插入模式下,Ctrl-r <寄存器> 会将指定寄存器的内容插入到光标位置。+ 是代表系统剪贴板的寄存器。所以 Ctrl-r + 是从系统剪贴板粘贴。

8.2. 复制文本

复制文本需要一个新的动词:y。它的行为类似于 dc,只是它不修改缓冲区;它只是复制由 y 之后的任何动作或文本对象定义的文本。

译者注: y (yank) 复制文本到寄存器,祖宗之法不可变。

你可能会问:“为什么是 y?”它代表“yank”(提取),这是 Vim 术语中“复制”的意思。我不知道为什么 vi 称之为“yank”,但我猜这是一个逆向首字母缩写词。最初的作者可能注意到键盘上的 y 当时是空闲的,于是决定想出一个与之匹配的词。剪贴板或复制/粘贴的概念当时还没有标准化,所以他们可以自由使用任何对他们有效的术语。

y 命令可以与你已知的所有动作和文本对象一起工作。它与 rR 远程 Seek 命令结合使用尤其有用。如果你需要从编辑器的其他地方(甚至是不同的窗口)复制文本到你当前的光标位置,yR<搜索><标签>p 是最快的方式,而且不会给你的跳转历史增加不必要的跳转。

yyY 命令分别复制整行,以及从光标到行尾的内容,与它们在删除和更改文本时的对应命令类似。

译者注:

  • yy: 复制当前整行。
  • Y: 复制从光标到行尾的内容 (等同于 y$)。

LazyVim 会短暂高亮你 yanked 的文本,以清晰地指示你的动作命令是否复制了正确的文本。

8.3. 先选择文本

到目前为止,你的 Vim 编辑体验还没有涉及选择文本的概念。是不是很奇怪?我们已经讲到第 8 章了!在普通的文字处理器和类 VS Code 的文本编辑器中,你必须先选择文本,然后才能执行删除、复制、剪切或更改等操作。考虑到在那些编辑器中文本选择是多么笨拙(你必须使用鼠标或 shift 与光标移动的某种组合,再加上额外的修饰键来进行更大范围的移动),任何人能完成任何事情都令人惊讶!

在 Vim 的世界里,你通常先执行动词,然后跟上一个文本动作或对象来隐式地选择文本,然后再对其进行操作。这通常是最有效的操作方式,但在某些情况下,反转心智模型并在操作之前高亮文本会很方便。

这就是可视模式 (Visual mode) 发挥作用的地方。可视模式是 Vim 的一个主要模式,就像普通模式和插入模式一样。严格来说,可视模式有三种子模式。我们将从“可视字符模式”(Visual Character mode) 开始,稍后再深入了解另外两种。

你可能会认为先选择文本以便看到你正在操作的内容是有意义的。两个较新的编辑器 Kakoune 和 Helix 一直在试验这种范式。它们非常酷,但我发现“先选择文本”的模型有点令人失望。编辑器无法确定任何给定的动作是旨在移动选择区还是扩展选择区,所以你最终需要额外按一次键来告诉它进行扩展。到那时,这与在 Neovim 中按 v 进入可视模式没什么不同。在使用 Helix 几个月后,我确定它平均需要的按键次数实际上比 Neovim 多,于是我换回来了。

要进入可视字符模式,在普通模式下使用 v 命令。然后使用你习惯于在普通模式下使用的大多数动作来移动光标。我说“大多数”只是因为可视模式的键映射独立于普通模式的键映射,插件偶尔会忽略为两种模式都设置它们。不过,LazyVim 在键映射方面做得非常好,所以你很少会感到惊讶。

译者注: v 进入字符可视模式。

你也可以通过用鼠标点击并拖动来进入可视模式。

一旦你在可视模式下选中文本,你可以使用你用来删除、更改或复制选择内容的相同动词。区别在于它们会立即作用于选择区,而不需要一个动作。你甚至可以使用像 x(作用与 d 相同)这样的单字符动词,或者用 r 将所有字符替换为同一个字符。完成动词操作后,编辑器会自动切换回普通模式。你也可以使用 Escape 或再按一次 v 来退出可视模式而不执行任何操作。

你可以暂时退出可视模式而完全不丢失你的选择。在普通模式下,使用 gv(“go to last visual selection”,转到上次的可视选择区)命令返回到选择区。这在你即将执行一个可视操作却意识到需要查找某些内容、进行编辑或从文件其他地方复制某些内容,然后再返回选择区时很有用。

使用 o(代表“other end”,另一端)命令将光标移动到块的相反端。例如,如果你选择了一些单词,然后意识到在块的另一端漏掉了一个,这会很有用。你不能从可视模式进入插入模式,所以 o 命令被重用于此目的。

8.3.1. 行可视模式 (Line-wise Visual Mode)

v 命令对于细粒度的选择很有用,但如果你知道你的选择将在行的边界开始和结束,你可以改用(Shift 后的)V 进入行可视模式。现在无论你将光标移动到哪里,光标所在的那整行都将被选中。

译者注: V 进入行可视模式。

除了选择整行之外,行可视模式的主要区别在于,当你应用操作剪贴板的动词时(包括 dcy),这些行将以行模式被剪切或复制。当你稍后放置它们时,它们将出现在下一行或上一行,而不是紧跟在光标之后。

8.3.2. 块可视模式 (Block-wise Visual Mode)

块可视模式是 Vim 中一个有点独特而巧妙的功能。它允许你可视化地选择和操作一个垂直连续但水平不一定连续的文本块。例如,在下面的截图中,我在四个不同的行上分别选择了一些字符:

visual block dark

图 32. 块可视模式

要进入块可视模式,请使用 Control-v,而不是用于可视模式和行可视模式的 vV

译者注: Ctrl-v 进入块可视模式 (Visual Block Mode)。

在这样的纯文本中,块可视模式似乎不是很有用,但如果你需要剪切和粘贴例如 csv 文件或 markdown 表格中的表格数据列,它就非常方便。我不常使用这个功能,但当需要时,我知道没有其他方法能高效地执行我需要的操作。

如果你使用 Control-V$,你会得到块可视模式的一个轻微变体,其中块会延伸到块中每一行的末尾。如果你需要块延伸到最长的那一行而不是光标当前所在的那一行,这会很方便。

块可视模式也可以用作多光标的一种(蹩脚的)模仿。如果在选择一个可视块后使用 IA 命令,然后输入一些文本后跟 Escape,你输入的文本将被复制到可视块的开头或结尾。这个功能的一个常见操作是在 Markdown 有序列表的开头添加 * 字符,或者给需要边框的块注释添加边框。

译者注: 在块可视模式下:

  • I (Insert): 在选中块的每一行的开始处进入插入模式。输入文本后按 Esc,输入的文本会应用到每一行。
  • A (Append): 在选中块的每一行的结束处进入插入模式。输入文本后按 Esc,输入的文本会应用到每一行。

8.4. 寄存器 (Registers)

寄存器是一种存储文本字符串以供以后访问的方式(所以可以想想这个词的汇编语言定义)。在这方面,它们与剪贴板无异。事实上,Vim 中的系统剪贴板就是一个 LazyVim 设置为默认寄存器的寄存器。

但 Vim 还有几十个其他寄存器。这意味着你可以拥有自定义剪贴板,每个剪贴板都包含完全不同的文本序列。这个功能非常有用,例如,当你在重构某些东西并且需要在多个调用点粘贴几段不同代码的副本时。

寄存器有几种不同的类型,但我将首先介绍命名寄存器。有超过二十六个命名寄存器,对应字母表中的每个字母。

要从普通模式访问寄存器,请使用 " 字符(即 Shift-<单引号>)后跟你想要访问的寄存器的名称。然后发出你想要对该寄存器执行的动词和动作。

译者注: 寄存器的使用语法通常是 "<寄存器名><动词><动作>"<寄存器名><粘贴命令>

所以如果我想删除三个单词并将它们存储在 a 寄存器中而不是系统剪贴板中,我会使用命令 "ad3w"a 选择寄存器,d3w 是删除三个单词的普通命令。如果我稍后想把同样的文本放到别处,我会使用 "ap 而不是仅仅 p,这样文本就会从 a 寄存器而不是默认寄存器粘贴。

"ad<动作> 总是会用你选择的任何文本动作或对象替换 a 寄存器的内容。然而,你也可以使用寄存器的大写名称从多个删除命令中构建寄存器。所以 "Ad<动作> 会将你删除的文本追加到现有的 a 寄存器中。

译者注: 使用小写字母寄存器名 ("a, "b…) 会覆盖寄存器内容。使用大写字母寄存器名 ("A, "B…) 会将新内容追加到对应的小写字母寄存器内容的末尾。

我发现这很有用,当我要把几行代码从一个函数复制到另一个函数,但源函数中有一个我不需要在目标函数中的条件语句或其他东西时。使用 "ay 复制条件之前的部分,使用 "Ay 追加条件之后的部分,然后用 "ap 一次性粘贴整个内容。

我可以用例如 "byS<标签> 将完全不同的文本复制到 b 寄存器中。现在我可以随时使用 "ap"bpab 寄存器粘贴。

如果你忘记了把文本放在哪个寄存器里,只需按 " 并等待菜单弹出,显示所有寄存器的内容。如果那个菜单太难导航,你可以改用 <Space>s" 命令打开一个允许你搜索所有寄存器的选择器对话框。只需输入你知道在你想要粘贴的寄存器中的几个字符,使用选择器命令导航列表,然后按 Enter 将该文本粘贴到最后的光标位置。

要从插入模式或命令模式显示相同的菜单,请使用 Ctrl-r 而不是 "

如果你在 <Space>s" 选择器对话框中,你会注意到除了命名的字母寄存器外,还有一堆其他寄存器。接下来我将讨论其中的每一个。

8.4.1. 剪贴板寄存器

在 LazyVim 中,默认情况下,名为 *+ 的寄存器总是与默认(未命名)寄存器相同,并代表系统剪贴板的内容。

要理解为什么,我们需要一些历史:vi 有寄存器,然后操作系统对剪贴板的概念感到兴奋,vi 用户想要将东西复制到系统剪贴板。默认(非 lazy)的 Vim 配置意味着如果你想将文本复制到系统剪贴板,你必须总是在 y 前面输入 "+。在现代工作流中,你经常需要将内容复制到浏览器、AI 聊天客户端和电子邮件中,这三个额外的按键(Shift'+)会变得相当单调。

译者注:

  • " (双引号寄存器): 默认寄存器,存储最近一次删除或复制的内容。
  • + (加号寄存器): 通常映射到系统剪贴板 (可以复制粘贴到其他应用程序)。
  • * (星号寄存器): 通常映射到 X11 的 PRIMARY 选择区 (在 Linux/Unix 系统中,通常通过鼠标中键粘贴)。

最重要的是,一些操作系统(通常是基于 Unix 的)实际上有两个操作系统剪贴板,一个是你选择文本时的隐式剪贴板,另一个是你用 Control-c(在大多数程序中)显式复制文本时的剪贴板。这个文本会存储在 "* 寄存器中,操作系统让你用(通常是)鼠标中键在别处粘贴它。

我建议坚持使用 LazyVim 的同步剪贴板配置,但是如果你已经有了使用旧式 Vim 的肌肉记忆,或者你厌倦了删除的文本随意覆盖你的系统剪贴板,你可以禁用这个集成,这样这三个寄存器的行为就会如上所述,而不是相互链接。为此,使用 space f c 打开 options.lua 配置文件并添加以下行:

列表 22. 禁用剪贴板同步

-- lua/config/options.lua
vim.opt.clipboard = "" -- 设为空字符串会禁用与系统剪贴板的隐式同步

说到你的剪贴板内容被随机覆盖,如果你事先知道不希望某个特定的删除或更改操作覆盖剪贴板内容,请使用“黑洞”寄存器 "_(这是一个下划线)。所以输入 "_d<动作> 来删除文本而不将其存储在任何寄存器中,包括系统剪贴板。

如果你想将一个寄存器的内容复制到另一个寄存器,你可以使用 ex 命令 :let @a = @b,其中 ab 是你想要复制到和复制来源的寄存器的名称。这个操作最常见的用途是将系统剪贴板的内容(可能来自不同的程序)复制到一个命名寄存器中,这样下次你发出动词时它就不会丢失。例如,:let @b = @+ 会将系统剪贴板复制到寄存器 b 中。

8.4.2. 最近 Yanked 或最近插入的文本

每当你发出一个 y 命令而没有指定目标寄存器时,文本将总是存储在 "0 寄存器中,同时也存储在默认寄存器中。并且它会一直留在 "0 中直到下一次 yank 操作,无论你做了多少次删除或更改来改变默认寄存器。

译者注: 0 (零号寄存器): 存储最近一次 yank (复制) 的内容。

所以如果你 yank 了文本 abc,然后删除了文本 defp 命令将粘贴文本 def,但你仍然可以使用 "0p 粘贴 abc

你也可以使用 ".(句点)寄存器来粘贴最近插入的文本的副本。所以如果你在文档的某处输入命令 ifoo<Escape>,然后移动到文档的其他地方并输入 ".p,它将在新的光标位置插入单词“foo”。". 是一个你可能偶尔想要复制到命名寄存器中以便重用已插入文本的寄存器。使用前面讨论过的 :let @c = @. 命令来完成此操作。

译者注: . (点号寄存器): 存储最近一次插入的文本。

8.4.3. 删除(编号)寄存器

编号寄存器应该非常有用,但我发现它们相当混乱。寄存器 "1"9 总是按升序包含你最近更改或删除的文本。所以在一次删除操作后,"1 中的内容被移到 "2"2 移到 "3,依此类推,而 "9 中的内容被丢弃。

译者注: 编号寄存器 19 形成了删除历史的队列。1 是最近删除的内容(超过一行的或用 c 更改的),2 是次近的,以此类推。0 始终是最近复制的内容。

永远记不住我最近删除的顺序,所以我通常需要使用 " 菜单来查看编号寄存器的内容。我最近删除的文本被存储起来并且我能以这种方式找到它,这很方便。然而,我通常使用 yanky.nvim 插件(本章稍后讨论)来代替,所以编号寄存器对我来说不是那么有用。

还有一个“小删除寄存器”,可以用 "- 访问。每当你删除任何文本时,它将被存储在编号寄存器中,但如果该文本长度小于一行,它将被存储在这个减号寄存器中。我对这个功能没什么用,因为我的大部分更改都小于一行。这意味着它在掉出编号寄存器之前就被清除了。

译者注: - (减号寄存器): 存储最近一次小范围删除 (少于一行) 的内容。

8.4.4. 当前文件名

你当前正在编辑的文件的名称存储在 "% 寄存器中。它总是相对于编辑器的当前工作目录(通常是你启动 Neovim 时所在的文件夹)。我唯一想访问这个寄存器的时候是想用 :let @+ = @% 将文件名复制到系统剪贴板,这样我就可以将其粘贴到 GUI 应用程序或我的终端中。

译者注: % (百分号寄存器): 存储当前缓冲区的文件名

8.5. 录制到寄存器

还记得我在第 6 章告诉你的录制命令吗:qq 录制和 Q 回放?事实证明我当时有点过于简单化了。

录制的命令实际上存储在一个命名寄存器中。在那一章,当我说用 qq 开始录制时,我随意选择了 q 寄存器。但你完全可以同样轻松地使用 qa 将其存储在 a 寄存器中,或使用 qf 将其存储在 f 寄存器中。

“追加到录制”的 qQ 命令类似于用于追加到寄存器的大写 "A<command>。在这种情况下,Q 仍然是一个任意的名称。你可以使用例如 qAqZ 将录制追加到除 q 之外的其他命名寄存器。

当你进行复杂的重构,需要在代码库的不同位置进行几种不同重复性更改之一时,拥有多组录制内容可能非常方便。

回放录制的 Q 命令总是回放最近录制的命令,无论寄存器如何。(译者注:如前所述,Q 通常进入 Ex 模式,@q 或被 LazyVim 映射为 Q 的键才是回放 q 寄存器)。如果你想从不同的寄存器回放,你会使用 @ 命令,后跟寄存器的名称。所以如果你使用 qa 录制,你会用 @a 回放。作为快捷方式,@@ 总是重放你最近播放的那个寄存器(这与 Q 不同,Q 总是播放最近录制的那个)。

8.5.1. 编辑录制内容

需要明确的是,录制内容被放置在普通寄存器中。所以如果你用 qa 将一系列按键录制到一个寄存器,然后用 "ap 放置该寄存器,你实际上会看到你录制的 Vim 命令列表。

如果你在录制时搞砸了并且需要修改按键,这会很有用。录制按键后,用例如 "a]p 将其粘贴到新行。此时它只是一行普通的文本,恰好包含 vim 命令。你可以修改它以添加其他 Vim 命令,因为它们都只是普通的按键。

例如,假设我录制了一个命令为 qadw2wdeq,它录制到 a 寄存器 (qa),删除一个单词 (dw),向前跳过两个单词 (2w),然后删除下一个单词 (de),最后用 q 结束录制。但为时已晚,我意识到我应该跳过 3 个单词,而不是 2 个。

我可以用 "ap 粘贴录制的内容,它看起来像这样:dw2wde。然后我可以用 f2 跳转到数字 2,后跟 r3 将其替换为 3。现在我可以用 "ayiw 将寄存器的内容替换为 dw3wde。(译者注:用 dd 删除旧行,然后 "aY 复制新行回寄存器 a 可能更直观)。

现在如果我想回放那个修改过的命令,我可以像往常一样使用 @a

8.6. Yanky.nvim 插件

Yanky.nvim 有一些优点,例如改进 yank 时文本的高亮显示和保留你的光标位置,以便你可以在粘贴后继续输入,但它的主要特点是更好地管理你的剪贴板历史记录。LazyVim 还为其配置了几个新的快捷键,使放置文本更加愉快。

该插件默认未启用,但它是一个推荐的 extra,所以如果你在第 5 章遵循了我的建议安装了所有推荐 extras,你可能已经启用了它。如果没有,前往 :LazyExtras,找到 yanky.nvim 并按 x。然后重启 Neovim。

现在 Yanky 已启用,查看剪贴板历史的最简单界面可以通过 <Space>p 访问。它会弹出一个包含你所有最近剪贴板条目的选择器菜单。最多存储一百个条目,这比你在编号寄存器中得到的要多得多,而且它存储你的 yanks,不仅仅是你的删除和更改。如果你需要粘贴不再在剪贴板中的东西,<Space>p 可能是找到它的最快方法。

译者注: <Space>p 可能是 “Paste from history” 的快捷键。

另一个非常有用的快捷键是 [y。如果你在一次放置操作之后立即调用此命令,被放置的文本将被替换为最近一次 yank 之前剪切或复制的文本。如果你再次按下它,它将向历史记录后退一步,最多 100 步。所以如果你不确定某个删除操作具体在哪个编号寄存器中,或者你想访问被 yanked 但已不在 "0 寄存器中的文本,你可以使用 p[y[y[y… 直到找到你真正想要粘贴的文本。如果你退得太远,可以用 ]y 向前循环。

译者注: [y]y 用于在粘贴历史中向后/向前切换。

LazyVim 还创建了一些有用的快捷键来改进文本的放置方式,特别是在缩进方面。最有用的两个是 [p]p

这些命令将在当前行的上方或下方粘贴剪贴板中的文本,具体取决于你使用的是 [ 还是 ]。你可能认为这与上面描述的自动行模式粘贴相同,但它略有不同,原因有二:

  1. 无论用于剪切或复制剪贴板中文本的命令是什么,它都会在新行上粘贴。
  2. 它会自动调整新行上文本的缩进以匹配当前行的缩进。

所以如果你正在将代码移动到一个嵌套块中并且需要更改缩进,请使用 ]p 而不是依赖行模式粘贴。这样你就不必在之后格式化它了。(并不是说在 LazyVim 中格式化很难;它在保存时就会发生)。

你也可以使用 >p<p>P<P 在放置代码时自动添加或删除缩进。

译者注:

  • [p/]p: 在当前行上方/下方粘贴,并自动调整缩进。
  • >p/<p: 在当前行下方粘贴,并增加/减少一级缩进。
  • >P/<P: 在当前行上方粘贴,并增加/减少一级缩进。

8.7. 总结

本章全部是关于选择和复制文本。我们学习了用于复制文本的 yank 动词,然后深入了解了可用于选择文本的各种可视模式。

接着我们了解到 Vim 有多个称为寄存器的“剪贴板”,以及如何剪切和复制到或从那些寄存器粘贴。我们甚至更详细地介绍了使用寄存器录制多个独立的命令序列,然后讨论了 yanky.nvim 插件,让你的粘贴生活更轻松一点。

在下一章中,我们将学习查看和处理多个打开文件的各种方法,以及如何通过折叠来显示和隐藏代码。