在vim中,<c-r>+寄存器和<c-r><c-p>+寄存器与.命令有什么区别?

8
我很难理解这个 vimgolf挑战 的解决方案。
最佳的解决方案是:
cw(<C-R><C-P>")<Esc>w.w.ZZ

接着我尝试去做...

cw(<C-R>")<Esc>w.w.ZZ

但是随后的文本变成了:

(one) (one)
(one)

代替
(one) (two)
(three)

有人能帮我理解为什么在这两个命令中,"." 的行为不同吗?

我猜你没有阅读过 :h i_ctrl-r_ctrl-p - romainl
我做了。你有好好读我的问题吗?:-) 实际上Ingo Karkat在回答这个问题时做得很好。 - autra
2个回答

6
这很有趣。CTRL-R CTRL-P(字面插入和修复缩进)的区别特点没有被使用;没有涉及缩进,而且字面插入也可以用CTRL-R CTRL-R实现(但那不起作用,也没有任何特殊字符像<BS>在这里涉及)。
与CTRL-R CTRL-P不同,CTRL-R CTRL-O也可以实现此功能;唯一的共同点(在帮助文档中)是两者都具有以下特点:

不替换字符!

关于如何通过“.”重复操作,我不知道(您需要查看源代码或询问vim_dev邮件列表)。基本上,它使<C-R><C-O>命令本身进入“.”寄存器,而不是在给出命令时寄存器中的文字。这意味着当您使用“.”重复操作时,您将获得指定寄存器中的任何内容,而不仅仅是再次插入相同的文字。
这将使Vim从重复命令变成:
将当前单词替换为 (,后跟 one,后跟 )
变为
将当前单词替换为 (,插入默认寄存器的寄存器内容,插入 )
即第一次重复存储寄存器插入的 结果,而第二次则实际记忆了寄存器插入的 操作
您还可以通过 :reg . 列出 ". 寄存器内容来查看效果。

1
实际上,这个解决方案的设计者在这里解释了它:http://vimgolf.com/challenges/5192f96ad8df110002000002#51941eeed8df110002000056 我认为你可以在你的答案中包含这个解释,我会选择它(因为它很好)。 - autra
该评论显然只对已注册用户可见:通过登录并提交您自己的条目,解锁剩余的585个解决方案。 - Ingo Karkat
确实。@udioica的意思是:“基本上,它会导致'<C-R><C-O>'命令本身进入'.'寄存器,而不是在给出命令时寄存器中的文字。这意味着当您使用'.'重复操作时,您将获得指定寄存器中的任何内容,而不仅仅是再次插入相同的文字。尝试'cw(<C-R>")<Esc>:reg<CR>'并将'.'寄存器的值与'cw(<C-R><C-O>")<Esc>:reg<CR>'进行比较。有关官方文档,请查看':help i_Ctrl-R_Ctrl-O<CR>'"编辑:然后文档就有意义了。 - autra
谢谢您提供这些信息,我已经将它们编辑进去了,虽然它并没有提供实际的原因(只是比我的答案更详细地描述了一下)。 - Ingo Karkat
1
@IngoKarkat:我接受了你的挑战,深入研究了源代码,发现了一些有趣的东西,并添加了一个答案,如果你感兴趣的话! - mgiuffrida
显示剩余2条评论

5

由于被接受的答案解释了CTRL-R CTRL-RCTRL-R CTRL-O之间的区别,但没有回答为什么.表现出它的特点,而且文档也不是很有帮助,我在源代码中进行了一些挖掘。

控制字符

当编辑器遇到控制字符时,比如 CTRL-H,它会执行该命令(在这种情况下是退格),而不是插入该字符。所以在插入模式下,

Hello<CTRL-H>World

产量
HellWorld

然而,如果控制字符前面有CTRL-V,那么字面上的控制字符将作为文本插入。因此,
Hello<CTRL-V><CTRL-H>World

给我们
Hello^HWorld

其中 ^H 实际上只是使用 CTRL-H 键得到的字符的表示。

控制字符

如果你想要插入一个寄存器中的文本,该怎么办呢?只用 CTRL-R 行不行?实际上,CTRL-R 插入文本就好像你在键盘上输入一样。因此,我们可以使用 CTRL-V 来欺骗 Vim 模拟 CTRL-R CTRL-R!假设我们要插入的文本在第一行:

1| Hello^HWorld

如果我们仅仅将其放在插入模式下,那么我们将会遇到麻烦:

"ay$o
<CTRL-R>a  -->  2| HellWorld

相反,让我们先插入^V:
:s/<CTRL-V><CTRL-H>/<CTRL-V><CTRL-V>&
"ay$o
<CTRL-R>a  -->  2| Hello^HWorld

这样我们就可以得到 Hello^HWorld,就像我们想要的一样。

直接插入

现在你基本上知道了 CTRL-R CTRL-R 是如何工作的——它在每个字符之前插入 ^V,确保每个字符都是按字面意义插入而不是仿佛已经键入。

插入模式使用 vgetorpeek 读取字符。这有三个部分,我们可以简化为两个:

  1. 尝试从 stuffbuffer 中获取字符。
  2. 如果 stuffbuffer 为空,则从用户处获取字符。

当你使用 CTRL-RCTRL-R CTRL-R 时,它将调用 insert_reg。该函数将注册内容插入到 stuffbuffer 中,因此 Vim 将以 仿佛正在输入 的方式读取它。

CTRL-R CTRL-R 的情况下,我们会在每个特殊字符(除了 Tab**)之前插入 CTRL-V 到 stuffbuffer 中。因此,这与仅使用正则表达式和 CTRL-R 没有什么不同——只是更加方便。

CTRL-R CTRL-O 和 . 命令

故事的道德是,当你使用 CTRL-R [CTRL-R] 时,. 命令只会看到你键入的一串文本。

但对于 CTRL-R CTRL-OCTRL-R CTRL-P,情况完全不同。这些功能显然是在 Bram 修复鼠标粘贴无法通过 . 正确重复的错误时添加的。我们不使用 insert_reg,而是调用 do_put,它直接将注册内容放入文本中,不需要经过任何解释按键序列的步骤。

一个后果(Bram的意图)是,即使使用CTRL-R CTRL-R,Tabs也不会被插入字面上,但使用CTRL-R CTRL-O会被插入字面上。在家里试试可见空格。我认为这就是:help中神秘的“不替换字符!”一行的含义。
但更有趣的副作用是,寄存器的值不会出现在重做缓冲区中——就像我们从未输入过一样。相反,Bram手动调用AppendCharToRedobuff,以便.将使用CTRL-R CTRL-O重复!
/* Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r */
AppendCharToRedobuff(Ctrl('R'));
AppendCharToRedobuff(fix_indent ? Ctrl('P') : Ctrl('O'));
AppendCharToRedobuff(regname == 0 ? '"' : regname);

老实说,这似乎是一种懒惰的解决方案,甚至可能是一个bug。但结果是,你可以使用CTRL-R CTRL-O来提取寄存器,并且每次重复时抓取寄存器的内容。。

这就是为什么你会看到不同的行为:

1| one two
2| three

cw(<C-R><C-P>")<Esc>w.w.

1| (one) (two)
2| (three)

当重复执行这个命令时,cw 会将 "two" 和 "three" 分别更新到 " 寄存器中,然后 <C-R><C-P> 命令会重新读取 " 寄存器。如果我们只使用 <C-R>"(或者 <C-R><C-R>",在这里没有区别),重复执行的操作只会再次插入 "one"。
cw(<C-R>")<Esc>w.w.ZZ

1| (one) (one)
2| (one)

您可以通过使用:reg .在每个版本后查看.寄存器中的差异,尽管我不知道.寄存器如何对应.操作。
**这解释了另一件奇怪的事情。如果您在插入模式下键入<CTRL-V><Tab>,即使您设置了:set expandtab,您也会得到一个制表符,因为<CTRL-V>强制使用字面上的Tab字符。
但是,如果您将Something<CTRL-V><Tab>Something复制到寄存器中,然后尝试使用<CTRL-R><CTRL-R>进行插入,则选项卡会扩展为空格,就像只使用<CTRL-R>一样。这是由stuffescaped函数解释的,出于某种原因,在制表符之前不插入<CTRL-V>
由于<CTRL-R><CTRL-O>不需要<CTRL-V>(实际上会将其作为文字^V字符插入!),因此制表符保持不变 - 或者像Bram所说的那样,

不替换字符!


1
非常有趣的答案!从 https://dev59.com/N3I95IYBdhLWcg3wzRTv 进来,想知道为什么“字面插入内容”被称为是有问题的。 - Ambareesh
谢谢!我很喜欢深入研究这个问题,但有点失望,因为原始问题没有得到很多赞。 - mgiuffrida

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接