4

I'm trying to implement a custom operator following :h :map-operator. The operator works just fine but has the side effect of moving the cursor position!

For example the code defining the <F4> operator which counts spaced used in :h :map-operator (also copied below). If the cursor is in the middle of a paragraph, typing <F4>ip correctly prints out the number of spaces in the paragraph, but moves the cursor to the top of the paragraph too! This is also the case for charter wise i.e. <F4>iw or visuals selection and pressing <F4>.

This behaviour seems also to be the case in existing operators such as for yank y. Within a paragraph, typing yip changes the cursor position to the top of the paragraph as well.

My question: Can this behaviour be changed globally for all operators? I feel this is a rather annoying behaviour and can't quite believe that this would have to be worked around for each operator as in the yank thread above.

Failing that, how could the code for the <F4> operator below adapter to keep the cursor position? I've already played with let save_pos = getpos(".") and call setpos('.', save_pos) without success.

nmap <silent> <F4> :set opfunc=CountSpaces<CR>g@
vmap <silent> <F4> :<C-U>call CountSpaces(visualmode(), 1)<CR>

function! CountSpaces(type, ...)
  let sel_save = &selection
  let &selection = "inclusive"
  let reg_save = @@

  if a:0  " Invoked from Visual mode, use gv command.
    silent exe "normal! gvy"
  elseif a:type == 'line'
    silent exe "normal! '[V']y"
  else
    silent exe "normal! `[v`]y"
  endif

  echomsg strlen(substitute(@@, '[^ ]', '', 'g'))

  let &selection = sel_save
  let @@ = reg_save
endfunction
1

3 Answers 3

4

There's no way to change this behavior for all operators (short of mapping them all.)

But you can make your <F4> keep the cursor position (and window view) by saving and restoring it before and after it's used.

Note that since <F4> ends in an operator-pending g@ command, saving and restoring need to happen in different contexts, you should save the view at the start of the :nmap <F4>, while only restore it at the end of your CountSpaces opfunc. Since the context needs to be preserved across this call, you'll need to store it in a global (or script-global, or buffer) variable.

See this answer, more specifically the part about question 4, for this particular technique.

In your specific case:

nnoremap <silent> <F4> :let g:restore_position=winsaveview()<Bar>set opfunc=CountSpaces<CR>g@
vnoremap <silent> <F4> :<C-U>let g:restore_position=winsaveview()<Bar>call CountSpaces(visualmode(), 1)<CR> 

function! CountSpaces(type, ...)
    ...
    call winrestview(g:restore_position)
endfunction

You might want to take a look at this answer I previously mentioned in full. It also covers preserving the cursor and view across a specific text object itself. I think you might find that part quite interesting too!

4
  • 1
    Exactly what I wanted to know! Although not explicitly part of the question, I was wondering if this can be somehow easily done the same way for the yank operator? Commented May 24, 2020 at 11:53
  • @SebastianMüller Yes, you can probably write a mapping for y that does the same. But note that there are quite a few corner cases to consider, such as taking a count, taking a register, yy is special, etc. It should be doable, but it might be quite tricky to do it right and cover all corner cases.
    – filbranden
    Commented May 24, 2020 at 11:55
  • 1
    I suspected something like this, but good to have confirmation. Also just realized you linked answer is a good starting point for this. This might be something to suggest as an issue/feature, possibly for neovim! Thanks! Commented May 24, 2020 at 11:59
  • Update: I've subbmitted an issue to neovim on this, so this might be addressed at some point. Commented Sep 29, 2020 at 10:09
1

I would suggest you this solution

pure vim solution:

augroup yank_restore_cursor
    autocmd!
    autocmd VimEnter,CursorMoved *
        \ let s:cursor = getpos('.')
    autocmd TextYankPost *
        \ if v:event.operator ==? 'y' |
            \ call setpos('.', s:cursor) |
        \ endif
augroup END

If you want to see the neovim solution visit the link above.

-2

This is known to be the normal behaviour. Don't try to be more Catholic than the Pope.

And, by the way, the code is miserably broken in Visual mode, as it forces "inclusive" yanking of the last character. I know that it's taken from :help, but it's not the reason to copy-paste it blindly.

1
  • 2
    How is this helpful to answer the question? I didn't claim it wasn't the normal behaviour, which is exactly why I asked the question since I want to change this normal behaviour. Commented May 24, 2020 at 11:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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