When writing functions to use in mappings and user-defined commands (for plug-ins), it's often convenient to use a Normal-mode command to yank or delete buffer contents. In those cases, it's also nice to save and restore register contents across the call, so that pasting from the affected registers works across the call. (In other words, using the register is an implementation detail and the user will be able to use
p commands to paste register contents yanked/deleted before the function was executed.)
Some caveats of saving/restoring registers are quite widely known (for instance, it's important to store the register type with
getregtype() and pass it back as the optional third argument to
setreg() to preserve, for example, visual block yanks.)
But there are still some subtle issues with saving and restoring registers.
Take, for example, this simple function and mapping:
function! VisualContents() abort let savereg = getreg('"') let savetype = getregtype('"') normal! gvy let result = @" call setreg('"', savereg, savetype) return result endfunction xnoremap <Leader>e :<C-u>echo '['.VisualContents().']'<CR>
Generally, this does a good job of saving and restoring the register's contents across the call. If you yanked something with
y in visual mode, or deleted something with a
dd or similar, after this function call you'll be able to put it with
p as if nothing affected the default register across this call.
However, if you yanked into a register other than
"0 (the default register for yanks), this call will overwrite the contents of
"0 with the register that was last used. For example, yank into register
"z, with a command such as
"zy from visual mode, then invoke the
<Leader>e mapping. Register
"z will be intact across the call, but register
"0 will be overwritten with the original contents of
"z. The same is true if the operation preceding the
<Leader>e call was a delete, in which case
"0 will be overwritten with the contents of
Note that the contents of the unnamed register will be unaffected (in a way), putting from the unnamed register should work fine, only putting from
"0 explicitly will be broken.
One idea would be to save and restore register
"0 across the call, but that will not really work. The problem is really that the unnamed register is a "pointer" and it's changing from whatever it was pointing earlier (such as
"z) to the default yank register (
"0), so restoring
"0 will actually change what the contents of the unnamed register seems to be, which is definitely bad.
Another option would be to use a register other than the default
"0 in the operation. For example, always use register
normal! gv"ay and saving/restoring register
"a. But that has a similar problem, in that the unnamed register ends up "pointing" to register
"a, which might (or will probably) differ from what it was pointing before the call, so putting from the unnamed register will not put the same contents as before the call, which is, again, not desirable.
One possibility would be to check which register the unnamed register points to (which is actually possible using
getreginfo()) and then using that same register for the operation. But that seems to be quite heavy handed and complex, since it involves assembling the
normal! command using an
execute to use the custom register. It also involves at least a few awkward situations (such as potentially using the short-contents register
"- for multiple lines, that seems to work, but is it the right thing to do?) or potentially invalid (is it possible that the unnamed register will be pointing to a read-only register? what about the expression register?)
What would be the most reliable way to save/restore registers across a call such as this one?
Is there a way to change what the unnamed register is "pointing" to without having to execute a yank/delete operation? (That would possibly help solve this issue, by restoring what the unnamed register points to after the operation.)