# HG changeset patch # User Steve Losh # Date 1318186887 14400 # Node ID 62e42f5b255fcad7c3755d6b0320bc780c661fa9 # Parent 484c72cf83cd323a7ee0f25a2216ce32eb84e0ab More. diff -r 484c72cf83cd -r 62e42f5b255f chapters/16.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/16.markdown Sun Oct 09 15:01:27 2011 -0400 @@ -0,0 +1,196 @@ +More Operator-Pending Mappings +============================== + +The idea of operators and movements is one of the most important concepts in Vim, +and it's one of the biggest reasons Vim is so efficient. We're going to +practice defining new motions a bit more, because extending this powerful idea +makes Vim even *more* powerful. + +Let's say you're writing some text in Markdown. If you haven't used Markdown +before, don't worry, for our purposes here it's very simple. Type the following +into a file: + + Topic One + ========= + + This is some text about topic one. + + It has multiple paragraphs. + + Topic Two + ========= + + This is some text about topic two. It has only one paragraph. + +The lines "underlined" with `=` characters are treated as heading by Markdown. +Lets create some mappings that let us target headings with movements. Run the +following command: + + :onoremap ih :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" + +This mapping is pretty complicated, so put your cursor in one of the paragraphs +(not the headings) and type `cih`. Vim will delete the heading of whatever +section you're in and put you in insert mode. + +It uses some things we've never seen before, so let's look at each piece +individually. The first part of the mapping, `:onoremap ih` is just the mapping +command that we've seen before, so we'll skip over that. We'll keep ignoring +the `` for the moment as well. + +Now we're looking at the remainder of the line: + + :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" + +Normal +------ + +The `:normal` command takes a set of characters and performs whatever action +they would do if they were typed in normal mode. Run this command: + + :normal gg + +Vim will move you to the top of the file. Now run this command: + + :normal >> + +Vim will indent the current line. + +In our mapping we're using a version of `:normal` with a `!` at the end. This +version will *not* take any existing mappings into account, whereas plain +`:normal` will use them. + +In effect, `:normal!` is to `:normal` as `nnoremap` is to `nmap`. You should +*always* prefer `:normal!` for the same reasons you should always use +`nnoremap`, which we discussed in an earlier chapter. + +Execute +------- + +The `execute` takes a string and performs it as a command. Run this: + + :execute "write" + +Vim will write your file, just as if you had typed `:write`. Now run this +command: + + :execute "normal! gg" + +Vim will run `:normal! gg`, which as we just saw will move you to the top of the +file. But why bother with this when we could just run the `normal!` command +itself? + +Look at the following command and try to guess what it will do: + + :normal! gg/a + +It seems like it should: + +* Move to the top of the file. +* Start a search. +* Fill in "a" as the target to search for. +* Press return to perform the search. + +Run it. Vim will move to the top of the file and nothing else! + +The problem is that `normal!` doesn't recognize "special characters" like +``. There are a number of ways around this, but the easiest to use and read +is `execute`. + +When `execute` looks at the string you tell it to run, it will substitute any +special characters it finds *before* running it. In this case, `\r` is an +escape sequence that means "carriage return". The double backslash is also an +escape sequence that puts a literal backslash in the string. + +If we perform this replacement in our mapping and look at the result we can see +that the mapping is going to perform: + + :normal! ?^==\+$:nohlsearchkvg_ + ^^^^ ^^^^ + || || + These are ACTUAL carriage returns, NOT the four characters + "left angle bracket", "c", "r", and "right angle bracket". + +So now `normal!` will execute these characters as if we had typed them in normal +mode. Let's split them apart at the returns to find out what they're doing: + + ?^==\+$ + :nohlsearch + kvg_ + +The first piece, `?^==\+$` performs a search backwards for any line that +consists of two or more equal signs and nothing else. This will leave our cursor +on the first character of the line of equal signs. + +We're searching backwards because when you say "change inside heading" while +your cursor is in a section of text, you probably want to change the heading for +*that* section, not the next one. + +The second piece is the `:nohlsearch` command. This simply clears the search +highlighting from the search we just performed so it's not distracting. + +The final piece is a sequence of three normal mode commands: + +* `k`: move up a line. Since we were on the first character of the line of + equal signs, we're now on the first character of the heading text. +* `v`: enter (characterwise) visual mode. +* `g_`: move to the last non-blank character of the current line. We use this + instead of `$` because `$` would highlight the newline character as well, and + this isn't what we want. + +Results +------- + +That was a lot of work, but now we've looked at each part of the mapping. To +recap it in a nutshell: + +* We created a operator-pending mapping for "inside this section's heading". +* We used `execute` and `normal!` to run the normal commands we needed to select + the heading, and allowing us to use special characters in those. +* Our mapping searches for the line of equal signs which denotes a heading and + visually selects the heading text above that. +* Vim handles the rest. + +Let's look at one more mapping before we move on. Run the following command: + + :onoremap ah :execute "normal! ?^==\\+\r:nohlsearch\rg_vk0" + +Try it by putting your cursor in a section's text and typing `cah`. This time +Vim will delete not only the heading's text but also the line of equal signs +that denotes a heading. You can think of this movement as "around this +section's heading". + +What's different about this mapping? Let's look at them side by side: + + :onoremap ih :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" + :onoremap ah :execute "normal! ?^==\\+\r:nohlsearch\rg_vk0" + +The only difference from the previous mapping is the very end, where we select +the text to operate on: + + kvg_ + g_vk0 + +The rest of the mapping is the same, so we still start on the first character of +the line of equal signs. From there: + +* `g_`: move to the last non-blank character in the line. +* `v`: enter (characterwise) visual mode. +* `k`: move up a line. This puts us on the line containing the heading's text. +* `0`: move to the first character of the line. + +The result is that both the text and the equal signs end up visually selected, +and Vim performs the operation on both. + +Exercises +--------- + +Markdown can also have headings delimited with lines of `-` characters. Adjust +the regex in these mappings to work for either type of heading. You may want to +check out `:help pattern-overview`. Remember that the regex is inside of +a string, so backslashes will need to be escaped. + +Read `:help normal`. + +Read `:help execute`. + +Read `:help expr-quote` to see the escape sequences you can use in strings.