5c05eeeacf38

GrepMotion part 1
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Tue, 25 Oct 2011 20:55:03 -0400
parents 996b7fcd546d
children 745eafc91857
branches/tags (none)
files chapters/32.markdown chapters/33.markdown outline.org

Changes

--- /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 `<leader>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:
+
+* `<leader>giw`: Grep for the word under the cursor.
+* `<leader>giW`: Grep for the WORD under the cursor.
+* `<leader>gi'`: Grep for the contents of the single-quoted string you're
+  currently in.
+* `viwe<leader>g`: 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 `<leader>g` for now.
+
+We'll start with a skeleton of the mapping and fill it in as we go.  Run this
+command:
+
+    :nnoremap <leader> g :grep -R something .<cr>
+
+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 <leader>g :grep -R <cword> .<cr>
+
+Now try it out.  `<cword>` 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 `<cWORD>` to get a WORD instead of a word.  Run this command:
+
+    :nnoremap <leader>g :grep -R <cWORD> .<cr>
+
+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 <leader>g :grep -R '<cWORD>' .<cr>
+
+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 <leader>g :execute "grep -R '<cWORD>' ."<cr>
+
+Try it out and make sure it still works.  If not, find any typos and fix them.
+Then run the following command, which uses `shellescape` to fix the search term:
+
+    :nnoremap <leader>g :execute "grep -R " . shellescape("<cWORD>") . " ."<cr>
+
+And now our mapping won't break if the word we're searching for happens to
+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 <leader>g :execute "grep! -R " . shellescape("<cWORD>") . " ."<cr>
+
+Try it out again and nothing will seem to happen.  Vim has filled the quickfix
+window with the results, but we haven't opened it yet.  Let's add that as the
+finishing touch.  Run the following command:
+
+    :nnoremap <leader>g :execute "grep! -R " . shellescape("<cWORD>") . " ."<cr>:copen<cr>
+
+Now try the mapping and you'll see that Vim automatically opens the quickfix
+window with the search results.  All we did was tack `:copen<cr>` 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.
--- 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