Vim Tips Wiki
(see also + minor tweaks)
(Add Related plugins section, with a link to my ExtractMatches plugin and Yanktitude.)
Tag: sourceedit
(19 intermediate revisions by 12 users not shown)
Line 5: Line 5:
 
|created=2003
 
|created=2003
 
|complexity=basic
 
|complexity=basic
|author=JAS
+
|author=
 
|version=7.0
 
|version=7.0
 
|rating=13/8
 
|rating=13/8
Line 11: Line 11:
 
|category2=
 
|category2=
 
}}
 
}}
Several methods allow you to capture all search hits. Usually, you want to copy each line which contains text matching your search pattern, but you can also copy just the matching text. We'll start with methods to easily list search hits, without copying them.
+
Several methods allow you to copy all search matches (search hits). Usually, you want to copy each line which contains text matching your search pattern, but you can also copy just the matching text. The tip starts with methods to easily list search hits, without copying them, then shows how to copy matches, or matching lines. Similar techniques, also shown below, can be used to delete all matching lines.
  +
  +
Copying or deleting search matches is easy when simple search patterns are used. The techniques shown here also work with multiline matches.
   
 
==Search within a file==
 
==Search within a file==
To search, press <tt>/</tt> then type what you want to find, or press <tt>*</tt> to [[VimTip1|search for the current word]].
+
To search, press <code>/</code> then type what you want to find, or press <code>*</code> to [[VimTip1|search for the current word]].
   
 
To list all lines matching your search pattern, use the following. The first command lists lines matching your last search. The second lists lines matching ''pattern''.
 
To list all lines matching your search pattern, use the following. The first command lists lines matching your last search. The second lists lines matching ''pattern''.
Line 22: Line 24:
 
</pre>
 
</pre>
   
  +
On some systems, at the "Press ENTER or type command to continue" prompt, you can select displayed text with the mouse then press Ctrl-Y to copy the selection to the clipboard. {{help|press-enter}}
To view a window of search results, see [[VimTip1543|Find in files within Vim]]. You can use <tt>%</tt> for the file path to search only the current file, for example:
 
  +
  +
To view a window of search results, see [[VimTip1543|Find in files within Vim]]. You can use <code>%</code> for the file path to search only the current file, for example:
 
<pre>
 
<pre>
 
" Save file, search it for 'pattern', and open a clickable list.
 
" Save file, search it for 'pattern', and open a clickable list.
Line 30: Line 34:
 
</pre>
 
</pre>
   
To view only the parts of a file that match your pattern, see [[VimTip282|Folding with Regular Expression]]. You can fold away non-matching lines, and use <tt>zr</tt> to reveal more context around the search hits.
+
To view only the parts of a file that match your pattern, see [[VimTip282|Folding with Regular Expression]]. You can fold away non-matching lines, and use <code>zr</code> to reveal more context around the search hits.
   
  +
Another approach is to put the cursor on a word of interest, then type <code>[I</code> to [[Displaying a variable/macro definition|list occurrences]].
==Copying lines containing search hits==
 
You can copy matching lines to the clipboard, so they can be pasted into another application.
 
   
  +
==Copy or delete matching lines==
To copy all lines containing a search pattern, use the following (see [[VimTip227|Power of g]]). The first command clears register <tt>a</tt> ({{help|q}}). The second appends all matching lines to that register ({{help|quotea}}). The third copies register <tt>a</tt> to the clipboard (register <tt>+</tt>) for easy pasting into another application. Replace ''pattern'' with what you want to search for.
 
  +
Matching lines can be copied to the clipboard, so they can be pasted into another application.
  +
  +
To copy all lines containing a search pattern, use the following (see [[Power of g|here]]). The first command clears register <code>a</code> ({{help|q}}). The second appends all matching lines to that register ({{help|quotea}}). The third copies register <code>a</code> to the clipboard (register <code>+</code>) for easy pasting into another application. Replace ''pattern'' with what you want to search for, or omit it (<code>:g//y A</code>) to use the last search.
 
<pre>
 
<pre>
 
qaq
 
qaq
Line 42: Line 48:
 
</pre>
 
</pre>
   
The above procedure captures only the first line of each match. If your pattern matches more than one line, use the following.
+
The above procedure captures only the first line of each match. See below for copying multiline matches.
  +
  +
It is also easy to delete lines containing single-line matches. The first of the following commands deletes all lines containing ''pattern'', while the second deletes all lines matching the last search:
 
<pre>
 
<pre>
  +
:g/pattern/d
" Use /pattern to search for something, then
 
  +
:g//d
" :call CopyMatchingLines()
 
  +
</pre>
" to copy all lines containing hits (whole lines).
 
  +
" The pattern may extend over multiple lines.
 
  +
Use <code>:v//d</code> to delete all lines that do ''not'' match the last search pattern, or
" The 'normal! $' attempts to avoid copying the same line more than once.
 
  +
<code>:v/pattern/d</code> to delete all lines that do ''not'' match the given pattern.
" FIX: For some patterns, it could miss a second hit?
 
  +
function! CopyMatchingLines()
 
  +
If a search pattern may find multiline matches, the above procedures to delete matching lines fail. In that case, use the following which works with any kind of match.
let posinit = getpos(".")
 
  +
<pre>
call cursor(1, 1)
 
  +
" Delete all lines in given range that contain a match, or part of a match.
let cnt = 0
 
  +
" :DeleteLines delete all lines matching last search
let hits = []
 
  +
" :DeleteLines pat delete all lines matching the given pattern 'pat'
let snum = search(@/, 'cW')
 
  +
" The deleted lines are NOT saved anywhere. Works with multiline matches.
while snum > 0
 
  +
function! DeleteLines(pattern) range
let enum = search(@/, 'ceW')
 
  +
let delid = '<!DELETE!LINE!ID!>' " an id that does not occur in buffer
call extend(hits, getline(snum, enum))
 
  +
if search(delid, 'cnw') > 0
let cnt += 1
 
  +
redraw " so message is seen
normal! $
 
  +
echo 'Error: buffer contains pattern used to delete lines'
let snum = search(@/, 'W')
 
  +
return
endwhile
 
if cnt > 0
 
let @+ = join(hits, "\n") . "\n"
 
 
endif
 
endif
  +
let pattern = empty(a:pattern) ? @/ : a:pattern
call cursor(posinit[1], posinit[2])
 
  +
let sub = a:firstline . ',' . a:lastline . 's/' . escape(pattern, '/')
echomsg cnt 'lines (or blocks) were appended to the clipboard.'
 
  +
" Retain newline if it is last character so do not delete following line.
  +
let rep = '/\=delid . (submatch(0) =~ "\n$" ? "\r" : "")/e'
  +
execute sub . rep . (&gdefault ? '' : 'g')
  +
execute 'g/\C' . delid . '/' . 'd'
 
endfunction
 
endfunction
  +
command! -nargs=? -range=% DeleteLines k'|<line1>,<line2>call DeleteLines(<q-args>)
 
</pre>
 
</pre>
   
  +
==Copy matches==
==Copying only the matching text==
 
  +
The simple script shown here copies only the text that matches search hits. It works with multiline matches. First save the buffer because the script changes it (the changes should result in the same text, but it is safer to save first). After sourcing the following, search for a pattern, then enter <code>:CopyMatches</code> to copy all matches to the clipboard, or <code>:CopyMatches x</code> where <code>x</code> is any register to hold the result.
A search pattern may use a regular expression. For example, the following finds all words that begin with 'a':
 
 
<pre>
 
<pre>
  +
function! CopyMatches(reg)
/\<a\w*\>
 
  +
let hits = []
  +
%s//\=len(add(hits, submatch(0))) ? submatch(0) : ''/ge
  +
let reg = empty(a:reg) ? '+' : a:reg
  +
execute 'let @'.reg.' = join(hits, "\n") . "\n"'
  +
endfunction
  +
command! -register CopyMatches call CopyMatches(<q-reg>)
 
</pre>
 
</pre>
   
  +
The above code works after any search, including searches which match text in more than one line. The expression used in the substitute replacement uses <code>len()</code> as a trick to call <code>add()</code> (which returns the list after appending a match to it).
The following procedures allow you to copy just the text which matches a pattern. Source the <tt>CopyMatches</tt> command and function, then perform the search above. To copy all matching text to the clipboard, enter the following. The first command clears the clipboard. The second appends matching text to the clipboard (since no pattern is given, the last search is used). The second command is equivalent to <tt>:%CopyMatches</tt>.
 
  +
  +
Use the following to copy matches from all buffers to register <code>a</code>:
 
<pre>
 
<pre>
  +
:let @a = ''
0"+y0
 
:g//CopyMatches
+
:bufdo CopyMatches A
 
</pre>
 
</pre>
   
  +
In the following text, use search pattern <code>\<A\_.\{-}Z\></code> (any word beginning with A, followed by any characters including newline, as few as possible, to a word ending with Z) to test <code>:CopyMatches</code> (it copies four hits: <code>A1...1Z</code> and <code>A2...2Z</code> and <code>A3...3Z</code> and <code>A4...4Z</code>):
You can also use the command to copy all matches from a range of lines, and into a register other than the clipboard:
 
 
<pre>
 
<pre>
  +
Test text bbb ccc A1 ddd eee
  +
Afake fff1Z A2 ggg2Z hhh A3 iii
  +
jjj3Z
  +
Nothing here.
  +
More A4 kkk lll4Z
  +
</pre>
  +
  +
==Copy matches or lines==
  +
The script in this section provides more features. It works with multiline matches, and provides a <code>CopyMatches</code> command to copy only matching text, as well as a <code>CopyLines</code> command to copy all lines that contain a match.
  +
  +
Each command accepts a range of lines, and can copy into any register (by default, the clipboard). The first of the following commands copies all text matching the last search into the clipboard, while the second copies all matches in lines 1 to 10 inclusive to register <code>a</code>, and the third copies matches from the current line&nbsp;(<code>.</code>) to the default register&nbsp;(<code>"</code>):
  +
<pre>
  +
:CopyMatches
 
:1,10CopyMatches a
 
:1,10CopyMatches a
  +
:.CopyMatches "
 
</pre>
 
</pre>
   
  +
Matches can be copied into a scratch buffer instead of a register. To do that, use <code>-</code> as the register name. If a bang (exclamation mark) is used, the line number of each match is included. Also, a search pattern can be entered on the command line. For example, the following commands display the results for two searches in the same scratch buffer. Search matches, with line numbers, are displayed for all words beginning with 'a', and for all strings of digits. Enter the first command, then switch back to the original buffer while keeping the scratch window open, then enter the second command:
 
<pre>
 
<pre>
  +
:CopyMatches! - \<a\w*
" Copy matches of the last search to a register (default is the clipboard).
 
  +
:CopyMatches! - \d\+
" Accepts a range (default is the current line).
 
  +
</pre>
" Matches are appended to the register and each match is terminated by \n.
 
  +
" Usage: [RANGE]CopyMatches [REGISTER]
 
  +
Create file <code>~/.vim/plugin/copymatches.vim</code> (Unix) or <code>$HOME/vimfiles/plugin/copymatches.vim</code> (Windows) containing the script below, then restart Vim.
command! -nargs=0 -range -register CopyMatches call s:CopyMatches(<line1>, <line2>, "<reg>")
 
  +
<pre>
function! s:CopyMatches(line1, line2, reg)
 
  +
" Plugin to copy matches (search hits which may be multiline).
let reg = a:reg != '' ? a:reg : '+'
 
  +
" Version 2012-05-03 from http://vim.wikia.com/wiki/VimTip478
for line in range(a:line1, a:line2)
 
  +
"
let txt = getline(line)
 
  +
" :CopyMatches copy matches to clipboard (each match has newline added)
let idx = match(txt, @/)
 
  +
" :CopyMatches x copy matches to register x
while idx > -1
 
  +
" :CopyMatches X append matches to register x
exec "let @".reg." .= matchstr(txt, @/, idx) . \"\n\""
 
  +
" :CopyMatches - display matches in a scratch buffer (does not copy)
let end = matchend(txt, @/, idx)
 
  +
" :CopyMatches pat (after any of above options) use 'pat' as search pattern
let idx = match(txt, @/, end)
 
  +
" :CopyMatches! (with any of above options) insert line numbers
endwhile
 
  +
" Same options work with the :CopyLines command (which copies whole lines).
  +
  +
" Jump to first scratch window visible in current tab, or create it.
  +
" This is useful to accumulate results from successive operations.
  +
" Global function that can be called from other scripts.
  +
function! GoScratch()
  +
let done = 0
  +
for i in range(1, winnr('$'))
  +
execute i . 'wincmd w'
  +
if &buftype == 'nofile'
  +
let done = 1
  +
break
  +
endif
 
endfor
 
endfor
  +
if !done
  +
new
  +
setlocal buftype=nofile bufhidden=hide noswapfile
  +
endif
 
endfunction
 
endfunction
</pre>
 
   
  +
" Append match, with line number as prefix if wanted.
The following alternative is to copy matches which extend over more than one line.
 
  +
function! s:Matcher(hits, match, linenums, subline)
  +
if !empty(a:match)
  +
let prefix = a:linenums ? printf('%3d ', a:subline) : ''
  +
call add(a:hits, prefix . a:match)
  +
endif
  +
return a:match
  +
endfunction
   
  +
" Append line numbers for lines in match to given list.
  +
function! s:MatchLineNums(numlist, match)
  +
let newlinecount = len(substitute(a:match, '\n\@!.', '', 'g'))
  +
if a:match =~ "\n$"
  +
let newlinecount -= 1 " do not copy next line after newline
  +
endif
  +
call extend(a:numlist, range(line('.'), line('.') + newlinecount))
  +
return a:match
  +
endfunction
  +
  +
" Return list of matches for given pattern in given range.
  +
" If 'wholelines' is 1, whole lines containing a match are returned.
  +
" This works with multiline matches.
  +
" Work on a copy of buffer so unforeseen problems don't change it.
  +
" Global function that can be called from other scripts.
  +
function! GetMatches(line1, line2, pattern, wholelines, linenums)
  +
let savelz = &lazyredraw
  +
set lazyredraw
  +
let lines = getline(1, line('$'))
  +
new
  +
setlocal buftype=nofile bufhidden=delete noswapfile
  +
silent put =lines
  +
1d
  +
let hits = []
  +
let sub = a:line1 . ',' . a:line2 . 's/' . escape(a:pattern, '/')
  +
if a:wholelines
  +
let numlist = [] " numbers of lines containing a match
  +
let rep = '/\=s:MatchLineNums(numlist, submatch(0))/e'
  +
else
  +
let rep = '/\=s:Matcher(hits, submatch(0), a:linenums, line("."))/e'
  +
endif
  +
silent execute sub . rep . (&gdefault ? '' : 'g')
  +
close
  +
if a:wholelines
  +
let last = 0 " number of last copied line, to skip duplicates
  +
for lnum in numlist
  +
if lnum > last
  +
let last = lnum
  +
let prefix = a:linenums ? printf('%3d ', lnum) : ''
  +
call add(hits, prefix . getline(lnum))
  +
endif
  +
endfor
  +
endif
  +
let &lazyredraw = savelz
  +
return hits
  +
endfunction
  +
  +
" Copy search matches to a register or a scratch buffer.
  +
" If 'wholelines' is 1, whole lines containing a match are returned.
  +
" Works with multiline matches. Works with a range (default is whole file).
  +
" Search pattern is given in argument, or is the last-used search pattern.
  +
function! s:CopyMatches(bang, line1, line2, args, wholelines)
  +
let l = matchlist(a:args, '^\%(\([a-zA-Z"*+-]\)\%($\|\s\+\)\)\?\(.*\)')
  +
let reg = empty(l[1]) ? '+' : l[1]
  +
let pattern = empty(l[2]) ? @/ : l[2]
  +
let hits = GetMatches(a:line1, a:line2, pattern, a:wholelines, a:bang)
  +
let msg = 'No non-empty matches'
  +
if !empty(hits)
  +
if reg == '-'
  +
call GoScratch()
  +
normal! G0m'
  +
silent put =hits
  +
" Jump to first line of hits and scroll to middle.
  +
''+1normal! zz
  +
else
  +
execute 'let @' . reg . ' = join(hits, "\n") . "\n"'
  +
endif
  +
let msg = 'Number of matches: ' . len(hits)
  +
endif
  +
redraw " so message is seen
  +
echo msg
  +
endfunction
  +
command! -bang -nargs=? -range=% CopyMatches call s:CopyMatches(<bang>0, <line1>, <line2>, <q-args>, 0)
  +
command! -bang -nargs=? -range=% CopyLines call s:CopyMatches(<bang>0, <line1>, <line2>, <q-args>, 1)
  +
</pre>
  +
  +
==GetMatches() alternative==
  +
The following shows an alternative method to copy matches. This script is less useful than others in this tip as it does not handle multiline matches. However, the technique may be of interest as it shows how to use the <code>match()</code> function. In addition, it does not involve changes to the current buffer.
 
<pre>
 
<pre>
  +
" Return list of matches for given pattern in given range.
" Use 0"+y0 to clear the clipboard, then
 
  +
" This only works for matches within a single line.
" :g/pattern/call CopyMultiMatches()
 
  +
" Empty hits are skipped so search for '\d*\ze,' is not stuck in '123,456'.
" to copy all multiline hits (just the matching text).
 
  +
" If omit match() 'count' argument, pattern '^.' matches every character.
" This is for when the match extends over multiple lines.
 
  +
" Using count=1 causes text before the 'start' argument to be considered.
" Only the first match from each line is found.
 
  +
function! GetMatches(line1, line2, pattern)
" BUG: When searching for "^function.*\_s*let" the '.*' stops at the end
 
  +
let hits = []
" of a line, but it greedily skips "\n" in the following (we copy too much).
 
  +
for line in range(a:line1, a:line2)
function! CopyMultiMatches()
 
let text = join(getline(".", "$"), "\n") . "\n"
+
let text = getline(line)
let @+ .= matchstr(text, @/) . "\n"
+
let from = 0
  +
while 1
  +
let next = match(text, a:pattern, from, 1)
  +
if next < 0
  +
break
  +
endif
  +
let from = matchend(text, a:pattern, from, 1)
  +
if from > next
  +
call add(hits, strpart(text, next, from - next))
  +
else
  +
let char = matchstr(text, '.', next)
  +
if empty(char)
  +
break
  +
endif
  +
let from = next + strlen(char)
  +
endif
  +
endwhile
  +
endfor
  +
return hits
 
endfunction
 
endfunction
 
</pre>
 
</pre>
   
 
==See also==
 
==See also==
*[[VimTip1063|Redirect g search output]] to redirect <tt>g//</tt> output to a new window or a file
+
*[[VimTip1063|Redirect g search output]] to redirect <code>g//</code> output to a new window or a file
 
*[[VimTip1557|Filter buffer on a search result]] to create a scratch buffer with matching lines
 
*[[VimTip1557|Filter buffer on a search result]] to create a scratch buffer with matching lines
 
*[[VimTip1543|Find in files within Vim]] for a clickable list of search hits
 
*[[VimTip1543|Find in files within Vim]] for a clickable list of search hits
 
*[[VimTip282|Folding with Regular Expression]] to fold away non-matching lines
 
*[[VimTip282|Folding with Regular Expression]] to fold away non-matching lines
  +
==Related plugins==
  +
* {{script|id=4795|text=ExtractMatches}} provides a toolbox of commands to copy all (or only unique first) search matches / matches of a passed pattern / entire lines matching, to a register, or directly <code>:put</code> them into the buffer.
  +
* {{script|id=4719|text=yankitute}} provides a <code>:[range]Yankitute[register]/{pattern}/[string]/[flags]/[join]</code> command.
   
 
==Comments==
 
==Comments==
  +
Just after my last major update to this tip (a week ago), I found a note pointing to a [http://groups.google.com/group/vim_use/browse_thread/thread/f17698d5bb9b66ad vim_use thread] where ZyX showed a simple yet brilliant technique for handling multiline matches. Testing shows that the posted code is not entirely correct as it misses multiline matches that occur in the same line after a short match. However, I have done extensive modifications and the resulting script (now in this tip) is sensational. [[User:JohnBeckett|JohnBeckett]] 03:31, May 3, 2012 (UTC)
{{Todo}}
 
*Some of the code needs fixing (see comments in the scripts).
 

Revision as of 10:45, 8 May 2015

Tip 478 Printable Monobook Previous Next

created 2003 · complexity basic · version 7.0


Several methods allow you to copy all search matches (search hits). Usually, you want to copy each line which contains text matching your search pattern, but you can also copy just the matching text. The tip starts with methods to easily list search hits, without copying them, then shows how to copy matches, or matching lines. Similar techniques, also shown below, can be used to delete all matching lines.

Copying or deleting search matches is easy when simple search patterns are used. The techniques shown here also work with multiline matches.

Search within a file

To search, press / then type what you want to find, or press * to search for the current word.

To list all lines matching your search pattern, use the following. The first command lists lines matching your last search. The second lists lines matching pattern.

:g/
:g/pattern

On some systems, at the "Press ENTER or type command to continue" prompt, you can select displayed text with the mouse then press Ctrl-Y to copy the selection to the clipboard. :help press-enter

To view a window of search results, see Find in files within Vim. You can use % for the file path to search only the current file, for example:

" Save file, search it for 'pattern', and open a clickable list.
:w
:vimgrep /pattern/ %
:copen

To view only the parts of a file that match your pattern, see Folding with Regular Expression. You can fold away non-matching lines, and use zr to reveal more context around the search hits.

Another approach is to put the cursor on a word of interest, then type [I to list occurrences.

Copy or delete matching lines

Matching lines can be copied to the clipboard, so they can be pasted into another application.

To copy all lines containing a search pattern, use the following (see here). The first command clears register a (:help q). The second appends all matching lines to that register (:help quotea). The third copies register a to the clipboard (register +) for easy pasting into another application. Replace pattern with what you want to search for, or omit it (:g//y A) to use the last search.

qaq
:g/pattern/y A
:let @+ = @a

The above procedure captures only the first line of each match. See below for copying multiline matches.

It is also easy to delete lines containing single-line matches. The first of the following commands deletes all lines containing pattern, while the second deletes all lines matching the last search:

:g/pattern/d
:g//d

Use :v//d to delete all lines that do not match the last search pattern, or :v/pattern/d to delete all lines that do not match the given pattern.

If a search pattern may find multiline matches, the above procedures to delete matching lines fail. In that case, use the following which works with any kind of match.

" Delete all lines in given range that contain a match, or part of a match.
" :DeleteLines      delete all lines matching last search
" :DeleteLines pat  delete all lines matching the given pattern 'pat'
" The deleted lines are NOT saved anywhere. Works with multiline matches.
function! DeleteLines(pattern) range
  let delid = '<!DELETE!LINE!ID!>'  " an id that does not occur in buffer
  if search(delid, 'cnw') > 0
    redraw  " so message is seen
    echo 'Error: buffer contains pattern used to delete lines'
    return
  endif
  let pattern = empty(a:pattern) ? @/ : a:pattern
  let sub = a:firstline . ',' . a:lastline . 's/' . escape(pattern, '/')
  " Retain newline if it is last character so do not delete following line.
  let rep = '/\=delid . (submatch(0) =~ "\n$" ? "\r" : "")/e'
  execute sub . rep . (&gdefault ? '' : 'g')
  execute 'g/\C' . delid . '/' . 'd'
endfunction
command! -nargs=? -range=% DeleteLines k'|<line1>,<line2>call DeleteLines(<q-args>)

Copy matches

The simple script shown here copies only the text that matches search hits. It works with multiline matches. First save the buffer because the script changes it (the changes should result in the same text, but it is safer to save first). After sourcing the following, search for a pattern, then enter :CopyMatches to copy all matches to the clipboard, or :CopyMatches x where x is any register to hold the result.

function! CopyMatches(reg)
  let hits = []
  %s//\=len(add(hits, submatch(0))) ? submatch(0) : ''/ge
  let reg = empty(a:reg) ? '+' : a:reg
  execute 'let @'.reg.' = join(hits, "\n") . "\n"'
endfunction
command! -register CopyMatches call CopyMatches(<q-reg>)

The above code works after any search, including searches which match text in more than one line. The expression used in the substitute replacement uses len() as a trick to call add() (which returns the list after appending a match to it).

Use the following to copy matches from all buffers to register a:

:let @a = ''
:bufdo CopyMatches A

In the following text, use search pattern \<A\_.\{-}Z\> (any word beginning with A, followed by any characters including newline, as few as possible, to a word ending with Z) to test :CopyMatches (it copies four hits: A1...1Z and A2...2Z and A3...3Z and A4...4Z):

Test text bbb ccc A1 ddd eee
Afake fff1Z A2 ggg2Z hhh A3 iii
jjj3Z
Nothing here.
More A4 kkk lll4Z

Copy matches or lines

The script in this section provides more features. It works with multiline matches, and provides a CopyMatches command to copy only matching text, as well as a CopyLines command to copy all lines that contain a match.

Each command accepts a range of lines, and can copy into any register (by default, the clipboard). The first of the following commands copies all text matching the last search into the clipboard, while the second copies all matches in lines 1 to 10 inclusive to register a, and the third copies matches from the current line (.) to the default register ("):

:CopyMatches
:1,10CopyMatches a
:.CopyMatches "

Matches can be copied into a scratch buffer instead of a register. To do that, use - as the register name. If a bang (exclamation mark) is used, the line number of each match is included. Also, a search pattern can be entered on the command line. For example, the following commands display the results for two searches in the same scratch buffer. Search matches, with line numbers, are displayed for all words beginning with 'a', and for all strings of digits. Enter the first command, then switch back to the original buffer while keeping the scratch window open, then enter the second command:

:CopyMatches! - \<a\w*
:CopyMatches! - \d\+

Create file ~/.vim/plugin/copymatches.vim (Unix) or $HOME/vimfiles/plugin/copymatches.vim (Windows) containing the script below, then restart Vim.

" Plugin to copy matches (search hits which may be multiline).
" Version 2012-05-03 from http://vim.wikia.com/wiki/VimTip478
"
" :CopyMatches      copy matches to clipboard (each match has newline added)
" :CopyMatches x    copy matches to register x
" :CopyMatches X    append matches to register x
" :CopyMatches -    display matches in a scratch buffer (does not copy)
" :CopyMatches pat  (after any of above options) use 'pat' as search pattern
" :CopyMatches!     (with any of above options) insert line numbers
" Same options work with the :CopyLines command (which copies whole lines).

" Jump to first scratch window visible in current tab, or create it.
" This is useful to accumulate results from successive operations.
" Global function that can be called from other scripts.
function! GoScratch()
  let done = 0
  for i in range(1, winnr('$'))
    execute i . 'wincmd w'
    if &buftype == 'nofile'
      let done = 1
      break
    endif
  endfor
  if !done
    new
    setlocal buftype=nofile bufhidden=hide noswapfile
  endif
endfunction

" Append match, with line number as prefix if wanted.
function! s:Matcher(hits, match, linenums, subline)
  if !empty(a:match)
    let prefix = a:linenums ? printf('%3d  ', a:subline) : ''
    call add(a:hits, prefix . a:match)
  endif
  return a:match
endfunction

" Append line numbers for lines in match to given list.
function! s:MatchLineNums(numlist, match)
  let newlinecount = len(substitute(a:match, '\n\@!.', '', 'g'))
  if a:match =~ "\n$"
    let newlinecount -= 1  " do not copy next line after newline
  endif
  call extend(a:numlist, range(line('.'), line('.') + newlinecount))
  return a:match
endfunction

" Return list of matches for given pattern in given range.
" If 'wholelines' is 1, whole lines containing a match are returned.
" This works with multiline matches.
" Work on a copy of buffer so unforeseen problems don't change it.
" Global function that can be called from other scripts.
function! GetMatches(line1, line2, pattern, wholelines, linenums)
  let savelz = &lazyredraw
  set lazyredraw
  let lines = getline(1, line('$'))
  new
  setlocal buftype=nofile bufhidden=delete noswapfile
  silent put =lines
  1d
  let hits = []
  let sub = a:line1 . ',' . a:line2 . 's/' . escape(a:pattern, '/')
  if a:wholelines
    let numlist = []  " numbers of lines containing a match
    let rep = '/\=s:MatchLineNums(numlist, submatch(0))/e'
  else
    let rep = '/\=s:Matcher(hits, submatch(0), a:linenums, line("."))/e'
  endif
  silent execute sub . rep . (&gdefault ? '' : 'g')
  close
  if a:wholelines
    let last = 0  " number of last copied line, to skip duplicates
    for lnum in numlist
      if lnum > last
        let last = lnum
        let prefix = a:linenums ? printf('%3d  ', lnum) : ''
        call add(hits, prefix . getline(lnum))
      endif
    endfor
  endif
  let &lazyredraw = savelz
  return hits
endfunction

" Copy search matches to a register or a scratch buffer.
" If 'wholelines' is 1, whole lines containing a match are returned.
" Works with multiline matches. Works with a range (default is whole file).
" Search pattern is given in argument, or is the last-used search pattern.
function! s:CopyMatches(bang, line1, line2, args, wholelines)
  let l = matchlist(a:args, '^\%(\([a-zA-Z"*+-]\)\%($\|\s\+\)\)\?\(.*\)')
  let reg = empty(l[1]) ? '+' : l[1]
  let pattern = empty(l[2]) ? @/ : l[2]
  let hits = GetMatches(a:line1, a:line2, pattern, a:wholelines, a:bang)
  let msg = 'No non-empty matches'
  if !empty(hits)
    if reg == '-'
      call GoScratch()
      normal! G0m'
      silent put =hits
      " Jump to first line of hits and scroll to middle.
      ''+1normal! zz
    else
      execute 'let @' . reg . ' = join(hits, "\n") . "\n"'
    endif
    let msg = 'Number of matches: ' . len(hits)
  endif
  redraw  " so message is seen
  echo msg
endfunction
command! -bang -nargs=? -range=% CopyMatches call s:CopyMatches(<bang>0, <line1>, <line2>, <q-args>, 0)
command! -bang -nargs=? -range=% CopyLines call s:CopyMatches(<bang>0, <line1>, <line2>, <q-args>, 1)

GetMatches() alternative

The following shows an alternative method to copy matches. This script is less useful than others in this tip as it does not handle multiline matches. However, the technique may be of interest as it shows how to use the match() function. In addition, it does not involve changes to the current buffer.

" Return list of matches for given pattern in given range.
" This only works for matches within a single line.
" Empty hits are skipped so search for '\d*\ze,' is not stuck in '123,456'.
" If omit match() 'count' argument, pattern '^.' matches every character.
" Using count=1 causes text before the 'start' argument to be considered.
function! GetMatches(line1, line2, pattern)
  let hits = []
  for line in range(a:line1, a:line2)
    let text = getline(line)
    let from = 0
    while 1
      let next = match(text, a:pattern, from, 1)
      if next < 0
        break
      endif
      let from = matchend(text, a:pattern, from, 1)
      if from > next
        call add(hits, strpart(text, next, from - next))
      else
        let char = matchstr(text, '.', next)
        if empty(char)
          break
        endif
        let from = next + strlen(char)
      endif
    endwhile
  endfor
  return hits
endfunction

See also

Related plugins

  • ExtractMatches provides a toolbox of commands to copy all (or only unique first) search matches / matches of a passed pattern / entire lines matching, to a register, or directly :put them into the buffer.
  • yankitute provides a :[range]Yankitute[register]/{pattern}/[string]/[flags]/[join] command.

Comments

Just after my last major update to this tip (a week ago), I found a note pointing to a vim_use thread where ZyX showed a simple yet brilliant technique for handling multiline matches. Testing shows that the posted code is not entirely correct as it misses multiline matches that occur in the same line after a short match. However, I have done extensive modifications and the resulting script (now in this tip) is sensational. JohnBeckett 03:31, May 3, 2012 (UTC)