Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vimscript Case Study: Grep Operator, Part 1


May 24, 2021 Vim


Table of contents


In this and the next chapter, we'll use Vimscript to implement a fairly complex program. We'll look at something we've never heard of, and we'll connect what we've learned before in action.

In this example study, when you encounter unfamiliar content, you have :help with :help. If you just go for a horse and see the flowers, you'll get nothing.

Grep

If you :grep you should now spend a minute :help :grep :help :make If you haven't used quickfix window before, :help quickfix-window

To be concise: :grep ... You will run an external grep program with the parameters you give, parse the results, fill the quickfix list, and you will be able to jump to the corresponding results in Vim.

We'll add a "grep operator" to any Vim's built-in (or custom!) ) action to select the text you want to search for, making :grep to use.

Usage

In writing down the first step in each meaningful Vimscript program, you need to think about a question: "How will it be used by the user?" ” Try to come up with an elegant, simple, intuitive way to call.

This time I'll do the work for you:

  • We'll create a "grep operator" and bind it to <leader>g
  • It behaves like any other Vim operator and can be added to key combinations such as w i{
  • It will immediately start searching and open the quickfix window to display the results.
  • It will jump to the first result because it will bother you when the first result is not what you want.

Some use cases of how you will use it:

  • <leader>giw Grep cursor.
  • <leader>giW under the Grep cursor.
  • <leader>gi' where Grep is currently located.
  • viwe<leader>g Visually select a word and expand the selection to the next word, and then Grep.

There are a lot of, a lot of other ways to use it. It looks like it needs to write a lot, a lot of code, but in fact we just need to implement the Operator function and Vim will do the rest.

A prototype

Before you bury your head in writing down the huge amount of Vimscript, one way you might be able to help is to simplify your goal and achieve it to speculate on the possible "shape" of your final solution.

Let's simplify our goal to "create a map to search for words under the cursor." T his is useful and should be simpler, so we can get runable results faster. For now, <leader>g map it to .

Let's start with a mapping skeleton and fill it gradually. To execute this command:

:nnoremap <leader>g :grep -R something .<cr>

If you've :help grep you can easily understand this command. We've seen a lot of mappings before, and nothing here is new.

Obviously we haven't done anything yet, so let's polish this mapping step by step until it meets our requirements.

The search section

First we need to search for words under the cursor, something Execute the following command:

:nnoremap <leader>g :grep -R <cword> .<cr>

Now give it a try. <cword> Vim's command-line pattern, Vim replaces it with "the word below the cursor" before executing the command.

You can get capital form (WORD) by using <cWORD> To execute this command:

:nnoremap <leader>g :grep -R <cWORD> .<cr>

Now try putting the cursor on foo-bar Vim will be grep foo-bar of part of it.

Our search section also has a problem: if there are any special shell characters in it, Vim will not hesitate to pass the grep command to the outside. This can cause the program to crash (or worse: make some big mistakes).

Let's see how to make it hang up. E nter foo;ls put the cursor up to perform the mapping. T he grep command fails, and Vim executes ls command! That must be bad, what if the word includes more dangerous commands than ls

To solve this problem, we'll call parameters in quotation marks. To execute this command:

:nnoremap <leader>g :grep -R '<cWORD>' .<cr>

Most shells take the single quotes as (largely) literal, so our mapping is now more robust.

Escape Shell command parameters

There is also a problem in the search section. T ry this mapping on that's It doesn't work because the single quote in the word conflicts with the single quote in the grep command!

To solve the problem, we can use Vim's shellescape function. Read :help escape() :help shellescape() how it works (really simple).

Because shellescape() a Vim string, we need to create execute with execute. Start by executing the following command to :grep map :execute "..." form:

:nnoremap <leader>g :execute "grep -R '<cWORD>' ."<cr>

Give it a try and make sure it works. I f not, find out the spelling mistake and correct it. Then execute the following command shellescape

:nnoremap <leader>g :execute "grep -R " . shellescape("<cWORD>") . " ."<cr>

Try this command on a general word such as foo I t can work. T ry a single quote word, such as that's I t still doesn't work! Why is this happening?

The problem is that Vim has already executed shellescape() before expanding special variables on the command line, such as shellescape() <cWORD> So Vim shell-escaped the literal "<cWORD>" (do nothing but add a pair of single quotes to it) and connect grep command.

You can see it all with your own eyes by executing the following commands.

:echom shellescape("<cWORD>")

Vim will output '<cWORD>' N ote that quotation marks are also part of the output string. Vim protects it as a shell command parameter.

To solve this problem, expand() use the expand() function to force the expansion of <cWORD> string, before it is shellescape

Let's take a look at how this part works. Move your cursor to a single quote word , such that's , and execute the following command:

:echom expand("<cWORD>")

Vim that's expand("<cWORD>") It's shellescape section:

:echom shellescape(expand("<cWORD>"))

This time Vim 'that'\''s' I f you think it looks ridiculous, you probably haven't felt the calmness of seeing through the crazy forms of shell escape. A t the moment, there's no need to get tangled up in this. It is believed that Vim expand the output of the expand and escaped it correctly.

So far we've got a completely escaped version of the word under the cursor. I t's time to connect it to our map! Execute the following command:

:nnoremap <leader>g :exe "grep -R " . shellescape(expand("<cWORD>")) . " ."<cr>

Give it a try. This mapping is no longer a problem, even if we use it to search for words with odd symbols.

"Start with a simple Vimscript and change it a little bit until you reach your goal" will be used by you over and over again.

Sort it out

There are also minor issues to deal with before the mapping is complete. F irst of all, we said we don't want to automatically jump to the first result, so grep! R eplace grep Execute the following command:

:nnoremap <leader>g :execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>

Try again and find that nothing happened. V im filled the quickfix window with the results, but we couldn't open it. Execute the following command:

:nnoremap <leader>g :execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>

Now try this mapping and you'll see Vim automatically open the quickfix window with search results. All we're doing is continuing at the end of the :copen<cr>

Finally, during the search, we're going to remove all of Vim's grep output. Execute the following command:

:nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>

We're done, give it a try and treat ourselves! silent command simply hides the normal output of a command while it is running.

Practice

Add the mapping we just made to ~/.vimrc file.

If you :help :grep read it.

Read :help cword .

Read :help cnext and help cprevious . Modify your grep maps and try them.

Set :cnext and : :cprevious to make it easier to move between matching content.

Read :help expand

Read :help copen .

Add the heat parameter to the : copen command in the :copen to see if the quickfix window can be opened at the specified height.

Read :help silent