--- a/chapters/01.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/01.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -9,6 +9,7 @@
Run the following command:
+ :::vim
:echo "Hello, world!"
You should see `Hello, world!` appear at the bottom of the window.
@@ -18,12 +19,14 @@
Now run the following command:
+ :::vim
:echom "Hello again, world!"
You should see `Hello again, world!` appear at the bottom of the window.
To see the difference between these two commands, run one more new command:
+ :::vim
:messages
You should see a list of messages. `Hello, world!` will *not* be in this list,
@@ -42,6 +45,7 @@
your `~/.vimrc` file or another one) you can add comments with the `"`
character, like this:
+ :::vim
" Make space more useful
nnoremap <space> za
--- a/chapters/02.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/02.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,10 +8,12 @@
Run the following command:
+ :::vim
:set number
Line numbers should appear in Vim. Now run this:
+ :::vim
:set nonumber
The line numbers should disappear. `number` is a boolean option -- it can be
@@ -24,10 +26,12 @@
You can also "toggle" boolean options to set them to the *opposite* of whatever
they are now. Run this:
+ :::vim
:set number!
The line numbers should reappear. Now run it again:
+ :::vim
:set number!
They should disappear once more. Adding a `!` (exclamation point or "bang") to
@@ -39,6 +43,7 @@
You can ask Vim what an option is currently set to by using a `?`. Run these
commands and watch what happens after each:
+ :::vim
:set number
:set number?
:set nonumber
@@ -53,6 +58,7 @@
Some options take a value instead of just being off or on. Run the following
commands and watch what happens after each:
+ :::vim
:set number
:set numberwidth=10
:set numberwidth=4
@@ -63,6 +69,7 @@
Try checking what a few other common options are set to:
+ :::vim
:set wrap?
:set numberwidth?
@@ -72,6 +79,7 @@
Finally, you can specify more than one option in the same `:set` command. Try
running this:
+ :::vim
:set number numberwidth=6
Exercises
--- a/chapters/03.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/03.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -10,6 +10,7 @@
Type a few lines of text into a file, then run:
+ :::vim
:map \ x
Put your cursor somewhere in the text and press `\`. Notice how Vim deleted the
@@ -18,6 +19,7 @@
We already have a key for "delete that character under the cursor", so let's
change that mapping to something slightly more useful. Run this command:
+ :::vim
:map \ dd
Now put your cursor on a line somewhere and press `\` again. This time Vim
@@ -29,6 +31,7 @@
You can use `<keyname>` to tell Vim about special keys that are hard to type.
Try running this command:
+ :::vim
:map <space> viw
Put your cursor on a word in your text and press the space bar. Vim will
@@ -36,6 +39,7 @@
You can also map modifier keys like Ctrl and Alt. Run this:
+ :::vim
:map <c-d> dd
Now pressing `Ctrl+d` on your keyboard will run `dd`.
@@ -46,6 +50,7 @@
Remember in the first lesson where we talked about comments? Mapping keys is
one of the places where Vim comments don't work. Try running this command:
+ :::vim
:map <space> viw " Use space to select a word
If you try pressing `<space>` now, something horrible will almost certainly
--- a/chapters/04.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/04.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -12,7 +12,8 @@
Run this command:
- nmap \ dd
+ :::vim
+ :nmap \ dd
Now put your cursor in your text file, make sure you're in normal mode, and
press `\`. Vim will delete the current line.
@@ -23,7 +24,8 @@
Run this command:
- vmap \ U
+ :::vim
+ :vmap \ U
Enter visual mode and select some text, then press `\`. Vim will convert the
text to uppercase!
@@ -50,7 +52,8 @@
Now that we've covered how to map keys in normal and visual mode, let's move on
to insert mode. Run this command:
- imap <c-d> dd
+ :::vim
+ :imap <c-d> dd
You might think that this would let you press `Ctrl+d` whenever you're in insert
mode to delete the current line. This would be nice, because now you don't need
@@ -68,7 +71,8 @@
To make this mapping do what we intended we need to be very explicit. Run this
command to change the mapping:
- imap <c-d> <esc>dd
+ :::vim
+ :imap <c-d> <esc>dd
The `<esc>` is our way of telling Vim to press the Escape key, which will take
us out of insert mode.
@@ -79,7 +83,8 @@
Run one more command to fix the mapping once and for all:
- imap <c-d> <esc>ddi
+ :::vim
+ :imap <c-d> <esc>ddi
The `i` at the end reenters insert mode for us, and our mapping is finally
complete.
--- a/chapters/05.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/05.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,6 +8,7 @@
Run the following commands:
+ :::vim
:nmap - dd
:nmap \ -
@@ -26,6 +27,7 @@
Run this command:
+ :::vim
:nmap dd O<esc>jddk
You might think that this would change `dd` to:
@@ -77,6 +79,7 @@
Vim offers another set of mapping commands that will *not* take mappings into
account when they perform their actions. Run these commands:
+ :::vim
:nmap x dd
:nnoremap \ x
--- a/chapters/06.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/06.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -21,6 +21,7 @@
Unlike Emacs, Vim makes it easy to map more than just single keys. Run these
commands:
+ :::vim
:nnoremap -d dd
:nnoremap -c ddO
@@ -42,6 +43,7 @@
Vim calls this "prefix" key "leader". You can set your leader key to whatever
you like. Run this command:
+ :::vim
:let mapleader = "-"
You can replace `-` with any key you like. I personally like `,` even though it
@@ -50,6 +52,7 @@
When you're creating new mappings you can use `<leader>` to mean "whatever
I have my leader key set to". Run this command:
+ :::vim
:nnoremap <leader>d dd
Now try it out by pressing your leader key and then `d`. Vim will delete the
@@ -78,6 +81,7 @@
We'll talk about how to make mappings for specific types of files later in the
book, but you can go ahead and set your "localleader" now:
+ :::vim
:let maplocalleader = "\\"
Notice that we have to use `\\` and not just `\` because `\` is the escape
--- a/chapters/07.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/07.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -22,6 +22,7 @@
Lets add a mapping that will open our `~/.vimrc` file in a split so we can edit
it and get back to coding. Run this command:
+ :::vim
:nnoremap <leader>ev :vsplit $MYVIMRC<cr>
I like to think of this command as "**e**dit my **v**imrc file".
@@ -94,6 +95,7 @@
Let's add a mapping to make this easier:
+ :::vim
:nnoremap <leader>sv :source $MYVIMRC<cr>
I like to think of this command as "**s**ource my **v**imrc file".
--- a/chapters/08.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/08.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,10 +8,12 @@
We're only going to worry about insert mode abbreviations in this book. Run the
following command:
+ :::vim
:iabbrev adn and
Now enter insert mode and type:
+ :::text
One adn two.
As soon as you hit space after typing the "adn" Vim will replace it with "and".
@@ -19,11 +21,13 @@
Correcting typos like this is a great use for abbreviations. Run these
commands:
+ :::vim
:iabbrev waht what
:iabbrev tehn then
Now enter insert mode again and type:
+ :::text
Well, I don't know waht we should do tehn.
Notice how *both* abbreviations were substituted, even though you didn't type
@@ -36,6 +40,7 @@
after an abbreviation. "Non-keyword character" means any character not in the
`iskeyword` option. Run this command:
+ :::vim
:set iskeyword?
You should see something like `iskeyword=@,48-57,_,192-255`. This format is
@@ -63,6 +68,7 @@
Abbreviations are useful for more than just correcting typos. Let's add a few
more that can help in day-to-day text editing. Run the following commands:
+ :::vim
:iabbrev @@ steve@stevelosh.com
:iabbrev ccopy Copyright 2011 Steve Losh, all rights reserved.
@@ -82,6 +88,7 @@
Run this command:
+ :::vim
:inoremap ssig --<cr>Steve Losh<cr>steve@stevelosh.com
This is a *mapping* intended to let you insert your signature quickly. Try it
@@ -90,6 +97,7 @@
It seems to work great, but there's a problem. Try entering insert mode and
typing this text:
+ :::text
Larry Lessig wrote the book "Remix".
You'll notice that Vim has expanded the `ssig` in Larry's name! Mappings don't
@@ -99,6 +107,7 @@
Remove the mapping and replace it with an abbreviation by running the following
commands:
+ :::vim
:iunmap ssig
:iabbrev ssig --<cr>Steve Losh<cr>steve@stevelosh.com
--- a/chapters/09.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/09.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -10,6 +10,7 @@
Run the following command:
+ :::vim
:nnoremap jk dd
Now make sure you're in normal mode and press `j` followed quickly by `k`. Vim
@@ -22,6 +23,7 @@
This mapping will make it painful to move around our file, so lets remove it.
Run the following command:
+ :::vim
:nunmap jk
Now typing `jk` in normal mode will move down and then up a line as usual.
@@ -32,6 +34,7 @@
You've seen a bunch of simple mappings so far, so it's time to look at something
with a bit more meat to it. Run the following command:
+ :::vim
:nnoremap <leader>" viw<esc>a"<esc>hbi"<esc>lel
Now *that's* an interesting mapping! First, go ahead and try it out. Enter
@@ -41,6 +44,7 @@
How does this work? Let's split it apart into pieces and think of what each one
does:
+ :::vim
viw<esc>a"<esc>hbi"<esc>lel
* `viw`: visually select the current word
--- a/chapters/10.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/10.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -7,6 +7,7 @@
Let's set up one more mapping that will save more wear on your left hand than
any other mapping you ever create. Run the following command:
+ :::vim
:inoremap jk <esc>
Now enter insert mode and type `jk`. Vim will act as if you pressed the escape
@@ -41,6 +42,7 @@
The trick to relearning a mapping is to *force* yourself to use it by
*disabling* the old key(s). Run the following command:
+ :::vim
:inoremap <esc> <nop>
This effectively disables the escape key in insert mode by telling Vim to
--- a/chapters/11.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/11.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -17,6 +17,7 @@
Switch to file `foo` and run the following commands:
+ :::vim
:nnoremap <leader>d dd
:nnoremap <buffer> <leader>x dd
@@ -60,10 +61,12 @@
Switch to file `foo` and run the following command:
+ :::vim
:setlocal wrap
Now switch to file `bar` and run this command:
+ :::vim
:setlocal nowrap
Make your Vim window smaller and you'll see that the lines in `foo` wrap, but
@@ -71,10 +74,12 @@
Let's try another option. Switch to `foo` and run this command:
+ :::vim
:setlocal number
Now switch over to `bar` and run this command:
+ :::vim
:setlocal nonumber
You now have line numbers in `foo` but not in `bar`.
@@ -88,6 +93,7 @@
Before we move on, let's look at a particularly interesting property of local
mappings. Switch over to `foo` and run the following commands:
+ :::vim
:nnoremap <buffer> Q x
:nnoremap Q dd
--- a/chapters/12.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/12.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -15,6 +15,7 @@
Let's change it so that Vim creates files as soon as you edit them. Run the
following command:
+ :::vim
:autocmd BufNewFile * :write
This is a lot to take in, but try it out and see that it works. Run `:edit foo`
@@ -29,6 +30,7 @@
Let's take a closer look at the autocommand we just created:
+ :::text
:autocmd BufNewFile * :write
^ ^ ^
| | |
@@ -55,6 +57,7 @@
when you want the command to fire. Start up a new Vim instance and run the
following command:
+ :::vim
:autocmd BufNewFile *.txt :write
This is almost the same as the last command, but this time it will only apply to
@@ -76,6 +79,7 @@
Let's define another autocommand, this time using a different event. Run the
following command:
+ :::vim
:autocmd BufWrite *.html :normal gg=G
We're getting a bit ahead of ourselves here because we're going to talk about
@@ -85,6 +89,7 @@
Create a new file called `foo.html`. Edit it with Vim and enter the following
text *exactly*, including the whitespace:
+ :::html
<html>
<body>
<p>Hello!</p>
@@ -111,6 +116,7 @@
You can create a single autocommand bound to *multiple* events by separating the
events with a comma. Run this command:
+ :::vim
:autocmd BufWrite,BufRead *.html :normal gg=G
This is almost like our last command, except it will also reindent the code
@@ -121,6 +127,7 @@
together to run a command whenever you open a certain kind of file, regardless
of whether it happens to exist already or not. Run the following command:
+ :::vim
:autocmd BufNewFile,BufRead *.html setlocal nowrap
This will turn line wrapping off whenever you're working on an HTML file.
@@ -134,6 +141,7 @@
Let's set up a few useful mappings for a variety of file types. Run the
following commands:
+ :::vim
:autocmd FileType javascript nnoremap <buffer> <localleader>c I//
:autocmd FileType python nnoremap <buffer> <localleader>c I#
--- a/chapters/13.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/13.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,10 +8,12 @@
Open your `foo` and `bar` files again, switch to `foo`, and run the following
command:
+ :::vim
:iabbrev <buffer> --- —
While still in `foo` enter insert mode and type the following text:
+ :::text
Hello --- world.
Vim will replace the `---` for you. Now switch to `bar` and try it. It should
@@ -26,6 +28,7 @@
Run the following commands:
+ :::vim
:autocmd FileType javascript :iabbrev <buffer> iff if ( ) {}<left><left><left><left><left>
:autocmd FileType python :iabbrev <buffer> iff if:<left>
--- a/chapters/14.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/14.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -3,6 +3,7 @@
A few chapters ago we learned about autocommands. Run the following command:
+ :::vim
:autocmd BufWrite * :echom "Writing buffer!"
Now write your current buffer with `:write` and run `:messages` to view the
@@ -13,6 +14,7 @@
Now run the exact same autocommand again:
+ :::vim
:autocmd BufWrite * :echom "Writing buffer!"
Write your current buffer one more time and run `:messages`. You will see the
@@ -35,11 +37,13 @@
To simulate this, try running the following command:
+ :::vim
:autocmd BufWrite * :sleep 200m
Now write the file. You may or may not notice a slight sluggishness in Vim's
writing time. Now run the command three more times:
+ :::vim
:autocmd BufWrite * :sleep 200m
:autocmd BufWrite * :sleep 200m
:autocmd BufWrite * :sleep 200m
@@ -60,6 +64,7 @@
Open a fresh instance of Vim to clear out the autocommands from before, then run
the following commands:
+ :::vim
:augroup testgroup
: autocmd BufWrite * :echom "Foo"
: autocmd BufWrite * :echom "Bar"
@@ -71,6 +76,7 @@
Write a buffer and check `:messages`. You should see both "Foo" and "Bar". Now
run the following commands:
+ :::vim
:augroup testgroup
: autocmd BufWrite * :echom "Baz"
:augroup END
@@ -92,6 +98,7 @@
If you want to *clear* a group you can use `autocmd!` inside the group. Run the
following commands:
+ :::vim
:augroup testgroup
: autocmd!
: autocmd BufWrite * :echom "Cats"
@@ -109,6 +116,7 @@
Add the follow to your `~/.vimrc` file:
+ :::vim
augroup filetype_html
autocmd!
autocmd FileType html nnoremap <buffer> <localleader>f Vatzf
--- a/chapters/15.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/15.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -11,6 +11,7 @@
Some examples of operators are `d`, `y`, and `c`. For example:
+ :::text
Operator
vvvvvv
dw " Delete to next word
@@ -22,10 +23,12 @@
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
@@ -40,6 +43,7 @@
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
@@ -48,10 +52,12 @@
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
@@ -82,11 +88,13 @@
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
@@ -99,6 +107,7 @@
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.
@@ -107,6 +116,7 @@
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
@@ -118,6 +128,7 @@
So now we know that the mapping is essentially just running the last block of
keys:
+ :::vim
F)vi(
This is fairly simple:
--- a/chapters/16.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/16.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -10,6 +10,7 @@
before, don't worry, for our purposes here it's very simple. Type the following
into a file:
+ :::markdown
Topic One
=========
@@ -26,6 +27,7 @@
Lets create some mappings that let us target headings with movements. Run the
following command:
+ :::vim
:onoremap ih :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
This mapping is pretty complicated, so put your cursor in one of the paragraphs
@@ -39,6 +41,7 @@
Now we're looking at the remainder of the line:
+ :::vim
:execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
Normal
@@ -47,10 +50,12 @@
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:
+ :::vim
:normal gg
Vim will move you to the top of the file. Now run this command:
+ :::vim
:normal >>
Vim will indent the current line.
@@ -68,11 +73,13 @@
The `execute` takes a string and performs it as a command. Run this:
+ :::vim
:execute "write"
Vim will write your file, just as if you had typed `:write`. Now run this
command:
+ :::vim
:execute "normal! gg"
Vim will run `:normal! gg`, which as we just saw will move you to the top of the
@@ -81,6 +88,7 @@
Look at the following command and try to guess what it will do:
+ :::vim
:normal! gg/a<cr>
It seems like it should:
@@ -104,6 +112,7 @@
If we perform this replacement in our mapping and look at the result we can see
that the mapping is going to perform:
+ :::text
:normal! ?^==\+$<cr>:nohlsearch<cr>kvg_
^^^^ ^^^^
|| ||
@@ -113,6 +122,7 @@
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:
+ :::vim
?^==\+$
:nohlsearch
kvg_
@@ -152,6 +162,7 @@
Let's look at one more mapping before we move on. Run the following command:
+ :::vim
: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
@@ -161,12 +172,14 @@
What's different about this mapping? Let's look at them side by side:
+ :::vim
: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:
+ :::vim
kvg_
g_vk0
--- a/chapters/17.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/17.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -5,11 +5,13 @@
window. This is done through the `statusline` option. Run the following
command:
+ :::vim
:set statusline=%f
You should see the path to the file (relative to the current directory) in the
status line. Now run this command:
+ :::vim
:set statusline=%f\ -\ FileType:\ %y
Now you'll see something like "foo.markdown - FileType: [markdown]" in the
@@ -28,6 +30,7 @@
Status lines can get extremely complicated very quickly, so there's a better way
to set them that will let us be more clear. Run the following commands:
+ :::vim
:set statusline=%f " Path to the file
:set statusline+=\ -\ " Separator
:set statusline+=FileType: " Label
@@ -40,6 +43,7 @@
Run the following command:
+ :::vim
:set statusline=%l " Current line
:set statusline+=/ " Separator
:set statusline+=%L " Total lines
@@ -53,6 +57,7 @@
Additional characters can be used in some of the various `%` codes to change how
the information is displayed. Run the following command:
+ :::vim
:set statusline=%4l
The line number in the status line will now be proceeded by enough spaces to
@@ -62,19 +67,23 @@
By default the padding spaces are added on the left side of the value. Run this
command:
+ :::vim
:set statusline=Current:\ %4l\ Total:\ %4L
Your status line will now look like this:
+ :::text
Current: 12 Total: 223
You can use `-` to place padding on the right instead of the left. Run this
command:
+ :::vim
:set statusline=Current:\ %-4l\ Total:\ %-4L
Your status line will now look like this:
+ :::text
Current: 12 Total: 223
This looks much nicer because the numbers are next to their labels.
@@ -82,6 +91,7 @@
For codes that result in a number you can tell Vim to pad with zeros instead of
spaces. Run the following command:
+ :::vim
:set statusline=%04l
Now your status line will read "0012" when on line twelve.
@@ -89,15 +99,18 @@
Finally, you can also set the maximum width of a code's output. Run this
command:
+ :::vim
:set statusline=%F
`%F` displays the *full* path to the current file. Now run this command to
change the maximum width:
+ :::vim
:set statusline=%.20F
The path will be truncated if necessary, looking something like this:
+ :::text
<hapters/17.markdown
This can be useful for preventing paths and other long codes from taking up the
@@ -108,6 +121,7 @@
The general format for a code in a status line is shown in `:help statusline`:
+ :::text
%-0{minwid}.{maxwid}{item}
Everything except the `%` and the item is optional.
@@ -118,6 +132,7 @@
We'll come back to status lines later in the book, but there's one more simple
code that can be very useful immediately. Run the following commands:
+ :::vim
:set statusline=%f " Path to the file
:set statusline+=%= " Switch to the right side
:set statusline+=%l " Current line
--- a/chapters/18.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/18.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -41,6 +41,7 @@
First we need to set up folding for Vimscript files. Add the following lines to
your `~/.vimrc` file:
+ :::vim
augroup filetype_vim
au!
au FileType vim setlocal foldmethod=marker
@@ -54,6 +55,7 @@
Now add lines before and after that autocommand group so that it looks like this:
+ :::vim
" Vimscript file settings ---------------------- {{{
augroup filetype_vim
au!
@@ -83,6 +85,7 @@
Vim allows you to use abbreviated names for most commands and options. For
example, both of these commands do exactly the same thing:
+ :::vim
:setlocal wrap
:setl wrap
--- a/chapters/19.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/19.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -11,12 +11,14 @@
Run the following commands:
+ :::vim
:let foo = "bar"
:echo foo
Vim will display "bar". `foo` is now a variable, and we've assigned it
a string: "bar". Now run these commands:
+ :::vim
:let foo = 42
:echo foo
@@ -30,6 +32,7 @@
You can read and set *options* as variables by using a special syntax. Run the
following commands:
+ :::vim
:set textwidth=80
:echo &textwidth
@@ -39,11 +42,13 @@
Let's see how Vim works with boolean options. Run the following commands:
+ :::vim
:set nowrap
:echo &wrap
Vim displays "0". Now try these commands:
+ :::vim
:set wrap
:echo &wrap
@@ -53,6 +58,7 @@
We can also *set* options as variables. Run the following commands:
+ :::vim
:let &textwidth = 100
:set textwidth?
@@ -61,6 +67,7 @@
Why would we want to do this when we could just use `set`? Run the following
commands:
+ :::vim
:let &textwidth = &textwidth + 10
:set textwidth?
@@ -76,10 +83,12 @@
Open two files in separate splits. Run the following command:
+ :::vim
:let &l:number = 1
Now switch to the other file and run this command:
+ :::vim
:let &l:number = 0
Notice that the first window has line numbers and the second does not.
@@ -89,6 +98,7 @@
You can also read and set *registers* as variables. Run the following command:
+ :::vim
:let @a = "hello!"
Now put your cursor somewhere in your text and type `"ap`. This command tells
@@ -97,12 +107,14 @@
Registers can also be read. Run the following command:
+ :::vim
:echo @a
Vim will echo "hello!".
Select a word in your file and yank it with `y`, then run this command:
+ :::vim
:echo @"
Vim will echo the word you just yanked. The `"` register is the "unnamed"
@@ -110,6 +122,7 @@
Perform a search in your file with `/someword`, then run the following command:
+ :::vim
:echo @/
Vim will echo the search pattern you just used. This lets you programmatically
--- a/chapters/20.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/20.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,12 +8,14 @@
Open two buffers in separate splits, then go into one of then and run the
following commands:
+ :::vim
:let b:hello = "world"
:echo hello
As expected, Vim displays "world". Now switch to the other buffer and run the
echo command again:
+ :::vim
:echo hello
This time Vim throws an error, saying it can't find the variable.
--- a/chapters/21.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/21.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -12,9 +12,10 @@
Multiple-Line Statements
------------------------
-Sometimes can't fit a piece of Vimscript on a single line of code. We saw this
+Sometimes you can't fit a piece of Vimscript on a single line of code. We saw this
when we talked about autocommand groups. Here's a chunk of code we used before:
+ :::vim
:augroup testgroup
: autocmd BufWrite * :echom "Baz"
:augroup END
@@ -24,6 +25,7 @@
you can separate each line with a pipe character (`|`). Run the following
command:
+ :::vim
:echom "foo" | echom "bar"
Vim will treat that as two separate commands. Use `:messages` to check the log
@@ -38,6 +40,7 @@
Now that we've got that out of the way, run the following commands:
+ :::vim
:if 1
: echom "ONE"
:endif
@@ -45,6 +48,7 @@
Vim will display "ONE", because the integer `1` is "truthy". Now try this
command:
+ :::vim
:if 0
: echom "ZERO"
:endif
@@ -52,6 +56,7 @@
Vim will *not* display "ZERO" because the integer `0` is "falsy". Let's see how
strings behave. Run this command:
+ :::vim
:if "something"
: echom "INDEED"
:endif
@@ -61,6 +66,7 @@
Let's dive a bit further down the rabbit hole. Run this command:
+ :::vim
:if "9024"
: echom "WHAT?!"
:endif
@@ -69,6 +75,7 @@
To try to wrap our heads around what's going on, run the following two commands:
+ :::vim
:echom "hello" + 10
:echom "10hello" + 10
:echom "hello10" + 10
@@ -93,6 +100,7 @@
Vim, like Python, supports both "else" and "else if" clauses. Run the following
commands:
+ :::vim
:if 0
: echom "if"
:elseif "nope!"
--- a/chapters/22.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/22.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -7,12 +7,14 @@
Run the following command:
+ :::vim
:if 10 > 1
: echom "foo"
:endif
Vim will, of course, display "foo". Now run this command:
+ :::vim
:if 10 > 2001
: echom "bar"
:endif
@@ -20,6 +22,7 @@
Vim displays nothing, because `10` is not greater than `2001`. So far
everything works as expected. Run this command:
+ :::vim
:if 10 == 11
: echom "first"
:elseif 10 == 10
@@ -29,6 +32,7 @@
Vim displays "second". Nothing surprising here. Let's try comparing strings.
Run this command:
+ :::vim
:if "foo" == "bar"
: echom "one"
:elseif "foo" == "foo"
@@ -43,6 +47,7 @@
Run the following commands:
+ :::vim
:set noignorecase
:if "foo" == "FOO"
: echom "vim is case insensitive"
@@ -53,6 +58,7 @@
Vim evaluates the `elseif`, so apparently Vimscript is case sensitive. Good to
know, but nothing earth-shattering. Now run these commands:
+ :::vim
:set ignorecase
:if "foo" == "FOO"
: echom "no, it couldn't be"
@@ -83,6 +89,7 @@
Run the following command:
+ :::vim
:set ignorecase
:if "foo" ==? "FOO"
: echom "first"
@@ -93,6 +100,7 @@
Vim displays "first" because `==?` is the "case-insensitive no matter what the
user has set" comparison operator. Now run the following command:
+ :::vim
:set ignorecase
:if "foo" ==# "FOO"
: echom "one"
--- a/chapters/23.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/23.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -6,6 +6,7 @@
Run the following commands:
+ :::vim
:function meow()
You might think this would start defining a function named `Meow`.
@@ -20,24 +21,28 @@
Okay, let's define a function for real this time. Run the following commands:
+ :::vim
:function Meow()
: echom "Meow!"
:endfunction
This time Vim will happily define the function. Let's try running it:
+ :::vim
:call Meow()
Vim will display "Meow!" as expected.
Let's try returning a value. Run the following commands:
+ :::vim
:function GetMeow()
: return "Meow String!"
:endfunction
Now try it out by running this command:
+ :::vim
:echom GetMeow()
Vim will call the function and give the result to `echom`, which will display
@@ -52,6 +57,7 @@
When you want to call a function directly you use the `call` command. Run the
following commands:
+ :::vim
:call Meow()
:call GetMeow()
@@ -62,6 +68,7 @@
The second way to call functions is in expressions. You don't need to use
`call` in this case, you can just name the function. Run the following command:
+ :::vim
:echom GetMeow()
As we saw before, this calls `GetMeow` and passes the return value to `echom`.
@@ -71,6 +78,7 @@
Run the following command:
+ :::vim
:echom Meow()
This will display two lines: "Meow!" and "0". The first obviously comes from
@@ -78,6 +86,7 @@
doesn't return a value, it implicitly return `0`. Let's use this to our
advantage. Run the following commands:
+ :::vim
:function TextwidthIsTooWide()
: if &l:numberwidth ># 80
: return 1
@@ -100,6 +109,7 @@
Let's try using it. Run the following commands:
+ :::vim
:set textwidth=80
:if TextwidthIsTooWide()
: echom "WARNING: Wide text!"
@@ -114,6 +124,7 @@
Because we never explicitly returned a value, Vim returned `0` from the
function, which is falsy. Let's try changing that. Run the following commands:
+ :::vim
:setlocal textwidth=100
:if TextwidthIsTooWide()
: echom "WARNING: Wide text!"
--- a/chapters/24.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/24.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -3,6 +3,7 @@
Vimscript functions can, of course, take arguments. Run the following commands:
+ :::vim
:function DisplayName(name)
: echom "Hello! My name is:"
: echom a:name
@@ -10,6 +11,7 @@
Run the function:
+ :::vim
:call DisplayName("Your Name")
Vim will display two lines: "Hello! My name is:" and "Your Name".
@@ -21,6 +23,7 @@
Let's remove this scope prefix and see how Vim reacts. Run the following
commands:
+ :::vim
:function UnscopedDisplayName(name)
: echom "Hello! My name is:"
: echom name
@@ -39,6 +42,7 @@
Vimscript functions can optionally take variable-length argument lists like
Javascript and Python. Run the following commands:
+ :::vim
:function Varg(...)
: echom a:0
: echom a:1
@@ -69,6 +73,7 @@
You can use varargs together with regular arguments too. Run the following
commands:
+ :::vim
:function Varg2(foo, ...)
: echom a:foo
: echom a:0
@@ -86,6 +91,7 @@
Try running the following commands:
+ :::vim
:function Assign(foo)
: let a:foo = "Nope"
: echom a:foo
@@ -96,6 +102,7 @@
Vim will throw an error, because you can't reassign argument variables. Now run
these commands:
+ :::vim
:function AssignGood(foo)
: let foo_tmp = a:foo
: let foo_tmp = "Yep"
--- a/chapters/25.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/25.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -12,25 +12,29 @@
You can specify Numbers in a few different ways. Run the following command.
+ :::vim
:echom 100
No surprises here -- Vim displays "100". Now run this command:
+ :::vim
:echom 0xff
This time Vim displays "255". You can specify numbers in hex notation by
prefixing them with `0x` or `0X`. Now run this command:
+ :::vim
:echom 010
You can also use octal by starting a number with a `0`. Be careful with this,
because it's easy to make mistakes. Try the following commands:
+ :::vim
:echom 017
:echom 019
-Vim will print "15" for the first command, because "18" in octal is equal to
-"18" in decimal. For the second command Vim treats it as a decimal number, even
+Vim will print "15" for the first command, because "17" in octal is equal to
+"15" in decimal. For the second command Vim treats it as a decimal number, even
though it starts with a `0`, because it's not a valid octal number.
Because Vim silently does the wrong thing in this case, I'd recommend avoiding
@@ -41,6 +45,7 @@
Floats can also be specified in multiple ways. Run the following command:
+ :::vim
:echo 100.1
Notice that we're using `echo` here and not `echom` like we usually to. We'll
@@ -49,20 +54,24 @@
Vim displays "100.1" as expected. You can also use exponential notation. Run
this command:
+ :::vim
:echo 5.45e+3
Vim displays "5450.0". A negative exponent can also be used. Run this command:
+ :::vim
:echo 15.45e-2
Vim displays "0.1545". The `+` or `-` before the power of ten is optional, if
it's omitted the it's assumed to be positive. Run the following command:
+ :::vim
:echo 15.3e9
Vim will display "1.53e10", which is equivalent. The decimal point and number
after it are *not* optional. Run the following command and see that it crashes:
+ :::vim
:echo 5e10
Coercion
@@ -72,6 +81,7 @@
other operation Vim will cast the Number to a Float, resulting in a Float. Run
the following command:
+ :::vim
:echo 2 * 2.0
Vim displays "4.0".
@@ -81,12 +91,14 @@
When dividing two Numbers, the remainder is dropped. Run the following command:
+ :::vim
:echo 3 / 2
Vim displays "1". If you want Vim to perform float point division one of the
numbers needs to be a Float, which will cause the other one to be coerced to
a Float as well. Run this command:
+ :::vim
:echo 3 / 2.0
Vim displays "1.5". The "3" is coerced to a Float, and then normal floating
--- a/chapters/26.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/26.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -6,6 +6,7 @@
Run the following command:
+ :::vim
:echom "Hello"
Vim will echo "Hello". So far, so good.
@@ -16,6 +17,7 @@
One of the most common things you'll want to do with strings is adding them
together. Run this command:
+ :::vim
:echom "Hello, " + "world"
What happened? Vim displayed "0" for some reason!
@@ -24,6 +26,7 @@
a string to `+` Vim will try to coerce it to a Number before performing the
addition. Run the following command:
+ :::vim
:echom "3 mice" + "2 cats"
This time Vim displays "5", because the strings are coerced to the numbers "3"
@@ -32,6 +35,7 @@
When I said "Number" I really *meant* Number. Vim will *not* coerce strings to
Floats! Try this command to see prove this:
+ :::vim
:echom 10 + "10.10"
Vim displays "20" because it dropped everything after the decimal point when
@@ -40,6 +44,7 @@
To combine strings you need to use the concatenation operator. Run the
following command:
+ :::vim
:echom "Hello, " . "world"
This time Vim displays "Hello, world". `.` is the "concatenate strings"
@@ -48,12 +53,14 @@
Coercion works both ways. Kind of. Try this command:
+ :::vim
:echom 10 . "foo"
Vim will display "10foo". First it coerces `10` to a String, then it
concatenates it with the string on the right hand side. Things get a bit
stickier when we're working with Floats, though. Run this command:
+ :::vim
:echom 10.1 . "foo"
This time Vim throws an error, saying we're using a Float as a String. Vim will
@@ -75,23 +82,27 @@
Like most programming languages, Vimscript lets you use escape sequences in
strings to represent hard-to-type characters. Run the following command:
+ :::vim
:echom "foo \"bar\""
The `\"` in the string is replaced with a double quote character, as you would
probably expect. Escape sequences work mostly as you would expect. Run the
following command:
+ :::vim
:echom "foo\\bar"
Vim displays `foo\bar`, because `\\` is the escape sequence for a literal
backslash, just like in most programming languages. Now run the following
command (note that it's an `echo` and *not* an `echom`):
+ :::vim
:echo "foo\nbar"
This time Vim will display two lines, "foo" and "bar", because the `\n` is
replaced with a newline. Now try running this command:
+ :::vim
:echom "foo\nbar"
Vim will display something like "foo^@bar". When you use `echom` with a String
@@ -105,12 +116,14 @@
Vim also lets you use "literal strings" to avoid excessive use of escape
sequences. Run the following command:
+ :::vim
:echom '\n\\'
Vim displays `\n\\`. Using single quotes tells Vim that you want the string
*exactly* as-in, with no escape sequences. The one exception is that two single
quotes in a row will produce a single single quote. Try this command:
+ :::vim
:echom 'That''s enough.'
Vim will display "That's enough." Two single quotes is the *only* sequence that
@@ -125,6 +138,7 @@
You might be wondering how Vim treats strings when used in an `if` statement.
Run the following command:
+ :::vim
:if "foo"
: echo "yes"
:else
--- a/chapters/27.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/27.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -9,11 +9,13 @@
The first function we'll look at is `strlen`. Run the following command:
+ :::vim
:echom strlen("foo")
Vim displays "3", which is the length of the string "foo". Now try the
following command:
+ :::vim
:echom len("foo")
Vim once again displays "3". When used with Strings `len` and `strlen` have
@@ -24,6 +26,7 @@
Run the following command (note that it's an `echo` and not an `echom`):
+ :::vim
:echo split("one two three")
Vim displays "['one', 'two', 'three']". The `split` function splits a String
@@ -33,6 +36,7 @@
You can also tell Vim to use a separator other than "whitespace" for splitting.
Run the following command:
+ :::vim
:echo split("one,two,three", ",")
Vim will once again display "['one', 'two', 'three']", because the second
@@ -45,12 +49,14 @@
Not only can you split strings, you can also join them. Run the following
command:
+ :::vim
:echo join(["foo", "bar"], "...")
Vim will display "foo...bar". Don't worry about the list syntax for now.
`split` and `join` can be paired to great effect. Run the following command:
+ :::vim
:echo join(split("foo bar"), ";")
Vim displays "foo;bar". First we split the string "foo bar" into a list, then
@@ -62,6 +68,7 @@
Vim has two functions to change the case of Strings. Run the following
commands:
+ :::vim
:echom tolower("Foo")
:echom toupper("Foo")
--- a/chapters/28.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/28.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -4,6 +4,7 @@
The `execute` command is used to evaluate a string as if it were a Vimscript
command. Run the following command:
+ :::vim
:execute "echom 'Hello, world!'"
Vim evaluates `echom 'Hello, world!'` as a command and dutifully echoes it to
@@ -14,6 +15,7 @@
`:edit "foo.txt"` in the same window to open a new buffer. Now run the
following command:
+ :::vim
:execute "rightbelow vsplit " . bufname("#")
Vim will open the first file in a vertical split to the right of the second
--- a/chapters/29.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/29.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -7,11 +7,13 @@
The answer is: "of course". Run the following command:
+ :::vim
:normal G
Vim will move your cursor to the last line in the current file, just like
pressing `G` in normal mode would. Now run the following command:
+ :::vim
:normal ggdd
Vim will move to the first line in the file (`gg`) and then delete it (`dd`).
@@ -24,10 +26,12 @@
Run the following command to map the `G` key to something else:
+ :::vim
:nnoremap G dd
Now pressing `G` in normal mode will delete a line. Try this command:
+ :::vim
:normal G
Vim will delete the current line. The `normal` command will take into account
@@ -39,6 +43,7 @@
Luckily Vim has a `normal!` command that does exactly this. Run this command:
+ :::vim
:normal! G
This time Vim moves to the bottom of the file even though `G` has been mapped.
@@ -53,6 +58,7 @@
If you play around with `normal!` long enough you'll probably notice a problem.
Try the following command:
+ :::vim
:normal! /foo<cr>
At first glance it may seem like this should perform a search for "foo", but
--- a/chapters/30.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/30.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -4,6 +4,7 @@
Now that we've seen `execute` and `normal!` we can talk about a common Vimscript
idiom. Run the following command:
+ :::vim
:execute "normal! gg/foo\<cr>dd"
This will move to the top of the file, search for the first occurrence of "foo",
@@ -17,6 +18,7 @@
string escape sequences to generate the non-printing characters you need. Try
the following command:
+ :::vim
:execute "normal! mqA;\<esc>`q"
What does this do? Let's break it apart:
--- a/chapters/31.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/31.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -7,6 +7,7 @@
Type the following text into a buffer:
+ :::python
max = 10
print "Starting"
@@ -30,6 +31,7 @@
Before we start we need to turn on search highlighting so we can see what we're
doing. Run the following command:
+ :::vim
:set hlsearch incsearch
`hlsearch` tells Vim to highlight all matches in a file when you perform
@@ -41,6 +43,7 @@
Put your cursor at the top of the file and run the following command:
+ :::vim
/print
As you type in each letter, Vim will start highlighting them in the first line.
@@ -49,6 +52,7 @@
Now try running the following command:
+ :::vim
:execute "normal! gg/print\<cr>"
This will go to the top of the file and perform a search for "print", putting us
@@ -58,6 +62,7 @@
To get to the second match in the file you can just add more commands onto the
end of the string. Run this command:
+ :::vim
:execute "normal! gg/print\<cr>n"
Vim will put the cursor on the second "print" in the buffer (and all the matches
@@ -65,6 +70,7 @@
Let's try going in the opposite direction. Run this command:
+ :::vim
:execute "normal! G?print\<cr>"
This time we move to the bottom of the file with `G` and use `?` to search
@@ -80,11 +86,13 @@
The `/` and `?` commands actually take regular expressions, not just literal
characters. Run the following command:
+ :::vim
:execute "normal! gg/for .+ in .+:\<cr>"
Vim complains that the pattern is not found! I told you that Vim supports
regular expressions in searches, so what's going on? Try the following command:
+ :::vim
:execute "normal! gg/for .\\+ in .\\+:\<cr>"
This time Vim highlights the "for" loop as we expected in the first place. Take
@@ -103,6 +111,7 @@
You can see this a bit easier by just running the search in Vim directly. Type
the following command and press return:
+ :::vim
/print .\+
You can see the `\+` working its magic now. The double backslashes were only
@@ -122,6 +131,7 @@
Try running the following command (note the single quotes and single backslashes
this time):
+ :::vim
:execute 'normal! gg/for .\+ in .\+:\<cr>'
Vim moves you to the top of the file but doesn't move you to the first match.
@@ -136,6 +146,7 @@
strings, so for larger commands we can use this to split apart the string into
easier to read chunks. Run the following command:
+ :::vim
:execute "normal! gg" . '/for .\+ in .\+:' . "\<cr>"
This concatenates the three smaller strings before sending them to `execute`,
@@ -152,6 +163,7 @@
Run the following command:
+ :::vim
:execute "normal! gg" . '/\vfor .+ in .+:' . "\<cr>"
We've split the pattern out from the rest of the command into its own literal
--- a/chapters/32.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/32.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -68,6 +68,7 @@
We'll start with a skeleton of the mapping and fill it in as we go. Run this
command:
+ :::vim
:nnoremap <leader> g :grep -R something .<cr>
If you've read `:help grep` this should be pretty easy to understand. We've
@@ -82,6 +83,7 @@
First we need to search for the word under the cursor, not the string
"something". Run the following command:
+ :::vim
:nnoremap <leader>g :grep -R <cword> .<cr>
Now try it out. `<cword>` is a special bit of text you can use in Vim's
@@ -90,6 +92,7 @@
You can use `<cWORD>` to get a WORD instead of a word. Run this command:
+ :::vim
:nnoremap <leader>g :grep -R <cWORD> .<cr>
Now try the mapping when your cursor is over something like "foo-bar". Vim will
@@ -106,6 +109,7 @@
To try to fix this we'll quote the argument in the grep call. Run this command:
+ :::vim
:nnoremap <leader>g :grep -R '<cWORD>' .<cr>
Most shells treat single-quoted text as (almost) literal, so our mapping is much
@@ -120,11 +124,13 @@
the command with `execute`. First run the following command to transform the
`:grep` mapping into `:execute "..."` form:
+ :::vim
:nnoremap <leader>g :execute "grep -R '<cWORD>' ."<cr>
Try it out and make sure it still works. If not, find any typos and fix them.
Then run the following command, which uses `shellescape` to fix the search term:
+ :::vim
:nnoremap <leader>g :execute "grep -R " . shellescape("<cWORD>") . " ."<cr>
And now our mapping won't break if the word we're searching for happens to
@@ -142,12 +148,14 @@
automatically, and we can use `grep!` instead of plain `grep` to do that. Run
this command:
+ :::vim
:nnoremap <leader>g :execute "grep! -R " . shellescape("<cWORD>") . " ."<cr>
Try it out again and nothing will seem to happen. Vim has filled the quickfix
window with the results, but we haven't opened it yet. Run the following
command:
+ :::vim
:nnoremap <leader>g :execute "grep! -R " . shellescape("<cWORD>") . " ."<cr>:copen<cr>
Now try the mapping and you'll see that Vim automatically opens the quickfix
@@ -157,6 +165,7 @@
As the finishing touch we'll remove all the grep output Vim displays while
searching. Run the following command:
+ :::vim
:nnoremap <leader>g :silent execute "grep! -R " . shellescape("<cWORD>") . " ."<cr>:copen<cr>
We're done, so try it out and admire your hard work! The `silent` command just
--- a/chapters/33.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/33.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -39,6 +39,7 @@
To create a new Vim operator you'll start with two components: a function and
a mapping. Start by adding the following code to `grep-operator.vim`:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
function! GrepOperator(type)
@@ -63,6 +64,7 @@
We've added the operator to normal mode, but we'll want to be able to use it
from visual mode as well. Add another mapping below the first:
+ :::vim
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
Write and source the file. Now visually select something and press `<leader>g`.
@@ -94,6 +96,7 @@
Edit the function body so the file looks like this:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
@@ -122,6 +125,7 @@
to search for, and the easiest way to do that is to simply copy it. Edit the
function to look like this:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<C-U>call GrepOperator(visualmode())<cr>
@@ -184,6 +188,7 @@
Now that we've got the text we need in a Vim string we can escape it like we did
in the previous chapter. Modify the `echom` command so it looks like this:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<C-U>call GrepOperator(visualmode())<cr>
@@ -209,6 +214,7 @@
We're finally ready to add the `grep!` command that will perform the actual
search. Replace the `echom` line so the code looks like this:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<C-U>call GrepOperator(visualmode())<cr>
--- a/chapters/34.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/34.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -16,6 +16,7 @@
before we yank and restore it after we've done. Change the code to look like
this:
+ :::vim
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<C-U>call GrepOperator(visualmode())<cr>
@@ -57,6 +58,7 @@
We can avoid polluting the global namespace by tweaking a couple of lines in our
code. Edit the file to look like this:
+ :::vim
nnoremap <leader>g :set operatorfunc=<SID>GrepOperator<cr>g@
vnoremap <leader>g :<C-U>call <SID>GrepOperator(visualmode())<cr>
--- a/chapters/35.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/35.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,11 +8,13 @@
Vimscript lists are ordered, heterogeneous collections of elements. Run the
following command:
+ :::vim
:echo ['foo', 3, 'bar']
Vim displays the list. Lists can of course be nested. Run the following
command:
+ :::vim
:echo ['foo', [3, 'bar']]
Vim happily displays the list.
@@ -23,11 +25,13 @@
Vimscript lists are zero-indexed, and you can get at the elements in the usual
way. Run this command:
+ :::vim
:echo [0, [1, 2]][1]
Vim displays "[1, 2]". You can also index from the end of the list, much like
Python. Try this command:
+ :::vim
:echo [0, [1, 2]][-2]
Vim displays `0`. The index `-1` refers to the last element in the list, `-2`
@@ -39,17 +43,20 @@
Vim lists can also be sliced. This will *look* familiar to Python programmers,
but it does *not* always act the same way! Run this command:
+ :::vim
:echo ['a', 'b', 'c', 'd', 'e'][0:2]
Vim displays "['a', 'b', 'c']" (elements 0, 1 and 2). You can safely exceed the
upper bound as well. Try this command:
+ :::vim
:echo ['a', 'b', 'c', 'd', 'e'][0:100000]
Vim simply displays the entire list.
Slice indexes can be negative. Try this command:
+ :::vim
:echo ['a', 'b', 'c', 'd', 'e'][-2:-1]
Vim displays "['d', 'e']" (elements -2 and -1).
@@ -57,6 +64,7 @@
When slicing lists you can leave off the first index to mean "the beginning"
and/or the last index to mean "the end". Run the following commands:
+ :::vim
:echo ['a', 'b', 'c', 'd', 'e'][:1]
:echo ['a', 'b', 'c', 'd', 'e'][3:]
@@ -65,6 +73,7 @@
Like Python, Vimscript allows you to index and slice strings too. Run the
following command:
+ :::vim
:echo "abcd"[0:2]
Vim displays "abc".
@@ -74,7 +83,8 @@
You can combine Vim lists with `+`. Try this command:
- echo ['a', 'b'] + ['c']
+ :::vim
+ :echo ['a', 'b'] + ['c']
Vim, unsurprisingly, displays "['a', 'b', 'c']". There's not much else to say
here -- Vimscript lists are surprisingly sane compared to the rest of the
@@ -86,6 +96,7 @@
Vim has a number of built-in functions for working with lists. Run these
commands:
+ :::vim
:let foo = ['a']
:call add(foo, 'b')
:echo foo
@@ -93,10 +104,12 @@
Vim mutates the list `foo` in-place to append `'b'` and displays "['a', 'b']".
Now run this command:
+ :::vim
:echo len(foo)
Vim displays "2", the length of the list. Try these commands:
+ :::vim
:echo get(foo, 0, 'default')
:echo get(foo, 100, 'default')
@@ -106,6 +119,7 @@
Run this command:
+ :::vim
:echo index(foo, 'b')
:echo index(foo, 'nope')
@@ -114,6 +128,7 @@
Now run this command:
+ :::vim
:echo join(foo)
:echo join(foo, '---')
:echo join([1, 2, 3], '')
@@ -124,6 +139,7 @@
Run the following commands:
+ :::vim
:call reverse(foo)
:echo foo
:call reverse(foo)
--- a/chapters/36.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/36.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -16,6 +16,7 @@
Java, C or Javascript `for` loops, but turns out to be quite elegant. Run the
following commands:
+ :::vim
:let c = 0
:for i in [1, 2, 3, 4]
@@ -36,6 +37,7 @@
Vim also supports the classic `while` loop. Run the following commands:
+ :::vim
:let c = 1
:let total = 0
--- a/chapters/37.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/37.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -11,6 +11,7 @@
Run this command:
+ :::vim
:echo {'a': 1, 100: 'foo'}
Vim displays `{'a': 1, '100': 'foo'}`, which shows that Vimscript does indeed
@@ -19,6 +20,7 @@
Vimscript avoids the stupidity of the Javascript standard and lets you use
a comma after the last element in a dictionary. Run the following command:
+ :::vim
:echo {'a': 1, 100: 'foo',}
Once again Vim displays `{'a': 1, '100': 'foo'}`. You should *always* use the
@@ -31,10 +33,12 @@
To look up a key in a dictionary you use the same syntax as most languages. Run
this command:
+ :::vim
:echo {'a': 1, 100: 'foo',}['a']
Vim displays `1`. Try it with a non-string index:
+ :::vim
:echo {'a': 1, 100: 'foo',}[100]
Vim coerces the index to a string before performing the lookup, which makes
@@ -44,6 +48,7 @@
a string consisting only of letters, digits and/or underscores. Try the
following commands:
+ :::vim
:echo {'a': 1, 100: 'foo',}.a
:echo {'a': 1, 100: 'foo',}.100
@@ -56,6 +61,7 @@
Adding entries to dictionaries is done by simply assigning them like variables.
Run this command:
+ :::vim
:let foo = {'a': 1}
:let foo.a = 100
:let foo.b = 200
@@ -70,6 +76,7 @@
There are two ways to remove entries from a dictionary. Run the following
commands:
+ :::vim
:let test = remove(foo, 'a')
:unlet foo.b
:echo foo
@@ -82,6 +89,7 @@
You cannot remove nonexistent entries from a dictionary. Try running this
command:
+ :::vim
:unlet foo["asdf"]
Vim throws an error.
@@ -97,6 +105,7 @@
Like lists, Vim has a number of built-in functions for working with
dictionaries. Run the following command:
+ :::vim
:echom get({'a': 100}, 'a', 'default')
:echom get({'a': 100}, 'b', 'default')
@@ -105,6 +114,7 @@
You can also check if a given key is present in a given dictionary. Run this
commands:
+ :::vim
:echom has_key({'a': 100}, 'a')
:echom has_key({'a': 100}, 'b')
@@ -114,6 +124,7 @@
You can pull the key-value pairs out of a dictionary with `items`. Run this
command:
+ :::vim
:echo items({'a': 100, 'b': 200})
Vim will display a nested list that looks something like `[['a', 100], ['b',
--- a/chapters/38.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/38.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -7,6 +7,7 @@
Run the following command:
+ :::vim
:nnoremap <leader>N :setlocal number!<cr>
Try it out by pressing `<leader>N` in normal mode. Vim will toggle the line
@@ -24,6 +25,7 @@
a mapping that will call it. Put the following into your `~/.vimrc` file (or
a separate file in `~/.vim/plugin/` if you prefer):
+ :::vim
nnoremap <c-f> :call FoldColumnToggle()<cr>
function! FoldColumnToggle()
@@ -37,6 +39,7 @@
Let's add in the actual toggling functionality. Edit the code to look like
this:
+ :::vim
nnoremap <c-f> :call FoldColumnToggle()<cr>
function! FoldColumnToggle()
@@ -64,6 +67,7 @@
thing to have a mapping for is the quickfix window. Let's start with the same
skeleton as before. Add the following code to your file:
+ :::vim
nnoremap <c-q> :call QuickfixToggle()<cr>
function! QuickfixToggle()
@@ -74,6 +78,7 @@
slightly more useful (but not completely finished yet). Change the code to
look like this:
+ :::vim
nnoremap <c-q> :call QuickfixToggle()<cr>
function! QuickfixToggle()
@@ -86,6 +91,7 @@
To get the "toggling" behavior we're looking for we'll use a quick, dirty
solution: a global variable. Change the code to look like this:
+ :::vim
nnoremap <c-q> :call QuickfixToggle()<cr>
function! QuickfixToggle()
@@ -105,6 +111,7 @@
Write and source the file, and try to run the mapping. Vim will complain that
the variable is not defined yet! Let's fix that by initializing it once:
+ :::vim
nnoremap <c-q> :call QuickfixToggle()<cr>
let g:quickfix_is_open = 0
@@ -152,6 +159,7 @@
To solve this we'll introduce an idiom that comes in handy a lot when writing
Vim plugins. Edit your code to look like this:
+ :::vim
nnoremap <c-q> :call QuickfixToggle()<cr>
let g:quickfix_is_open = 0
--- a/chapters/39.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/39.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -29,6 +29,7 @@
Add the following function to your file:
+ :::vim
function! Sorted(l)
let new_list = deepcopy(a:l)
call sort(new_list)
@@ -46,6 +47,7 @@
This prevents side effects and helps us write code that is easier to reason
about and test. Let's add a few more helper functions in this same style:
+ :::vim
function! Reversed(l)
let new_list = deepcopy(a:l)
call reverse(new_list)
@@ -89,6 +91,7 @@
Vimscript supports using variables to store functions, but the syntax is a bit
obtuse. Run the following commands:
+ :::vim
:let Myfunc = function("Append")
:echo Myfunc([1, 2], 3)
@@ -99,6 +102,7 @@
Functions can be stored in lists just like any other kind of variable. Run the
following commands:
+ :::vim
:let funcs = [function("Append"), function("Pop")]
:echo funcs[1](['a', 'b', 'c'], 1)
@@ -115,6 +119,7 @@
We'll begin with the trusty "map" function. Add this to your file:
+ :::vim
function! Mapped(fn, l)
let new_list = deepcopy(a:l)
call map(new_list, string(a:fn) . '(v:val)')
@@ -123,6 +128,7 @@
Source and write the file, and try it out by running the following commands:
+ :::vim
:let mylist = [[1, 2], [3, 4]]
:echo Mapped(function("Reversed"), mylist)
@@ -140,6 +146,7 @@
Now we'll create a few other common higher-order functions. Add the following to
your file:
+ :::vim
function! Filtered(fn, l)
let new_list = deepcopy(a:l)
call filter(new_list, string(a:fn) . '(v:val)')
@@ -148,6 +155,7 @@
Try `Filtered()` out with the following commands:
+ :::vim
:let mylist = [[1, 2], [], ['foo'], []]
:echo Filtered(function('len'), mylist)
@@ -160,6 +168,7 @@
Finally we'll create the counterpart to `Filtered()`:
+ :::vim
function! Removed(fn, l)
let new_list = deepcopy(a:l)
call filter(new_list, '!' . string(a:fn) . '(v:val)')
@@ -168,6 +177,7 @@
Try it out just like we did with `Filtered()`:
+ :::vim
:let mylist = [[1, 2], [], ['foo'], []]
:echo Removed(function('len'), mylist)
--- a/chapters/40.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/40.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -12,6 +12,7 @@
Sometimes it's handy to be able to get the absolute path of a certain file for
use with external scripts. Run the following commands:
+ :::vim
:echom expand('%')
:echom expand('%:p')
:echom fnamemodify('foo.txt', ':p')
@@ -35,12 +36,14 @@
You might also want to get a listing of files in a specific directory. Run the
following command:
+ :::vim
:echo globpath('.', '*')
Vim will display all of the files and directories in the current directory. The
`globpath()` function returns a string, with each name separated by a newline.
To get a list you'll need to `split()` it yourself. Run this command:
+ :::vim
:echo split(globpath('.', '*'), '\n')
This time Vim displays a Vimscript list containing each path. If you've got
@@ -49,12 +52,14 @@
`globpath()`'s wildcards work mostly as you would expect. Run the following
command:
+ :::vim
:echo split(globpath('.', '*.txt'), '\n')
Vim displays a list of all `.txt` files in the current directory.
You can recursively list files with `**`. Run this command:
+ :::vim
:echo split(globpath('.', '**'), '\n')
Vim will list all files and directories under the current directory.
--- a/chapters/43.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/43.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -41,16 +41,19 @@
called `mycolor.vim` (you can leave it empty for this demonstration). Open Vim
and run this command:
+ :::vim
:color mycolor
Vim will display an error, because it doesn't know to look on your Desktop for
files. Now run this command:
+ :::vim
:set runtimepath=/Users/sjl/Desktop
You'll need to change the path to match the path of your own Desktop, of course.
Now try the color command again:
+ :::vim
:color mycolor
This time Vim doesn't throw an error, because it was able to find the
@@ -85,6 +88,7 @@
Our plugin's repository will wind up looking like this:
+ :::text
potion/
README
LICENSE
@@ -115,5 +119,3 @@
Push the repository up to Bitbucket or GitHub.
Read `:help runtimepath`.
-
-
--- a/chapters/44.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/44.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -5,6 +5,7 @@
plugin. Create a `factorial.pn` file somewhere and put the following Potion
code inside it:
+ :::text
factorial = (n):
total = 1
n to 1 (i):
@@ -21,6 +22,7 @@
the results each time. Go ahead and run it with `potion factorial.pn`. The
output should look like this:
+ :::text
0! is: 0
1! is: 1
2! is: 2
@@ -44,6 +46,7 @@
Open `factorial.pn` in Vim and run the following command:
+ :::vim
:set filetype?
Vim will display `filetype=` because it doesn't know what a `.pn` file is yet.
@@ -52,6 +55,7 @@
Create `ftdetect/potion.vim` in your plugin's repo. Put the following lines
into it:
+ :::vim
au BufNewFile,BufRead *.pn set filetype=potion
This creates a single autocommand: a command to set the filetype of `.pn` files
@@ -64,6 +68,7 @@
Close the `factorial.pn` file and reopen it. Now run the previous command
again:
+ :::vim
:set filetype?
This time Vim displays `filetype=potion`. When Vim started up it loaded the
--- a/chapters/45.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/45.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -8,6 +8,7 @@
Create a `syntax/potion.vim` file in your plugin's repo. Put the following code
into the file:
+ :::vim
if exists("b:current_syntax")
finish
endif
@@ -36,6 +37,7 @@
Replace the placeholder `echom` in the file with the following code:
+ :::vim
syntax keyword potionKeyword to times
highlight link potionKeyword Keyword
@@ -59,6 +61,7 @@
Potion has a bunch of other keywords that we haven't used in our toy program, so
lets edit our syntax file to highlight those too:
+ :::vim
syntax keyword potionKeyword loop times to while
syntax keyword potionKeyword if elsif else
syntax keyword potionKeyword class return
@@ -89,6 +92,7 @@
built-in Potion functions to our highlighting script. Edit the guts of your
syntax file so it looks like this:
+ :::vim
syntax keyword potionKeyword loop times to while
syntax keyword potionKeyword if elsif else
syntax keyword potionKeyword class return
--- a/chapters/46.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/46.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -27,6 +27,7 @@
match it (and the rest of the comment). We'll do this with `syntax match`
instead of `syntax keyword`. Add the following lines to your syntax file:
+ :::vim
syntax match potionComment "\v#.*$"
highlight link potionComment Comment
@@ -64,6 +65,7 @@
Another part of Potion we need regexes to highlight is operators. Add the
following to your syntax file:
+ :::vim
syntax match potionOperator "\v\*\="
syntax match potionOperator "\v/\="
syntax match potionOperator "\v\+\="
@@ -114,6 +116,7 @@
Let's go ahead and add `=` as an operator, now that we've had our lesson:
+ :::vim
syntax match potionOperator "\v\="
Take a second and think about where you need to put this in the syntax file.
--- a/chapters/47.markdown Tue Nov 29 14:29:58 2011 +1100
+++ b/chapters/47.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -14,6 +14,7 @@
world!"`. We should highlight these as strings. To do this we'll use the
`syntax region` command. Add the following to your Potion syntax file:
+ :::vim
syntax region potionString start=/\v"/ skip=/\v\\./ end=/\v"/
highlight link potionString String
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chapters/48.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -0,0 +1,147 @@
+Basic Folding
+=============
+
+If you've never used code folding in Vim, you don't know what you're missing.
+Read `:help usr_28` and spend some time playing around with it in your normal
+work. Come back to this chapter once you've got it in your fingers.
+
+Types of Folding
+----------------
+
+Vim supports six different ways of defining how your text should be folded.
+
+### Manual
+
+You create the folds by hand and they're stored in RAM by Vim. When you close
+Vim they go away and you have to recreate them the next time you edit the file.
+
+This method can be handy if you combine it with some custom mappings to make it
+easy to create folds. We won't do that in this book, but keep it in the back
+of your mind in case you run across a case where it could be handy.
+
+### Marker
+
+Vim folds your code based on characters in the actual text.
+
+Usually these characters are put in comments (like `// {{{`), but in some
+languages you can get away with using something in the language's syntax itself,
+like `{` and `}` in Javascript files.
+
+It may seem ugly to clutter up your code with comments that are purely for your
+text editor, but the advantage is that it lets you hand-craft folds for
+a specific file. This can be really nice if you're working with a large file
+that you want to organize in a very specific way.
+
+### Diff
+
+A special folding mode used when diff'ing files. We won't talk about this one
+at all because Vim automatically handles it.
+
+### Expr
+
+This lets you use a custom piece of Vimscript to define where folds occur. It's
+the most powerful method, but also requires the most work. We'll talk about
+this in the next chapter.
+
+### Indent
+
+Vim uses your code's indentation to determine folds. Lines at the same
+indentation level fold together, and lines with only whitespace (and blank
+lines) are simply folded with their neighbors.
+
+This is essentially free to use because your code is already indented; all you
+have to do is turn it on. This will be our first method of adding folding to
+Potion files.
+
+Potion Folding
+--------------
+
+Let's take a look at our sample Potion file once again:
+
+ :::text
+ factorial = (n):
+ total = 1
+ n to 1 (i):
+ total *= i.
+ total.
+
+ 10 times (i):
+ i string print
+ '! is: ' print
+ factorial (i) string print
+ "\n" print.
+
+The bodies of the function and loop are both indented. This means we can get
+some basic folding with very little effort by using indent folding.
+
+Before we start, go ahead and add a comment above the `total *= i.` line so we
+have a nice multiple-line inner block to test with. You'll learn why we need to
+do this when you do the exercises, but for now just trust me. The file should
+now look like this:
+
+ :::text
+ factorial = (n):
+ total = 1
+ n to 1 (i):
+ # Multiply the running total.
+ total *= i.
+ total.
+
+ 10 times (i):
+ i string print
+ '! is: ' print
+ factorial (i) string print
+ "\n" print.
+
+Create an `ftplugin` folder in your Potion plugin's repository, and create
+a `potion` folder inside that. Finally, create a `folding.vim` file inside of
+*that*.
+
+Remember that Vim will run the code in this file whenever it sets a buffer's
+`filetype` to `potion` (because it's in a folder named `potion`).
+
+Putting all folding-related code into its own file is generally a good idea and
+will help us keep the various functionality of our plugin organized.
+
+Add the following line to this file:
+
+ :::vim
+ setlocal foldmethod=indent
+
+Close Vim and open the `factorial.pn` file again. Play around with the new
+folding with `zR`, `zM`, and `za`.
+
+One line of Vimscript gave us some useful folding! That's pretty cool!
+
+You might notice that the lines inside the inner loop of the `factorial`
+function aren't folded even though they're indented. What's going on?
+
+It turns out that by default Vim will ignore lines beginning with a `#`
+character when using `indent` folding. This works great when editing C files
+(where `#` signals a preprocessor directive) but isn't very helpful when you're
+editing other types of files.
+
+Let's add one more line to the `ftplugin/potion/folding.vim` file to fix this:
+
+ :::vim
+ setlocal foldmethod=indent
+ setlocal foldignore=
+
+Close and reopen `factorial.pn` and now the inner block will be folded properly.
+
+Exercises
+---------
+
+Read `:help foldmethod`.
+
+Read `:help fold-manual`.
+
+Read `:help fold-marker` and `:help foldmarker`.
+
+Read `:help fold-indent`.
+
+Read `:help fdl` and `:help foldlevelstart`.
+
+Read `:help foldminlines`.
+
+Read `:help foldignore`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chapters/49.markdown Sat Dec 31 13:27:50 2011 -0500
@@ -0,0 +1,761 @@
+Advanced Folding
+================
+
+In the last chapter we used Vim's `indent` folding to add some quick and dirty
+folding to Potion files.
+
+Open `factorial.pn` and make sure all the folds are closed with `zM`. The file
+should now look something like this:
+
+ :::text
+ factorial = (n):
+ +-- 5 lines: total = 1
+
+ 10 times (i):
+ +-- 4 lines: i string print
+
+Toggle the first fold and it will look like this:
+
+ :::text
+ factorial = (n):
+ total = 1
+ n to 1 (i):
+ +--- 2 lines: # Multiply the running total.
+ total.
+
+ 10 times (i):
+ +-- 4 lines: i string print
+
+This is pretty nice, but I personally prefer to fold the first line of a block
+with its contents. In this chapter we'll write some custom folding code, and
+when we're done our folds will look like this:
+
+ :::text
+ factorial = (n):
+ total = 1
+ +--- 3 lines: n to 1 (i):
+ total.
+
+ +-- 5 lines: 10 times (i):
+
+This is more compact and (to me) easier to read. If you prefer the `indent`
+method that's okay, but do this chapter anyway just to get some practice writing
+Vim folding expressions.
+
+Folding Theory
+--------------
+
+When writing custom folding code it helps to have an idea of how Vim "thinks" of
+folding. Here are the rules in a nutshell:
+
+* Each line of code in a file has a "foldlevel". This is always either zero or
+ a positive integer.
+* Lines with a foldlevel of zero are *never* included in any fold.
+* Adjacent lines with the same foldlevel are folded together.
+* If a fold of level X is closed, any subsequent lines with a foldlevel greater
+ than or equal to X are folded along with it until you reach a line with
+ a level less than X.
+
+It's easiest to get a feel for this with an example. Open a Vim window and
+paste the following text into it.
+
+ :::text
+ a
+ b
+ c
+ d
+ e
+ f
+ g
+
+Turn on `indent` folding by running the following command:
+
+ :::vim
+ :setlocal foldmethod=indent
+
+Play around with the folds for a minute to see how they behave.
+
+Now run the following command to view the foldlevel of line 1:
+
+ :::vim
+ :echom foldmethod(1)
+
+Vim will display `0`. Now let's find the foldlevel of line 2:
+
+ :::vim
+ :echom foldmethod(2)
+
+Vim will display `1`. Let's try line 3:
+
+ :::vim
+ :echom foldmethod(3)
+
+Once again Vim displays `1`. This means that lines 2 and 3 are part of a level
+1 fold.
+
+Here are the foldlevels for each line:
+
+ :::text
+ a 0
+ b 1
+ c 1
+ d 2
+ e 2
+ f 1
+ g 0
+
+Reread the rules at the beginning of this section. Open and close each fold in
+this file, look at the foldlevels, and make sure you understand why the folds
+behave as they do.
+
+Once you're confident that you understand how every line's foldlevel works to
+create the folding structure, move on to the next section.
+
+
+First: Make a Plan
+------------------
+
+Before we dive into writing code, let's try to sketch out some rough "rules" for
+our folding.
+
+First, lines that are indented should be folded together. We also want the
+*previous* line folded with them, so that something like this:
+
+ :::text
+ hello = (name):
+ 'Hello, ' print
+ name print.
+
+Will fold like this:
+
+ :::text
+ +-- 3 lines: hello = (name):
+
+Blank lines should be at the same level as *later* lines, so blank lines at the
+end of a fold won't be included in it. This means that this:
+
+ :::text
+ hello = (name):
+ 'Hello, ' print
+ name print.
+
+ hello('Steve')
+
+Will fold like this:
+
+ :::text
+ +-- 3 lines: hello = ():
+
+ hello('Steve')
+
+And *not* like this:
+
+ :::text
+ +-- 4 lines: hello = ():
+ hello('Steve')
+
+These rules are a matter of personal preference, but for now this is the way
+we're going to implement folding.
+
+Getting Started
+---------------
+
+Let's get started on our custom folding code by opening Vim with two splits.
+One should contain our `ftplugin/potion/folding.vim` file, and the other should
+contain our sample `factorial.pn`.
+
+In the previous chapter we closed and reopened Vim to make our changes to
+`folding.vim` take effect, but it turns out there's an easier way to do that.
+
+Remember that any files inside `ftplugin/potion/` will be run whenever the
+`filetype` of a buffer is set to `potion`. This means you can simply run `:set
+ft=potion` in the split containing `factorial.pn` and Vim will reload the
+folding code!
+
+This is much faster than closing and reopening the file every time. The only
+thing you need to remember is that you have to *save* `folding.vim` to disk,
+otherwise your unsaved changes won't be taken into account.
+
+Expr Folding
+------------
+
+We're going to use Vim's `expr` folding to give us unlimited flexibility in how
+our code is folded.
+
+We can go ahead and remove the `foldignore` from `folding.vim` because it's only
+relevant when using `indent` folding. We also want to tell Vim to use `expr`
+folding, so change the contents of `folding.vim` to look like this:
+
+ :::vim
+ setlocal foldmethod=expr
+ setlocal foldexpr=GetPotionFold(v:lnum)
+
+ function! GetPotionFold(lnum)
+ return '0'
+ endfunction
+
+The first line simply tells Vim to use `expr` folding.
+
+The second line defines the expression Vim should use to get the foldlevel of
+a line. When Vim runs the expression it will set `v:lnum` to the line number of
+the line it wants to know about. Our expression will call a custom function
+with this number as an argument.
+
+Finally we define a dummy function that simply returns `'0'` for every line.
+Note that it's returning a String and not an Integer. We'll see why shortly.
+
+Go ahead and reload the folding code by saving `folding.vim` and running `:set
+ft=potion` in `factorial.pn`. Our function returns `'0'` for every line, so
+Vim won't fold anything at all.
+
+Blank Lines
+-----------
+
+Let's take care of the special case of blank lines first. Modify the
+`GetPotionFold` function to look like this:
+
+ :::vim
+ function! GetPotionFold(lnum)
+ if getline(a:lnum) =~? '\v^\s*$'
+ return '-1'
+ endif
+
+ return '0'
+ endfunction
+
+We've added an `if` statement to take care of the blank lines. How does it
+work?
+
+First we use `getline(a:lnum)` to get the content of the current line as
+a String.
+
+We compare this to the regex `\v^\s*$`. Remember that `\v` turns on "very
+magic" ("sane") mode. This regex will match "beginning of line, any number
+of whitespace characters, end of line".
+
+The comparison is using the case-insensitive match operator `=~?`. Technically
+we don't have to be worried about case since we're only matching whitespace, but
+I prefer to be more explicit when using comparison operators on Strings. You
+can use `=~` instead if you prefer.
+
+If you need a refresher on using regular expressions in Vim you should go back
+and reread the "Basic Regular Expressions" chapter and the chapters on the "Grep
+Operator".
+
+If the current line has some non-whitespace characters it won't match and we'll
+just return `'0'` as before.
+
+If the current line *does* match the regex (i.e. is it's empty or just
+whitespace) we return the string `'-1'`.
+
+Earlier I said that a line's foldlevel can be zero or a positive integer, so
+what's happening here?
+
+Special Foldlevels
+------------------
+
+Your custom folding expression can return a foldlevel directly, or return one of
+a few "special" strings that tell Vim how to fold the line without directly
+specifying its level.
+
+`'-1'` is one of these special strings. It tells Vim that the level of this
+line is "undefined". Vim will interpret this as "the foldlevel of this line is
+equal to the foldlevel of the line above or below it, whichever is smaller".
+
+This isn't *exactly* what our plan called for, but we'll see that it's close
+enough and will do what we want.
+
+Vim can "chain" these undefined lines together, so if you have two in a row
+followed by a line at level 1, it will set the last undefined line to 1, then
+the next to last to 1, then the first to 1.
+
+When writing custom folding code you'll often find a few types of line that you
+can easily set a specific level for. Then you'll use `'-1'` (and some other
+special foldlevels we'll see soon) to "cascade" the proper folding levels to the
+rest of the file.
+
+If you reload the folding code for `factorial.pn` Vim *still* won't fold any
+lines together. This is because all the lines have a foldlevel of either zero
+or "undefined". The level `0` will "cascade" through the undefined lines and
+eventually all the lines will have their foldlevel set to `0`.
+
+An Indentation Level Helper
+---------------------------
+
+To tackle non-blank lines we'll need to know their indentation level, so let's
+create a small helper function to calculate it for us. Add the following
+function above `GetPotionFold`:
+
+ :::vim
+ function! IndentLevel(lnum)
+ return indent(a:lnum) / &shiftwidth
+ endfunction
+
+Reload the folding code. Test out your function by running the following
+command in the `factorial.pn` buffer:
+
+ :::vim
+ :echom IndentLevel(1)
+
+Vim displays `0` because line 1 is not indented. Now try it on line 2:
+
+ :::vim
+ :echom IndentLevel(2)
+
+This time Vim displays `1`. Line two has 4 spaces at the beginning, and
+`shiftwidth` is set to 4, so 4 divided by 4 is 1.
+
+`IndentLevel` is fairly straightforward. The `indent(a:lnum)` returns the
+number of spaces at the beginning of the given line number. We divide that by
+the `shiftwidth` of the buffer to get the indentation level.
+
+Why did we use `&shiftwidth` instead of just dividing by 4? If someone prefers
+two-space indentation in their Potion files, dividing by 4 would produce an
+incorrect result. We use the `shiftwidth` setting to allow for any number of
+spaces per level.
+
+One More Helper
+---------------
+
+It might not be obvious where to go from here. Let's stop and think about what
+type of information we need to have to figure out how to fold a non-blank line.
+
+We need to know the indentation level of the line itself. We've got that
+covered with the `IndentLevel` function, so we're all set there.
+
+We'll also need to know the indentation level of the *next non-blank line*,
+because we want to fold the "header" lines with their indented bodies.
+
+Let's write a helper function to get the number of the next non-blank line after
+a given line. Add the following function above `IndentLevel`:
+
+ :::vim
+ function! s:NextNonBlankLine(lnum)
+ let numlines = line('$')
+ let current = a:lnum + 1
+
+ while current <= numlines
+ if getline(current) =~? '\v\S'
+ return current
+ endif
+
+ let current += 1
+ endwhile
+
+ return -2
+ endfunction
+
+This function is a bit longer, but is pretty simple. Let's take it
+piece-by-piece.
+
+First we store the total number of lines in the file with `line('$')`. Check
+out the documentation for `line()` to see how this works.
+
+Next we set the variable `current` to the number of the next line.
+
+We then start a loop that will walk through each line in the file.
+
+If the line matches the regex `\v\S`, which means "match a character that's
+*not* a whitespace character", then it must be non-blank, so we should return
+its line number.
+
+If the line doesn't match, we loop around to the next one.
+
+If the loop gets all the way to the end of the file without ever returning, then
+there are *no* non-blank lines after the current line! We return `-2` if that
+happens to indicate this. `-2` isn't a valid line number, so it's an easy way
+to say "sorry, there's no valid result".
+
+We could have returned `-1`, because that's not a valid line number either.
+I could have even picked `0`, since line numbers in Vim start at `1`! So why
+did I pick `-2`, which seems like a strange choice?
+
+I chose `-2` because we're working with folding code, and `'-1'` (and `'0'`) is
+a special Vim foldlevel string.
+
+When my eyes are reading over this file and I see a `-1` my brain immediately
+thinks "undefined foldlevel". The same is true with `0`. I picked `-2` here
+simply to make it obvious that it's *not* a foldlevel, but is instead an
+"error".
+
+If this feels weird to you, you can safely change the `-2` to a `-1` or a `0`.
+It's just a coding style preference.
+
+Finishing the Fold Function
+---------------------------
+
+This is turning out to be quite a long chapter, so let's wrap up the folding
+function. Change `GetPotionFold` to look like this:
+
+ :::vim
+ function! GetPotionFold(lnum)
+ if getline(a:lnum) =~? '\v^\s*$'
+ return '-1'
+ endif
+
+ let this_indent = IndentLevel(a:lnum)
+ let next_indent = IndentLevel(NextNonBlankLine(a:lnum))
+
+ if next_indent == this_indent
+ return this_indent
+ elseif next_indent < this_indent
+ return this_indent
+ elseif next_indent > this_indent
+ return '>' . next_indent
+ endif
+ endfunction
+
+That's a lot of new code! Let's step through it to see how it all works.
+
+### Blanks
+
+First we have our check for blank lines. Nothing's changed there.
+
+If we get past that check we know we're looking at a non-blank line.
+
+### Finding Indentation Levels
+
+Next we use our two helper functions to get the indent level of the current
+line, and the indent level of the next non-blank line.
+
+You might wonder what happens if `NextNonBlankLine` returns our error condition
+of `-2`. If that happens, `indent(-2)` will be run. Running `indent()` on
+a nonexistent line number will just return `-1`. Go ahead and try it yourself
+with `:echom indent(-2)`.
+
+`-1` divided by any `shiftwidth` larger than `1` will return `0`. This may seem
+like a problem, but it turns out that it won't be. For now, don't worry about
+it.
+
+### Equal Indents
+
+Now that we have the indentation levels of the current line and the next
+non-blank line, we can compare them and decide how to fold the current line.
+
+Here's the `if` statement again:
+
+ :::vim
+ if next_indent == this_indent
+ return this_indent
+ elseif next_indent < this_indent
+ return this_indent
+ elseif next_indent > this_indent
+ return '>' . next_indent
+ endif
+
+First we check if the two lines have the same indentation level. If they do, we
+simply return that indentation level as the foldlevel!
+
+An example of this would be:
+
+ :::text
+ a
+ b
+ c
+ d
+ e
+
+If we're looking at the line containing "c", it has an indentation level of 1.
+This is the same as the level of the next non-blank line ("d"), so we return `1`
+as the foldlevel.
+
+If we're looking at "a", it has an indentation level of 0. This is the same as
+the level of the next non-blank line ("b"), so we return `0` as the foldlevel.
+
+This case fills in two foldlevels in this simple example:
+
+ :::text
+ a 0
+ b ?
+ c 1
+ d ?
+ e ?
+
+By pure luck this also handles the special "error" case of the last line as
+well! Remember we said that `next_indent` will be `0` if our helper function
+returns `-2`.
+
+In this example the line "e" has an indent level of `0`, and `next_indent` will
+also be set to `0`, so this case matches and returns `0`. The foldlevels now
+look like this:
+
+ :::text
+ a 0
+ b ?
+ c 1
+ d ?
+ e 0
+
+### Lesser Indent Levels
+
+Once again, here's the `if` statement:
+
+ :::vim
+ if next_indent == this_indent
+ return this_indent
+ elseif next_indent < this_indent
+ return this_indent
+ elseif next_indent > this_indent
+ return '>' . next_indent
+ endif
+
+The second part of the `if` checks if the indentation level of the next line is
+*smaller* than the current line. This would be like line "d" in our example.
+
+If that's the case, we once again return the indentation level of the current
+line.
+
+Now our example looks like this:
+
+ :::text
+ a 0
+ b ?
+ c 1
+ d 1
+ e 0
+
+You could, of course, combine these two cases with `&&`, but I prefer to keep
+them separate to make it more explicit. You might feel differently. It's
+a style issue.
+
+Again, purely by luck, this case handles the other possible "error" case of our
+helper function. Imagine that we have a file like this:
+
+ :::text
+ a
+ b
+ c
+
+The first case takes care of line "b":
+
+ :::text
+ a ?
+ b 1
+ c ?
+
+Line "c" is the last line, and it has an indentation level of 1. The
+`next_indent` will be set to `0` thanks to our helper functions. The second
+part of the `if` matches and sets the foldlevel to the current indentation
+level, or `1`:
+
+ :::text
+ a ?
+ b 1
+ c 1
+
+This works out great, because "b" and "c" will be folded together.
+
+### Greater Indentation Levels
+
+Here's that tricky `if` statement for the last time:
+
+ :::vim
+ if next_indent == this_indent
+ return this_indent
+ elseif next_indent < this_indent
+ return this_indent
+ elseif next_indent > this_indent
+ return '>' . next_indent
+ endif
+
+And our example file:
+
+ :::text
+ a 0
+ b ?
+ c 1
+ d 1
+ e 0
+
+The only line we haven't figured out is "b", because:
+
+* "b" has an indent level of `0`.
+* "c" has an indent level of `1`.
+* 1 is not equal to 0, nor is 1 less than 0.
+
+The last case checks if the next line has a *larger* indentation level than the
+current one.
+
+This is the case that Vim's `indent` folding gets wrong, and it's the entire
+reason we're writing this custom folding in the first place!
+
+The final case says that when the next line is indented more than the current
+one, it should return a string of a `>` character and the indentation level of
+the *next* line. What the heck is *that*?
+
+Returning a string like `>1` from the fold expression is another one of Vim's
+"special" foldlevels. It tells Vim that the current line should *open* a fold
+of the given level.
+
+In this simple example we could have just returned the number, but we'll see
+why this is important shortly.
+
+In this case line "b" will open a fold at level 1, which makes our example look
+like this:
+
+ :::text
+ a 0
+ b >1
+ c 1
+ d 1
+ e 0
+
+That's exactly what we want! Hooray!
+
+Review
+------
+
+If you've made it this far you should feel proud of yourself. Even simple
+folding code like this can be tricky and mind bending.
+
+Before we end, let's go through our original `factorial.pn` code and see how our
+folding expression fills in the foldlevels of its lines.
+
+Here's `factorial.pn` for reference:
+
+ :::text
+ factorial = (n):
+ total = 1
+ n to 1 (i):
+ # Multiply the running total.
+ total *= i.
+ total.
+
+ 10 times (i):
+ i string print
+ '! is: ' print
+ factorial (i) string print
+ "\n" print.
+
+First, any blank lines' foldlevels will be set to undefined:
+
+ :::text
+ factorial = (n):
+ total = 1
+ n to 1 (i):
+ # Multiply the running total.
+ total *= i.
+ total.
+ undefined
+ 10 times (i):
+ i string print
+ '! is: ' print
+ factorial (i) string print
+ "\n" print.
+
+Any lines where the next line's indentation is *equal* to its own are set to its
+own level:
+
+ :::text
+ factorial = (n):
+ total = 1 1
+ n to 1 (i):
+ # Multiply the running total. 2
+ total *= i.
+ total.
+ undefined
+ 10 times (i):
+ i string print 1
+ '! is: ' print 1
+ factorial (i) string print 1
+ "\n" print. 1
+
+The same thing happens when the next line's indentation is *less* than the
+current line's:
+
+ :::text
+ factorial = (n):
+ total = 1 1
+ n to 1 (i):
+ # Multiply the running total. 2
+ total *= i. 2
+ total. 1
+ undefined
+ 10 times (i):
+ i string print 1
+ '! is: ' print 1
+ factorial (i) string print 1
+ "\n" print. 1
+
+The last case is when the next line's indentation is *greater* than the current
+line's. When that happens the line's foldlevel is set to *open* a fold of the
+*next* line's foldlevel:
+
+ :::text
+ factorial = (n): >1
+ total = 1 1
+ n to 1 (i): >2
+ # Multiply the running total. 2
+ total *= i. 2
+ total. 1
+ undefined
+ 10 times (i): >1
+ i string print 1
+ '! is: ' print 1
+ factorial (i) string print 1
+ "\n" print. 1
+
+Now we've got a foldlevel for every line in the file. All that's left is for
+Vim to resolve any undefined lines.
+
+Earlier I said that undefined lines will take on the smallest foldlevel of
+either of their neighbors.
+
+That's how Vim's manual describes it, but it's not entirely accurate. If that
+were the case, the blank line in our file would take foldlevel 1, because both
+of its neighbors have a foldlevel of 1.
+
+In reality, the blank line will be given a foldlevel of 0!
+
+The reason for this is that we didn't just set the `10 times (i):` line to
+foldlevel `1` directly. We told Vim that the line *opens* a fold of level `1`.
+Vim is smart enough to know that this means the undefined line should be set to
+`0` instead of `1`.
+
+The exact logic of this is probably buried deep within Vim's source code. In
+general Vim behaves pretty intelligently when resolving undefined lines against
+"special" foldlevels, so it will usually do what you want.
+
+Once Vim's resolved the undefined line it has a complete description of how to
+fold each line in the file, which looks like this:
+
+ :::text
+ factorial = (n): 1
+ total = 1 1
+ n to 1 (i): 2
+ # Multiply the running total. 2
+ total *= i. 2
+ total. 1
+ 0
+ 10 times (i): 1
+ i string print 1
+ '! is: ' print 1
+ factorial (i) string print 1
+ "\n" print. 1
+
+That's it, we're done! Reload the folding code and play around with the fancy
+new folding in `factorial.pn`.
+
+Exercises
+---------
+
+Read `:help foldexpr`.
+
+Read `:help fold-expr`. Pay particular attention to all the "special" strings
+your expression can return.
+
+Read `:help getline`.
+
+Read `:help indent()`.
+
+Read `:help line()`.
+
+Figure out why it's important that we use `.` to combine the `>` character with
+the number in our folding function. What would happen if you used `+` instead?
+Why?
+
+We defined our helper functions as global functions, but that's not a good idea.
+Change them to be script-local functions.
+
+Put this book down and go outside for a while to let your brain recover from
+this chapter.
--- a/outline.org Tue Nov 29 14:29:58 2011 +1100
+++ b/outline.org Sat Dec 31 13:27:50 2011 -0500
@@ -47,15 +47,15 @@
** DONE plugin layout
** DONE pathogen
** DONE ftdetect
-** TODO syntax
-** TODO autoload
-** TODO folding
+** DONE syntax
+** DONE folding
** TODO compilers
*** makeprg
*** errorformat
+** TODO autoload
** TODO customization
-** TODO documentation
+** TODO dynamic status lines
** TODO mapping
-** TODO dynamic status lines
** TODO customizing maps
+** TODO documentation
** TODO distributing