In this chapter we're going to explore one more rabbit hole in Vim's mapping system: "operator-pending mappings". Let's step back for a second and make sure we're clear on vocabulary.
An operator is a command that waits for you to enter a movement command, and then does something on the text between where you currently are and where the movement would take you.
Some examples of operators are d
, y
, and c
. For example:
Keys Operator Movement
---- -------- -------------
dw Delete to next word
ci( Change inside parens
yt, Yank until comma
Vim lets you create new movements that work with all existing commands. Run the following command:
:onoremap p i(
Now type the following text into a buffer:
return person.get_pets(type="cat", fluffy_only=True)
Put your cursor on the word "cat" and type dp
. What happened? Vim deleted
all the text inside the parentheses. You can think of this new movement as
"parameters".
The onoremap
command tells Vim that when it's waiting for a movement to give
to an operator and it sees p
, it should treat it like i(
. When we ran dp
it was like saying "delete parameters", which Vim translates to "delete inside
parentheses".
We can use this new mapping immediately with all operators. Type the same text as before into the buffer (or simply undo the change):
return person.get_pets(type="cat", fluffy_only=True)
Put your cursor on the word "cat" and type cp
. What happened? Vim deleted
all the text inside the parentheses, but this time it left you in insert mode
because you used "change" instead of "delete".
Let's try another example. Run the following command:
:onoremap b /return<cr>
Now type the following text into a buffer:
def count(i):
i += 1
print i
return foo
Put your cursor on the i
in the second line and press db
. What happened?
Vim deleted the entire body of the function, all the way up until the return
,
which our mapping used Vim's normal search to find.
When you're trying to think about how to define a new operator-pending movement, you can think of it like this:
It's your job to fill in step three with the appropriate keys.
You may have already seen a problem in what we've learned so far. If our movements always have to start at the current cursor position it limits what we can do.
Vim isn't in the habit of limiting what you can do, so of course there's a way around this problem. Run the following command:
:onoremap in( :<c-u>normal! f(vi(<cr>
This might look frightening, but let's try it out. Enter the following text into the buffer:
print foo(bar)
Put your cursor somewhere in the word print
and type cin(
. Vim will delete
the contents of the parentheses and place you in insert mode between them.
You can think of this mapping as meaning "inside next parentheses", and it will perform the operator on the text inside the next set of parentheses on the current line.
Let's make a companion "inside last parentheses" ("previous" would be a better word, but it would shadow the "paragraph" movement). Run the following command:
:onoremap il( :<c-u>normal! F)vi(<cr>
Try it out on some text of your own to make sure it works.
So how do these mappings work? First, the <c-u>
is something special that you
can ignore for now -- just trust me that it needs to be there to make the
mappings work in all cases. If we remove that we're left with:
:normal! F)vi(<cr>
:normal!
is something we'll talk about in a later chapter, but for now it's
enough to know that it is a command used to simulate pressing keys in normal
mode. For example, running :normal! dddd
will delete two lines, just like
pressing dddd
. The <cr>
at the end of the mapping is what executes the
:normal!
command.
So now we know that the mapping is essentially just running the last block of keys:
F)vi(
This is fairly simple:
F)
: Move backwards to the nearest )
character.vi(
: Visually select inside the parentheses.We end up with the text we want to operate on visually selected, and Vim performs the operation on it as normal.
A good way to keep the multiple ways of creating operator-pending mappings straight is to remember the following two rules:
Create operator-pending mappings for "around next parentheses" and "around last parentheses".
Create similar mappings for in/around next/last for curly brackets.
Read :help omap-info
and see if you can puzzle out what the <c-u>
in the
examples is for.