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.
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 inside the vimfiles
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.
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!
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 <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
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.
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 <leader>g
.
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 '<,'>
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 <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
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.
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:
viw<leader>g
echoes v
because we were in characterwise visual
mode.Vjj<leader>g
echoes V
because we were in linewise visual mode.<leader>giw
echoes char
because we used a characterwise motion
with the operator.<leader>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.
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
<leader>giw
, <leader>g2e
and 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 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:
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 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:
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.
Read :help visualmode()
.
Read :help c_ctrl-u
.
Read :help operatorfunc
.
Read :help map-operator
.