No edit summary |
(Add Related plugins section, with a link to my ExtractMatches plugin and Yanktitude.) Tag: sourceedit |
||
(31 intermediate revisions by 15 users not shown) | |||
Line 1: | Line 1: | ||
− | {{review}} |
||
{{TipImported |
{{TipImported |
||
|id=478 |
|id=478 |
||
|previous=477 |
|previous=477 |
||
|next=479 |
|next=479 |
||
− | |created= |
+ | |created=2003 |
|complexity=basic |
|complexity=basic |
||
− | |author= |
+ | |author= |
− | |version= |
+ | |version=7.0 |
|rating=13/8 |
|rating=13/8 |
||
+ | |category1=Searching |
||
+ | |category2= |
||
}} |
}} |
||
+ | 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 <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''. |
||
+ | <pre> |
||
+ | :g/ |
||
+ | :g/pattern |
||
+ | </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 <code>%</code> for the file path to search only the current file, for example: |
||
+ | <pre> |
||
+ | " Save file, search it for 'pattern', and open a clickable list. |
||
+ | :w |
||
+ | :vimgrep /pattern/ % |
||
+ | :copen |
||
+ | </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 <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]]. |
||
+ | |||
+ | ==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 [[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> |
||
+ | qaq |
||
+ | :g/pattern/y A |
||
+ | :let @+ = @a |
||
+ | </pre> |
||
+ | |||
+ | 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> |
||
+ | :g/pattern/d |
||
+ | :g//d |
||
+ | </pre> |
||
+ | |||
+ | Use <code>:v//d</code> to delete all lines that do ''not'' match the last search pattern, or |
||
+ | <code>:v/pattern/d</code> 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. |
||
<pre> |
<pre> |
||
+ | " Delete all lines in given range that contain a match, or part of a match. |
||
− | " previous clear the clipboard with this command :normal "*y0 |
||
+ | " :DeleteLines delete all lines matching last search |
||
− | " Usage: :g/<pattern>/call CopyPattern() |
||
+ | " :DeleteLines pat delete all lines matching the given pattern 'pat' |
||
− | function CopyPattern() |
||
+ | " The deleted lines are NOT saved anywhere. Works with multiline matches. |
||
− | let idx = 0 |
||
+ | function! DeleteLines(pattern) range |
||
− | let xEnd = 0 |
||
+ | let delid = '<!DELETE!LINE!ID!>' " an id that does not occur in buffer |
||
− | while idx >= 0 |
||
+ | if search(delid, 'cnw') > 0 |
||
− | let @* = @* . matchstr(getline("."), '' . histget("/", -1), idx) . "\n" |
||
+ | redraw " so message is seen |
||
− | let xEnd = matchend(getline("."), '' . histget("/", -1), idx) |
||
+ | echo 'Error: buffer contains pattern used to delete lines' |
||
− | let idx = match(getline("."), '' . histget("/", -1), xEnd) |
||
+ | return |
||
− | endwhile |
||
+ | endif |
||
− | unlet idx |
||
+ | let pattern = empty(a:pattern) ? @/ : a:pattern |
||
− | unlet xEnd |
||
+ | 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 |
endfunction |
||
+ | command! -nargs=? -range=% DeleteLines k'|<line1>,<line2>call DeleteLines(<q-args>) |
||
</pre> |
</pre> |
||
− | == |
+ | ==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 <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 few more details or an example would be helpful, do you run this command in your vimrc file and what is it doing exactly? I was looking to create a mapping to easily copy and paste text from my windows session to my vim using something like vmap <C-c> "*y but this did not seem to work in non gui mode, and I was lookign for some answers with that... this might be somethign different but I still could not understand the context of this example immediately from reading it. thanks, ben |
||
+ | <pre> |
||
+ | 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>) |
||
+ | </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 one is for multi-line pattern (like using \_.). |
||
+ | Use the following to copy matches from all buffers to register <code>a</code>: |
||
<pre> |
<pre> |
||
+ | :let @a = '' |
||
− | " previous clear the clipboard with this command :normal "*y0 |
||
+ | :bufdo CopyMatches A |
||
− | " Usage: :g/<pattern>/call CopyMPattern() |
||
+ | </pre> |
||
− | function CopyMPattern() |
||
+ | |||
− | let resultTxt = '' |
||
+ | 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>): |
||
− | let allLines = getline(".", "$") |
||
+ | <pre> |
||
− | for line in allLines |
||
+ | Test text bbb ccc A1 ddd eee |
||
− | let resultTxt .= line . "\n" |
||
+ | 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 (<code>.</code>) to the default register (<code>"</code>): |
||
+ | <pre> |
||
+ | :CopyMatches |
||
+ | :1,10CopyMatches a |
||
+ | :.CopyMatches " |
||
+ | </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> |
||
+ | :CopyMatches! - \<a\w* |
||
+ | :CopyMatches! - \d\+ |
||
+ | </pre> |
||
+ | |||
+ | 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. |
||
+ | <pre> |
||
+ | " 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 |
endfor |
||
+ | if !done |
||
− | echo resultTxt |
||
+ | new |
||
− | let @* = @* . matchstr(resultTxt, '' . histget("/", -1), 0) . "\n" |
||
+ | setlocal buftype=nofile bufhidden=hide noswapfile |
||
− | unlet resultTxt |
||
+ | endif |
||
− | unlet allLines |
||
endfunction |
endfunction |
||
− | </pre> |
||
+ | " 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. |
||
− | I think this function works well. Please, try on it~ (BY WJY) |
||
+ | 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. |
||
− | fu! CopyMPattern() |
||
+ | " This only works for matches within a single line. |
||
− | let posinit = getpos(".") |
||
+ | " Empty hits are skipped so search for '\d*\ze,' is not stuck in '123,456'. |
||
− | cal cursor(1, 1) |
||
+ | " If omit match() 'count' argument, pattern '^.' matches every character. |
||
− | let snum = 0 |
||
+ | " Using count=1 causes text before the 'start' argument to be considered. |
||
− | let enum = 0 |
||
+ | function! GetMatches(line1, line2, pattern) |
||
− | let cnt = 0 |
||
− | let |
+ | let hits = [] |
+ | for line in range(a:line1, a:line2) |
||
− | wh 1 |
||
− | let |
+ | let text = getline(line) |
− | + | let from = 0 |
|
+ | while 1 |
||
− | let enum = search(histget("/", -1), 'e', line("$")) |
||
− | let |
+ | let next = match(text, a:pattern, from, 1) |
− | + | if next < 0 |
|
+ | break |
||
− | endw |
||
− | + | endif |
|
+ | let from = matchend(text, a:pattern, from, 1) |
||
− | for line in searchedLines |
||
− | + | 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 |
endfor |
||
+ | return hits |
||
− | let @+ = resultTxt |
||
+ | endfunction |
||
− | cal cursor(posinit[1], posinit[2]) |
||
− | echom cnt 'lines(or blocks) were copied to the clipboard.' |
||
− | endf |
||
</pre> |
</pre> |
||
+ | |||
+ | ==See also== |
||
+ | *[[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 |
||
+ | *[[VimTip1543|Find in files within Vim]] for a clickable list of search hits |
||
+ | *[[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== |
||
+ | 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) |
Revision as of 10:45, 8 May 2015
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
- Redirect g search output to redirect
g//
output to a new window or a file - Filter buffer on a search result to create a scratch buffer with matching lines
- Find in files within Vim for a clickable list of search hits
- Folding with Regular Expression to fold away non-matching lines
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)