# HG changeset patch # User Steve Losh # Date 1325355996 18000 # Node ID c40a55cd4ae4e07eea48c768e948731312f2a5d4 # Parent 4ba338c385b308553d8aa6abeaf380683ca844df# Parent e0ad28e711966a98632cdd8a1c6935ccf3668425 Merge. diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/01.markdown --- a/chapters/01.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/01.markdown Sat Dec 31 13:26:36 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 za diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/02.markdown --- a/chapters/02.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/02.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/03.markdown --- a/chapters/03.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/03.markdown Sat Dec 31 13:26:36 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 `` to tell Vim about special keys that are hard to type. Try running this command: + :::vim :map 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 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 viw " Use space to select a word If you try pressing `` now, something horrible will almost certainly diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/04.markdown --- a/chapters/04.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/04.markdown Sat Dec 31 13:26:36 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 dd + :::vim + :imap 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 dd + :::vim + :imap dd The `` 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 ddi + :::vim + :imap ddi The `i` at the end reenters insert mode for us, and our mapping is finally complete. diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/05.markdown --- a/chapters/05.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/05.markdown Sat Dec 31 13:26:36 2011 -0500 @@ -8,6 +8,7 @@ Run the following commands: + :::vim :nmap - dd :nmap \ - @@ -26,6 +27,7 @@ Run this command: + :::vim :nmap dd Ojddk 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/06.markdown --- a/chapters/06.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/06.markdown Sat Dec 31 13:26:36 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 `` to mean "whatever I have my leader key set to". Run this command: + :::vim :nnoremap 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/07.markdown --- a/chapters/07.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/07.markdown Sat Dec 31 13:26:36 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 ev :vsplit $MYVIMRC 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 sv :source $MYVIMRC I like to think of this command as "**s**ource my **v**imrc file". diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/08.markdown --- a/chapters/08.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/08.markdown Sat Dec 31 13:26:36 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 --Steve Loshsteve@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 --Steve Loshsteve@stevelosh.com diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/09.markdown --- a/chapters/09.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/09.markdown Sat Dec 31 13:26:36 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 " viwa"hbi"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 viwa"hbi"lel * `viw`: visually select the current word diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/10.markdown --- a/chapters/10.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/10.markdown Sat Dec 31 13:26:36 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 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 This effectively disables the escape key in insert mode by telling Vim to diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/11.markdown --- a/chapters/11.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/11.markdown Sat Dec 31 13:26:36 2011 -0500 @@ -17,6 +17,7 @@ Switch to file `foo` and run the following commands: + :::vim :nnoremap d dd :nnoremap 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 Q x :nnoremap Q dd diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/12.markdown --- a/chapters/12.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/12.markdown Sat Dec 31 13:26:36 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

Hello!

@@ -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 c I// :autocmd FileType python nnoremap c I# diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/13.markdown --- a/chapters/13.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/13.markdown Sat Dec 31 13:26:36 2011 -0500 @@ -8,10 +8,12 @@ Open your `foo` and `bar` files again, switch to `foo`, and run the following command: + :::vim :iabbrev --- — 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 iff if ( ) {} :autocmd FileType python :iabbrev iff if: diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/14.markdown --- a/chapters/14.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/14.markdown Sat Dec 31 13:26:36 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 f Vatzf diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/15.markdown --- a/chapters/15.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/15.markdown Sat Dec 31 13:26:36 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 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( :normal! f(vi( 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( :normal! F)vi( 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( `: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: diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/16.markdown --- a/chapters/16.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/16.markdown Sat Dec 31 13:26:36 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 :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" 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_" 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 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! ?^==\+$:nohlsearchkvg_ ^^^^ ^^^^ || || @@ -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 :execute "normal! ?^==\\+\r:nohlsearch\rg_vk0" 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 :execute "normal! ?^==\\+$\r:nohlsearch\rkvg_" :onoremap ah :execute "normal! ?^==\\+$\r:nohlsearch\rg_vk0" The only difference from the previous mapping is the very end, where we select the text to operate on: + :::vim kvg_ g_vk0 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/17.markdown --- a/chapters/17.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/17.markdown Sat Dec 31 13:26:36 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 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" diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/23.markdown --- a/chapters/23.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/23.markdown Sat Dec 31 13:26:36 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!" diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/24.markdown --- a/chapters/24.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/24.markdown Sat Dec 31 13:26:36 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" diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/25.markdown --- a/chapters/25.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/25.markdown Sat Dec 31 13:26:36 2011 -0500 @@ -12,20 +12,24 @@ 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 @@ -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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/26.markdown --- a/chapters/26.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/26.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/27.markdown --- a/chapters/27.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/27.markdown Sat Dec 31 13:26:36 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") diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/28.markdown --- a/chapters/28.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/28.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/29.markdown --- a/chapters/29.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/29.markdown Sat Dec 31 13:26:36 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 At first glance it may seem like this should perform a search for "foo", but diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/30.markdown --- a/chapters/30.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/30.markdown Sat Dec 31 13:26:36 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\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;\`q" What does this do? Let's break it apart: diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/31.markdown --- a/chapters/31.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/31.markdown Sat Dec 31 13:26:36 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\" 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\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\" 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 .+:\" 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 .\\+:\" 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 .\+:\' 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 .\+:' . "\" 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 .+:' . "\" We've split the pattern out from the rest of the command into its own literal diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/32.markdown --- a/chapters/32.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/32.markdown Sat Dec 31 13:26:36 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 g :grep -R something . 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 g :grep -R . Now try it out. `` is a special bit of text you can use in Vim's @@ -90,6 +92,7 @@ You can use `` to get a WORD instead of a word. Run this command: + :::vim :nnoremap g :grep -R . 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 g :grep -R '' . 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 g :execute "grep -R '' ." 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 g :execute "grep -R " . shellescape("") . " ." 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 g :execute "grep! -R " . shellescape("") . " ." 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 g :execute "grep! -R " . shellescape("") . " .":copen 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 g :silent execute "grep! -R " . shellescape("") . " .":copen We're done, so try it out and admire your hard work! The `silent` command just diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/33.markdown --- a/chapters/33.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/33.markdown Sat Dec 31 13:26:36 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 g :set operatorfunc=GrepOperatorg@ 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 g :call GrepOperator(visualmode()) Write and source the file. Now visually select something and press `g`. @@ -94,6 +96,7 @@ Edit the function body so the file looks like this: + :::vim nnoremap g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) @@ -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 g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) @@ -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 g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) @@ -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 g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/34.markdown --- a/chapters/34.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/34.markdown Sat Dec 31 13:26:36 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 g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) @@ -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 g :set operatorfunc=GrepOperatorg@ vnoremap g :call GrepOperator(visualmode()) diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/35.markdown --- a/chapters/35.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/35.markdown Sat Dec 31 13:26:36 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) diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/36.markdown --- a/chapters/36.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/36.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/37.markdown --- a/chapters/37.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/37.markdown Sat Dec 31 13:26:36 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', diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/38.markdown --- a/chapters/38.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/38.markdown Sat Dec 31 13:26:36 2011 -0500 @@ -7,6 +7,7 @@ Run the following command: + :::vim :nnoremap N :setlocal number! Try it out by pressing `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 :call FoldColumnToggle() function! FoldColumnToggle() @@ -37,6 +39,7 @@ Let's add in the actual toggling functionality. Edit the code to look like this: + :::vim nnoremap :call FoldColumnToggle() 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 :call QuickfixToggle() function! QuickfixToggle() @@ -74,6 +78,7 @@ slightly more useful (but not completely finished yet). Change the code to look like this: + :::vim nnoremap :call QuickfixToggle() 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 :call QuickfixToggle() 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 :call QuickfixToggle() 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 :call QuickfixToggle() let g:quickfix_is_open = 0 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/39.markdown --- a/chapters/39.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/39.markdown Sat Dec 31 13:26:36 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) diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/40.markdown --- a/chapters/40.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/40.markdown Sat Dec 31 13:26:36 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. diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/43.markdown --- a/chapters/43.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/43.markdown Sat Dec 31 13:26:36 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`. - - diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/44.markdown --- a/chapters/44.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/44.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/45.markdown --- a/chapters/45.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/45.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/46.markdown --- a/chapters/46.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/46.markdown Sat Dec 31 13:26:36 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. diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/47.markdown --- a/chapters/47.markdown Wed Nov 23 23:48:39 2011 -0400 +++ b/chapters/47.markdown Sat Dec 31 13:26:36 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 diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/48.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/48.markdown Sat Dec 31 13:26:36 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`. diff -r e0ad28e71196 -r c40a55cd4ae4 chapters/49.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/49.markdown Sat Dec 31 13:26:36 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. diff -r e0ad28e71196 -r c40a55cd4ae4 outline.org --- a/outline.org Wed Nov 23 23:48:39 2011 -0400 +++ b/outline.org Sat Dec 31 13:26:36 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