8

I struggle to understand the solution to this vimgolf challenge.

The best proposed solution is

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

Then I tried to do

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

But then the text becomes

(one) (one)
(one)

instead of

(one) (two)
(three)

Can someone help me to understand why . behaves differently with those two commands?

autra
  • 895
  • 6
  • 21

2 Answers2

6

This is interesting. The distinguishing characteristic of CTRL-R CTRL-P (insert literally and fix indent) is not used; indent is not involved, and the literal insert also happens with CTRL-R CTRL-R (but that doesn't work, nor are any special chars like <BS> involved here).

Instead of CTRL-R CTRL-P, this also works with CTRL-R CTRL-O; the only commonality (in the help) is that both have:

Does not replace characters!

How this relates to repeat via ., I don't know (you'd have to dig through the source code or ask on the vim_dev mailing list). Basically, it causes the <C-R><C-O> command itself to go into the . register, instead of the literal text which was in the register when you gave the command. This means that when you repeat the operation with ., you'll get whatever's in the specified register at the time of the repeat, instead of just inserting the same literal text again.

This turns Vim from repeating the command as

replace the current word with (, followed by one, followed by )
to
replace the current word with (, insert the register contents of the default register, insert ).

I.e. the first repeat stores the result of the register insertion, whereas the second actually memorizes the action of the register insertion.

You'll see the effect when you list the ". register contents via :reg ., too.

Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • 1
    And actually the designer of this solution explained it there http://vimgolf.com/challenges/5192f96ad8df110002000002#51941eeed8df110002000056 I think you can include this explanation on your answer and I will choose it (cause it is good). – autra Dec 20 '13 at 10:13
  • That comment apparently is only visible for registered users: _Unlock 585 remaining solutions by signing in and submitting your own entry_ – Ingo Karkat Dec 20 '13 at 10:22
  • Indeed. Basically, @udioica says : "Basically, it causes the '' command itself to go into the '.' register, instead of the literal text which was in the register when you gave the command. This means that when you repeat the operation with '.', you'll get whatever's in the specified register at the time of the repeat, instead of just inserting the same literal text again. Try 'cw("):reg' and compare the value of the '.' register to that from 'cw("):reg' For the official documentation, check ':help i_Ctrl-R_Ctrl-O'" EDIT: then the doc makes sense. – autra Dec 20 '13 at 11:12
  • Thank you for providing that information; I've edited it in, although it also doesn't provide the actual reason (just describes it more detailed than what my answer had). – Ingo Karkat Dec 20 '13 at 11:21
  • Yes, but it is good enough to make people understand I think. Thanks a lot! – autra Dec 20 '13 at 13:03
  • 1
    @IngoKarkat: I took up your challenge and dug through the source, found some interesting stuff and added an answer if you're curious! – mgiuffrida Feb 20 '15 at 09:17
  • @mgiuffrida: Great writeup; thanks! I wish I could upvote this more :-) – Ingo Karkat Feb 20 '15 at 10:12
5

Since the accepted answer addresses the difference between CTRL-R CTRL-R and CTRL-R CTRL-O but doesn't answer the question as to why . behaves as it does, and the documentation is pretty unhelpful, I dug through the source a bit.

Control characters

When the editor encounters a control character, like CTRL-H, instead of inserting that character it executes the command (in this case, backspace). So in insert mode,

Hello<CTRL-H>World

yields

HellWorld

However, if the control character is preceded by CTRL-V, the literal control character is inserted as text. Thus,

Hello<CTRL-V><CTRL-H>World

gives us

Hello^HWorld

where ^H is really just a representation of the character you get with CTRL-H.

Controlling the characters

So if you wanted to insert text from a register literally, how could you do it using only CTRL-R? Well, CTRL-R inserts text as if it were typed. But that means we can just use CTRL-V to trick Vim into simulating CTRL-R CTRL-R! Let's say the text we want to insert is on line 1:

1| Hello^HWorld

If we just put that in insert mode, we'll have a bad time:

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

Instead, let's first insert ^V:

:s/<CTRL-V><CTRL-H>/<CTRL-V><CTRL-V>&
"ay$o
<CTRL-R>a  -->  2| Hello^HWorld

This gives us Hello^HWorld, just like we wanted.

Literal insertion

Now you basically know how CTRL-R CTRL-R works -- it inserts ^V before each character, ensuring that every character is inserted literally instead of as if it were typed.

Insert mode reads characters with vgetorpeek. This has three parts, which we can simplify into two:

  1. Try to get a character from the stuffbuffer.
  2. If the stuffbuffer is empty, get a character from the user.

When you CTRL-R or CTRL-R CTRL-R, this calls insert_reg. That function inserts the contents of the register into the stuffbuffer, so Vim will read it in as if it's being typed.

In the case of CTRL-R CTRL-R , we insert CTRL-V into the stuffbuffer before every special character (aside from Tab**). So really, this is no different from using our regular expression and CTRL-R alone -- just way more convenient.

CTRL-R CTRL-O and the . command

Moral of the story, when you use CTRL-R [CTRL-R], the . command just sees a stream of text you typed.

But the story is completely different for CTRL-R CTRL-O and CTRL-R CTRL-P, which were apparently added when Bram fixed a bug where pasting with the mouse wasn't repeated properly by .. Instead of using insert_reg, we call do_put, which directly puts the content of the register into the text, do not pass Go, do not interpret key sequences.

One consequence (what Bram was going for) was that Tabs, which even with CTRL-R CTRL-R weren't inserted literally, get inserted literally with CTRL-R CTRL-O. Try it at home with visible whitespace. I think this is what the cryptic "Does not replace characters!" line in :help means.

But the more interesting side effect is that the value of the register doesn't show up in the redo buffer -- like we never typed it in. Instead, Bram manually calls AppendCharToRedobuff so that . will repeat using 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);

Honestly, this seems like a lazy solution, maybe even a bug. But as a result, you can use CTRL-R CTRL-O to pull in registers and grab the contents of the register each time you repeat.

This is why you're seeing different behavior:

1| one two
2| three

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

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

When this is repeated, cw updates the " register with "two", then "three", and <C-R><C-P>" comes along and re-reads the " register.

If we just use <C-R>" (or <C-R><C-R>", it makes no difference here), repeating the action just repeats the insertion of "one".

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

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

You can see the difference in the . register by using :reg . after each version, although I don't know exactly how the . register corresponds to the . action.


** This explains something else that's weird. If you type <CTRL-V><Tab> in insert mode, you'll get a tab even if you have :set expandtab, because <CTRL-V> forces the literal Tab character.

However, if you yank Something<CTRL-V><Tab>Something into a register and then try to insert it with <CTRL-R><CTRL-R>, the tab expands to spaces, just like if you had only used <CTRL-R>. This is explained by the stuffescaped function which, for whatever reason, doesn't insert <CTRL-V> before a tab.

Since <CTRL-R><CTRL-O> doesn't need <CTRL-V> (and in fact will insert those as literal ^V characters!) the tabs stay intact -- or as Bram says,

Does not replace characters!

Community
  • 1
  • 1
mgiuffrida
  • 3,299
  • 1
  • 26
  • 27
  • 1
    Fascinating answer! Got here from https://stackoverflow.com/questions/2147875/what-vim-commands-can-be-used-to-quote-unquote-words, and was wondering why "Insert contents literally" was purportedly broken. – Ambareesh Apr 19 '20 at 02:29
  • Thanks! I enjoyed digging into this but was kind of bummed nobody saw this since the original question didn't get many upvotes. – mgiuffrida Sep 08 '20 at 16:21