chapters/15.markdown @ 8d3cdc4c9c2a

Update acknowledgements
author Steve Losh <steve@stevelosh.com>
date Thu, 15 Dec 2016 15:22:41 -0500
parents 10e255a80c3a
children (none)
Operator-Pending Mappings
=========================

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:

    :::text
    Keys   Operator   Movement
    ----   --------   -------------
    dw     Delete     to next word
    ci(    Change     inside parens
    yt,    Yank       until comma

Movement Mappings
-----------------

Vim lets you create new movements that work with all existing commands.  Run the
following command:

    :::vim
    :onoremap p i(

Now type the following text into a buffer:

    :::text
    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):

    :::text
    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:

    :::vim
    :onoremap b /return<cr>

Now type the following text into a buffer:

    :::python
    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:

1. Start at the cursor position.
2. Enter visual mode (charwise).
3. ... mapping keys go here ...
4. All the text you want to include in the movement should now be selected.

It's your job to fill in step three with the appropriate keys.

Changing the Start
------------------

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:

    :::vim
    :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:

    :::python
    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:

    :::vim
    :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:

    :::vim
    :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:

    :::vim
    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.

General Rules
-------------

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

* If your operator-pending mapping ends with some text visually selected, Vim
  will operate on that text.
* Otherwise, Vim will operate on the text between the original cursor position
  and the new position.

Exercises
---------

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.