a8dd2fa6d24c

Merge remote-tracking branch 'upstream/master'
[view raw] [browse files]
author Richard Cheng <rcheng@neuratron.com>
date Mon, 16 Apr 2012 14:38:23 +0100
parents dd723c5dadcd (current diff) ee7e65e9658e (diff)
children b9e0500a6e02
branches/tags (none)
files

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chapters/50.markdown	Mon Apr 16 14:38:23 2012 +0100
@@ -0,0 +1,115 @@
+Section Movement Theory
+=======================
+
+If you've never used Vim's section movement commands (`[[`, `]]`, `[]` and `][`)
+take a second and read the help for them now.  Go ahead and read `:help section`
+as well.
+
+Confused yet?  That's okay, so was I the first time I read that stuff.  We're
+going to take a quick detour from writing code to learn about how these
+movements work, and then in the next chapter we'll make our Potion plugin
+support them.
+
+Nroff Files
+-----------
+
+The four "section movement" commands are conceptually meant to move around
+between "sections" of a file.
+
+All of these commands are designed to work with [nroff files][] by default.
+Nroff is a language like LaTeX or Markdown -- it's used to write text that will
+be reformatted later (it's actually the format used by UNIX man pages).
+
+Nroff files use a certain set of "macros" to define "section headings".  For
+example, here's an excerpt from the `awk` man page:
+
+    :::nroff
+    .SH NAME                                                     ***
+    awk \- pattern-directed scanning and processing language
+    .SH SYNOPSIS                                                 ***
+    .B awk
+    [
+    .BI \-F
+    .I fs
+    ]
+    [
+    .BI \-v
+    .I var=value
+    ]
+    [
+    .I 'prog'
+    |
+    .BI \-f
+    .I progfile
+    ]
+    [
+    .I file ...
+    ]
+    .SH DESCRIPTION                                              ***
+    .I Awk
+    scans each input
+    .I file
+    for lines that match ...
+
+The lines starting with `.SH` are section headings.  I've marked them with
+`***`.  The four section movement commands will move your cursor between these
+section heading lines.
+
+Vim considers any line starting with `.` and one of the nroff heading macros to
+be a section header, *even when you're not editing an nroff file*!
+
+You can change the macros by changing the `sections` setting, but Vim still
+requires a period at the beginning of the line, and the macros must be pairs of
+characters, so that setting doesn't add enough flexibility for Potion files.
+
+Braces
+------
+
+Section movement commands *also* look for one more thing: an opening or closing
+curly brace (`{` or `}`) as the first character on a line.
+
+`[[` and `]]` look for opening braces, while `[]` and `][` look for closing
+braces.
+
+This extra "hack" allows you to move between sections of C-like languages
+easily.  However, these rules are always the same no matter what type of file
+you're in!
+
+Put the following into a buffer:
+
+    :::text
+    Test           A B
+    Test
+
+    .SH Hello      A B
+
+    Test
+
+    {              A
+    Test
+    }                B
+
+    Test
+
+    .H World       A B
+
+    Test
+    Test           A B
+
+Now run `:set filetype=basic` to tell Vim that this is a BASIC file, and try the
+section movement comments.
+
+The `[[` and `]]` commands will move between the lines marked `A`, while `[]`
+and `][` move between the lines marked `B`.
+
+This shows us that Vim always uses these same two rules for section movement,
+even for languages where neither one makes sense (like BASIC)!
+
+[nroff files]: http://en.wikipedia.org/wiki/Nroff
+
+Exercises
+---------
+
+Read `:help section` again, now that you know the story of section movement.
+
+Read `:help sections` just for the fun of it.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chapters/51.markdown	Mon Apr 16 14:38:23 2012 +0100
@@ -0,0 +1,372 @@
+Potion Section Movement
+=======================
+
+Now that we know how section movement works, let's remap the commands to work
+in a way that makes sense for Potion files.
+
+First we need to decide what "section" should mean for a Potion file.  There are
+two pairs of section movement commands, so we can come up with two "schemes" and
+our users can choose the one they prefer.
+
+Let's use the following two schemes to define where Potion sections start:
+
+1. Any line following a blank line that contains non-whitespace as the first
+   character, or the first line in the file.
+2. Any line that contains non-whitespace as the first character, an equal sign
+   somewhere inside the line, and ends with a colon.
+
+Using a slightly-expanded version of our sample `factorial.pn` file, here's
+what these rules will consider to be section headers:
+
+    :::text
+    # factorial.pn                              1
+    # Print some factorials, just for fun.
+
+    factorial = (n):                            1 2
+        total = 1
+
+        n to 1 (i):
+            total *= i.
+
+        total.
+
+    print_line = ():                            1 2
+        "-=-=-=-=-=-=-=-\n" print.
+
+    print_factorial = (i):                      1 2
+        i string print
+        '! is: ' print
+        factorial (i) string print
+        "\n" print.
+
+    "Here are some factorials:\n\n" print       1
+
+    print_line ()                               1
+    10 times (i):
+        print_factorial (i).
+    print_line ()
+
+Our first definition tends to be more liberal.  It defines a section to be
+roughly a "top-level chunk of text".
+
+The second definition is more restrictive.  It defines a section to be
+(effectively) a function definition.
+
+Custom Mappings
+---------------
+
+Create a `ftplugin/potion/sections.vim` file in your plugin's repo.  This
+is where we'll put the code for section movement.  Remember that this code will
+be run whenever a buffer's `filetype` is set to `potion`.
+
+We're going to remap all four section movement commands, so go ahead and create
+a "skeleton" file:
+
+    :::vim
+    noremap <script> <buffer> <silent> [[ <nop>
+    noremap <script> <buffer> <silent> ]] <nop>
+
+    noremap <script> <buffer> <silent> [] <nop>
+    noremap <script> <buffer> <silent> ][ <nop>
+
+Notice that we use `noremap` commands instead of `nnoremap`, because we want
+these to work in operator-pending mode too.  That way you'll be able to do
+things like `d]]` to "delete from here to the next section".
+
+We make the mappings buffer-local so they'll only apply to Potion files and
+won't take over globally.
+
+We also make them silent, because the user won't care about the details of how
+we move between sections.
+
+Using a Function
+----------------
+
+The code for performing the section movements is going to be very similar for
+all of the various commands, so let's abstract it into a function that our
+mappings will call.
+
+You'll see this strategy in a lot of Vim plugins that create a number of similar
+mappings.  It's easier to read and maintain then stuffing all the functionality
+in to a bunch of mapping lines.
+
+Change the `sections.vim` file to contain this:
+
+    :::vim
+    function! s:NextSection(type, backwards)
+    endfunction
+
+    noremap <script> <buffer> <silent> ]] :call <SID>NextSection(1, 0)<cr>
+    noremap <script> <buffer> <silent> [[ :call <SID>NextSection(1, 1)<cr>
+
+    noremap <script> <buffer> <silent> ][ :call <SID>NextSection(2, 0)<cr>
+    noremap <script> <buffer> <silent> [] :call <SID>NextSection(2, 1)<cr>
+
+Notice that we're using `<SID>` and a script-local function to avoid polluting
+the global namespace with our helper function.
+
+Each mapping simply calls `NextSection` with the appropriate arguments to
+perform the movement.  Now we can start implementing `NextSection`.
+
+Base Movement
+-------------
+
+Let's think about what our function needs to do.  We want to move the cursor to
+the next "section", and an easy way to move the cursor somewhere is with the `/`
+and `?` commands.
+
+Edit `NextSection` to look like this:
+
+    :::vim
+    function! s:NextSection(type, backwards)
+        if a:backwards
+            let dir = '?'
+        else
+            let dir = '/'
+        endif
+
+        execute 'silent normal! ' . dir . 'foo' . "\r"
+    endfunction
+
+Now the function uses the `execute normal!` pattern we've seen before to perform
+either `/foo` or `?foo`, depending on the value given for `backwards`.  This is
+a good start.
+
+Moving on, we're obviously going to need to search for something other than
+`foo`, and that pattern is going to depend on whether we want to use the first
+or second definition of section headings.
+
+Change `NextSection` to look like this:
+
+    :::vim
+    function! s:NextSection(type, backwards)
+        if a:type == 1
+            let pattern = 'one'
+        elseif a:type == 2
+            let pattern = 'two'
+        endif
+
+        if a:backwards
+            let dir = '?'
+        else
+            let dir = '/'
+        endif
+
+        execute 'silent normal! ' . dir . pattern . "\r"
+    endfunction
+
+Now we just need to fill in the patterns, so let's go ahead and do that.
+
+Top Level Text Sections
+-----------------------
+
+Replace the first `let pattern = '...'` line with the following:
+
+    :::vim
+    let pattern = '\v(\n\n^\S|%^)'
+
+To understand how the regular expression works, remember the definition of
+"section" that we're implementing:
+
+> Any line following a blank line that contains a non-whitespace as the first
+> character, or the first line in the file.
+
+The `\v` at the beginning simply forces "very magic" mode like we've seen
+several times before.
+
+The remainder of the regex is a group with two options.  The first, `\n\n^\S`,
+searches for "a newline, followed by a newline, followed by a non-whitespace
+character".  This finds the first set of lines in our definition.
+
+The other option is `%^`, which is a special Vim regex atom that means
+"beginning of file".
+
+Now we're at a point where we can try out the first two mappings.  Save
+`ftplugin/potion/sections.vim` and run `:set filetype=potion` in your sample
+Potion buffer.  The `[[` and `]]` commands should work, but somewhat oddly.
+
+Search Flags
+------------
+
+You'll notice that when you move between sections your cursor gets placed on the
+blank line above the one we actually want to move to.  Think about why this
+happens before reading on.
+
+The answer is that we searched using `/` (or `?`) and by default Vim places your
+cursor at the beginning of matches.  For example, when you run `/foo` your
+cursor will be placed on the "f" in "foo".
+
+To tell Vim to put the cursor at the end of the match instead of the beginning,
+we can use a search flag.  Try searching in your Potion file like so:
+
+    :::vim
+    /factorial/e
+
+Vim will find the word "factorial" and move you to it.  Press `n` a few times to
+move through the matches.  The `e` flag tells Vim to put the cursor at the end
+of matches instead of the beginning.  Try it in the other direction too:
+
+    :::vim
+    ?factorial?e
+
+Let's modify our function to use a search flag to put our cursor on the other
+end of the matches for this section:
+
+    :::vim
+    function! s:NextSection(type, backwards)
+        if a:type == 1
+            let pattern = '\v(\n\n^\S|%^)'
+            let flags = 'e'
+        elseif a:type == 2
+            let pattern = 'two'
+            let flags = ''
+        endif
+
+        if a:backwards
+            let dir = '?'
+        else
+            let dir = '/'
+        endif
+
+        execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
+    endfunction
+
+We've changed two things here.  First, we set a `flags` variable depending on
+the type of section movement.  For now we only worry about the first type, which
+is going to need a flag of `e`.
+
+Second, we've concatenated `dir` and `flags` to the search string.  This will
+add `?e` or `/e` depending on which direction we're searching.
+
+Save the file, switch back to your sample Potion file and run `:set ft=potion`
+to make the changes take effect.  Now try `[[` and `]]` to see them working
+properly!
+
+Function Definitions
+--------------------
+
+It's time to tackle our second definition of "section", and luckily this one is
+much more straightforward than the first.  Recall the definition we need to
+implement:
+
+> Any line that contains a non-whitespace as the first character, an equal sign
+> somewhere inside the line, and ends with a colon.
+
+We can use a fairly simple regex to find these lines.  Change the second `let
+pattern = '...'` line in the function to this:
+
+    :::vim
+    let pattern = '\v^\S.*\=.*:$'
+
+This regex should look much less frightening than the last one.  I'll leave it
+as an exercise for you to figure out how it works -- it's a pretty
+straightforward translation of our definition.
+
+Save the file, run `:set filetype=potion` in `factorial.pn`, and try out the new
+`][` and `[]` mappings.  They should work as expected.
+
+We don't need a search flag here because putting the cursor at the beginning of
+the match (the default) works just fine.
+
+Visual Mode
+-----------
+
+Our section movement commands work great in normal mode, but we need to add
+a bit more to make them work in visual mode as well.  First, change the function
+to look like this:
+
+    :::vim
+    function! s:NextSection(type, backwards, visual)
+        if a:visual
+            normal! gv
+        endif
+
+        if a:type == 1
+            let pattern = '\v(\n\n^\S|%^)' 
+            let flags = 'e'
+        elseif a:type == 2
+            let pattern = '\v^\S.*\=.*:$'
+            let flags = ''
+        endif
+
+        if a:backwards
+            let dir = '?'
+        else
+            let dir = '/'
+        endif
+
+        execute 'silent normal! ' . dir . pattern . dir . flags . "\r"
+    endfunction
+
+Two things have changed.  First, the function takes an extra argument so it know
+whether it's being called from visual mode or not.  Second, if it's called from
+visual mode we run `gv` to restore the visual selection.
+
+Why do we need to do this?  Let's try something that will make it clear.
+Visually select some text in any buffer and then run the following command:
+
+    :::vim
+    :echom "hello"
+
+Vim will display "hello" but the visual selection will also be cleared!
+
+When running an ex mode command with `:` the visual selection is always cleared.
+The `gv` command reselects the previous visual selection, so this will "undo"
+the clearing.  It's a useful command, and can be handy in your day-to-day work
+too.
+
+Now we need to update the existing mappings to pass `0` in for the new `visual`
+argument:
+
+    :::vim
+    noremap  <script> <buffer> <silent> ]] :call <SID>NextSection(1, 0, 0)<cr>
+    noremap  <script> <buffer> <silent> [[ :call <SID>NextSection(1, 1, 0)<cr>
+
+    noremap  <script> <buffer> <silent> ][ :call <SID>NextSection(2, 0, 0)<cr>
+    noremap  <script> <buffer> <silent> [] :call <SID>NextSection(2, 1, 0)<cr>
+
+Nothing too complex there.  Now let's add the visual mode mappings as the final
+piece of the puzzle:
+
+    :::vim
+    vnoremap <script> <buffer> <silent> ]] :<c-u>call <SID>NextSection(1, 0, 1)<cr>
+    vnoremap <script> <buffer> <silent> [[ :<c-u>call <SID>NextSection(1, 1, 1)<cr>
+
+    vnoremap <script> <buffer> <silent> ][ :<c-u>call <SID>NextSection(2, 0, 1)<cr>
+    vnoremap <script> <buffer> <silent> [] :<c-u>call <SID>NextSection(2, 1, 1)<cr>
+
+These mappings all pass `1` for the `visual` argument to tell Vim to reselect
+the last selection before performing the movement.  They also use the `<c-u>`
+trick we learned about in the Grep Operator chapters.
+
+Save the file, `:set ft=potion` in the Potion file and you're done!  Give your
+new mappings a try.  Things like `v]]` and `d[]` should all work properly now.
+
+Why Bother?
+-----------
+
+This has been a long chapter for some seemingly small functionality, but you've
+learned and practiced a lot of useful things along the way:
+
+* Using `noremap` instead of `nnoremap` to create mappings that work as
+  movements and motions.
+* Using a single function with several arguments to simplify creating related
+  mappings.
+* Building up functionality in a Vimscript function incrementally.
+* Building up an `execute 'normal! ...'` string programatically.
+* Using simple searches to move around with regexes.
+* Using special regex atoms like `%^` (beginning of file).
+* Using search flags to modify how searches work.
+* Handling visual mode mappings that need to retain the visual selection.
+
+Go ahead and do the exercises (it's just a bit of `:help` reading) and then grab
+some ice cream.  You've earned it after this chapter!
+
+Exercises
+---------
+
+Read `:help search()`.  This is a useful function to know, but you can also use
+the flags listed with the `/` and `?` commands.
+
+Read `:help ordinary-atom` to learn about more interesting things you can use in
+search patterns.