# HG changeset patch # User Steve Losh # Date 1319590503 14400 # Node ID 5c05eeeacf38ed7554eea2a6e379378ee4c1b745 # Parent 996b7fcd546d8adc12103d51949f43cad3d5e4f2 GrepMotion part 1 diff -r 996b7fcd546d -r 5c05eeeacf38 chapters/32.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chapters/32.markdown Tue Oct 25 20:55:03 2011 -0400 @@ -0,0 +1,173 @@ +Case Study: GrepMotion, 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 +haven't seen before, as well as how some of the things we've studied fit +together in practice. + +As you work through this case study make sure to look up anything unfamiliar +with `:help`. If you coast through without fully understanding everything you +won't learn much. + +Grep +---- + +If you've never used `:grep` you should take a minute to read `:help :grep` and +`:help :make` now. Read `:help quickfix-window` if you've never used the +quickfix window before. + +In a nutshell: `:grep ...` will run an external grep program with any arguments +you give, parse the result, and fill the quickfix list for easy use inside of +Vim. + +Our example is going to make it easier to invoke by adding a "grep operator" +that you can use with any of Vim's built-in (or custom!) motions to select the +text you want to search for. + +Usage +----- + +The first thing you should think about when creating any non-trivial piece of +Vimscript is: "how will this functionality be used?". Try to come up with +a smooth, easy, intuitive way for you and your code's users to invoke it. + +In this case I'll do that step for you: + +* We're going to create a "grep operator" and bind it to `g`. +* It will act like any other Vim operator and take a motion, like `w` or `i{`. +* It will perform the search immediately and open the quickfix window to show + the results. +* It will *not* jump to the first result, because that can be jarring if the + first result isn't what you're expecting. + +Some examples of how you might end up using it: + +* `giw`: Grep for the word under the cursor. +* `giW`: Grep for the WORD under the cursor. +* `gi'`: Grep for the contents of the single-quoted string you're + currently in. +* `viweg`: Visually select a word, extend the selection to the end of + the word after it, then grep for the selected text. + +There are many, *many* other ways to use this. It may seem like it will take +a lot of coding, but actually all we need to do is implement the operator +functionality -- Vim will handle the rest. + +A Preliminary Sketch +-------------------- + +One thing that's sometimes helpful when writing tricky bits of Vimscript is to +simplify your goal and implement that to get an idea of the "shape" your final +solution will take. + +Let's simplify our goal to: "create a mapping to search for the word under the +cursor". This is useful but should be easier, so we can get something running +much faster. We'll map this to `g` for now. + +We'll start with a skeleton of the mapping and fill it in as we go. Run this +command: + + :nnoremap g :grep -R something . + +If you've read `:help grep` this should be pretty easy to understand. We've +looked at lots of mappings before, and there's nothing new here. + +Obviously we're not done yet, so lets refine this mapping until it meets our +simplified goal. + +The Search Term +--------------- + +First we need to search for the word under the cursor, not the string +"something". Run the following command: + + :nnoremap g :grep -R . + +Now try it out. `` is a special bit of text you can use in Vim's +command-line mode, and Vim will replace it with "the word under the cursor" +before running the command. + +You can use `` to get a WORD instead of a word. Run this command: + + :nnoremap g :grep -R . + +Now try the mapping when your cursor is over something like "foo-bar". Vim will +grep for "foo-bar" instead of just part of the word. + +There's still a problem with our search term: if there are any special shell +characters in it Vim will happily pass them along to the external grep command, +which will explode (or, worse, do something terrible). + +Go ahead and try this to make sure it breaks. Type `foo;ls` into a file and run +the mapping while your cursor is over it. The grep command will fail, and Vim +will actually run an `ls` command as well! Clearly this could be bad if the +word contained a command more dangerous than `ls`. + +To try to fix this we'll quote the argument in the grep call. Run this command: + + :nnoremap g :grep -R '' . + +Most shells treat single-quoted text as (almost) literal, so our mapping is much +more robust now. However there's still one more problem with the search term! +Try the mapping on the word "that's". It won't work, because the single quote +inside the word interferes with the quotes in the grep command! + +To get around this we can use Vim's `shellescape` function. Read `:help +escape()` and `:help shellescape()` to see how it works (it's pretty simple). + +Because `shellescape()` works on Vim strings, we'll need to dynamically build +the command with `execute`. First run the following command to transform the +`:grep` mapping into `:execute "..."` form: + + :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: + + :nnoremap g :execute "grep -R " . shellescape("") . " ." + +And now our mapping won't break if the word we're searching for happens to +contain strange characters. + +The process of starting with a trivial bit of Vimscript and transforming it +little-by-little into something closer to your goal is one you'll find yourself +using often. + +Cleanup +------- + +There are still a couple of small things to take care of before our mapping is +finished. First, we said that we don't want to go to the first result +automatically, and we can use `grep!` instead of plain `grep` to do that. Run +this command: + + :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: + + :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. + +Exercises +--------- + +Add the mapping we just created to your `~/.vimrc` file. + +Read `:help :grep`. + +Read `:help cnext` and `:help cprevious`. Try them out after using your new +grep mapping. + +Set up mappings for `:cnext` and `:cprevious` to make it easier to quickly run +through matches. + +Read `:help copen`. + +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. diff -r 996b7fcd546d -r 5c05eeeacf38 chapters/33.markdown diff -r 996b7fcd546d -r 5c05eeeacf38 outline.org --- a/outline.org Tue Oct 25 19:37:40 2011 -0400 +++ b/outline.org Tue Oct 25 20:55:03 2011 -0400 @@ -31,6 +31,7 @@ ** DONE execute ** DONE execute normal! ** DONE basic regexes +** TODO Case Study: GrepMotion ** TODO lists ** TODO looping ** TODO dictionaries