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.
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).
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
endfunctionTo 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!