dotar/vim/plugin/greplace.vim
2011-11-17 16:00:49 -06:00

311 lines
8.4 KiB
VimL
Executable File

" File: greplace.vim
" Script to search and replace pattern across multiple files
" Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com)
" Version: 1.0 Beta1
" Last Modified: March 3, 2007
"
if exists("loaded_greplace")
finish
endif
let loaded_greplace = 1
" Requires Vim 7.0 and above
if v:version < 700
finish
endif
" Line continuation used here
let s:cpo_save = &cpo
set cpo&vim
if &isfname =~ '['
let s:gRepl_bufname = '[Global\ Replace]'
else
let s:gRepl_bufname = '\[Global\ Replace\]'
endif
" Character to use to quote patterns
if !exists("Greplace_Shell_Quote_Char")
if has("win32") || has("win16") || has("win95")
let Greplace_Shell_Quote_Char = ''
else
let Greplace_Shell_Quote_Char = "'"
endif
endif
let s:save_qf_list = {}
function! s:warn_msg(msg)
echohl WarningMsg
echomsg a:msg
echohl None
endfunction
highlight GReplaceText term=reverse cterm=reverse gui=reverse
function! s:gReplace()
if empty(s:save_qf_list)
return
endif
let change_all = v:cmdbang
let changeset = {}
" Parse the replace buffer contents and get a List of changed lines
let lines = getbufline('%', 1, '$')
for l in lines
if l !~ '[^:]\+:\d\+:.*'
continue
endif
let match_l = matchlist(l, '\([^:]\+\):\(\d\+\):\(.*\)')
let fname = match_l[1]
let lnum = match_l[2]
let text = match_l[3]
let key = fname . ':' . lnum
if s:save_qf_list[key].text == text
" This line is not changed
continue
endif
let fname = s:save_qf_list[key].fname
if !has_key(changeset, fname)
let changeset[fname] = {}
endif
let changeset[fname][lnum] = text
endfor
if empty(changeset)
" The replace buffer is not changed by the user
call s:warn_msg('Error: No changes in the replace buffer')
return
endif
" Make the changes made by the user to the buffers
for f in keys(changeset)
let f_l = changeset[f]
if !filereadable(f)
continue
endif
silent! exe 'hide edit ' . f
let change_buf_all = 0 " Accept all the changes in this buffer
for lnum in keys(f_l)
exe lnum
let cur_ltext = getline(lnum)
let new_ltext = f_l[lnum]
let s_idx =0
while cur_ltext[s_idx] == new_ltext[s_idx]
let s_idx += 1
endwhile
let e_idx1 = strlen(cur_ltext) - 1
let e_idx2 = strlen(new_ltext) - 1
while e_idx1 >= 0 && cur_ltext[e_idx1] == new_ltext[e_idx2]
let e_idx1 -= 1
let e_idx2 -= 1
endwhile
let e_idx1 += 2
if (s_idx + 1) == e_idx1
" If there is nothing to highlight, then highlight the
" last character
let e_idx1 += 1
endif
let hl_pat = '/\%'.lnum.'l\%>'.s_idx.'v.*\%<'.e_idx1.'v/'
exe '2match GReplaceText ' . hl_pat
redraw!
try
let change_line = 0
if !change_all && !change_buf_all
let new_text_frag = strpart(new_ltext, s_idx,
\ e_idx2 - s_idx + 1)
echo "Replace with '" . new_text_frag . "' (y/n/a/b/q)?"
let ans = 'x'
while ans !~? '[ynab]'
let ans = nr2char(getchar())
if ans == 'q' || ans == "\<Esc>" " Quit
return
endif
endwhile
if ans == 'a' " Accept all
let change_all = 1
endif
if ans == 'b' " Accept changes in the current buffer
let change_buf_all = 1
endif
if ans == 'y' " Yes
let change_line = 1
endif
endif
if change_all || change_buf_all || change_line
call setline(lnum, f_l[lnum])
endif
finally
2match none
endtry
endfor
endfor
endfunction
function! s:gReplace_show_matches()
let qf = getqflist()
if empty(qf)
call s:warn_msg('Error: Quickfix list is empty')
return
endif
let new_qf = {}
" Populate the buffer with the current quickfix list
let lines = []
for l in qf
if l.valid && l.lnum > 0 && l.bufnr > 0
let fname = fnamemodify(bufname(l.bufnr), ':.')
let buf_text = fname . ':' . l.lnum . ':' . l.text
let k = fname . ':' . l.lnum
let new_qf[k] = {}
let new_qf[k].fname = fnamemodify(bufname(l.bufnr), ':p')
let new_qf[k].text = l.text
else
let buf_text = l.text
endif
call add(lines, buf_text)
endfor
if empty(lines)
" No valid matching lines
return
endif
let w = bufwinnr(s:gRepl_bufname)
if w == -1
" Create a new window
silent! exe 'new ' . s:gRepl_bufname
else
exe w . 'wincmd w'
" Discard the contents of the buffer
%d _
endif
call append(0, '#')
call append(1, '# Modify the contents of this buffer and then')
call append(2, '# use the ":Greplace" command to merge the changes.')
call append(3, '#')
call append(4, lines)
call cursor(5, 1)
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal nomodified
command! -buffer -nargs=0 -bang Greplace call s:gReplace()
let s:save_qf_list = new_qf
endfunction
" gSearch
" Search for a pattern in a group of files using ':grep'
function! s:gSearch(type, ...)
let grep_opt = '-r'
let pattern = ''
let filenames = ''
" Parse the arguments
" grep command-line flags are specified using the "-flag" format
" the next argument is assumed to be the pattern
" and the next arguments are assumed to be filenames or file patterns
let argcnt = 1
while argcnt <= a:0
if &grepprg =~ 'findstr' && a:{argcnt} =~ '^/'
let grep_opt = grep_opt . ' ' . a:{argcnt}
elseif a:{argcnt} =~ '^-'
let grep_opt = grep_opt . ' ' . a:{argcnt}
elseif pattern == ''
let pattern = a:{argcnt}
else
let filenames = filenames . ' ' . a:{argcnt}
endif
let argcnt += 1
endwhile
" If search pattern is not specified on command-line, ask for it
if pattern == ''
let pattern = input('Search pattern: ', expand('<cword>'))
if pattern == ''
return
endif
" Quote the supplied pattern
let pattern = g:Greplace_Shell_Quote_Char . pattern .
\ g:Greplace_Shell_Quote_Char
endif
if a:type == 'grep'
if filenames == ''
let filenames = input('Search in files: ', '*', 'file')
endif
elseif a:type == 'args'
" Search in all the filenames in the argument list
let arg_cnt = argc()
if arg_cnt == 0
call s:warn_msg('Error: Argument list is empty')
return
endif
let filenames = ''
for i in range(0, arg_cnt - 1)
let filenames .= ' ' . argv(i)
endfor
else
" Get a list of all the buffer names
let filenames = ''
for i in range(1, bufnr('$'))
let bname = bufname(i)
if bufexists(i) && buflisted(i) && filereadable(bname) &&
\ getbufvar(i, '&buftype') == ''
let filenames .= ' ' . bufname(i)
endif
endfor
endif
if filenames == ''
call s:warn_msg('Error: No valid file names')
return
endif
" Use ! after grep, so that Vim doesn't automatically jump to the
" first match
let grep_cmd = 'grep! ' . grep_opt . ' ' . pattern . ' ' . filenames
" Run the grep and get the matches
silent! exe grep_cmd
call s:gReplace_show_matches()
endfunction
command! -nargs=0 Gqfopen call s:gReplace_show_matches()
command! -nargs=* -complete=file Gsearch call s:gSearch('grep', <f-args>)
command! -nargs=* Gargsearch call s:gSearch('args', <f-args>)
command! -nargs=* Gbuffersearch call s:gSearch('buffer', <f-args>)
" restore 'cpo'
let &cpo = s:cpo_save
unlet s:cpo_save