diff --git a/plugin/incsearch.vim b/plugin/incsearch.vim new file mode 100644 index 0000000..6014331 --- /dev/null +++ b/plugin/incsearch.vim @@ -0,0 +1,225 @@ + +if !get(g:, 'eregex_incsearch_enable', 1) + \ || !exists('##CmdlineChanged') + \ || !exists('##CmdlineLeave') + \ || !has('timers') + finish +endif + +augroup eregex_incsearch_augroup + autocmd! + autocmd CmdlineChanged * call s:delayUpdate() + autocmd CmdlineLeave * call s:onLeave() +augroup END + +if !has('nvim') + if get(g:, 'eregex_incsearch_abort_fix', 1) + function! _eregex_incsearch_abort_cr() + let g:eregex_incsearch_abort = 0 + return "\" + endfunction + cnoremap _eregex_incsearch_abort_cr() + endif +endif + +" it's buggy if update immediately on #CmdlineChanged, +" especially for keymap such as +" nnoremap xxx :S/ +function! s:delayUpdate() + if get(s:, 'delayUpdateId', -1) == -1 + let s:delayUpdateId = timer_start(10, function('s:delayUpdateAction')) + endif +endfunction +function! s:delayUpdateCancel() + if get(s:, 'delayUpdateId', -1) != -1 + call timer_stop(s:delayUpdateId) + let s:delayUpdateId = -1 + endif +endfunction +function! s:delayUpdateAction(...) + let s:delayUpdateId = -1 + call s:onUpdate() +endfunction + +function! s:onUpdate() + if getcmdtype() != ':' + \ || !get(b:, 'eregex_incsearch', get(g:, 'eregex_incsearch', &incsearch)) + return + endif + let cmd = s:cmdParse(getcmdline()) + if empty(cmd) + return + endif + let Fn_filter = get(b:, 'Fn_eregex_incsearch_filter', get(g:, 'Fn_eregex_incsearch_filter', '')) + if !empty(Fn_filter) && Fn_filter(cmd['pattern']) + if exists('s:patternSaved') + let @/ = s:patternSaved + endif + if exists('s:stateSaved') + call winrestview(s:stateSaved) + endif + redraw! + return + endif + let modifiers = cmd['modifiers'] + if get(b:, 'eregex_incsearch_force_case', get(g:, 'eregex_incsearch_force_case', get(g:, 'eregex_force_case', 0))) + \ && match(cmd['modifiers'], 'i') < 0 + \ && match(cmd['modifiers'], 'I') < 0 + \ && match(cmd['pattern'], '\\c') < 0 + \ && match(cmd['pattern'], '\\C') < 0 + let modifiers .= 'I' + endif + let pattern = E2v(cmd['pattern'], modifiers) + let backward = (cmd['delim'] == '?') + + if !exists('s:hlsearchSaved') + let s:hlsearchSaved = &hlsearch + endif + if !exists('s:patternSaved') + let s:patternSaved = @/ + if !has('nvim') + let g:eregex_incsearch_abort = 1 + endif + endif + if !exists('s:stateSaved') + let s:stateSaved = winsaveview() + endif + + set nohlsearch + + if !empty(pattern) + let @/ = pattern + + try + silent! let pos = searchpos(pattern, backward ? 'bcnw' : 'cnwz') + catch + let pos = [0, 0] + endtry + if pos[0] > 0 && pos[1] > 0 + " 'reverse' the cursor by one char so that the next `:M` action + " would jump to the nearest position + " + " won't work if 'backward search with multiline pattern' + " but that only cause to jump to 'next' match, seems no other side effects + if backward + let pos[1] += 1 + else + if pos[1] > 1 + let pos[1] -= 1 + endif + endif + + let curpos = getpos('.') + let curpos[1] = pos[0] + let curpos[2] = pos[1] + call setpos('.', curpos) + else + call winrestview(s:stateSaved) + endif + else + call winrestview(s:stateSaved) + endif + + if s:hlsearchSaved && !empty(pattern) + set hlsearch + else + set nohlsearch + endif + redraw +endfunction + +function! s:onLeave() + call s:delayUpdateCancel() + + if exists('s:hlsearchSaved') + let hlsearchSaved = s:hlsearchSaved + unlet s:hlsearchSaved + endif + if exists('s:patternSaved') + let patternSaved = s:patternSaved + unlet s:patternSaved + endif + if exists('s:stateSaved') + let stateSaved = s:stateSaved + unlet s:stateSaved + endif + + if exists('hlsearchSaved') + if hlsearchSaved + set hlsearch + else + set nohlsearch + endif + endif + + if has('nvim') + let abort = get(v:event, 'abort', 0) + else + let abort = get(g:, 'eregex_incsearch_abort', 0) + endif + if !abort + return + endif + + if exists('patternSaved') + let @/ = patternSaved + endif + if exists('stateSaved') + call winrestview(stateSaved) + endif + + redraw! +endfunction + +" input: M/\cabc +" output: { +" 'delim' : '/', // `/` or `?` +" 'modifiers' : '', // modifiers passed to E2v() +" 'pattern' : 'abc', +" } +" +" input: 1,3S/\Cabc/xyz/g +" output: { +" 'delim' : '/', +" 'modifiers' : '', +" 'pattern' : 'abc', +" } +function! s:cmdParse(cmdline) + let bslashToken = nr2char(127) + let slashToken = nr2char(128) + let questionToken = nr2char(129) + let cmdline = substitute(a:cmdline, '\\\\', bslashToken, 'g') + let cmdline = substitute(cmdline, '\\/', slashToken, 'g') + let cmdline = substitute(cmdline, '\\?', questionToken, 'g') + + let modes = get(b:, 'eregex_incsearch_modes', get(g:, 'eregex_incsearch_modes', 'MSGV')) + let delims = get(b:, 'eregex_incsearch_delims', get(g:, 'eregex_incsearch_delims', + \ get(g:, 'eregex_forward_delim', '/') . get(g:, 'eregex_backward_delim', '?') + \ )) + " ^[0-9,\.\$% \t]*([MSGV])[ \t]*([\/\?]).*$ + let method = substitute(cmdline, '^[0-9,\.\$% \t]*\([' . modes . ']\)[ \t]*\([' . delims . ']\).*$', '\1', '') + let delim = substitute(cmdline, '^[0-9,\.\$% \t]*\([' . modes . ']\)[ \t]*\([' . delims . ']\).*$', '\2', '') + if len(method) != 1 || len(delim) != 1 + " { + " 'module_name' : function(cmdline), + " } + for Fn in values(get(g:, 'eregex_incsearch_custom_cmdparser', {})) + let ret = Fn(a:cmdline) + if !empty(ret) + return ret + endif + endfor + return {} + endif + + let pattern = get(split(cmdline, delim), 1, '') + let pattern = substitute(pattern, questionToken, '\\?', 'g') + let pattern = substitute(pattern, slashToken, '\\/', 'g') + let pattern = substitute(pattern, bslashToken, '\\\\', 'g') + return { + \ 'delim' : delim, + \ 'modifiers' : get(b:, 'eregex_incsearch_default_modifiers', get(g:, 'eregex_incsearch_default_modifiers', '')), + \ 'pattern' : pattern, + \ } +endfunction +