311 lines
8.4 KiB
VimL
Executable File
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
|
|
|