4

I want to create a function like this:

function Foo()
    copy something from buffer into a register
    do something
    restore register
endfunction

Foo must have no side effect, every register should remain the same after you call it, how should i do it?

Attempt

unnamed register

function Foo()
    let [regText, regType] = [getreg('"'), getregtype('"')]
    norm! y{motion}
    do something
    call setreg('"', regText, regType)
endfunction

side effect:

  • register 0 was changed
  • if 'clipboard' includes unnamed or unnamedplus, regster * or + was also changed.
  • if " point to other register before calling Foo, it stopped pointing to it after the calling.

named register

function Foo()
    let [regText, regType] = [getreg('a'), getregtype('a')]
    norm! "ay{motion}
    do something
    call setreg('a', regText, regType)
endfunction

side effect:

  • " now point to a.

explicit unnamed register

function Foo()
    let [regText, regType] = [getreg('"'), getregtype('"')]
    norm! ""y{motion}
    do something
    call setreg('"', regText, regType)
endfunction

side effect:

  • if " point to other register before calling Foo, it stopped pointing to it after the calling.
  • update register 0 was changed.
  • Why do you want to both copy a value in a register and restore the register to the original value in the same function? Would copying the in a variable work instead? – padawin May 17 '19 at 14:29
  • It's convenient to copy something from buffer into a register first. – dedowsdi May 17 '19 at 14:36
  • ok, and the issue here is that "ay{motion} sets in the " (unnamed register) as well as a? – padawin May 17 '19 at 14:38
  • No, it set " point to a, check :h quotequote. – dedowsdi May 17 '19 at 23:19
2

If your Vim contains the patch 8.2.0924, then try to use getreginfo():

function Foo()
    let save_cb = &cb
    let regInfo = getreginfo('"')
    try
        norm! y{motion}
        do something
    finally
        let &cb = save_cb
        call setreg('"', regInfo)
    endtry
endfunction

The latter function returns a dictionary providing all the info you need to save and restore the unnamed register:

  • its contents as a list (which allows you to not lose NULs) via the regcontents key
  • its type via the regtype key
  • the name of the register it points to via the points_to key

getreginfo() is the only way to restore the register to which the unnamed register pointed to originally.

You can test it like this:

vim -es -Nu NONE -i NONE -S <(cat <<'EOF'
    call setline(1, ['yanked text', 'deleted text', 'line deleted by mapping'])
    norm! yyjdd
    pu=['registers before mapping'] + [execute('reg \"01')]

    nno cd :call Func()<cr>
    fu Func()
        let save = getreginfo('"')
        norm! 2Gdd
        call setreg('"', save)
    endfu

    au VimEnter * call feedkeys('cd', 'xt')
        \ | $pu=['', 'registers after mapping'] + [execute('reg \"01')]
        \ | exe 'g/^registers/t.|norm! Vr='
        \ | 2,$p | qa!
EOF
)

registers before mapping
========================

Type Name Content
  l  ""   deleted text^J
  l  "0   yanked text^J
  l  "1   deleted text^J

registers after mapping
=======================

Type Name Content
  l  ""   deleted text^J
  l  "0   yanked text^J
  l  "1   deleted text^J

Note that if your Vim doesn't support getreginfo(), you should ask getreg() to give you the contents of a register as a list instead of a string, by passing it a third non-zero argument:

let [regText, regType] = [getreg('"'), getregtype('"')]
                          ^---------^
                               ✘

let [regText, regType] = [getreg('"', 1, 1), getregtype('"')]
                          ^---------------^
                                  ✔

That's the only way to correctly handle possible NULs. See :h getreg():

If {list} is present and |TRUE|, the result type is changed to |List|. Each list item is one text line. Use it if you care about zero bytes possibly present inside register: without third argument both NLs and zero bytes are represented as NLs (see |NL-used-for-Nul|).


I don't think you need to temporarily remove the y flag from 'cpo'. The dot command keeps its original behavior after your function is finished:

set cpo+=y
norm! dd
fu Func()
    norm! yy
endfu
call Func()
norm! .
" dot keeps removing the current line

See :h function-search-undo.

As for the dot register, it contains the last inserted text (see :h quote_.). But a yank does not insert any text, so it can't alter the register, regardless of whether y is in 'cpo'. And even if it did, it would probably not matter.

| improve this answer | |
3

You've identified most of the issues - I think the best one can do is using the unnamed register, saving its contents and type, and temporarily resetting 'clipboard' and :help cpo-y for yanks. I don't think it's possible to avoid the aliasing of register 0, but as these numbered registers are ephemeral and therefore rarely used, it's not so bad.

Here's the implementation of ingo#register#KeepRegisterExecuteOrFunc() from my ingo-library plugin.

function! ingo#register#KeepRegisterExecuteOrFunc( Action, ... )
"******************************************************************************
"* PURPOSE:
"   Commands in the executed a:Action do not modify the default register.
"* ASSUMPTIONS / PRECONDITIONS:
"   None.
"* EFFECTS / POSTCONDITIONS:
"   None.
"* INPUTS:
"   a:Action    Either a Funcref or Ex commands to be :executed.
"   a:arguments Value(s) to be passed to the a:Action Funcref (but not the
"       Ex commands).
"* RETURN VALUES:
"   Result of evaluating a:Action, for Ex commands you need to use :return.
"******************************************************************************
    let l:save_clipboard = &clipboard
    set clipboard= " Avoid clobbering the selection and clipboard registers.
    if stridx(&cpoptions, 'y') != -1
        let l:save_cpoptions = &cpoptions
        set cpoptions-=y
    endif
    let l:save_reg = getreg('"')
    let l:save_regmode = getregtype('"')
    try
        return call('ingo#actions#ExecuteOrFunc', [a:Action] + a:000)
    finally
        call setreg('"', l:save_reg, l:save_regmode)
        if exists('l:save_cpoptions')
            let &cpoptions = l:save_cpoptions
        endif
        let &clipboard = l:save_clipboard
    endtry
endfunction
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.