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
~/.vimrc file -- we're going to use the same keystroke for our new
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
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 inside the
directory in your home directory. (Use the command:
:echo $HOME in Vim if
you're not sure where this is). If this directory doesn't exist, create it.
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
Remember that you must write the file before you source it for the changes to be seen!
To create a new Vim operator you'll start with two components: a function and
a mapping. Start by adding the following code to
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@ function! GrepOperator(type) echom "Test" endfunction
Write the file and source it with
:source %. Try it out by pressing
<leader>giw to say "grep inside word". Vim will echo
Test after accepting
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.
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 <leader>g :<c-u>call GrepOperator(visualmode())<cr>
Write and source the file. Now visually select something and press
Nothing happens, but Vim does echo
Test, so our function is getting called.
We've seen the
<c-u> 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
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
<c-u> 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 GrepOperator() is simply a function call like we've seen before, but
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,
linewise, and a
Ctrl-v character for blockwise.
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 <leader>g :set operatorfunc=GrepOperator<cr>g@ vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr> 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:
vbecause we were in characterwise visual mode.
Vbecause we were in linewise visual mode.
charbecause we used a characterwise motion with the operator.
linebecause 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.
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 <leader>g :set operatorfunc=GrepOperator<cr>g@ vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr> function! GrepOperator(type) if a:type ==# 'v' execute "normal! `<v`>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
vi(<leader>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
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
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
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.
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 <leader>g :set operatorfunc=GrepOperator<cr>g@ vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr> function! GrepOperator(type) if a:type ==# 'v' normal! `<v`>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
<leader>g. Vim will echo a version of
the selected text suitable for passing to a shell command.
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 <leader>g :set operatorfunc=GrepOperator<cr>g@ vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr> function! GrepOperator(type) if a:type ==# 'v' normal! `<v`>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
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:
viw<leader>g: Visually select a word, then grep for it.
<leader>g4w: Grep for the next four words.
<leader>gt;: Grep until semicolon.
<leader>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.