diff --git a/autoload/incsearch.vim b/autoload/incsearch.vim index e825311..824d863 100644 --- a/autoload/incsearch.vim +++ b/autoload/incsearch.vim @@ -77,11 +77,16 @@ let s:U = incsearch#util#import() " Main: {{{ -" @return vital-over command-line interface object. it's experimental!!! +" @return vital-over command-line interface object. [experimental] function! incsearch#cli() abort return incsearch#cli#get() endfunction +"" Make vital-over command-line interface object and return it [experimental] +function! incsearch#make(...) abort + return incsearch#cli#make(incsearch#config#make(get(a:, 1, {}))) +endfunction + "" NOTE: this global variable is only for handling config from go_wrap func " It avoids to make config string temporarily let g:incsearch#_go_config = {} @@ -169,7 +174,7 @@ nnoremap (_incsearch-winrestview) :call winrestview(g:incsea xnoremap (_incsearch-winrestview) :call winrestview(g:incsearch#_view)gv function! s:stay(cli, input) abort - let [raw_pattern, offset] = incsearch#cli_parse_pattern(a:cli) + let [raw_pattern, offset] = a:cli._parse_pattern() let pattern = incsearch#convert(raw_pattern) " NOTE: do not move cursor but need to handle {offset} for n & N ...! {{{ @@ -180,7 +185,7 @@ function! s:stay(cli, input) abort call s:cleanup_cmdline() elseif !empty(offset) && mode(1) !=# 'no' let cmd = incsearch#with_ignore_foldopen( - \ function('s:generate_command'), a:cli, a:input) + \ s:U.dictfunction(a:cli._generate_command, a:cli), a:input) call feedkeys(cmd, 'n') let g:incsearch#_view = a:cli._w call feedkeys("\(_incsearch-winrestview)", 'm') @@ -203,7 +208,7 @@ endfunction function! s:search(cli, input) abort call incsearch#autocmd#auto_nohlsearch(1) " NOTE: `.` repeat doesn't handle this - return s:generate_command(a:cli, a:input) + return a:cli._generate_command(a:input) endfunction function! s:get_input(cli) abort @@ -223,47 +228,6 @@ function! s:get_input(cli) abort return input endfunction -function! s:generate_command(cli, input) abort - let is_cancel = a:cli.exit_code() - if is_cancel - return s:U.is_visual(a:cli._mode) ? "\gv" : "\" - else - call s:call_execute_event(a:cli) - let [pattern, offset] = incsearch#parse_pattern(a:input, a:cli._base_key) - " TODO: implement convert input method - let p = incsearch#combine_pattern(a:cli, incsearch#convert(pattern), offset) - return a:cli._build_search_cmd(p) - endif -endfunction - -"" Call on_execute_pre and on_execute event -" assume current position is the destination and a:cli._w is the position to -" start search -function! s:call_execute_event(cli, ...) abort - let view = get(a:, 1, winsaveview()) - try - call winrestview(a:cli._w) - call a:cli.callevent('on_execute_pre') - finally - call winrestview(view) - endtry - call a:cli.callevent('on_execute') -endfunction - -function! incsearch#build_search_cmd(cli, mode, pattern) abort - let op = (a:mode == 'no') ? v:operator - \ : s:U.is_visual(a:mode) ? 'gv' - \ : '' - let zv = (&foldopen =~# '\vsearch|all' && a:mode !=# 'no' ? 'zv' : '') - " NOTE: - " Should I consider o_v, o_V, and o_CTRL-V cases and do not - " ? exists for flexible v:count with using s:cli._vcount1, - " but, if you do not move the cursor while incremental searching, - " there are no need to use . - return printf("\\"%s%s%s%s%s\%s", - \ v:register, op, a:cli._vcount1, a:cli._base_key, a:pattern, zv) -endfunction - " Assume the cursor move is already done. " This function handle search related stuff which doesn't be set by :execute " in function like @/, hisory, jumplist, offset, error & warning emulation. @@ -278,7 +242,7 @@ function! s:set_search_related_stuff(cli, cmd, ...) abort call s:cleanup_cmdline() return endif - let [raw_pattern, offset] = incsearch#cli_parse_pattern(a:cli) + let [raw_pattern, offset] = a:cli._parse_pattern() let should_execute = !empty(offset) || empty(raw_pattern) if should_execute " Execute with feedkeys() to work with @@ -294,7 +258,7 @@ function! s:set_search_related_stuff(cli, cmd, ...) abort " Add history if necessary " Do not save converted pattern to history let pattern = incsearch#convert(raw_pattern) - let input = incsearch#combine_pattern(a:cli, raw_pattern, offset) + let input = a:cli._combine_pattern(raw_pattern, offset) call histadd(a:cli._base_key, input) let @/ = pattern @@ -346,24 +310,6 @@ function! incsearch#parse_pattern(expr, search_key) abort return result endfunction -" CommandLine Interface parse pattern wrapper -" function! s:cli_parse_pattern(cli) abort -function! incsearch#cli_parse_pattern(cli) abort - if v:version == 704 && !has('patch421') - " Ignore \ze* which clash vim 7.4 without 421 patch - " Assume `\m` - let [p, o] = incsearch#parse_pattern(a:cli.getline(), a:cli._base_key) - let p = (p =~# s:non_escaped_backslash . 'z[se]\%(\*\|\\+\)' ? '' : p) - return [p, o] - else - return incsearch#parse_pattern(a:cli.getline(), a:cli._base_key) - endif -endfunction - -function! incsearch#combine_pattern(cli, pattern, offset) abort - return empty(a:offset) ? a:pattern : a:pattern . a:cli._base_key . a:offset -endfunction - " convert implementation. assume pattern is not empty function! s:_convert(pattern) abort return incsearch#magic() . a:pattern diff --git a/autoload/incsearch/cli.vim b/autoload/incsearch/cli.vim index 4439234..02ca7d5 100644 --- a/autoload/incsearch/cli.vim +++ b/autoload/incsearch/cli.vim @@ -23,7 +23,7 @@ endfunction " @config: whole configuration function! incsearch#cli#make(config) abort - let cli = s:copy_cli(s:cli) + let cli = deepcopy(s:cli) call incsearch#cli#set(cli, a:config) return cli endfunction @@ -37,6 +37,7 @@ function! incsearch#cli#set(cli, config) abort let a:cli._mode = a:config.mode let a:cli._pattern = a:config.pattern let a:cli._prompt = a:config.prompt + let a:cli._keymap = a:config.keymap let a:cli._flag = a:config.is_stay ? 'n' \ : a:config.command is# '/' ? '' \ : a:config.command is# '?' ? 'b' @@ -53,13 +54,6 @@ function! incsearch#cli#set(cli, config) abort return a:cli endfunction -"" partial deepcopy() for cli.connect(module) instead of copy() -function! s:copy_cli(cli) abort - let cli = copy(a:cli) - let cli.variables = deepcopy(a:cli.variables) - return cli -endfunction - let s:cli = s:V.import('Over.Commandline').make_default("/") let s:modules = s:V.import('Over.Commandline.Modules') @@ -102,46 +96,8 @@ unlet s:KeyMapping s:emacs_like s:vim_cmap s:smartbackword call s:cli.connect(incsearch#over#modules#pattern_saver#make()) call s:cli.connect(incsearch#over#modules#incsearch#make()) -let s:default_keymappings = { -\ "\" : { -\ "key" : "(incsearch-next)", -\ "noremap" : 1, -\ }, -\ "\" : { -\ "key" : "(incsearch-prev)", -\ "noremap" : 1, -\ }, -\ "\" : { -\ "key" : "(incsearch-scroll-f)", -\ "noremap" : 1, -\ }, -\ "\" : { -\ "key" : "(incsearch-scroll-b)", -\ "noremap" : 1, -\ }, -\ "\" : { -\ "key" : "(buffer-complete)", -\ "noremap" : 1, -\ }, -\ "\" : { -\ "key": "\", -\ "noremap": 1 -\ }, -\ } - -" https://github.com/haya14busa/incsearch.vim/issues/35 -if has('mac') - call extend(s:default_keymappings, { - \ '"+gP' : { - \ 'key': "\+", - \ 'noremap': 1 - \ }, - \ }) -endif - -" FIXME: arguments? -function! s:cli.keymapping(...) abort - return extend(copy(s:default_keymappings), g:incsearch_cli_key_mappings) +function! s:cli.__keymapping__() abort + return copy(self._keymap) endfunction call incsearch#over#extend#enrich(s:cli) diff --git a/autoload/incsearch/config.vim b/autoload/incsearch/config.vim index 34ccd2e..a9b603c 100644 --- a/autoload/incsearch/config.vim +++ b/autoload/incsearch/config.vim @@ -11,6 +11,8 @@ let s:TRUE = !0 let s:FALSE = 0 lockvar s:TRUE s:FALSE +let s:U = incsearch#util#import() + "" incsearch config " TODO: more detail documentation " @command is equivalent with base_key TODO: fix this inconsistence @@ -24,25 +26,72 @@ let s:config = { \ 'mode': 'n', \ 'count1': 1, \ 'prompt': '', -\ 'modules': [] +\ 'modules': [], +\ 'keymap': {} \ } " @return config for lazy value function! s:lazy_config() abort let m = mode(1) - return {'count1': v:count1, 'mode': m, 'is_expr': (m is# 'no')} + return { + \ 'count1': v:count1, + \ 'mode': m, + \ 'is_expr': (m is# 'no'), + \ 'keymap': s:keymap() + \ } endfunction " @return config with default value function! incsearch#config#make(additional) abort let default = extend(copy(s:config), s:lazy_config()) - let c = extend(default, a:additional) + let c = s:U.deepextend(default, a:additional) if c.prompt is# '' let c.prompt = c.command endif return c endfunction +let s:default_keymappings = { +\ "\" : { +\ "key" : "(incsearch-next)", +\ "noremap" : 1, +\ }, +\ "\" : { +\ "key" : "(incsearch-prev)", +\ "noremap" : 1, +\ }, +\ "\" : { +\ "key" : "(incsearch-scroll-f)", +\ "noremap" : 1, +\ }, +\ "\" : { +\ "key" : "(incsearch-scroll-b)", +\ "noremap" : 1, +\ }, +\ "\" : { +\ "key" : "(buffer-complete)", +\ "noremap" : 1, +\ }, +\ "\" : { +\ "key": "\", +\ "noremap": 1 +\ }, +\ } + +" https://github.com/haya14busa/incsearch.vim/issues/35 +if has('mac') + call extend(s:default_keymappings, { + \ '"+gP' : { + \ 'key': "\+", + \ 'noremap': 1 + \ }, + \ }) +endif + +function! s:keymap() abort + return extend(copy(s:default_keymappings), g:incsearch_cli_key_mappings) +endfunction + let &cpo = s:save_cpo unlet s:save_cpo " __END__ diff --git a/autoload/incsearch/over/extend.vim b/autoload/incsearch/over/extend.vim index b2a8a02..3ae3234 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:non_escaped_backslash = '\m\%(\%(^\|[^\\]\)\%(\\\\\)*\)\@<=\\' + let s:U = incsearch#util#import() function! incsearch#over#extend#enrich(cli) abort @@ -23,17 +25,18 @@ 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 = incsearch#combine_pattern(self, incsearch#convert(pattern), offset) + let p = self._combine_pattern(incsearch#convert(pattern), offset) return self._build_search_cmd(p) endif endfunction " @return search cmd -function! s:cli._build_search_cmd(pattern) abort - let op = (self._mode == 'no') ? v:operator - \ : s:U.is_visual(self._mode) ? 'gv' +function! s:cli._build_search_cmd(pattern, ...) abort + let mode = get(a:, 1, self._mode) + let op = (mode == 'no') ? v:operator + \ : s:U.is_visual(mode) ? 'gv' \ : '' - let zv = (&foldopen =~# '\vsearch|all' && self._mode !=# 'no' ? 'zv' : '') + let zv = (&foldopen =~# '\vsearch|all' && mode !=# 'no' ? 'zv' : '') " NOTE: " Should I consider o_v, o_V, and o_CTRL-V cases and do not " ? exists for flexible v:count with using s:cli._vcount1, @@ -43,6 +46,9 @@ function! s:cli._build_search_cmd(pattern) abort \ v:register, op, self._vcount1, self._base_key, a:pattern, zv) endfunction +"" Call on_execute_pre and on_execute event +" assume current position is the destination and a:cli._w is the position to +" start search function! s:cli._call_execute_event(...) abort let view = get(a:, 1, winsaveview()) try @@ -54,6 +60,23 @@ function! s:cli._call_execute_event(...) abort call self.callevent('on_execute') endfunction +function! s:cli._parse_pattern() abort + if v:version == 704 && !has('patch421') + " Ignore \ze* which clash vim 7.4 without 421 patch + " Assume `\m` + let [p, o] = incsearch#parse_pattern(self.getline(), self._base_key) + let p = (p =~# s:non_escaped_backslash . 'z[se]\%(\*\|\\+\)' ? '' : p) + return [p, o] + else + return incsearch#parse_pattern(self.getline(), self._base_key) + endif +endfunction + +function! s:cli._combine_pattern(pattern, offset) abort + return empty(a:offset) ? a:pattern : a:pattern . self._base_key . a:offset +endfunction + + let &cpo = s:save_cpo unlet s:save_cpo " __END__ diff --git a/autoload/incsearch/over/modules/incsearch.vim b/autoload/incsearch/over/modules/incsearch.vim index d21ece9..bb3ab1c 100644 --- a/autoload/incsearch/over/modules/incsearch.vim +++ b/autoload/incsearch/over/modules/incsearch.vim @@ -104,7 +104,8 @@ endfunction function! s:on_char_pre(cmdline) abort " NOTE: " `:call a:cmdline.setchar('')` as soon as possible! - let [pattern, offset] = incsearch#cli_parse_pattern(a:cmdline) + let [raw_pattern, offset] = a:cmdline._parse_pattern() + let pattern = incsearch#convert(raw_pattern) " Interactive :h last-pattern if pattern is empty if ( a:cmdline.is_input("(incsearch-next)") @@ -186,7 +187,7 @@ function! s:on_char_pre(cmdline) abort endfunction function! s:on_char(cmdline) abort - let [raw_pattern, offset] = incsearch#cli_parse_pattern(a:cmdline) + let [raw_pattern, offset] = a:cmdline._parse_pattern() if raw_pattern ==# '' call s:hi.disable_all() @@ -249,8 +250,8 @@ function! s:move_cursor(cli, pattern, ...) abort " doesn't reach this block let is_visual_mode = s:U.is_visual(mode(1)) let cmd = incsearch#with_ignore_foldopen( - \ function('incsearch#build_search_cmd'), - \ a:cli, 'n', incsearch#combine_pattern(a:cli, a:pattern, offset)) + \ s:U.dictfunction(a:cli._build_search_cmd, a:cli), + \ a:cli._combine_pattern(a:pattern, offset), 'n') " NOTE: " :silent! " Shut up errors! because this is just for the cursor emulation diff --git a/autoload/incsearch/util.vim b/autoload/incsearch/util.vim index b35e712..da8f170 100644 --- a/autoload/incsearch/util.vim +++ b/autoload/incsearch/util.vim @@ -52,6 +52,8 @@ let s:functions = [ \ , 'sort_pos' \ , 'count_pattern' \ , 'silent_feedkeys' +\ , 'deepextend' +\ , 'dictfunction' \ ] @@ -151,6 +153,41 @@ function! s:silent_feedkeys(expr, name, ...) abort endif endfunction +" deepextend (nest: 1) +function! s:deepextend(expr1, expr2) abort + let expr2 = copy(a:expr2) + for [k, V] in items(a:expr1) + if (type(V) is type({}) || type(V) is type([])) && has_key(expr2, k) + let a:expr1[k] = extend(a:expr1[k], expr2[k]) + unlet expr2[k] + endif + unlet V + endfor + return extend(a:expr1, expr2) +endfunction + +let s:funcmanage = {} +function! s:funcmanage() abort + return s:funcmanage +endfunction + +function! s:dictfunction(dictfunc, dict) abort + let funcname = '_' . matchstr(string(a:dictfunc), '\d\+') + let s:funcmanage[funcname] = { + \ 'func': a:dictfunc, + \ 'dict': a:dict + \ } + let prefix = '' . s:SID() . '_' + let fm = printf("%sfuncmanage()['%s']", prefix, funcname) + execute join([ + \ printf("function! s:%s(...) abort", funcname), + \ printf(" return call(%s['func'], a:000, %s['dict'])", fm, fm), + \ "endfunction" + \ ], "\n") + return function(printf('%s%s', prefix, funcname)) +endfunction + + " Restore 'cpoptions' {{{ let &cpo = s:save_cpo unlet s:save_cpo diff --git a/doc/incsearch.txt b/doc/incsearch.txt index ddd0254..02d0a2b 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.0 +Version : 1.2.1 License : MIT license {{{ Copyright (c) 2014-2015 haya14busa @@ -263,6 +263,29 @@ Command line interface~ | || | || +g:incsearch_cli_key_mappings *g:incsearch_cli_key_mappings* + Define keymapping by dictionary instead of |:IncSearchNoreMap|. + You can use following expression. > + + '{lhs}': '{rhs}' + +< or > + + '{lhs}': { + 'key': '{rhs}', + 'noremap': (1 or 0) + } + +< Example: > + + let g:incsearch_cli_key_mappings = { + \ "\": { + \ 'key': '(buffer-complete)', + \ 'noremap': 1 + \ }, + \ "\": "\", + \ } + Buffer completion *incsearch-(buffer-complete)* *incsearch-(buffer-complete-prev)* Completion with the buffer text. @@ -478,6 +501,25 @@ modules *incsearch-config-modules* noremap z/ incsearch#go(config()) +keymap *incsearch-config-keymap* + Additional keymappings for commandline interface. See also + |g:incsearch_cli_key_mappings|. + + Example: > + + function! s:config() abort + return { + \ 'keymap': { + \ "\": { + \ 'key': '(buffer-complete)', + \ 'noremap': 1 + \ }, + \ "\": "\" + \ } + \ } + endfunction + + noremap z/ incsearch#go(config()) ============================================================================== KNOWN ISSUES *incsearch-issues* @@ -497,6 +539,10 @@ Version 2.0(?) Roadmap~ 3. More configurable options - e.g. option for prompt format including right prompt feature +1.2.1 2015-06-26 + 1. Add |incsearch-config-keymap| option to |incsearch-config| + 2. Some refactoring + 1.2.0 2015-06-06 1. Now, fix unexpected key input with |feedkeys()| by auto-nohlsearch feature (|incsearch-additional-usage|) #82 diff --git a/test/api/config/keymap.vimspec b/test/api/config/keymap.vimspec new file mode 100644 index 0000000..b06e596 --- /dev/null +++ b/test/api/config/keymap.vimspec @@ -0,0 +1,50 @@ +Describe api.config.keymap + + It 'should set injected keymapping' + let map = {'a': 'b'} + let config = {'keymap': map} + let default = incsearch#make().keymapping() + let injected = incsearch#make(config).keymapping() + Assert Equals(injected, extend(copy(default), map)) + End + + It 'should return copied keymapping dictionary' + let map = {'a': 'b'} + let cli = incsearch#make() + let d = copy(cli.keymapping()) + call extend(cli.keymapping(), map) + Assert Equals(cli.keymapping(), d) + End + + It 'can override default keymapping' + let map = {"\": "\t"} + let config = {'keymap': map} + let default = incsearch#make().keymapping() + let injected = incsearch#make(config).keymapping() + Assert Equals(injected, extend(copy(default), map)) + End + + It 'should support rich keymapping configuration' + let map = { + \ 'a': { + \ 'key': 'b', + \ 'noremap': 0 + \ }, + \ 'c': 'd' + \ } + let config = {'keymap': map} + let default = incsearch#make().keymapping() + let injected = incsearch#make(config).keymapping() + Assert Equals(injected, extend(copy(default), map)) + End + + It 'should not affect other object' + let map = {'a': 'b'} + let config = {'keymap': map} + let cli = incsearch#make(config) + let d = copy(cli.keymapping()) + Assert NotEquals(incsearch#make().keymapping(), d) + End + +End + diff --git a/test/autonohlsearch.vim b/test/autonohlsearch.vim index 0114cd2..90e0bb4 100644 --- a/test/autonohlsearch.vim +++ b/test/autonohlsearch.vim @@ -137,18 +137,18 @@ function! s:suite.work_with_search_offset() for key in ['/', '?', 'g/'] silent! autocmd! incsearch-auto-nohlsearch call s:assert.equals(exists('#incsearch-auto-nohlsearch#CursorMoved'), 0) - exec "normal" key . "pattern/e\" + exec "silent! normal" key . "pattern/e\" call s:assert.equals(exists('#incsearch-auto-nohlsearch#CursorMoved'), 1) endfor endfunction function! s:suite.work_with_other_search_mappings() for key in ['n', 'N', '*', '#', 'g*', 'g#'] - silent! autocmd! incsearch-auto-nohlsearch + autocmd! incsearch-auto-nohlsearch call s:assert.equals(exists('#incsearch-auto-nohlsearch#CursorMoved'), 0) - exec "normal!" key + exec "silent! normal!" key call s:assert.equals(exists('#incsearch-auto-nohlsearch#CursorMoved'), 0) - exec "normal" key + exec "silent! normal" key call s:assert.equals(exists('#incsearch-auto-nohlsearch#CursorMoved'), 1) endfor endfunction diff --git a/test/default_settings.vim b/test/default_settings.vim index c28ba18..ccc9c8a 100644 --- a/test/default_settings.vim +++ b/test/default_settings.vim @@ -78,9 +78,12 @@ endfunction " https://github.com/haya14busa/incsearch.vim/issues/69 function! s:suite.handle_keymapping_option() call s:assert.equals(g:incsearch_cli_key_mappings, {}) - let d = copy(incsearch#cli().keymapping()) - let g:incsearch_cli_key_mappings['a'] = 'b' - call s:assert.equals(incsearch#cli().keymapping(), extend(copy(d), {'a': 'b'})) - unlet g:incsearch_cli_key_mappings['a'] - call s:assert.equals(incsearch#cli().keymapping(), d) + let d = copy(incsearch#make().keymapping()) + try + let g:incsearch_cli_key_mappings['a'] = 'b' + call s:assert.equals(incsearch#make().keymapping(), extend(copy(d), {'a': 'b'})) + finally + unlet g:incsearch_cli_key_mappings['a'] + endtry + call s:assert.equals(incsearch#make().keymapping(), d) endfunction