3

I've done extensive reading including :h omap-info and Vim the hardway operator pending mode, this part is espcially good:

A good way to keep the multiple ways of creating operator-pending mappings straight is to remember the following two rules:

  1. If your operator-pending mapping ends with some text visually selected, Vim will operate on that text.

  2. Otherwise, Vim will operate on the text between the original cursor position and the new position.

I feel have been fighting point 2 above for days, so I have lot's of questions, if you could answer even one that would be awesome:

Question 1

An omap consists of movement commands, or creating a visual range e.g. To operate on the whole buffer:

function MyTest_E()
    normal! ggVG
endfun
onoremap E :<C-u> call MyTest_E()<Cr>

The above works, but what if I wish to perform some function before or after. This does not restore the position (which is my purpose for trying to figure this out):

function MyTest_F()
    let restore_position = winsaveview()
    normal! ggVG
    call winrestview(restore_position)
endfun
onoremap F :<C-u> call MyTest_F()<Cr>

Question 2

How does one cancel an operator pending command, so one can "hi-jack" it, e.g.: If one calls the below function with dA, it should end up YANKING the whole buffer, but I can't force the existing operator pending command to cancel, but always ends up executing the orginal function (i.e. d in this case) after ggVGy.

function MyTest_H()
    normal \<Esc>
    call feedkeys("\e")
    call feedkeys("\<Esc>")
    call feedkeys("\<Esc>", "ix")
    call feedkeys("\<Esc>", "tx")
    call feedkeys("\<Esc>", "itx")
    let restore_position = winsaveview()
    normal! ggVGy
    call winrestview(restore_position)
endfun
onoremap H :<C-u> call MyTest_H()<Cr>

I also tried to change opfunc to nothing (Dummy() below) so that when it exits the motion handler, it does not perform the orginal opfunc, but it didnt work, the orginal opfunc is still run:

function! Dummy()
    echom "Psyc!"
endfun
function! MyTest_J(op)
    echom "J orig_op: ".a:op
    set opfunc=Dummy
    normal ggVG
endfun
onoremap J :call MyTest_J(v:operator)<Cr>

What the flip does it take to abort an operator in motion, and run one's own function instead??

Question 3

Can one simply return a range within an omap e.g. :1,$ to mean all lines. The best I could come up with is as follows, but I feel there must be a much simpler way:

function MyTest_H()
    let restore_position = winsaveview()
    let startpos = [0,1,0,'off']
    let endpos   = [0,line("$"),0,'off']
    call setpos("'<", startpos)
    call setpos("'>", endpos)
    normal gv
    call winrestview(restore_position)
endfun
onoremap H :<C-u> call MyTest_H()<Cr>

Question 4

Maybe I am on the wrong track, and instead of trying to restore the position in the motion, I should be doing it after the operator. I am not a fan of this, because this means one would have to create a custom mapping for every operator. Is it possible to change the y command to map to something like the code below? (I have tried with g@ but get errors about it being too recursive):

:call SavePosition()|y{motion}|:call RestorePosition()

Note: Using NeoVim if that makes any difference.

  • That's odd, for question 1, the MyTest_F function restores the cursor position for me... it deletes everything above the cursor, which is the expected behavior – Zorzi Apr 24 at 17:13
  • And for question 2, taking out all of the feedkey and the 1st normal does the trick for me (which leaves you with a 3 lines function) – Zorzi Apr 24 at 17:58
4

Interesting question! 👍

Reading your attempts of code made me think of the order in which the operations are actually executed, which is:

  1. The omap motion or text object is executed and selected.
  2. The operator command (y, d, g~, etc.) is executed on that block of text.

It seems to me that some of your assumptions have the model backwards and assume that the operator command is somehow already "pending" when the omap function is executed.

With that in mind, let's address your more specific questions one-by-one:

Question 1

But what if I wish to perform some function before or after? This does not restore the position (which is my purpose for trying to figure this out.)

If you think of the order, you'll see that the omap function will run to completion first and then the operator will execute. To have something execute after the operator you need to have that action pending to only be processed after that action finished.

It turns out that you can do that using feedkeys(), since it only adds keystrokes to the typeahead buffer and will only process them once it's done with the current action.

Since the second action will take place out of the context of the current function, you'll have to preserve the information (in this case the restore_position) in some kind of global variable. You can use either g:, s: or b:, whatever works best in your case. (In my case I'll use global ones for simplicity, but it should just work in all other cases as well.)

A first solution to this problem (a little bit naive):

function MyTest_F()
    let g:restore_position = winsaveview()
    normal! ggVG
    call feedkeys(":call winrestview(g:restore_position)\<CR>", "n")
endfunction
onoremap F :<C-U>call MyTest_F()<CR>

This will work as you expect. yF will yank the whole buffer, but leave the cursor in the same place. Also a nice test is g~F to swap case of the whole buffer, again leaving the cursor and window view where they currently are.

One small shortcoming here is that yF will not show you the 44 lines yanked message, instead the :call winrestview(...) command will be left in the command-line. You can clear the command-line by adding a "\<C-L>" to the end of that feedkeys, but a better solution is to use a separate normal-mode mapping to execute the second part of the mapping, make that one <silent> and feed the keys for that one instead. You can use the <Plug> special key for that purpose.

So a better solution is:

function MyTest_F()
    let g:restore_position = winsaveview()
    normal! ggVG
    call feedkeys("\<Plug>(MyTest_RestoreView)")
endfunction
onoremap F :<C-U>call MyTest_F()<CR>
nnoremap <silent> <Plug>(MyTest_RestoreView) :call winrestview(g:restore_position)<CR>

This should also preserve the message printed by the operator, whenever one is printed!

Question 2

How does one cancel an operator pending command, so one can "hi-jack" it?

Again, if you think of the order, first the omap happens and selects a region and then the operator is executed on that region. So you can't really send a cancellation keystroke (like <Esc> or <C-C>) the the operator... But what you can do is send it an empty region, in which case it will do nothing, which in essence is the same as cancelling it.

So with this function:

function MyTest_H()
    let restore_position = winsaveview()
    normal! ggyG
    call winrestview(restore_position)
endfun
onoremap H :<C-U>call MyTest_H()<CR>

Running dH will yank the whole buffer and not delete anything. (In fact, with this specific function you'll even get the 44 lines yanked message.)

It turns out you can even access v:register from inside your omap function, so you can even have "adH yank the whole buffer into the "a register, with:

function MyTest_H()
    let restore_position = winsaveview()
    silent execute "normal! gg\"".v:register."yG"
    call winrestview(restore_position)
endfun
onoremap H :<C-U>call MyTest_H()<CR>

Note that hijacking an operator this way looks pretty hacky... Yeah it works, but is it really a good idea to do so? In any case, I guess the key here is that creating an empty motion is a simple way to cancel the original operation, which I guess is what you were trying to determine here.

Regarding the opfunc attempt, setting opfunc is not really applicable unless you're going to use g@ at some point, which you're not really doing here.

Question 3

Can one simply return a range within an omap e.g. :1,$ to mean all lines. The best I could come up with is as follows, but I feel there must be a much simpler way.

Well, you need to have a visual selection at the end of your function (assuming you don't want the cursor position in any of the two extremities of the selection), so no I don't think there's a much easier way than this one.

You could use:

call setpos(".", startpos)
normal! v
call setpos(".", endpos)

But that's about the same thing, so probably not that much simpler...

Note that you can't have a winrestview() there, it will undo your visual selection, so you'll be left with nothing. (See question #1 again for a way to handle restoring the view after the operator has finished.)

So the small simplification I have to suggest is:

function MyTest_H()
    let startpos = [0,1,0,'off']
    let endpos   = [0,line("$"),0,'off']
    call setpos(".", startpos)
    normal! v
    call setpos(".", endpos)
endfun
onoremap H :<C-U>call MyTest_H()<CR>

Question 4

Instead of trying to restore the position in the motion, I should be doing it after the operator. Is it possible to change the y command to map to something like the code below?

Yes, it is possible to create a y operator that will restore the cursor to its original position.

In order to do so, you set an opfunc that will execute the yanking and then you call g@ to take a motion for it.

Here there's a question of order of operations again, since by the time your opfunc starts to execute, your cursor will have already moved as a result of the motion or omap captured by g@. So you need to save your view before you execute g@. Which means you'll have to save it and restore it in two different contexts, meaning again you'll need to use a global to preserve it between them.

Here's a way to make that work, creating a separate \y (or <leader>y) mapping that will preserve the cursor position after the yank:

function! YankRestore(type, ...) abort
    if a:type == 'line'
        normal! '[y']
    else
        normal! `[y`]
    endif
    call winrestview(g:restore_position)
endfunction
nnoremap <silent> <leader>y :let g:restore_position=winsaveview()<Bar>set opfunc=YankRestore<CR>g@

See :help :map-operator for an example of writing an appropriate opfunc.

You may also want to have your function handle visual mode and create a corresponding xnoremap to handle <leader>y the same way from visual mode. (You could even have it preserve the visual selection, by running gv after the yank!)

Hopefully this advice has been useful and this answer will improve your understanding of how operators and omap functions work!

| improve this answer | |
  • 1
    Absolutely brillant! That makes so much sense. The high level execution order is something I couldnt find online. I hope many people will benefit from the insight you have shared. I really appreciate it thank you! – run_the_race Apr 25 at 10:19
  • 1
    Very thorough solution, I was trying to solve the silent issue a few days ago but couldn't get it right. The "better Solution" to question 1 is exactly what I was trying to accomplish. – run_the_race Apr 25 at 16:40

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.