chapters/15.markdown @ 8a50aa737e18

Link to pathogen in exercise.
author Jason Ribeiro <jason.ribeiro@gmail.com>
date Tue, 17 Apr 2012 19:53:27 -0400
parents ff713b4d6c53
children 1ca745014709 cf919ee07541
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
           Operator
           vvvvvv
    dw   " Delete to next word
    ci(  " Change inside parens
    yt,  " Yank   until comma
                  ^^^^^^^^^^^
                  Movement

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

    :::vim
    :onoremap p i(

Now type the follow text into a buffer:

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

    :::python
    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 you 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 parenthesis", and it will
perform the operator on the text inside the next set of parenthesis on the
current line.

Let's make a companion "inside last parenthesis" ("previous" woud 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 know 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's a command that can be 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 the 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 parenthesis" and "around last
parenthesis".

Create similar mappings for in/around next/last for curly brackets.

Create a "inside next email address" operator-pending mapping so you can say
"change inside next email address".  `in@` is a good candidate for the keys to
map. You'll probably want to use `/...some regex...<cr>` for this.

Read `:help omap-info` and see if you can puzzle out what the `<c-u>` in the
examples is for.