diff --git a/.travis.yml b/.travis.yml index da26c5e..17aa2d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,19 @@ install: - if [ x"$PPA" == "xyes" ] ; then sudo add-apt-repository ppa:pi-rho/dev -y; fi - sudo apt-get update -q - sudo apt-get install vim-nox - -before_script: - - vim --version + - sudo pip install vim-vint - git clone https://github.com/thinca/vim-themis - git clone https://github.com/syngan/vim-vimlint /tmp/vim-vimlint - git clone https://github.com/ynkdir/vim-vimlparser /tmp/vim-vimlparser + - git clone https://github.com/vim-jp/vital.vim /tmp/vital.vim + +before_script: + - vim --version + - vint --version script: - - vim-themis/bin/themis --reporter spec + - vim-themis/bin/themis --runtimepath /tmp/vital.vim --reporter spec - vim --cmd "try | helptags doc/ | catch | cquit | endtry" --cmd quit - sh /tmp/vim-vimlint/bin/vimlint.sh -l /tmp/vim-vimlint -p /tmp/vim-vimlparser -e EVL102.l:_=1 -c func_abort=1 autoload/incsearch.vim - sh /tmp/vim-vimlint/bin/vimlint.sh -l /tmp/vim-vimlint -p /tmp/vim-vimlparser -e EVL102.l:_=1 -c func_abort=1 autoload/incsearch + - vint autoload/incsearch autoload/incsearch.vim plugin diff --git a/README.md b/README.md index b22a0a1..529282a 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,45 @@ Move the cursor to next/previous matches while incremental searching like Emacs. | `(incsearch-scroll-f)` | scroll to the next page match. default: `` | | `(incsearch-scroll-b)` | scroll to the previous page match. default: `` | +:tada: Version 2.0 :tada: +------------------------- +Now, incsearch.vim provides some (experimental) API. +You can implement or use very useful yet another search command :mag_right: + +### Experimental API +- `:h incsearch#go()` +- `:h incsearch-config` + +Starts incsearch.vim with your custom configuration. See help docs for more detail. + +### Converter feature +- `:h incsearch-config-converters` +- The list of converter extensions: https://github.com/haya14busa/incsearch.vim/wiki/List-of-plugins-for-incsearch.vim#converter-extensions + +#### Example + +```vim +function! s:noregexp(pattern) abort + return '\V' . escape(a:pattern, '\') +endfunction + +function! s:config() abort + return {'converters': [function('s:noregexp')]} +endfunction + +noremap z/ incsearch#go(config()) +``` + +incsearch.vim x fuzzy https://github.com/haya14busa/incsearch-fuzzy.vim +![incsearch-fuzzy.gif](https://raw.githubusercontent.com/haya14busa/i/master/incsearch.vim/extensions/incsearch-fuzzy.gif) + +### Module extension +- `:h incsearch-config-modules` +- The list of module extentions: https://github.com/haya14busa/incsearch.vim/wiki/List-of-plugins-for-incsearch.vim#module-extensions + +incsearch.vim x fuzzy x vim-easymotion https://github.com/haya14busa/incsearch-easymotion.vim +![incsearch-fuzzy-easymotion.gif](https://raw.githubusercontent.com/haya14busa/i/master/incsearch.vim/extensions/incsearch-fuzzy-easymotion.gif) + Author ------ haya14busa (https://github.com/haya14busa) diff --git a/autoload/incsearch.vim b/autoload/incsearch.vim index 824d863..e6c6c08 100644 --- a/autoload/incsearch.vim +++ b/autoload/incsearch.vim @@ -136,16 +136,24 @@ function! incsearch#_go(config) abort normal! gv endif let cli = incsearch#cli#make(a:config) - let l:Search = function(a:config.is_stay ? 'incsearch#stay' : 'incsearch#search') - let cmd = l:Search(cli) - if !a:config.is_expr - let should_set_jumplist = (cli._flag !=# 'n') - call s:set_search_related_stuff(cli, cmd, should_set_jumplist) - if a:config.mode is# 'no' - call s:set_vimrepeat(cmd) + let input = s:get_input(cli) + if cli._does_exit_from_incsearch + " Outer incsearch-plugin handle it so do not something in paticular + return cli._return_cmd + else + " After getting input, generate command, take aftercare, and return + " command. + let l:F = function(cli._flag is# 'n' ? 's:stay' : 's:search') + let cmd = l:F(cli, input) + if !a:config.is_expr + let should_set_jumplist = (cli._flag !=# 'n') + call s:set_search_related_stuff(cli, cmd, should_set_jumplist) + if a:config.mode is# 'no' + call s:set_vimrepeat(cmd) + endif endif + return cmd endif - return cmd endfunction "" To handle recursive mapping, map command to (_incsearch-dotrepeat) @@ -157,16 +165,6 @@ function! s:set_vimrepeat(cmd) abort silent! call repeat#set("\(_incsearch-dotrepeat)") endfunction -" similar to incsearch#forward() but do not move the cursor unless explicitly -" move the cursor while searching -" @expr but sometimes called by non- -" @return: command which is excutable with expr-mappings or `exec 'normal!'` -function! incsearch#stay(cli) abort - let input = s:get_input(a:cli) - let l:F = function(a:cli._flag is# 'n' ? 's:stay' : 's:search') - return l:F(a:cli, input) -endfunction - let g:incsearch#_view = get(g:, 'incsearch#_view', {}) noremap (_incsearch-winrestview) noremap! (_incsearch-winrestview) @@ -175,7 +173,7 @@ xnoremap (_incsearch-winrestview) :call winrestview(g:incsea function! s:stay(cli, input) abort let [raw_pattern, offset] = a:cli._parse_pattern() - let pattern = incsearch#convert(raw_pattern) + let pattern = a:cli._convert(raw_pattern) " NOTE: do not move cursor but need to handle {offset} for n & N ...! {{{ " FIXME: cannot set {offset} if in operator-pending mode because this @@ -202,10 +200,6 @@ function! s:stay(cli, input) abort return s:U.is_visual(a:cli._mode) ? "\gv" : "\" " just exit endfunction -function! incsearch#search(cli) abort - return s:search(a:cli, s:get_input(a:cli)) -endfunction - function! s:search(cli, input) abort call incsearch#autocmd#auto_nohlsearch(1) " NOTE: `.` repeat doesn't handle this return a:cli._generate_command(a:input) @@ -257,7 +251,7 @@ function! s:set_search_related_stuff(cli, cmd, ...) abort else " Add history if necessary " Do not save converted pattern to history - let pattern = incsearch#convert(raw_pattern) + let pattern = a:cli._convert(raw_pattern) let input = a:cli._combine_pattern(raw_pattern, offset) call histadd(a:cli._base_key, input) let @/ = pattern @@ -310,16 +304,6 @@ function! incsearch#parse_pattern(expr, search_key) abort return result endfunction -" convert implementation. assume pattern is not empty -function! s:_convert(pattern) abort - return incsearch#magic() . a:pattern -endfunction - -function! incsearch#convert(pattern) abort - " TODO: convert pattern if required in addition to appending magic flag - return a:pattern is# '' ? a:pattern : s:_convert(a:pattern) -endfunction - function! incsearch#detect_case(pattern) abort " Ignore \%C, \%U, \%V for smartcase detection let p = substitute(a:pattern, s:non_escaped_backslash . '%[CUV]', '', 'g') @@ -391,7 +375,7 @@ function! s:emulate_search_error(direction, ...) abort silent! call incsearch#execute_search(keyseq . "\") call winrestview(from) if g:incsearch#do_not_save_error_message_history - if v:errmsg != '' + if v:errmsg !=# '' call s:Error(v:errmsg) else let v:errmsg = old_errmsg @@ -405,13 +389,13 @@ function! s:emulate_search_error(direction, ...) abort catch /^Vim\%((\a\+)\)\=:E/ let first_error = matchlist(v:exception, '\v^Vim%(\(\a+\))=:(E.*)$')[1] call s:Error(first_error, 'echom') - if last_error != '' && last_error !=# first_error + if last_error !=# '' && last_error !=# first_error call s:Error(last_error, 'echom') endif finally call winrestview(from) endtry - if v:errmsg == '' + if v:errmsg ==# '' let v:errmsg = old_errmsg endif endif diff --git a/autoload/incsearch/cli.vim b/autoload/incsearch/cli.vim index 02ca7d5..499bdd0 100644 --- a/autoload/incsearch/cli.vim +++ b/autoload/incsearch/cli.vim @@ -38,6 +38,7 @@ function! incsearch#cli#set(cli, config) abort let a:cli._pattern = a:config.pattern let a:cli._prompt = a:config.prompt let a:cli._keymap = a:config.keymap + let a:cli._converters = a:config.converters let a:cli._flag = a:config.is_stay ? 'n' \ : a:config.command is# '/' ? '' \ : a:config.command is# '?' ? 'b' @@ -54,7 +55,7 @@ function! incsearch#cli#set(cli, config) abort return a:cli endfunction -let s:cli = s:V.import('Over.Commandline').make_default("/") +let s:cli = s:V.import('Over.Commandline').make_default('/') let s:modules = s:V.import('Over.Commandline.Modules') " Add modules diff --git a/autoload/incsearch/config.vim b/autoload/incsearch/config.vim index a9b603c..d8b9de0 100644 --- a/autoload/incsearch/config.vim +++ b/autoload/incsearch/config.vim @@ -27,6 +27,7 @@ let s:config = { \ 'count1': 1, \ 'prompt': '', \ 'modules': [], +\ 'converters': [], \ 'keymap': {} \ } @@ -43,7 +44,7 @@ endfunction " @return config with default value function! incsearch#config#make(additional) abort - let default = extend(copy(s:config), s:lazy_config()) + let default = extend(deepcopy(s:config), s:lazy_config()) let c = s:U.deepextend(default, a:additional) if c.prompt is# '' let c.prompt = c.command @@ -53,28 +54,28 @@ endfunction let s:default_keymappings = { \ "\" : { -\ "key" : "(incsearch-next)", -\ "noremap" : 1, +\ 'key' : '(incsearch-next)', +\ 'noremap' : 1, \ }, \ "\" : { -\ "key" : "(incsearch-prev)", -\ "noremap" : 1, +\ 'key' : '(incsearch-prev)', +\ 'noremap' : 1, \ }, \ "\" : { -\ "key" : "(incsearch-scroll-f)", -\ "noremap" : 1, +\ 'key' : '(incsearch-scroll-f)', +\ 'noremap' : 1, \ }, \ "\" : { -\ "key" : "(incsearch-scroll-b)", -\ "noremap" : 1, +\ 'key' : '(incsearch-scroll-b)', +\ 'noremap' : 1, \ }, \ "\" : { -\ "key" : "(buffer-complete)", -\ "noremap" : 1, +\ 'key' : '(buffer-complete)', +\ 'noremap' : 1, \ }, \ "\" : { -\ "key": "\", -\ "noremap": 1 +\ 'key': "\", +\ 'noremap': 1 \ }, \ } diff --git a/autoload/incsearch/highlight.vim b/autoload/incsearch/highlight.vim index 3e853d2..8ca5d86 100644 --- a/autoload/incsearch/highlight.vim +++ b/autoload/incsearch/highlight.vim @@ -39,14 +39,30 @@ let s:U = incsearch#util#import() " Management: let s:V = vital#of('incsearch') -let s:hi = s:V.import("Coaster.Highlight").make() +let s:hi = s:V.import('Coaster.Highlight').make() let g:incsearch#highlight#_hi = s:hi function! incsearch#highlight#update() abort + " it's intuiive to call incsearch#highlight#on() & off() but there are no + " need to execute `:nohlsearch` when updating. call s:hi.disable_all() call s:hi.enable_all() endfunction +function! incsearch#highlight#on() abort + call s:hi.enable_all() + if ! g:incsearch#no_inc_hlsearch + let &hlsearch = &hlsearch + endif +endfunction + +function! incsearch#highlight#off() abort + call s:hi.disable_all() + if ! g:incsearch#no_inc_hlsearch + nohlsearch + endif +endfunction + function! s:init_hl() abort hi link IncSearchMatch Search hi link IncSearchMatchReverse IncSearch @@ -168,13 +184,13 @@ function! incsearch#highlight#emulate_visual_highlight(...) abort " Note: the default pos value assume visual selection is not cleared. " It uses curswant to emulate visual-block let v_start_pos = get(a:, 3, - \ (is_visual_now ? [line("v"),col("v")] : [line("'<"), col("'<")])) + \ (is_visual_now ? [line('v'),col('v')] : [line("'<"), col("'<")])) " See: https://github.com/vim-jp/issues/issues/604 " getcurpos() could be negative value, so use winsaveview() instead let end_curswant_pos = \ (exists('*getcurpos') ? getcurpos()[4] : winsaveview().curswant + 1) let v_end_pos = get(a:, 4, (is_visual_now - \ ? [line("."), end_curswant_pos < 0 ? s:INT.MAX : end_curswant_pos ] + \ ? [line('.'), end_curswant_pos < 0 ? s:INT.MAX : end_curswant_pos ] \ : [line("'>"), col("'>")])) let pattern = incsearch#highlight#get_visual_pattern(mode, v_start_pos, v_end_pos) let hgm = incsearch#highlight#hgm() @@ -214,15 +230,15 @@ function! incsearch#highlight#get_visual_pattern(mode, v_start_pos, v_end_pos) a elseif a:mode ==# 'V' return printf('\v%%%dl\_.*%%%dl', v_start[0], v_end[0]) elseif a:mode ==# "\" + " @vimlint(EVL102, 1, l:min_c) let [min_c, max_c] = s:U.sort_num([v_start[1], v_end[1]]) let max_c += 1 " increment needed let max_c = max_c < 0 ? s:INT.MAX : max_c - return '\v'.join(map(range(v_start[0], v_end[0]), ' - \ printf("%%%dl%%%dc.*%%%dc", - \ v:val, - \ min_c, - \ min([max_c, s:U.get_max_col(v:val)])) - \ '), "|") + let mapfunc = " + \ printf('%%%dl%%%dc.*%%%dc', + \ v:val, min_c, min([max_c, s:U.get_max_col(v:val)])) + \ " + return '\v'.join(map(range(v_start[0], v_end[0]), mapfunc), '|') else throw 'incsearch.vim: unexpected mode ' . a:mode endif diff --git a/autoload/incsearch/over/extend.vim b/autoload/incsearch/over/extend.vim index 3ae3234..f61a4a3 100644 --- a/autoload/incsearch/over/extend.vim +++ b/autoload/incsearch/over/extend.vim @@ -7,6 +7,8 @@ scriptencoding utf-8 let s:save_cpo = &cpo set cpo&vim +let s:TRUE = !0 +let s:FALSE = 0 let s:non_escaped_backslash = '\m\%(\%(^\|[^\\]\)\%(\\\\\)*\)\@<=\\' let s:U = incsearch#util#import() @@ -15,7 +17,11 @@ function! incsearch#over#extend#enrich(cli) abort return extend(a:cli, s:cli) endfunction -let s:cli = {} +let s:cli = { +\ '_does_exit_from_incsearch': s:FALSE, +\ '_return_cmd': '', +\ '_converter_cache': {} +\ } function! s:cli._generate_command(input) abort let is_cancel = self.exit_code() @@ -25,7 +31,7 @@ function! s:cli._generate_command(input) abort call self._call_execute_event() let [pattern, offset] = incsearch#parse_pattern(a:input, self._base_key) " TODO: implement convert input method - let p = self._combine_pattern(incsearch#convert(pattern), offset) + let p = self._combine_pattern(self._convert(pattern), offset) return self._build_search_cmd(p) endif endfunction @@ -33,7 +39,7 @@ endfunction " @return search cmd function! s:cli._build_search_cmd(pattern, ...) abort let mode = get(a:, 1, self._mode) - let op = (mode == 'no') ? v:operator + let op = (mode ==# 'no') ? v:operator \ : s:U.is_visual(mode) ? 'gv' \ : '' let zv = (&foldopen =~# '\vsearch|all' && mode !=# 'no' ? 'zv' : '') @@ -76,6 +82,36 @@ function! s:cli._combine_pattern(pattern, offset) abort return empty(a:offset) ? a:pattern : a:pattern . self._base_key . a:offset endfunction +function! s:cli._convert(pattern) abort + if a:pattern is# '' + return a:pattern + elseif empty(self._converters) + return incsearch#magic() . a:pattern + elseif has_key(self._converter_cache, a:pattern) + return self._converter_cache[a:pattern] + else + let ps = [incsearch#magic() . a:pattern] + for l:Converter in self._converters + let l:Convert = type(l:Converter) is type(function('function')) + \ ? l:Converter : l:Converter.convert + let ps += [l:Convert(a:pattern)] + unlet l:Converter + endfor + " Converters may return upper case even if a:pattern doesn't contain upper + " case letter, so prepend case flag explicitly + " let case = incsearch#detect_case(a:pattern) + let case = incsearch#detect_case(a:pattern) + let self._converter_cache[a:pattern] = case . s:U.regexp_join(ps) + return self._converter_cache[a:pattern] + endif +endfunction + +function! s:cli._exit_incsearch(...) abort + let cmd = get(a:, 1, '') + let self._return_cmd = cmd + let self._does_exit_from_incsearch = s:TRUE + call self.exit() +endfunction let &cpo = s:save_cpo unlet s:save_cpo diff --git a/autoload/incsearch/over/modules/incsearch.vim b/autoload/incsearch/over/modules/incsearch.vim index bb3ab1c..f044226 100644 --- a/autoload/incsearch/over/modules/incsearch.vim +++ b/autoload/incsearch/over/modules/incsearch.vim @@ -16,7 +16,7 @@ let s:hi = g:incsearch#highlight#_hi let s:U = incsearch#util#import() let s:inc = { -\ "name" : "incsearch", +\ 'name' : 'incsearch', \} " NOTE: for InsertRegister handling @@ -58,7 +58,7 @@ function! s:inc.on_leave(cmdline) abort " push rest of keymappings with feedkeys() " FIXME: assume 'noremap' but it should take care wheter or not the " mappings should be remapped or not - if a:cmdline.input_key_stack_string() != '' + if a:cmdline.input_key_stack_string() !=# '' call feedkeys(a:cmdline.input_key_stack_string(), 'n') endif endfunction @@ -97,7 +97,7 @@ function! s:on_searching(func, ...) abort catch /E888:/ " E888: (NFA regexp) cannot repeat (with /\ze*) call s:hi.disable_all() catch - echohl ErrorMsg | echom v:throwpoint . " " . v:exception | echohl None + echohl ErrorMsg | echom v:throwpoint . ' ' . v:exception | echohl None endtry endfunction @@ -105,11 +105,11 @@ function! s:on_char_pre(cmdline) abort " NOTE: " `:call a:cmdline.setchar('')` as soon as possible! let [raw_pattern, offset] = a:cmdline._parse_pattern() - let pattern = incsearch#convert(raw_pattern) + let pattern = a:cmdline._convert(raw_pattern) " Interactive :h last-pattern if pattern is empty - if ( a:cmdline.is_input("(incsearch-next)") - \ || a:cmdline.is_input("(incsearch-prev)") + if ( a:cmdline.is_input('(incsearch-next)') + \ || a:cmdline.is_input('(incsearch-prev)') \ ) && empty(pattern) call a:cmdline.setchar('') " Use history instead of @/ to work with magic option and converter @@ -117,43 +117,43 @@ function! s:on_char_pre(cmdline) abort " Just insert last-pattern and do not count up, but the incsearch-prev " should move the cursor to reversed directly, so do not return if the " command is prev - if a:cmdline.is_input("(incsearch-next)") | return | endif + if a:cmdline.is_input('(incsearch-next)') | return | endif endif - if a:cmdline.is_input("(incsearch-next)") + if a:cmdline.is_input('(incsearch-next)') call a:cmdline.setchar('') if a:cmdline._flag ==# 'n' " exit stay mode let a:cmdline._flag = '' else let a:cmdline._vcount1 += 1 endif - elseif a:cmdline.is_input("(incsearch-prev)") + elseif a:cmdline.is_input('(incsearch-prev)') call a:cmdline.setchar('') if a:cmdline._flag ==# 'n' " exit stay mode let a:cmdline._flag = '' endif let a:cmdline._vcount1 -= 1 - elseif (a:cmdline.is_input("(incsearch-scroll-f)") + elseif (a:cmdline.is_input('(incsearch-scroll-f)') \ && (a:cmdline._flag ==# '' || a:cmdline._flag ==# 'n')) - \ || (a:cmdline.is_input("(incsearch-scroll-b)") && a:cmdline._flag ==# 'b') + \ || (a:cmdline.is_input('(incsearch-scroll-b)') && a:cmdline._flag ==# 'b') call a:cmdline.setchar('') if a:cmdline._flag ==# 'n' | let a:cmdline._flag = '' | endif - let pos_expr = a:cmdline.is_input("(incsearch-scroll-f)") ? 'w$' : 'w0' - let to_col = a:cmdline.is_input("(incsearch-scroll-f)") + let pos_expr = a:cmdline.is_input('(incsearch-scroll-f)') ? 'w$' : 'w0' + let to_col = a:cmdline.is_input('(incsearch-scroll-f)') \ ? s:U.get_max_col(pos_expr) : 1 let [from, to] = [getpos('.')[1:2], [line(pos_expr), to_col]] let cnt = s:U.count_pattern(pattern, from, to) let a:cmdline._vcount1 += cnt - elseif (a:cmdline.is_input("(incsearch-scroll-b)") + elseif (a:cmdline.is_input('(incsearch-scroll-b)') \ && (a:cmdline._flag ==# '' || a:cmdline._flag ==# 'n')) - \ || (a:cmdline.is_input("(incsearch-scroll-f)") && a:cmdline._flag ==# 'b') + \ || (a:cmdline.is_input('(incsearch-scroll-f)') && a:cmdline._flag ==# 'b') call a:cmdline.setchar('') if a:cmdline._flag ==# 'n' let a:cmdline._flag = '' let a:cmdline._vcount1 -= 1 endif - let pos_expr = a:cmdline.is_input("(incsearch-scroll-f)") ? 'w$' : 'w0' - let to_col = a:cmdline.is_input("(incsearch-scroll-f)") + let pos_expr = a:cmdline.is_input('(incsearch-scroll-f)') ? 'w$' : 'w0' + let to_col = a:cmdline.is_input('(incsearch-scroll-f)') \ ? s:U.get_max_col(pos_expr) : 1 let [from, to] = [getpos('.')[1:2], [line(pos_expr), to_col]] let cnt = s:U.count_pattern(pattern, from, to) @@ -163,10 +163,10 @@ function! s:on_char_pre(cmdline) abort " Handle nowrapscan: " if you `:set nowrapscan`, you can't move to the reversed direction if !&wrapscan && ( - \ a:cmdline.is_input("(incsearch-next)") - \ || a:cmdline.is_input("(incsearch-prev)") - \ || a:cmdline.is_input("(incsearch-scroll-f)") - \ || a:cmdline.is_input("(incsearch-scroll-b)") + \ a:cmdline.is_input('(incsearch-next)') + \ || a:cmdline.is_input('(incsearch-prev)') + \ || a:cmdline.is_input('(incsearch-scroll-f)') + \ || a:cmdline.is_input('(incsearch-scroll-b)') \ ) if a:cmdline._vcount1 < 1 let a:cmdline._vcount1 = 1 @@ -187,6 +187,9 @@ function! s:on_char_pre(cmdline) abort endfunction function! s:on_char(cmdline) abort + if a:cmdline._does_exit_from_incsearch + return + endif let [raw_pattern, offset] = a:cmdline._parse_pattern() if raw_pattern ==# '' @@ -206,7 +209,7 @@ function! s:on_char(cmdline) abort call winrestview(w) endif - let pattern = incsearch#convert(raw_pattern) + let pattern = a:cmdline._convert(raw_pattern) " Improved Incremental cursor move! call s:move_cursor(a:cmdline, pattern, offset) @@ -222,8 +225,8 @@ function! s:on_char(cmdline) abort \ [a:cmdline._w.lnum, a:cmdline._w.col]) " functional `normal! zz` after scroll for mappings - if ( a:cmdline.is_input("(incsearch-scroll-f)") - \ || a:cmdline.is_input("(incsearch-scroll-b)")) + if ( a:cmdline.is_input('(incsearch-scroll-f)') + \ || a:cmdline.is_input('(incsearch-scroll-b)')) call winrestview({'topline': max([1, line('.') - winheight(0) / 2])}) endif endfunction diff --git a/autoload/incsearch/util.vim b/autoload/incsearch/util.vim index da8f170..45c8ad3 100644 --- a/autoload/incsearch/util.vim +++ b/autoload/incsearch/util.vim @@ -28,6 +28,14 @@ let s:save_cpo = &cpo set cpo&vim " }}} +let s:TRUE = !0 +let s:FALSE = 0 + +" Public Utilities: +function! incsearch#util#deepextend(...) abort + return call(function('s:deepextend'), a:000) +endfunction + " Utilities: function! incsearch#util#import() abort @@ -54,6 +62,7 @@ let s:functions = [ \ , 'silent_feedkeys' \ , 'deepextend' \ , 'dictfunction' +\ , 'regexp_join' \ ] @@ -136,15 +145,15 @@ endfunction function! s:silent_feedkeys(expr, name, ...) abort " Ref: " https://github.com/osyo-manga/vim-over/blob/d51b028c29661d4a5f5b79438ad6d69266753711/autoload/over.vim#L6 - let mode = get(a:, 1, "m") - let name = "incsearch-" . a:name - let map = printf("(%s)", name) - if mode == "n" - let command = "nnoremap" + let mode = get(a:, 1, 'm') + let name = 'incsearch-' . a:name + let map = printf('(%s)', name) + if mode ==# 'n' + let command = 'nnoremap' else - let command = "nmap" + let command = 'nmap' endif - execute command "" map printf("%s:nunmap %s", a:expr, map) + execute command '' map printf('%s:nunmap %s', a:expr, map) if mode(1) !=# 'ce' " FIXME: mode(1) !=# 'ce' exists only for the test " :h feedkeys() doesn't work while runnning a test script @@ -180,14 +189,89 @@ function! s:dictfunction(dictfunc, dict) abort let prefix = '' . s:SID() . '_' let fm = printf("%sfuncmanage()['%s']", prefix, funcname) execute join([ - \ printf("function! s:%s(...) abort", funcname), + \ printf('function! s:%s(...) abort', funcname), \ printf(" return call(%s['func'], a:000, %s['dict'])", fm, fm), - \ "endfunction" + \ 'endfunction' \ ], "\n") return function(printf('%s%s', prefix, funcname)) endfunction +"--- regexp + +let s:escaped_backslash = '\m\%(^\|[^\\]\)\%(\\\\\)*\zs' + +function! s:regexp_join(ps) abort + let rs = map(filter(copy(a:ps), 's:_is_valid_regexp(v:val)'), 's:escape_unbalanced_left_r(v:val)') + return printf('\m\%%(%s\m\)', join(rs, '\m\|')) +endfunction + +function! s:_is_valid_regexp(pattern) abort + try + if '' =~# a:pattern + endif + return s:TRUE + catch + return s:FALSE + endtry +endfunction + +" \m,\v: [ -> \[ +" \M,\V: \[ -> [ +function! s:escape_unbalanced_left_r(pattern) abort + let rs = [] + let cs = split(a:pattern, '\zs') + " escape backslash (\, \\\, \\\\\, ...) + let escape_bs = s:FALSE + let flag = &magic ? 'm' : 'M' + let i = 0 + while i < len(cs) + let c = cs[i] + " characters to add to rs + let addcs = [c] + if escape_bs && s:_is_flag(c) + let flag = c + elseif c is# '[' && s:_may_replace_left_r_cond(escape_bs, flag) + let idx = s:_find_right_r(cs, i) + if idx is# -1 + if s:_is_flag(flag, 'MV') + " Remove `\` before unbalanced `[` + let rs = rs[:-2] + else + " Escape unbalanced `[` + let addcs = ['\' . c] + endif + else + let addcs = cs[(i):(i+idx)] + let i += idx + endif + endif + let escape_bs = (escape_bs || c isnot# '\') ? s:FALSE : s:TRUE + let rs += addcs + let i += 1 + endwhile + return join(rs, '') +endfunction + +" @ return boolean +function! s:_is_flag(flag, ...) abort + let chars = get(a:, 1, 'mMvV') + return a:flag =~# printf('\m[%s]', chars) +endfunction + +" @ return boolean +function! s:_may_replace_left_r_cond(escape_bs, flag) abort + return (a:escape_bs && s:_is_flag(a:flag, 'MV')) || (!a:escape_bs && s:_is_flag(a:flag, 'mv')) +endfunction + +" @return index +function! s:_find_right_r(cs, i) abort + return match(join(a:cs[(a:i+1):], ''), s:escaped_backslash . ']') +endfunction + +"--- end of regexp + + " Restore 'cpoptions' {{{ let &cpo = s:save_cpo unlet s:save_cpo diff --git a/doc/incsearch.txt b/doc/incsearch.txt index 02d0a2b..b9e1cc1 100644 --- a/doc/incsearch.txt +++ b/doc/incsearch.txt @@ -1,7 +1,7 @@ *incsearch.txt* Incrementally highlight all pattern matches Author : haya14busa -Version : 1.2.1 +Version : 2.0.0 License : MIT license {{{ Copyright (c) 2014-2015 haya14busa @@ -486,6 +486,8 @@ prompt *incsearch-config-prompt* modules *incsearch-config-modules* Additional |Vital.Over.Commandline-modules| to connect. + The list of module extentions: + https://github.com/haya14busa/incsearch.vim/wiki/List-of-plugins-for-incsearch.vim#module-extensions Type: |list| of |Vital.Over.Commandline-modules| Default: [] Example: > @@ -504,7 +506,8 @@ modules *incsearch-config-modules* keymap *incsearch-config-keymap* Additional keymappings for commandline interface. See also |g:incsearch_cli_key_mappings|. - + Type: |dict| + Default: {} Example: > function! s:config() abort @@ -520,6 +523,37 @@ keymap *incsearch-config-keymap* endfunction noremap z/ incsearch#go(config()) + +converters *incsearch-config-converters* + The list of pattern converters to add internal additional patterns to + search. Converters is |Funcref| or converter object (|Dictionary| with a + |Dictionary-function| named "convert" right now). The converter + feature is experimental, so converter objects will have to have more + required methods or fields later. + + Converter Feature~ + incsearch.vim calls the list of convert functions with pattern. The + patterns don't contain |search-offset| nor search command(|/|, |?|). + The convert functions should return valid |regular-expression| and + incsearch.vim use their returned patterns in addition to the default + input pattern as |regular-expression| to search. + + The list of converter extensions: + https://github.com/haya14busa/incsearch.vim/wiki/List-of-plugins-for-incsearch.vim#converter-extensions + Type: |list| of converter + Default: [] + Example: > + + function! s:noregexp(pattern) abort + return '\V' . escape(a:pattern, '\') + endfunction + + function! s:config() abort + return {'converters': [function('s:noregexp')]} + endfunction + + noremap z/ incsearch#go(config()) + ============================================================================== KNOWN ISSUES *incsearch-issues* @@ -530,14 +564,14 @@ KNOWN ISSUES *incsearch-issues* ============================================================================== CHANGELOG *incsearch-changelog* -Version 2.0(?) Roadmap~ -2.0.0 1. Pattern converter feature - - https://github.com/haya14busa/incsearch.vim/tree/converter - - Implement fuzzy-search, migemo, spellcheck and other feature - - Make public pattern converter API for customization - - Write test for converer feature - 3. More configurable options - - e.g. option for prompt format including right prompt feature +Version 2.0~ + +2.0.0 2015-07-06 + 1. |incsearch#go()| and |incsearch-config| as a API + 2. Pattern converter feature |incsearch-config-converters| + 3. Injection of vital-over module |incsearch-config-modules| + 4. External extention plugins like fuzzy, easymotion, migemo... + - https://github.com/haya14busa/incsearch.vim/wiki/List-of-plugins-for-incsearch.vim 1.2.1 2015-06-26 1. Add |incsearch-config-keymap| option to |incsearch-config| diff --git a/plugin/incsearch.vim b/plugin/incsearch.vim index 81ed75f..ddd2fcb 100644 --- a/plugin/incsearch.vim +++ b/plugin/incsearch.vim @@ -82,8 +82,7 @@ function! s:key_mapping(lhs, rhs, noremap) abort endfunction function! s:as_keymapping(key) abort - execute 'let result = "' . substitute(escape(a:key, '\"'), '\(<.\{-}>\)', '\\\1', 'g') . '"' - return result + return eval('"' . substitute(escape(a:key, '\"'), '\(<.\{-}>\)', '\\\1', 'g') . '"') endfunction command! -nargs=* IncSearchNoreMap diff --git a/test/.themisrc b/test/.themisrc index 2561e88..e4f6653 100644 --- a/test/.themisrc +++ b/test/.themisrc @@ -1,4 +1,6 @@ call themis#option('recursive', 1) +" For development +call themis#option('runtimepath', expand('~/.vim/bundle/vital.vim')) let g:Expect = themis#helper('expect') call themis#helper('command').with(themis#helper('assert')).with({'Expect': g:Expect}) diff --git a/test/api/converter.vimspec b/test/api/converter.vimspec new file mode 100644 index 0000000..614100d --- /dev/null +++ b/test/api/converter.vimspec @@ -0,0 +1,45 @@ +Describe api.converter + + Before all + function! g:ReturnU(...) abort + return 'U' + endfunction + End + + After all + delfunction g:ReturnU + End + + Describe converter with case handling + It should not break smartcase detection + let ignorecase_save = &ignorecase + let &ignorecase = 1 + let smartcase_save = &smartcase + let &smartcase = 1 + try + let config = {'converters': [function('g:ReturnU')]} + let cli = incsearch#make(config) + Assert Match('PatTern', cli._convert('pattern')) + finally + let &ignorecase = ignorecase_save + let &smartcase = smartcase_save + endtry + End + + It should care smartcase + let ignorecase_save = &ignorecase + let &ignorecase = 1 + let smartcase_save = &smartcase + let &smartcase = 1 + try + let config = {'converters': [function('g:ReturnU')]} + let cli = incsearch#make(config) + Assert NotMatch('PatTern', cli._convert('Pattern')) + finally + let &ignorecase = ignorecase_save + let &smartcase = smartcase_save + endtry + End + + End +End diff --git a/test/default_settings.vim b/test/default_settings.vim index ccc9c8a..1b770be 100644 --- a/test/default_settings.vim +++ b/test/default_settings.vim @@ -67,7 +67,6 @@ function! s:suite.test_autoload_function() endtry call s:assert.exists('*incsearch#go') call s:assert.exists('*incsearch#_go') - call s:assert.exists('*incsearch#stay') call s:assert.exists('*incsearch#parse_pattern') endfunction diff --git a/test/util/regexp.vimspec b/test/util/regexp.vimspec new file mode 100644 index 0000000..f85a9eb --- /dev/null +++ b/test/util/regexp.vimspec @@ -0,0 +1,161 @@ +Describe util.regexp + Before all + let SL = vital#of('vital').import('Vim.ScriptLocal') + let U = SL.sfuncs('autoload/incsearch/util.vim') + End + + Describe .regexp_join() + It returns joined regular expression from patterns + let ps = ['incsearch', 'vim'] + let r = U.regexp_join(ps) + Assert Equals('\m\%(incsearch\m\|vim\m\)', r) + for p in ps + Assert True(p =~# r) + endfor + End + + It supports regular expressions including various flags + let ps = ['\d\+', '\v(neo)?vim'] + let r = U.regexp_join(ps) + Assert Equals('\m\%(\d\+\m\|\v(neo)?vim\m\)', r) + for p in ['1', '12310', 'vim', 'neovim'] + Assert True(p =~# r) + endfor + End + + It handles unbalanced `[` with \m\v flag + let ps = ['\d\+[', '[vim]'] + let r = U.regexp_join(ps) + Assert Equals('\m\%(\d\+\[\m\|[vim]\m\)', r) + for p in ['1[', '12310[', 'v', 'i', 'm'] + Assert True(p =~# r) + endfor + for p in ['\', '\['] + Assert False(p =~# r) + endfor + End + End + + Describe .escape_unbalanced_left_r() + It case 0 + Assert Equals('\[', U.escape_unbalanced_left_r('[')) + End + It case 1 + Assert Equals('\[', U.escape_unbalanced_left_r('\[')) + End + It case 2 + Assert Equals('\\\[', U.escape_unbalanced_left_r('\\[')) + End + It case 3 + Assert Equals('\\\[', U.escape_unbalanced_left_r('\\\[')) + End + It case 4 + Assert Equals('[]', U.escape_unbalanced_left_r('[]')) + End + It case 5 + Assert Equals('\[]', U.escape_unbalanced_left_r('\[]')) + End + It case 6 + Assert Equals('\\[]', U.escape_unbalanced_left_r('\\[]')) + End + It case 7 + Assert Equals('\\\[]', U.escape_unbalanced_left_r('\\\[]')) + End + It case 8 + Assert Equals('[]\[', U.escape_unbalanced_left_r('[][')) + End + It case 9 + Assert Equals('\[]\[', U.escape_unbalanced_left_r('\[][')) + End + It case 10 + Assert Equals('\\[]\[', U.escape_unbalanced_left_r('\\[][')) + End + It case 11 + Assert Equals('\\\[]\[', U.escape_unbalanced_left_r('\\\[][')) + End + It case 12 + Assert Equals('[]...\[', U.escape_unbalanced_left_r('[]...[')) + End + It case 13 + Assert Equals('\[]...\[', U.escape_unbalanced_left_r('\[]...[')) + End + It case 14 + Assert Equals('\\[]...\[', U.escape_unbalanced_left_r('\\[]...[')) + End + It case 15 + Assert Equals('\\\[]...\[', U.escape_unbalanced_left_r('\\\[]...[')) + End + It case 16 + Assert Equals('\m\[', U.escape_unbalanced_left_r('\m[')) + End + It case 17 + Assert Equals('\m\[', U.escape_unbalanced_left_r('\m\[')) + End + It case 18 + Assert Equals('\m\\\[', U.escape_unbalanced_left_r('\m\\[')) + End + It case 19 + Assert Equals('\m\\\[', U.escape_unbalanced_left_r('\m\\\[')) + End + It case 20 + Assert Equals('\v\[', U.escape_unbalanced_left_r('\v[')) + End + It case 21 + Assert Equals('\v\[', U.escape_unbalanced_left_r('\v\[')) + End + It case 22 + Assert Equals('\v\\\[', U.escape_unbalanced_left_r('\v\\[')) + End + It case 23 + Assert Equals('\v\\\[', U.escape_unbalanced_left_r('\v\\\[')) + End + It case 24 + Assert Equals('\M[', U.escape_unbalanced_left_r('\M[')) + End + It case 25 + Assert Equals('\M[', U.escape_unbalanced_left_r('\M\[')) + End + It case 26 + Assert Equals('\M\\[', U.escape_unbalanced_left_r('\M\\[')) + End + It case 27 + Assert Equals('\M\\[', U.escape_unbalanced_left_r('\M\\\[')) + End + It case 28 + Assert Equals('\V[', U.escape_unbalanced_left_r('\V[')) + End + It case 29 + Assert Equals('\V[', U.escape_unbalanced_left_r('\V\[')) + End + It case 30 + Assert Equals('\V\\[', U.escape_unbalanced_left_r('\V\\[')) + End + It case 31 + Assert Equals('\V\\[', U.escape_unbalanced_left_r('\V\\\[')) + End + It case 32 + Assert Equals('\[\M[\v\[', U.escape_unbalanced_left_r('[\M\[\v[')) + End + It case 33 + Assert Equals('[\M\[\v[]', U.escape_unbalanced_left_r('[\M\[\v[]')) + End + It case 34 + Assert Equals('[[]', U.escape_unbalanced_left_r('[[]')) + End + It case 35 + Assert Equals('[\[]', U.escape_unbalanced_left_r('[\[]')) + End + It case 36 + Assert Equals('\M\[[]', U.escape_unbalanced_left_r('\M\[[]')) + End + It case 37 + let p = '[' . repeat('[', 10000) . ']' + Assert Equals(p, U.escape_unbalanced_left_r(p)) + End + It case 38 + let p = '[' . repeat('\[', 10000) . ']' + Assert Equals(p, U.escape_unbalanced_left_r(p)) + End + End + +End