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:
- Try to get a character from the stuffbuffer.
- 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!