3

How would I go about setting up a hotkey (eg: CTRL+g) to perform a VIMGREP operation on the current visual selection in the current buffer? My intent is to show a line-numbered list in the "quickfix" window of all matching search results.

Right now, if I want to get a list of results for a regex search, I could do a command-mode query like so:

:vimgrep /foo/ %

However, there are two problems with this:

  1. I don't want to have to type out the entire query. I could always do a visual selection, then use CTRL+r, CTRL+w, to paste the current visual selection into the command buffer, but I'd like something simpler than this.
  2. The above approach requires that the current buffer is already saved to a file. I'd like to be able to work on a temporary buffer I've pasted into VIM rather than having to save a file buffer each time I want to do this.

Thank you.

Cloud
  • 18,753
  • 15
  • 79
  • 153
  • Wow. I'll have to take a good deal of time to set aside to compare the merits of the answers provided and provide, in turn, an equally well thought-out decision on which one is "correct". Thank you for the thorough answers provided so far. – Cloud Oct 28 '14 at 22:04

3 Answers3

6

A low-level solution

Try [I and the :ilist command:

[I                 " lists every occurrence of the word under the cursor
                   " in the current buffer (and includes)

:ilist /foo<CR>    " lists every occurrence of foo in the current buffer 
                   " (and includes)

Press : followed by a line number and <CR> to jump to that line.

You can use them on the visual selection with a simple mapping:

xnoremap <key> "vy:<C-u>ilist /<C-r>v<CR>:

You'll probably need to sanitize the register upon insertion, though.

See :help :ilist.

Another even lower-level solution

Since we are at it, let's dig even deeper and find the amazingly simple and elegant:

:g/foo/#

that you could use in the same way as :ilist above:

xnoremap <key> "vy:<C-u>g/<C-r>v/#<CR>:

Limitations

The solutions above don't use the quickfix window, obviously, but they allow you to:

  • see their result as a list,
  • use line numbers to actually get to where you want.

They have limitations, though:

  • the list is not cached so you must perform the search again if you want to get to a different occurrence,
  • the list is not transient like the quickfix list so you can't use navigation commands like :cnext or :clast to move around the result.

A higher-level solution

If those limitations are a showstopper, the function below, adapted from justinmk's answer in this /r/vim thread, gives you an almost complete solution:

  • press [I in normal mode to search for the word under the cursor in the whole buffer,
  • press ]I in normal mode to search for the word under the cursor after the current line,
  • press [I in visual mode to search for the selected text in the whole buffer,
  • press ]I in visual mode to search for the selected text after the current line.

The function below uses the quickfix list/window when the buffer is associated to a file and falls back to the regular behavior of [I and ]I otherwise. It could probably be modified to be used as part of an :Ilist command.

" Show ]I and [I results in the quickfix window.
" See :help include-search.
function! Ilist_qf(selection, start_at_cursor)

    " there's a file associated with this buffer
    if len(expand('%')) > 0

        " we are working with visually selected text
        if a:selection

            " we build a clean search pattern from the visual selection
            let old_reg = @v
            normal! gv"vy
            let search_pattern = substitute(escape(@v, '\/.*$^~[]'), '\\n', '\\n', 'g')
            let @v = old_reg

            " and we redirect the output of our command for later use
            redir => output
                silent! execute (a:start_at_cursor ? '+,$' : '') . 'ilist /' . search_pattern
            redir END

        " we are working with the word under the cursor
        else

            " we redirect the output of our command for later use
            redir => output
                silent! execute 'normal! ' . (a:start_at_cursor ? ']' : '[') . "I"
            redir END
        endif
        let lines = split(output, '\n')

        " better safe than sorry
        if lines[0] =~ '^Error detected'
            echomsg 'Could not find "' . (a:selection ? search_pattern : expand("<cword>")) . '".'
            return
        endif

        " we retrieve the filename
        let [filename, line_info] = [lines[0], lines[1:-1]]

        " we turn the :ilist output into a quickfix dictionary
        let qf_entries = map(line_info, "{
                    \ 'filename': filename,
                    \ 'lnum': split(v:val)[1],
                    \ 'text': getline(split(v:val)[1])
                    \ }")
        call setqflist(qf_entries)

        " and we finally open the quickfix window if there's something to show
        cwindow

    " there's no file associated with this buffer
    else

        " we are working with visually selected text
        if a:selection

            " we build a clean search pattern from the visual selection
            let old_reg = @v
            normal! gv"vy
            let search_pattern = substitute(escape(@v, '\/.*$^~[]'), '\\n', '\\n', 'g')
            let @v = old_reg

            " and we try to perform the search
            try
                execute (a:start_at_cursor ? '+,$' : '') . 'ilist /' .  search_pattern . '<CR>:'
            catch
                echomsg 'Could not find "' . search_pattern . '".'
                return
            endtry

        " we are working with the word under the cursor
        else

            " we try to perform the search
            try
                execute 'normal! ' . (a:start_at_cursor ? ']' : '[') . "I"
            catch
                echomsg 'Could not find "' . expand("<cword>") . '".'
                return
            endtry
        endif
    endif
endfunction

nnoremap <silent> [I :call Ilist_qf(0, 0)<CR>
nnoremap <silent> ]I :call Ilist_qf(0, 1)<CR>
xnoremap <silent> [I :<C-u>call Ilist_qf(1, 0)<CR>
xnoremap <silent> ]I :<C-u>call Ilist_qf(1, 1)<CR>

NB: <C-r><C-w> inserts the word under the cursor, not the visual selection for which there's unfortunately no such shortcut. We have no choice but to yank.

romainl
  • 186,200
  • 21
  • 280
  • 313
  • 1
    You can upgrade your `:g` command a bit by using `:caddexpr`. e.g. `:g/FOO/caddexpr expand("%") . ":" . line(".") . ":" . getline(".")`. This will add matches to the quickfix list. See `:h :caddexpr` for more information – Peter Rincker Oct 25 '14 at 06:58
  • @PeterRincker, that is *very* cool, do you mind if I add it to my answer? – romainl Oct 25 '14 at 07:07
  • Go for it. It should be noted that using `:g` like this will only show the first match on a line. You may also want to start a new quickfix list via `:cexpr []` – Peter Rincker Oct 25 '14 at 07:16
  • @PeterRincker, I've spotted an embarrassing oversight in my function anyway so more work needs to be done on this answer. – romainl Oct 25 '14 at 07:42
5

Grepping a scratch buffer

You can use the :global command combined with :caddexpr to add entries to the current quickfix list. Here is the example from :h :caddexpr:

:g/mypattern/caddexpr expand("%") . ":" . line(".") .  ":" . getline(".")

There are a few issues with this:

  • This approach only does one match per line
  • Does not start a new quickfix list
  • Really long to type out
  • Assumes the default global 'errorformat' hasn't been changed

To overcome these issues (all but the multiple matches per line) put the following command in your ~/.vimrc file:

command! -nargs=1 -bar Cgrep
      \ let s:errorformat = &errorformat |
      \ try |
      \   let &errorformat='%f:%l:%m' |
      \   cexpr [] |
      \   execute 'g'.<q-args>.'caddexpr expand("%").":".line(".").":".getline(".")' |
      \   cc |
      \ finally |
      \   let &errorformat = s:errorformat |
      \ endtry

Now you can use :Cgrep/foo/ to grep the current buffer.

A visual mapping

To make it so you can do a visual version of this you need to yank in the selected text and pass it to our :Cgrep command via <c-r>. Here is an example visual mapping g/ to do just that:

xnoremap g/ y:<c-u>Cgrep/<c-r>"/<cr>

There are some issues with this mapping too:

  • This clobber the unnamed register, @"
  • This assumes the visually selected text will be a valid pattern

The following mapping fixes the mapping by using the expression register via <c-r>= and very no magic, \V:

xnoremap g/ :<c-u>let @/=@"<cr>gvy:let [@/,@"]=[@",@/]<cr>Cgrep/\V<cr>=substitute(escape(@/,'/\'),'\n','\\n','g')<cr>/<cr>

Conclusion

Personally I would forgo the mapping and get a visual star plugin (there are few out there). There is a nice Vimcast about this: Search for the selected text. I would then use that with the :Cgrep command we just created via :Cgrep// or better yet xmap g/ *:Cgrep//<cr>

For more help see:

:h :caddexpr
:h :cexpr
:h :g
:h 'efm
:h registers
:h /\V
Peter Rincker
  • 43,539
  • 9
  • 74
  • 101
1

To search for the visual selected text in files *.c via the hotkey CTRL+g do:

:vmap <silent> <unique> <c-g> y:vimgrep "<c-r>"" *.c<CR>

Two problems remain:

  1. You want to search in a buffer, not in a file.
  2. You want line numbers in the quickfix.

To 1: As far as I know vimgrep can only search in files. The solution would be to write the buffer in a temporary file and search in this file and delete the temporary file when it is no longer needed. This solution requires a script which is called via the hotkey. Just a hint: To get a suitable filename the VIM function tempname() can be used. Example:

let s:tmpfile = tempname()

(Sorry, I currently have no time to show a script solution here, maybe I add one later. Maybe somebody else has a better solution or can provide a script?)

To 2: This command will enable line number in the current buffer:

:set number
Habi
  • 3,262
  • 25
  • 23