# HG changeset patch # User Steve Losh # Date 1319599313 14400 # Node ID 745eafc91857af43818d14f839c93bfa669dda5a # Parent 5c05eeeacf38ed7554eea2a6e379378ee4c1b745 Moar. diff -r 5c05eeeacf38 -r 745eafc91857 chapters/32.markdown --- a/chapters/32.markdown Tue Oct 25 20:55:03 2011 -0400 +++ b/chapters/32.markdown Tue Oct 25 23:21:53 2011 -0400 @@ -1,5 +1,5 @@ -Case Study: GrepMotion, Part One -================================ +Case Study: Grep Operator, Part One +=================================== In this chapter and the next we're going to walk through creating a fairly-complicated piece of Vimscript. We'll talk about several things we @@ -145,15 +145,24 @@ :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. Let's add that as the -finishing touch. Run the following command: +window with the results, but we haven't opened it yet. Run the following +command: - :nnoremap g :execute "grep! -R " . shellescape("") . " .":copen + :nnoremap G :execute "grep! -R " . shellescape("") . " .":copen Now try the mapping and you'll see that Vim automatically opens the quickfix window with the search results. All we did was tack `:copen` onto the end of our mapping. +As the finishing touch we'll remove all the grep output Vim displays while +searching. Run the following command: + + :nnoremap G :silent execute "grep! -R " . shellescape("") . " .":copen + +We're done, so try it out and admire your hard work! The `silent` command just +runs the command that follows it while hiding any messages it would normally +display. + Exercises --------- @@ -171,3 +180,5 @@ Add a height to the `:copen` command in the mapping we created to make sure the quickfix window is opened to whatever height you prefer. + +Read `:help silent`. diff -r 5c05eeeacf38 -r 745eafc91857 chapters/33.markdown --- a/chapters/33.markdown Tue Oct 25 20:55:03 2011 -0400 +++ b/chapters/33.markdown Tue Oct 25 23:21:53 2011 -0400 @@ -0,0 +1,255 @@ +Case Study: Grep Operator, Part Two +=================================== + +Now that we've got a preliminary sketch of our solution, it's time to flesh it +out into something powerful. + +Remember: our original goal was to create a "grep operator". There are a whole +bunch of new things we need to cover to do this, but we're going to follow the +same process we did in the last chapter: start with something simple and +transform it until it does what you need. + +Before we start, comment out the mapping we creating the previous chapter from +your `~/.vimrc` file -- we're going to use the same keystroke for our new +operator. + +Create a File +------------- + +Creating an operator will take a number of commands and typing those out by +hand will get tedious very quickly. You could add it to your `~/.vimrc` file, +but let's create a separate file just for this operator instead. It's meaty +enough to warrant a file of its own. + +First, find your Vim `plugin` directory. On Linux or OS X this will be at +`~/.vim/plugin`. If you're on Windows it will be at TODO. If this directory +doesn't exist, create it. + +Inside `plugin/` create a file named `grep-operator.vim`. This is where you'll +place the code for this new operator. When you're editing the file you can run +`:source %` to reload the code at any time. This file will also be loaded each +time you open Vim just like `~/.vimrc`. + +Remember that you *must* write the file before you source it for the changes to +be seen! + +Skeleton +-------- + +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`: + + nnoremap g :set operatorfunc=GrepOperatorg@ + + function! GrepOperator(type) + echom "Test" + endfunction + +Write the file and source it with `:source %`. Try it out by pressing +`giw` to say "grep inside word". Vim will echo "Test" *after* accepting +the `iw` motion, which means we've laid out the skeleton. + +The function is simple and nothing we haven't seen before, but that mapping is +a bit more complicated. First we set the `operatorfunc` option to our function, +and then we run `g@` which calls this function as an operator. This may seem +a bit convoluted, but it's how Vim works. + +For now it's okay to consider this mapping to be black magic. You can delve +into the detailed documentation later. + +Visual Mode +----------- + +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: + + vnoremap g :call GrepOperator(visualmode()) + +Write and source the file. Now visually select something and press `g`. +Nothing happens, but Vim does echo "Test", so our function is getting called. + +We've seen the `` in this mapping before but never explained what it did. +Try visually selecting some text and pressing `:`. Vim will open a command line +as it usually does when `:` is pressed, but it automatically fills in `'<,'>` at +the beginning of the line! + +Vim is trying to be helpful and inserts this text to make the command you're +about to run function on the visually selected range. In this case, however, we +don't want the help. We use `` to say "delete from the cursor to the +beginning of the line", removing the text. This leaves us with a bare `:`, +ready for the `call` command. + +The `call GrepOperator()` is simply a function call like we've seen before, but +the `visualmode()` we're passing as an argument is new. This function is +a built-in Vim function that returns a one-character string representing the +last type of visual mode used: `"v"` for characterwise, `"V"` for +linewise, and a `ctrl-v` character for blockwise. + +Motion Types +------------ + +The function we defined takes a `type` argument. We know that when we use the +operator from visual mode it will be the result of `visualmode()`, but what +about when we run it as an operator from normal mode? + +Edit the function body so the file looks like this: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! GrepOperator(type) + echom a:type + endfunction + +Source the file, then go ahead and try it out in a variety of ways. Some +examples of the output you get are: + +* Pressing `viwg` echoes `v` because we were in characterwise visual + mode. +* Pressing `Vjjg` echoes `V` because we were in linewise visual mode. +* Pressing `giw` echoes `char` because we used a characterwise motion + with the operator. +* Pressing `gG` echoes `line` because we used a linewise motion with the + operator. + +Now we know how we can tell the difference between motion types, which will be +important when we select the text to search for. + +Copying the Text +---------------- + +Our function is going to need to somehow get access to the text the user wants +to search for, and the easiest way to do that is to simply copy it. Edit the +function to look like this: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! GrepOperator(type) + if a:type ==# 'v' + execute "normal! `y" + elseif a:type ==# 'char' + execute "normal! `[v`]y" + else + return + endif + + echom @@ + endfunction + +Wow. That's a lot of new stuff. Try it out by pressing things like +`giw`, `g2e` and `vi(g`. Each time Vim will echo the +text that the motion covers, so clearly we're making progress! + +Let's break this new code down one step at a time. First we have an `if` +statement that checks the `a:type` argument. If the type is `'v'` it was called +from characterwise visual mode, so we do something to copy the visually-selected +text. + +Notice that we use the case-sensitive comparison `==#`. If we used plain `==` +and the user has `ignorecase` set it would match `"V"` as well, which is *not* +what we want. Code defensively! + +The second case of the `if` fires if the operator was called from normal mode +using a characterwise motion. + +The final case simply returns. We explicitly ignore the cases of +linewise/blockwise visual mode and linewise/blockwise motions. Grep doesn't +search across lines by default, so having a newline in the search pattern +doesn't make any sense! + +Each of our two `if` cases runs a `normal!` command that does two +things: + +* Visually select the range of text we want by: + * Moving to mark at the beginning of the range. + * Entering characterwise visual mode. + * Moving to the mark at the end of the range. +* Yanking the visually selected text. + +Don't worry about the specific marks for now. You'll learn why they need to be +different when you complete the exercises at the end of this chapter. + +The final line of the function echoes the variable `@@`. Remember that +variables starting with an `@` are registers. `@@` is the "unnamed" register: +the one that Vim places text into when you yank or delete without specify +a particular register. + +In a nutshell: we select the text to search for, yank it, then echo the yanked +text. + +Escaping the Search Term +------------------------ + +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: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! GrepOperator(type) + if a:type ==# 'v' + normal! `y + elseif a:type ==# 'char' + normal! `[v`]y + else + return + endif + + echom shellescape(@@) + endfunction + +Write and source the file and try it out by visually selecting some text with +a special character in it and pressing `g`. Vim will echo a version of +the selected text suitable for passing to a shell command. + +Running Grep +------------ + +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: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! GrepOperator(type) + if a:type ==# 'v' + normal! `y + elseif a:type ==# 'char' + normal! `[v`]y + else + return + endif + + silent execute "grep! -R " . shellescape(@@) . " ." + copen + endfunction + +This should look familiar. We simply execute the `silent execute "grep! ..."` +command we came up with in the last chapter. It's even more readable here +because we're not trying to stuff the entire thing into a `nnoremap` command! + +Write and source the file, then try it out and enjoy the fruits of your labor! + +Because we've defined a brand new Vim operator we can use it in a lot of +different ways, such as: + +* `viwg`: Visually select a word, then grep for it. +* `g4w`: Grep for the next four words. +* `gt;`: Grep until semicolon. +* `gi[`: Grep inside square brackets. + +This highlights one of the best things about Vim: its editing commands are like +a language. When you add a new verb it automatically works with (most of) the +existing nouns and adjectives. + +Exercises +--------- + +Read `:help visualmode()`. + +Read `:help c_ctrl-u`. + +Read `:help operatorfunc`. + +Read `:help map-operator`. diff -r 5c05eeeacf38 -r 745eafc91857 chapters/34.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/34.markdown Tue Oct 25 23:21:53 2011 -0400 @@ -0,0 +1,97 @@ +Case Study: Grep Operator, Part Three +===================================== + +Our shiny new "grep operator" is working great, but part of writing Vimscript is +being considerate and making your users' lives easier. We can do two more +things to make our operator play nicely in the Vim ecosystem. + + +Saving Registers +---------------- + +By yanking the text into the unnamed register we destroy anything that was +previously in there. + +This isn't very nice to our users, so let's save the contents of that register +before we yank and restore it after we've done. Change the code to look like +this: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! GrepOperator(type) + let saved_unnamed_register = @@ + + if a:type ==# 'v' + normal! `y + elseif a:type ==# 'char' + normal! `[v`]y + else + return + endif + + silent execute "grep! -R " . shellescape(@@) . " ." + copen + + let @@ = saved_unnamed_register + endfunction + +We've added two `let` statements at the top and bottom of the function. The +first saves the contents of `@@` into a variable and the second restores it. + +Write and source the file. Make sure it works by yanking some text, then +pressing `giw` to run our operator, then pressing `p` to paste the text +you yanked before. + +When writing Vim plugins you should *always* strive to save and restore any +settings or registers your code modifies so you don't surprise and confuse your +users. + +Namespacing +----------- + +Our script created a function named "GrepOperator" in the global namespace. +This probably isn't a big deal, but when you're writing Vimscript it's far +better to be safe than sorry. + +We can avoid polluting the global namespace by tweaking a couple of lines in our +code. Edit the file to look like this: + + nnoremap g :set operatorfunc=GrepOperatorg@ + vnoremap g :call GrepOperator(visualmode()) + + function! s:GrepOperator(type) + let saved_unnamed_register = @@ + + if a:type ==# 'v' + normal! `y + elseif a:type ==# 'char' + normal! `[v`]y + else + return + endif + + silent execute "grep! -R " . shellescape(@@) . " ." + copen + + let @@ = saved_unnamed_register + endfunction + +The first three lines of the script have changed. First, we modified the +function name to start with `s:` which places it in the current script's +namespace. + +We also modified the mappings and prepended the `GrepOperator` function name +with `` so they could find the function. If we hadn't done this they would +have tried to find the function in the global namespace, which wouldn't have +worked. + +Congratulations, our `grep-operator.vim` script is not only extremely useful, +but it's also a considerate Vimscript citizen! + +Exercises +--------- + +Read `:help `. + +Treat yourself to a snack. You deserve it! diff -r 5c05eeeacf38 -r 745eafc91857 outline.org --- a/outline.org Tue Oct 25 20:55:03 2011 -0400 +++ b/outline.org Tue Oct 25 23:21:53 2011 -0400 @@ -31,7 +31,7 @@ ** DONE execute ** DONE execute normal! ** DONE basic regexes -** TODO Case Study: GrepMotion +** DONE Case Study: GrepMotion ** TODO lists ** TODO looping ** TODO dictionaries diff -r 5c05eeeacf38 -r 745eafc91857 proofreader-volunteers.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/proofreader-volunteers.txt Tue Oct 25 23:21:53 2011 -0400 @@ -0,0 +1,6 @@ +@rhdoenges +@elazar +@chartjes +@robhudson +@cemicolon +@execat