Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Hassle-free buffer-local searches in vim

Listing pattern-matches in the current file

Option 1: The :global command

We can search for a pattern in the current file and list out the matching lines using the powerful global command:

:g[lobal]/pattern/#

This prints the matching lines, along with their line numbers (#), and prompts to enter a command. We can then jump to one of the matches using :n<CR>, where n is the line-number.

However, what if we wanted to just list out the matches, and not immediately jump to any matching line? For example, a popular mapping to view the structure of a markdown file is:

nnoremap <key> :g/^#/

Now if we simply press <CR> or <Esc>, we find that we are lost! Our cursor is actually at the last line that matches our specified pattern. To get back to where we were, we need to press <C-O>. Though this doesn't sound much troublesome, it's sometimes enough to lose the context.

Option 2: Include lists

We can also use the :ilist command to achieve a similar goal, without the above issue:

:il[ist] /pattern/#

(Note that :ilist also lists matches from the included files by default.)

However, there is still one annoyance for our use-case: apart from the line-numbers, there is an additional entry-number listed, which, in this case, just serves one purpose: confuse us regarding which number to type, to jump to the desired entry.

Thus, for the simple need of searching a pattern, and optionally jumping to an entry, :global seems a better friend, with one little annoyance (jump to the last match on an empty command).

A wrapped :global command

The following function prompts for a pattern, invokes :global, prompts for a line-number and jumps to it; and in case the user doesn't supply a line-number, it restores the cursor-position.

function! GlobalSearch() abort
  " Prompt for a pattern as usual
  let pattern = input(':g/')
  if !empty(pattern)
    " Print lines matching the pattern, with line-numbers
    execute "g/" . pattern . "/#"
    " The valid value of 'choice' is a line-number
    let choice = input(':')
    if !empty(choice)
      " Jump to the entered line-number
      execute choice
    else
      " If no choice was entered, restore the cursor position
      execute "normal! \<C-O>"
    endif
  endif
endfunction

To call the above function using ,g, create a map as follows:

nnoremap <silent> ,g :call GlobalSearch()<CR>

One last thing. What about the mapping we had above to list out the structure of a markdown file? That requires the ability to pass a predefined pattern to our GlobalSearch() function. Let's do the same:

function! GlobalSearch(...) abort
  " If no pattern was supplied, prompt for one
  if a:0 == 0
    let pattern = input(':g/')
  else
    let pattern = a:1
  endif
  if !empty(pattern)
    " Print lines matching the pattern, with line-numbers
    execute "g/" . pattern . "/#"
    " The valid value of 'choice' is a line-number
    let choice = input(':')
    if !empty(choice)
      " Jump to the entered line-number
      execute choice
    else
      " If no choice was entered, restore the cursor position
      execute "normal! \<C-O>"
    endif
  endif
endfunction

" Call without arguments using ,g
nnoremap <silent> ,g :call GlobalSearch()<CR>

" List the structure of a markdown file; add to ftplugin/markdown.vim
nnoremap <buffer> <key> :call GlobalSearch("^#")<CR>

" Bonus regex (magic-mode); add to ftplugin/java.vim
" list all classes and methods in a Java file (assuming each method has an explicit access-modifier)
nnoremap <buffer> <key> :call GlobalSearch("^\\s*\\(class\\\|public\\\|private\\\|protected\\).*{")<CR>

The new GlobalSearch() function uses unnamed arguments (see :help function-argument) to take different actions based on whether an argument was passed or not; a:0 gives the number of arguments and a:1 gives the first argument.

Note that :global can be used to perform a large number of things apart from the simple search-and-jump feature we targetted here. For example, to delete the matching lines, we can use :g/pattern/d. The pattern can be any regular-expression in vim's regex format, and the default command is p for 'print'. (Thus, :g/regular-expression/p is equivalent to :g/regular-expression). In fact, :global/regular-expression/print is the command from which the famous multi-file search command grep derives its name!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment