Recently, I was going to write a vim introductory article for new Go developers, so I started to organize my vim configuration and plug-in system. While sorting out the file-related plugins, I found myself using the ack plugin. ack itself has no file search capability; it uses tools like ack/ag to search for articles. The plugin itself only serves the purpose of calling commands and displaying results. Since it is relatively simple, why not implement one yourself? So I used ag.vim, the main character of today’s article, which is a 15-line plugin that does the basic functions of the ack plugin. How did I do it? Let’s take a look.

The most commonly used file manipulation tool in Unix environments is grep. grep, however, is rather crotchety, so ag was created. ag is similar to grep, but faster. However, the package name of ag is the_silver_searcher, which is a bit strange. That is, when we install it with homebrew or apt-get, we type the_silver_searcher instead of ag. But when we use it, we type ag.

ag is the chemical element symbol for silver.

Some people felt that ag was still not fast enough, so they developed rg, whose full name is ripgrep. But in any case, both ag and rg support the so-called vimgrep output format.

1
2
3
# ag 'Ag\b' --vimgrep
# rg 'Ag\b' --vimgrep
pack/vendor/start/ag/README.md:10:10:" search Ag

This is the output format specified by vimgrep. Each line corresponds to one search result, and each result is divided into four parts, separated by :. The first part is the path to the file, the second and third parts are the row and column numbers of the search results, respectively, and the last part is the search result.

Vim itself provides a QuickFix function that displays the search contents in vimgrep format and supports jumping to the corresponding file location.

Displaying the QuickFix window is as simple as executing :copen. The crux of the problem is how to append content to it. That’s where the setqflist() function comes in. You can see the details of its parameters and functions in :h setqflist(). For our scenario, the first argument passes the empty list []. The second parameter indicates the specific action we need to use a for append and f for clear. The third parameter is the search result to be displayed. This is a dictionary with many fields, but we need only three: * title sets the Quick Search results.

  • title sets the QuickFix title, which I use to display specific search commands and keywords
  • lines the list of search results, we agreed on the vimgrep format
  • efm is the full name of the error format. QuickFix was originally designed to display a list of errors so that programmers could quickly locate them. We borrowed its display and jumping functionality. The value of this field is '%f:%l:%c:%m', where %f corresponds to the file path, %l to the line number, %c to the column number, and %m to the display content (i.e. the search result). Theoretically, the output here and ag can match the results on the line.

Said half a day more abstract, we may wish to test. To the above output, for example, we can call setqflist(): setqflist().

1
call setqflist([], 'a', { 'title': 'ag Ag\b', 'lines': ['pack/vendor/start/ag/README.md:10:10:" search Ag'], 'efm':'%f:%l:%c:%m'})

Execute it and then run :copen to see a line of results. Move the cursor over it and press enter vim will open the corresponding file and jump to the location of the search result (to the exact row and column).

This is the interface-related part. Let’s talk about how to execute the ag command.

Vim and NeoVim support executing system commands via system() and returning the output. However, this command will block the editor interface. That is, if we use it to search for larger items, Vim will get stuck and not do anything until the search is complete. This shows unscientific. We need an asynchronous way to execute the search command.

Vim didn’t support asynchronous functionality for a long time, and it didn’t want to. This is one of the main reasons for the birth of NeoVim, which first introduced the jobstart() function to execute system commands asynchronously in the background. Later, Vim’s maintainers added the job_start() function in response to pressure from NeoVim. The two functions are similar, but incompatible. Today I will use the NeoVim version as an example. If you are a Vim user, don’t be afraid to combine Vim’s documentation :h job_start with this article to achieve the same effect.

If we want to perform a search asynchronously, we can do this.

1
call jobstart('ag --vimgrep Ag\b')

But the question arises, how do we get the output of ag? This requires setting the output callback via the second parameter of jobstart().

1
call jobstart(s:cmd, { 'on_stdout':function('s:show'), 'stdin': "null" })

The second argument is also a dictionary, there are many keys, but I only need to use on_stdout and stdin. Setting stdin to null is for compatibility with the rg command. It is not necessary if you are using ag only. Specify a function with on_stdout, where function('s:show') is the vim script syntax for referencing a function. NeoVim listens to the stdout output while ag is running in the background, and calls the function specified by on_stdout when it receives the output.

The argument structure of the s:show function is as follows.

1
2
3
4
function! s:show(id, lines, name) 
if a:lines == [""] | return | end
call setqflist([], 'a', { 'title': 'ag', 'lines': lines, 'efm':'%f:%l:%c:%m'})
end

The second argument is a list that holds the output of ag. ag will be searched and output at the same time, so s:show() will be called multiple times. after ag is searched, NeoVim will call s:show() and set the lines argument to ["", indicating that the search is complete.

So we just need to add the search results to the QuicFix list in s:show().

Finally, we just call :open to see the list of search results. The full code is available on GitHub open source, feel free to try it out. Fifteen lines of ag.vim versus 300+ lines of ack plugins is more than enough.

That’s all there is to this article. At this point, I can’t help but think of a friend’s soul question - what do you want to do with vim? Vim is a pure character editor, which only provides basic functions. Users can write plug-ins according to their needs and use them in combination with any tools. I think this is the beauty of vim.