62e42f5b255f

More.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sun, 09 Oct 2011 15:01:27 -0400
parents 484c72cf83cd
children effedad80866
branches/tags (none)
files chapters/16.markdown

Changes

--- /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 :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
+
+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 `<c-u>` for the moment as well.
+
+Now we're looking at the remainder of the line:
+
+    :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
+
+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<cr>
+
+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
+`<cr>`.  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! ?^==\+$<cr>:nohlsearch<cr>kvg_
+                    ^^^^    ^^^^
+                     ||      ||
+    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 :<c-u>execute "normal! ?^==\\+\r:nohlsearch\rg_vk0"<cr>
+
+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 :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
+    :onoremap ah :<c-u>execute "normal! ?^==\\+\r:nohlsearch\rg_vk0"<cr>
+
+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.