Vim will move the cursor to the beginning of an object after invoking operator upon it. From an interactive editing perspective this may be considered annoying however it is the consistent choice as operators can be destructive. As such restoring the cursor to its prior position after invoking an operator on an object may not make sense.
There are many ways possible to alter this behaviour to your preference with mappings and/or scripting. But with custom operator mappings this can be particularly ugly.
A common method is to drop a mark or save a view in the mapping. However the
mapping must end with g@
so Vim will wait for the operator, meaning the
mapping itself can't move the cursor movement back to its original location.
As such the movement would have to be placed inside the function that
operatorfunc
has been set to. I consider this quite ugly as functions should
not have to have such a priori knowledge about the mappings that are calling
them. Note that dropping a mark or saving a view inside the operatorfunc
function is not an option as the cursor has already been moved by the time this
is invoked.
A neat solution to this is to save a view whenever operatorfunc
is set
with an autocmd
. Then whenever the cursor is moved check if
operatorfunc
is set, if so restore the view, dispose of the temporary
variable containing said view, and unset operatorfunc
with autocmd
's
disabled as to avoid a loop.
To avoid having an autocmd
always fire when the cursor is moved we can
instantiate it only when operatorfunc
is set. Now that this
instantiation occurs whenever operatorfunc
is set we can make two
further refinements. Firstly we can stop checking if operatorfunc
is
set and secondly the autocmd
that restores the view can be one shot.
function! OpfuncSteady() abort
let w:opfuncview = winsaveview()
autocmd OpfuncSteady CursorMoved,TextYankPost *
\ call winrestview(w:opfuncview)
\ | unlet w:opfuncview
\ | noautocmd set operatorfunc=
\ | autocmd! OpfuncSteady CursorMoved,TextYankPost *
endfunction
augroup OpfuncSteady
autocmd!
autocmd OptionSet operatorfunc call OpfuncSteady()
augroup END
One could even create re-implementations of Vim's built in operators to leverage this technique, though I'll leave that as an exercise for the reader.
Hi Martin,
I've been meaning to factor this out into a proper plugin for some time but just haven't got around to it. This doesn't work with
.
as-is and it's not something I've looked into, though I'm sure it could be done with the help of something like repeat.vim.I've not implemented in my own config as this is potentially what I describe as "destructive" in the post above. That is after the operation is performed the line and column the cursor was initially on may no longer reside within the object that was operated upon. For example if you are near the end of a paragraph and
gqip
ends up reducing the number of lines a paragraph takes up you'd be restoring you cursor to a position no longer inside that paragraph. This is precisely why Vim's default behavior, whilst not the most elegant at first glance, makes sense.That said assuming you're not relying on an external
'formatprg'
Vim'sgw
has you covered. It does not suffer from what I have just described as it is more sophisticated in that it truly "puts the cursor back at the same position in the text" as described in the help.The same could be implemented for this but that probably outgrows the scope of a gist and would be something for a proper plugin.
Cheers,
George