Without the yr
mapping (or any other mapping that starts with y
), the code does not work. Why?
That's because you're calling feedkeys()
with the 'x'
option, which will also process the typeahead buffer. So you need the next keypress to be in that buffer in order to execute the full command.
For example, if you type yG
, you need the G
to be on the buffer at the time the function is called. Having another longer mapping defined with the same prefix ensures that Vim will wait for more keystrokes (up to 'timeoutlen'
, depending on 'timeout'
being enabled). If the additional keystrokes don't match a mapping, the additional keystrokes will end up in the typeahead buffer, which is exactly what you wanted.
the command y
works as expected with one-character motions (e.g. y{
, yG
, y0
), but it doesn't work with motions that take more characters (e.g. ygg
, y5k
). Why, and can this be fixed?
That's because the full motion is not in the typeahead buffer at the time the function is called. Since yg
doesn't match our longer dummy mapping yr
, that means Vim will call the function with only g
in the typeahead buffer, which means feedkeys will execute yg
, which doesn't make sense...
You can work around that, to some extent, with a similar trick you used for the original prefix itself. You could define a mapping longer than yg
that will make Vim wait for one more character after those two:
nnoremap ygr :echom "Yet another mapping."<CR>
Or, perhaps cleaner:
nnoremap y<Esc> <Nop>
nnoremap yg<Esc> <Nop>
Using <Esc>
is cleaner than r
and using <Nop>
works similarly to <Esc>
simply cancelling the command.
This will fix ygg
or yg$
, ygm
, etc. But not other prefixes. If you consider counts, such as y5k
, it gets really tricky to cover them all.
Is feedkeys robust? Should I use it for this purpose?
While feedkeys is a fine function and there are places where using it makes sense. However, there's a much better way to implement what you're trying to accomplish here: operator-pending mappings.
The way you use it is by setting 'operatorfunc'
to point to a function you defined and then call the g@
command at the end of your mapping, which will keep waiting for a {motion}
and then executes the set operator function when it gets the motion. That way you can manage to run some code (in your case, setpos()
to restore the cursor) after the operation is executed.
Here's a very naive example:
function! Yank_fixed_cursor()
let g:save_cursor = getcurpos()
set operatorfunc=Yank_restore_cursor
return 'g@'
endfunction
function! Yank_restore_cursor(type)
if a:type == 'line'
silent exe "normal! '[V']y"
else
silent exe "normal! `[v`]y"
endif
call setpos('.', g:save_cursor)
endfunction
nnoremap <expr> y Yank_fixed_cursor()
As you can see, the Yank_fixed_cursor
function now sets up the operator by using a separate function, Yank_restore_cursor
, to execute the yank and restore the cursor. It finally returns g@
, which is used in an <expr>
mapping to create an operator-pending mapping, which will wait for a motion and execute the operator function.
The Yank_restore_cursor
gets from Vim more context about the motion, such as an argument indicating whether it was a linewise or character-wise motion and the markers [
and ]
set to the block around the motion. We then use that to execute the yank operation. Finally, we can restore the cursor to the saved position.
Since the save and restore of the cursor happen in different functions, you'll need a global variable to preserve the information in between the two function calls. (If you're in a plug-in, you can use a s:
variable, which leaves you on an isolated namespace and makes it harder for some other script to interfere with your global state.)
There are quite a few shortcomings with this naive implementation, you need a lot more to make it completely robust. Some of the items missing:
Using yy
won't work, since y
by itself is not a valid motion... You can fix that with an additional nmap yy y_
mapping, since _
is a valid motion corresponding to the double letter commands (yy
, dd
, etc.) You might also want to map Y
with nmap Y y_
. Note you need recursive mappings for those, to trigger your original y
mapping.
You should handle yanking to a non-default register. You can look at v:register
to execute your operation. Note that you'll get your register information in Yank_fixed_cursor
, so that's also something you'll want to save in a global variable to later use in Yank_restore_cursor
when you actually execute the operation.
Same for counts, for example 5yj
. You can look at v:count
in Yank_fixed_cursor
to check whether there was a count. Since in this case you want to "translate" it into y5j
(i.e. use the count on the motion), you can handle this by returning 'g@'.v:motion
from Yank_fixed_cursor
when a count is passed, since this will leave the count pending when the motion is expected, and will then apply to the motion.
You might want to map y
in Visual mode as well. In many cases you can reuse the execution function Yank_restore_cursor
between both operator-pending mode and Visual mode. You typically do that by taking a variable number of arguments and having the Visual mode mappings pass an extra argument to indicate it was called in that context. See the examples in :help :map-operator
for more details.
As you can see, this can get quite complex very quickly. There are many corner cases and covering them all takes work. There is quite a bit of complexity involved, but Vim gives you the tools to cover these in order to emulate a command that looks and behaves same as a native command.
If this topic interests you, you might want to read "Grep Operator" from "Learn Vimscript the Hard Way", by Steve Losh.(You'll actually want to read the whole material, but pointing you at the relevant section so you'll see that's thoroughly covered here.)
vim -Nu minimal.vim