searching through multiple buffers

71 views
Skip to first unread message

Ed S. Peschko

unread,
Sep 10, 2007, 10:15:03 PM9/10/07
to vim...@googlegroups.com
hey all,

Is there a way of searching through multiple buffers? ie: I'd like a derivative of '/' to
be able to span files, ie: if it doesn't find it in one file, it goes to the next
in the bufferlist, and so on..

Having the ability to autosave if it exits the buffer would be cool too..

Ed

(ps - where this is coming from: I'm doing auto edits on thousands of files through
macros: finding a pattern plus using a complicated set of edit commands to do the
modifications.

It'd be really nice if I could macro this up so that I could say: 'qa', record the
correct keystrokes, and then say '100@a' to get done really quick.. it snags though,
when you go to the next file and a pattern isn't found..
)

Ben Schmidt

unread,
Sep 11, 2007, 12:35:21 AM9/11/07
to vim...@googlegroups.com
> Is there a way of searching through multiple buffers? ie: I'd like a derivative of '/' to
> be able to span files, ie: if it doesn't find it in one file, it goes to the next
> in the bufferlist, and so on..

I'm not aware of any functionality quite like that, though there is the :bufdo
command.

I suspect it would be easiest to use the functionality of :vimgrep to do the
searching, provided your files are all in the same directory or something
similarly convenient. Your recording would just use :cn rather than just n to jump
to the line containing the next match.

Ben.

Send instant messages to your online friends http://au.messenger.yahoo.com

Mikolaj Machowski

unread,
Sep 11, 2007, 2:17:37 PM9/11/07
to vim...@googlegroups.com
Dnia wtorek 11 wrzesień 2007, Ed S. Peschko napisał:
> hey all,
>
> Is there a way of searching through multiple buffers? ie: I'd like a
> derivative of '/' to be able to span files, ie: if it doesn't find it in
> one file, it goes to the next in the bufferlist, and so on..
>
> Having the ability to autosave if it exits the buffer would be cool
> too..
>

If files are sharing common element in name you can use:

:vimgrep /re/ {namewithwildcards}

For buffers you can also use

:bufdo vimgrep /re/ %

But it has few downsides: matches for all files will be saved into
separated quickfix lists, Vim will remember only last ten lists.

But you could probably write some simple script to save all data in
external error list and later load it.

:help quickfix
:help location-list

m.

Matthew Gilbert

unread,
Sep 11, 2007, 3:34:37 PM9/11/07
to vim_dev
On Sep 10, 10:15 pm, "Ed S. Peschko" <e...@pge.com> wrote:
> Is there a way of searching through multiple buffers? ie: I'd like a derivative of '/' to
> be able to span files, ie: if it doesn't find it in one file, it goes to the next
> in the bufferlist, and so on..

I made a script that searches through all of my open buffers using
bufdo and shows them in a location list like window. It might help get
you started since I don't think it does quite what you want.

It doesn't use vimgrep because a lot of times I edit multiple unnamed
buffers that I need to search through. The results window keeps a
history of searches and matches, which I find useful.

HTH, _matt

"
========================================================================
" Search Stack - a quick solution to searching all open buffers (even
unnamed)
" To search - :SS /search pattern/
" This will open a window with the search stack results (most recent
search on
" top)
" To follow a match, hit enter.
" To return to the search results use <Leader>sh
"
========================================================================

if exists("loaded_sestack")
finish
endif
let loaded_sestack = 1

let s:results = []
let s:last_line = -1

nmap <silent> <unique> <Leader>sh :SH<CR>

function! <SID>Search(pat)
if len(s:results) > 10
remove(s:results, 0)
endif
call add(s:results, [])
let l:result = s:DoSearch(a:pat, 's:AppendResults')
if l:result == -1
return
endif
if len(s:results[-1]) == 0
echo "No matches!"
return
endif
call s:ShowResults()
normal 5G
call s:ShowMatch()
endfunction
command! -nargs=1 SS call <SID>Search(<q-args>)

function! <SID>DoSearch(pat, func)
let v:errmsg = ""
let l:startbuf = bufnr("%")
let l:startwin = winnr()
if a:pat !~ "^/.*/$"
echo "Search must start and end with \"/\""
return -1
endif
let l:pat = a:pat[1:-2]
let l:cmd = "bufdo g/" . l:pat . "/call " . a:func . "()"
silent! exe l:cmd
exe l:startwin . "wincmd w"
exe "buffer! " . l:startbuf
if v:errmsg != ""
echo v:errmsg
endif
endfunction

function! <SID>AppendResults()
if bufname('%') != '[SS]'
let l:curtext = getline('.')
let l:curline = line('.')
let l:curbuf = bufnr('%')
let l:bufname = bufname('%')
if l:bufname == ''
let l:bufname = '[Buffer #' . l:curbuf . ']'
endif
call add(s:results[-1], [@/, [l:curbuf, l:bufname, l:curline,
l:curtext]])
endif
endfunction

function! <SID>FindWindow()
for i in range(winnr('$'))
let l:bn = winbufnr(i + 1)
if l:bn != -1
if bufname(l:bn) == '[SS]'
return i
endif
endif
endfor
return -1
endfunction

function! <SID>ShowResults()
let l:winnr = s:FindWindow()
if l:winnr == -1
exe "silent! botright sp [SS]"
resize 6
else
" TODO: How do I goto a window, instead of assuming it's still
" the bottom right window?
wincmd b
exe "silent! e! [SS]"
endif

setlocal bufhidden=delete
setlocal buftype=nofile
setlocal modifiable
setlocal noswapfile
setlocal nowrap

nnoremap <buffer> <silent> <cr> :call <SID>SelectMatch()<cr>
autocmd! CursorMoved <buffer> silent call <SID>ShowMatch()
nnoremap <buffer> <silent> <LeftMouse> <LeftMouse>:call
<SID>SelectMatch()<cr>

let l:revlist = reverse(copy(s:results))
for r in l:revlist
let l:ph = 0
for m in r
if !l:ph
put = ''
put = '================================='
put = 'Search string: ' . m[0]
let l:ph = 1
endif
let i = m[1]
put = i[0] . ' ' . i[1] . ' ' . i[2] . ' : ' . i[3]
endfor
endfor

setlocal nomodifiable

" Goto the saved line
if s:last_line != -1
exe 'normal ' . s:last_line . 'G'
endif
endfunction
command! -nargs=0 SH call <SID>ShowResults()

function! <SID>SelectMatch()
let l:line = getline('.')
let l:ml = matchlist(getline('.'), '\v^\s*(\d+).{-}(\d+) :')
let s:last_line = line('.')
exe "normal \<c-w>p"
exe 'b ' . l:ml[1]
exe 'normal ' . l:ml[2] . 'G'
endfunction

function! <SID>ShowMatch()
let l:ss = ''
let l:line = line('.')
while getline(l:line) !~ '\v^\=+$'
let l:ml = matchlist(getline(l:line), '\vSearch string: (.*)
$')
if len(l:ml)
let l:ss = l:ml[1]
break
endif
let l:line = l:line - 1
if l:line == 0
break
endif
endwhile

if l:ss != ''
exe '2match Search /' . l:ss . '/'
exe "let @/ = \'" . escape(l:ss, "'") . "'"
autocmd! BufLeave <buffer> call <SID>SeStackLeave()
endif
endfunction

function! <SID>SeStackLeave()
silent 2match none
let s:last_line = line('.')
endfunction

function! <SID>SelectPrev()
if s:FindWindow() == -1
call s:ShowResults()
endif
" TODO: Goto the window (again blindly)
wincmd b

if s:last_line == -1
normal G
endif

let l:res = search('\v^\s*\d+', 'bW')
if l:res == 0
echo 'No previous matches'
return
endif
call s:SelectMatch()
endfunction
command! -nargs=0 SP call <SID>SelectPrev()
nmap <silent> <unique> <Leader>sp :SP<CR>

function! <SID>SelectNext()
if s:FindWindow() == -1
call s:ShowResults()
endif
" TODO: Goto the window (again blindly)
wincmd b

if s:last_line == -1
normal gg
endif

let l:res = search('\v^\s*\d+', 'W')
if l:res == 0
echo 'No more matches'
wincmd p
return
endif
call s:ShowMatch()
call s:SelectMatch()
endfunction
command! -nargs=0 SN call <SID>SelectNext()
nmap <silent> <unique> <Leader>sn :SN<CR>

function! <SID>ClearSearchStack()
let s:results = []
endfunction
command! -nargs=0 SC call <SID>ClearSearchStack()

Charles E Campbell Jr

unread,
Sep 11, 2007, 4:00:29 PM9/11/07
to vim...@googlegroups.com
Mikolaj Machowski wrote:

Well, I was going to suggest

:let qlist=[]
:bufdo silent vimgrep /pattern/ % | call add(qlist,getqflist())
:call setqflist(qlist)
:cope

Unfortunately, the formats for what getqflist() delivers and setqflist()
takes are different, and beating them into compatiblity is more than I
want to attempt at the moment.

Regards,
Chip Campbell


Yegappan Lakshmanan

unread,
Sep 11, 2007, 6:13:57 PM9/11/07
to vim...@googlegroups.com
Hi Charles,

This does work. You have to use extend() instead of add() in
the above commands. When you call add(), the current quickfix
list is added as a single item to qlist.

- Yegappan

Ben Schmidt

unread,
Sep 12, 2007, 2:42:09 AM9/12/07
to vim...@googlegroups.com
>>> hey all,
>>>
>>> Is there a way of searching through multiple buffers? ie: I'd like a
>>> derivative of '/' to be able to span files, ie: if it doesn't find it in
>>> one file, it goes to the next in the bufferlist, and so on..
>>>
>>> Having the ability to autosave if it exits the buffer would be cool
>>> too..
>>>
>> If files are sharing common element in name you can use:
>>
>> :vimgrep /re/ {namewithwildcards}
>>
>> For buffers you can also use
>>
>> :bufdo vimgrep /re/ %
>>
>> But it has few downsides: matches for all files will be saved into
>> separated quickfix lists, Vim will remember only last ten lists.

You should be able to join them together by doing

:bufdo vimgrepadd /re/ %

shouldn't you?

Need to clear the list beforehand, I suppose. I can't spot a simple command for
that, but I think

:cexpr ""

will do it if nothing else!

Charles E Campbell Jr

unread,
Sep 12, 2007, 10:03:12 AM9/12/07
to vim...@googlegroups.com
Yegappan Lakshmanan wrote:

Thanks, Yegappan!

Guess I didn't look much further as to why the above didn't work when I
read, under the help for setqflist(), that:
"Note that the list is not exactly the same as what |getqflist()| returns."

So, here's a vim snippet implementing the above:

" qfbp.vim
" Author: Charles E. Campbell, Jr.
" Date: Sep 12, 2007
" Version: 1
" ---------------------------------------------------------------------
" Load Once: {{{1
if &cp || exists("g:loaded_qfpat")
finish
endif
let g:loaded_qfpat= "v1"
let s:keepcpo = &cpo
set cpo&vim

" ---------------------------------------------------------------------
" Public Interface: {{{1
com! -nargs=1 QFBP call s:QuickFixBufPat(<q-args>)

" ---------------------------------------------------------------------
" s:QuickFixBufPat: {{{2
fun! s:QuickFixBufPat(pat)
" call Dfunc("s:QuickFixBufPat(pat<".a:pat.">)")
let qlist=[]
exe "bufdo silent vimgrep /".a:pat."/ % | call extend(qlist,getqflist())"
call setqflist(qlist)
cope
" call Dret("s:QuickFixBufPat")
endfun

" ---------------------------------------------------------------------
" Restore: {{{1
let &cpo= s:keepcpo
unlet s:keepcpo
" vim: ts=4 fdm=marker


Regards,
Chip Campbell

Reply all
Reply to author
Forward
0 new messages