mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 04:00:04 +08:00
859 lines
25 KiB
VimL
859 lines
25 KiB
VimL
"==============================================================
|
|
" file: complete_parameter.vim
|
|
" brief:
|
|
" VIM Version: 8.0
|
|
" author: tenfyzhong
|
|
" email: tenfy@tenfy.cn
|
|
" created: 2017-06-07 20:29:10
|
|
"==============================================================
|
|
|
|
let s:complete_parameter = {'index': 0, 'items': [], 'complete_pos': [], 'success': 0, 'echos': []}
|
|
let g:complete_parameter_last_failed_insert = ''
|
|
|
|
let s:log_index = 0
|
|
|
|
function! cmp#init() abort "{{{
|
|
runtime! cm_parser/*.vim
|
|
|
|
" ultisnips will remove all smaps, this will without this plugin
|
|
let g:UltiSnipsMappingsToIgnore = get(g:, 'UltiSnipsMappingsToIgnore', []) + ["complete_parameter"]
|
|
" neosnippet will remove all smaps
|
|
let g:neosnippet#disable_select_mode_mappings = 0
|
|
|
|
" 4 error
|
|
" 2 error + debug
|
|
" 1 erro + debug + trace
|
|
let g:complete_parameter_log_level = get(g:, 'complete_parameter_log_level', 5)
|
|
|
|
let g:complete_parameter_use_ultisnips_mappings = get(g:, 'complete_parameter_use_ultisnips_mappings', 0)
|
|
endfunction "}}}
|
|
|
|
function! cmp#default_echos(completed_item)
|
|
return []
|
|
endfunction
|
|
|
|
let s:ftfunc_prefix = 'cm_parser#'
|
|
let s:ftfunc = {'ft': ''}
|
|
function! cmp#new_ftfunc(filetype) abort "{{{
|
|
if empty(a:filetype)
|
|
throw 'filetype is empty'
|
|
endif
|
|
|
|
let ftfunc = deepcopy(s:ftfunc)
|
|
let ftfunc['ft'] = a:filetype
|
|
try
|
|
let ftfunc['parameters'] = function(s:ftfunc_prefix . a:filetype .'#parameters')
|
|
let ftfunc['parameter_delim'] = function(s:ftfunc_prefix . a:filetype . '#parameter_delim')
|
|
let ftfunc['parameter_begin'] = function(s:ftfunc_prefix. a:filetype . '#parameter_begin')
|
|
let ftfunc['parameter_end'] = function(s:ftfunc_prefix . a:filetype . '#parameter_end')
|
|
if exists('*'.s:ftfunc_prefix.a:filetype.'#echos')
|
|
let ftfunc['echos'] = function(s:ftfunc_prefix.a:filetype.'#echos')
|
|
else
|
|
let ftfunc['echos'] = function('cmp#default_echos')
|
|
endif
|
|
catch /^E700/
|
|
throw 'the function should be defined: ' . v:exception
|
|
endtry
|
|
|
|
return ftfunc
|
|
endfunction "}}}
|
|
|
|
function! s:filetype_func_exist(filetype) abort "{{{
|
|
let filetype_func_prefix = s:ftfunc_prefix.a:filetype.'#'
|
|
let parameters_func_name = filetype_func_prefix.'parameters'
|
|
let parameter_delim_func_name = filetype_func_prefix.'parameter_delim'
|
|
let parameter_begin_func_name = filetype_func_prefix.'parameter_begin'
|
|
let parameter_end_func_name = filetype_func_prefix.'parameter_end'
|
|
|
|
if !exists('*'.parameters_func_name) ||
|
|
\!exists('*'.parameter_delim_func_name) ||
|
|
\!exists('*'.parameter_begin_func_name) ||
|
|
\!exists('*'.parameter_end_func_name)
|
|
return 0
|
|
endif
|
|
return 1
|
|
endfunction "}}}
|
|
|
|
function! cmp#filetype_func_check(ftfunc) abort "{{{
|
|
if !<SID>filetype_func_exist(a:ftfunc['ft'])
|
|
return 0
|
|
endif
|
|
|
|
" let parameters = a:ftfunc.parameters(v:completed_item)
|
|
" if type(parameters) != 3
|
|
" return 0
|
|
" endif
|
|
|
|
if !exists('*'.string(a:ftfunc.parameter_delim))
|
|
return 0
|
|
endif
|
|
let delim = a:ftfunc.parameter_delim()
|
|
if type(delim) != 1 || empty(delim)
|
|
return 0
|
|
endif
|
|
|
|
if !exists('*'.string(a:ftfunc.parameter_begin))
|
|
return 0
|
|
endif
|
|
let begin = a:ftfunc.parameter_begin()
|
|
if type(begin) != 1 || empty(begin)
|
|
return 0
|
|
endif
|
|
|
|
if !exists('*'.string(a:ftfunc.parameter_end))
|
|
return 0
|
|
endif
|
|
let end = a:ftfunc.parameter_end()
|
|
if type(end) != 1 || empty(end)
|
|
return 0
|
|
endif
|
|
return 1
|
|
endfunction "}}}
|
|
|
|
" check v:completed_item is empty or not
|
|
function! s:empty_completed_item() abort "{{{
|
|
let completed_item = v:completed_item
|
|
if empty(completed_item)
|
|
return 1
|
|
endif
|
|
let menu = get(completed_item, 'menu', '')
|
|
let info = get(completed_item, 'info', '')
|
|
let kind = get(completed_item, 'kind', '')
|
|
let abbr = get(completed_item, 'abbr', '')
|
|
return empty(menu) && empty(info) && empty(kind) && empty(abbr)
|
|
endfunction "}}}
|
|
|
|
" select an item if need, and the check need to revert or not
|
|
" else call the complete function
|
|
function! cmp#pre_complete(failed_insert) abort "{{{
|
|
let s:log_index = <SID>timenow_ms()
|
|
|
|
if !pumvisible()
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
let completed_word = get(v:completed_item, 'word', '')
|
|
|
|
if <SID>empty_completed_item() && pumvisible()
|
|
let feed = printf("\<C-r>=cmp#check_revert_select('%s', '%s')\<ENTER>", a:failed_insert, completed_word)
|
|
call feedkeys(feed, 'n')
|
|
return "\<C-n>"
|
|
else
|
|
return cmp#complete(a:failed_insert)
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! cmp#default_failed_insert(failed_insert) "{{{
|
|
if a:failed_insert =~# '()$'
|
|
return "\<LEFT>"
|
|
else
|
|
return ''
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! s:failed_event(failed_insert) abort "{{{ return the text to insert and toggle event
|
|
let keys = ''
|
|
if exists('*CompleteParameterFailed')
|
|
let keys = CompleteParameterFailed(a:failed_insert)
|
|
else
|
|
let keys = cmp#default_failed_insert(a:failed_insert)
|
|
endif
|
|
let content = getline(line('.'))
|
|
let parameter = a:failed_insert
|
|
let pos = col('.') - 2
|
|
if pos > 0
|
|
let posEnd = pos + len(parameter) - 1
|
|
if content[pos : posEnd] !=# parameter &&
|
|
\content[pos] ==# parameter[0]
|
|
let parameter = substitute(parameter, '\m.\(.*\)', '\1', '')
|
|
endif
|
|
endif
|
|
|
|
let keys = parameter . keys
|
|
call <SID>trace_log(keys)
|
|
|
|
return keys
|
|
endfunction "}}}
|
|
|
|
" if the select item is not match with completed_word, the revert
|
|
" else call the complete function
|
|
function! cmp#check_revert_select(failed_insert, completed_word) abort "{{{
|
|
let select_complete_word = get(v:completed_item, 'word', '')
|
|
call <SID>trace_log('s:completed_word: ' . a:completed_word)
|
|
call <SID>trace_log('select_complete_word: ' . select_complete_word)
|
|
redraw!
|
|
if select_complete_word !=# a:completed_word
|
|
return <SID>failed_event("\<C-p>".a:failed_insert)
|
|
else
|
|
let keys = cmp#complete(a:failed_insert)
|
|
return keys
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! cmp#check_parameter_return(parameter, parameter_begin, parameter_end) abort "{{{
|
|
if len(a:parameter) < 2
|
|
return 0
|
|
endif
|
|
" echom printf('mb, begin: %s, p[0]: %s, result: %d', a:parameter_begin, a:parameter[0], match(a:parameter_begin, a:parameter[0]) != -1)
|
|
" echom printf('me, end: %s, p[-1]: %s, result: %d', a:parameter_end, a:parameter[-1], match(a:parameter_end, a:parameter[len(a:parameter)-1]) != -1)
|
|
return match(a:parameter_begin, a:parameter[0]) != -1 &&
|
|
\match(a:parameter_end, a:parameter[len(a:parameter)-1]) != -1
|
|
endfunction "}}}
|
|
|
|
function! cmp#complete(failed_insert) abort "{{{
|
|
call <SID>trace_log(string(v:completed_item))
|
|
if <SID>empty_completed_item()
|
|
call <SID>debug_log('v:completed_item is empty')
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
let filetype = &ft
|
|
if empty(filetype)
|
|
call <SID>debug_log('filetype is empty')
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
try
|
|
let ftfunc = cmp#new_ftfunc(filetype)
|
|
catch
|
|
call <SID>debug_log('new_ftfunc failed. '.string(v:exception))
|
|
return <SID>failed_event(a:failed_insert)
|
|
endtry
|
|
if !cmp#filetype_func_check(ftfunc)
|
|
call <SID>error_log('ftfunc check failed')
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
|
|
" example: the complete func like this`func Hello(param1 int, param2 string) int`
|
|
" the parsed must be a list and the element of the list is a dictional,
|
|
" the dictional must have the below keys
|
|
" text: the text to be complete -> `(param1, param2)`
|
|
" delim: the delim of parameters -> `,`
|
|
" prefix: the begin of text -> `(`
|
|
" suffix: the end of the text -> `)`
|
|
let parseds = ftfunc.parameters(v:completed_item)
|
|
call <SID>debug_log(string(parseds))
|
|
if type(parseds) != 3
|
|
call <SID>error_log('return type error')
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
let parameter_begin = ftfunc.parameter_begin()
|
|
let parameter_end = ftfunc.parameter_end()
|
|
|
|
if empty(parseds) || len(parseds[0]) < 2 || !cmp#check_parameter_return(parseds[0], parameter_begin, parameter_end)
|
|
call <SID>debug_log("parseds is empty")
|
|
return <SID>failed_event(a:failed_insert)
|
|
endif
|
|
|
|
let s:complete_parameter['index'] = 0
|
|
let s:complete_parameter['items'] = parseds
|
|
|
|
let s:complete_parameter['complete_pos'] = [line('.'), col('.')]
|
|
let col = s:complete_parameter['complete_pos'][1]
|
|
let s:complete_parameter['success'] = 1
|
|
if get(g:, 'complete_parameter_echo_signature', 1)
|
|
let s:complete_parameter['echos'] = ftfunc.echos(v:completed_item)
|
|
endif
|
|
|
|
" if the first char of parameter was inserted, remove it from the parameter
|
|
let content = getline(line('.'))
|
|
let parameter = s:complete_parameter['items'][0]
|
|
if col > 1
|
|
if content[col-2] ==# parameter[0]
|
|
let parameter = substitute(parameter, '\m.\(.*\)', '\1', '')
|
|
let s:complete_parameter['complete_pos'][1] = col - 1
|
|
endif
|
|
endif
|
|
|
|
" must be insert mode
|
|
" the cursor is in the last char+1,col('$')
|
|
" we need the pass the last char col, so col('.')-1
|
|
return cmp#goto_first_param(parameter, content, col('.')-1)
|
|
endfunction "}}}
|
|
|
|
function! cmp#goto_first_param(parameter, content, current_col) abort "{{{
|
|
let old_ei = &ei
|
|
set ei=InsertLeave,InsertEnter,TextChanged
|
|
if s:complete_parameter['success']
|
|
call <SID>trace_log(printf('content:[%s] current_col:%d, left:[%s], right:[%s]', a:content, a:current_col, a:content[:a:current_col-1], a:content[a:current_col:]))
|
|
let content = a:content[:a:current_col-1] . a:parameter . a:content[a:current_col:]
|
|
call <SID>trace_log("content: " . content)
|
|
call <SID>trace_log("current_col: " . a:current_col)
|
|
" the current_col is no in the `()`
|
|
" show we need to add 1
|
|
let keys = cmp#goto_next_param_keys(1, content, a:current_col+1)
|
|
let keys = printf("%s\<ESC>%s", a:parameter, keys)
|
|
call <SID>trace_log("keys: ". keys)
|
|
|
|
let index = s:complete_parameter['index']
|
|
if len(s:complete_parameter['echos']) > index && s:complete_parameter['echos'][index] !=# ''
|
|
echon s:complete_parameter['echos'][index]
|
|
endif
|
|
let &ei=old_ei
|
|
return keys
|
|
else
|
|
let &ei=old_ei
|
|
return a:parameter
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! cmp#goto_next_param_keys(forward, content, current_col) abort "{{{
|
|
let filetype = &ft
|
|
if empty(filetype)
|
|
call <SID>debug_log('filetype is empty')
|
|
return ''
|
|
endif
|
|
|
|
try
|
|
let ftfunc = cmp#new_ftfunc(filetype)
|
|
catch
|
|
call <SID>debug_log('new ftfunc failed')
|
|
return ''
|
|
endtry
|
|
if !cmp#filetype_func_check(ftfunc)
|
|
return ''
|
|
endif
|
|
|
|
let step = a:forward ? 1 : -1
|
|
|
|
let delim = ftfunc.parameter_delim()
|
|
let border_begin = a:forward ? ftfunc.parameter_begin() : ftfunc.parameter_end()
|
|
let border_end = a:forward ? ftfunc.parameter_end() : ftfunc.parameter_begin()
|
|
|
|
" if in the insert mode
|
|
" go back to the first none space
|
|
" this can select the parameter after cursor
|
|
let pos = a:current_col-1
|
|
let scope_end = step > 0 ? -1 : len(a:content)
|
|
if mode() ==# 'i'
|
|
while pos != scope_end &&
|
|
\(a:content[pos-1] =~# '['.delim.border_begin.']' ||
|
|
\ a:content[pos-1] ==# ' ')
|
|
let pos -= 1
|
|
if a:content[pos] =~# '['.delim.border_begin.']'
|
|
break
|
|
endif
|
|
endwhile
|
|
endif
|
|
|
|
let [word_begin, word_end] = cmp#parameter_position(a:content, pos+1, delim, border_begin, border_end, step)
|
|
call <SID>trace_log(printf('content:[%s],current_col:%d,word_begin:%d,word_end:%d', a:content, a:current_col, word_begin, word_end))
|
|
if word_begin == 0 && word_end == 0
|
|
call <SID>debug_log('word_begin and word_end is 0')
|
|
return ''
|
|
endif
|
|
let word_len = word_end - word_begin
|
|
call <SID>trace_log('word_len:'.word_len)
|
|
let keys = printf("\<ESC>0%dl", word_begin-2)
|
|
if word_len == 0
|
|
if a:forward
|
|
return keys . "a\<RIGHT>"
|
|
endif
|
|
else
|
|
let keys .= "lv"
|
|
if &selection ==# 'exclusive'
|
|
let right_len = word_len
|
|
else
|
|
let right_len = word_len - 1
|
|
endif
|
|
if right_len > 0
|
|
let keys .= right_len
|
|
let keys .= "l"
|
|
endif
|
|
let keys .= "\<C-G>"
|
|
return keys
|
|
endif
|
|
return ''
|
|
endfunction "}}}
|
|
|
|
function! cmp#goto_next_param(forward) abort "{{{
|
|
let s:log_index = <sid>timenow_ms()
|
|
|
|
let filetype = &ft
|
|
if empty(filetype)
|
|
call <SID>debug_log('filetype is empty')
|
|
return ''
|
|
endif
|
|
|
|
try
|
|
let ftfunc = cmp#new_ftfunc(filetype)
|
|
catch
|
|
call <SID>debug_log('new ftfunc failed')
|
|
return ''
|
|
endtry
|
|
if !cmp#filetype_func_check(ftfunc)
|
|
return ''
|
|
endif
|
|
|
|
let step = a:forward ? 1 : -1
|
|
|
|
let border_end = a:forward ? ftfunc.parameter_end() : ftfunc.parameter_begin()
|
|
|
|
let lnum = line('.')
|
|
let content = getline(lnum)
|
|
let current_col = col('.')
|
|
|
|
let pos = current_col - 1
|
|
|
|
let parameter_delim = ftfunc.parameter_delim()
|
|
if !a:forward && &selection==#'exclusive' &&
|
|
\(match(parameter_delim, content[pos])!=-1 ||
|
|
\ match(ftfunc.parameter_end(), content[pos])!=-1)
|
|
let current_col -= 1
|
|
let pos -= 1
|
|
endif
|
|
|
|
" if the selected is an object and the cursor char is an border_end
|
|
" go back to border_begin and it can select the item in the object.
|
|
if mode() == 'n' && match(border_end, content[pos]) != -1
|
|
normal %
|
|
let current_col = col('.')
|
|
endif
|
|
|
|
|
|
let keys = cmp#goto_next_param_keys(a:forward, content, current_col)
|
|
call feedkeys(keys, 'n')
|
|
return ''
|
|
endfunction "}}}
|
|
|
|
" items: all overload complete function parameters
|
|
" current_line: current line content
|
|
" complete_pos: the pos where called complete
|
|
" forward: down or up
|
|
" [success, item, next_index, old_item_len]
|
|
function! cmp#next_overload_content(items, current_index, current_line, complete_pos, forward) abort "{{{
|
|
if len(a:items) <= 1 ||
|
|
\a:current_index >= len(a:items) ||
|
|
\empty(a:current_line) ||
|
|
\len(a:current_line) < a:complete_pos[1]
|
|
return [0]
|
|
endif
|
|
|
|
let current_overload_len = len(a:items[a:current_index])
|
|
|
|
let pos = a:complete_pos[1] - 1
|
|
let pos_end = pos+current_overload_len-1
|
|
let content = a:current_line[ pos : pos_end ]
|
|
if content !=# a:items[a:current_index]
|
|
return [0]
|
|
endif
|
|
let overload_len = len(a:items)
|
|
if a:forward
|
|
let next_index = (a:current_index + 1) % overload_len
|
|
else
|
|
let next_index = (a:current_index+overload_len-1)%overload_len
|
|
endif
|
|
return [1, a:items[next_index], next_index, len(a:items[a:current_index])]
|
|
endfunction "}}}
|
|
|
|
function! s:timenow_us()
|
|
let t = reltime()
|
|
return t[0] * 1000000 + t[1]
|
|
endfunction
|
|
|
|
function! s:timenow_ms()
|
|
return <SID>timenow_us()
|
|
endfunction
|
|
|
|
function! cmp#overload_next(forward) abort "{{{
|
|
let s:log_index = <SID>timenow_ms()
|
|
|
|
let overload_len = len(s:complete_parameter['items'])
|
|
if overload_len <= 1
|
|
return
|
|
endif
|
|
let complete_pos = s:complete_parameter['complete_pos']
|
|
let current_line = line('.')
|
|
let current_col = col('.')
|
|
" if no in the complete content
|
|
" then return
|
|
if current_line != complete_pos[0] || current_col < complete_pos[1]
|
|
call <SID>trace_log('no more overload')
|
|
return
|
|
endif
|
|
|
|
let current_index = s:complete_parameter['index']
|
|
let current_line = getline(current_line)
|
|
let result = cmp#next_overload_content(
|
|
\s:complete_parameter['items'],
|
|
\current_index,
|
|
\current_line,
|
|
\s:complete_parameter['complete_pos'],
|
|
\a:forward)
|
|
if result[0] == 0
|
|
call <SID>debug_log('get overload content failed')
|
|
return
|
|
endif
|
|
|
|
let current_overload_len = result[3]
|
|
|
|
call cursor(complete_pos[0], complete_pos[1])
|
|
call <sid>trace_log(printf('pos: %d %d', complete_pos[0], complete_pos[1]))
|
|
|
|
exec 'normal! d'.current_overload_len.'l'
|
|
|
|
let next_content = result[1]
|
|
|
|
let s:complete_parameter['index'] = result[2]
|
|
let s:complete_parameter['success'] = 1
|
|
let content = getline(line('.'))
|
|
let current_col = col('.')
|
|
let insert_method = 'a'
|
|
if current_col != col('$')-1
|
|
" if no the last char
|
|
" the cursor in the last complete char+1
|
|
" we need to -1
|
|
let current_col -= 1
|
|
let insert_method = 'i'
|
|
endif
|
|
let ret = insert_method.cmp#goto_first_param(next_content, content, current_col)
|
|
call <SID>trace_log(ret)
|
|
call feedkeys(ret, 'n')
|
|
endfunction "}}}
|
|
|
|
let s:stack = {'data':[]}
|
|
|
|
function! s:new_stack() abort "{{{
|
|
return deepcopy(s:stack)
|
|
endfunction "}}}
|
|
|
|
function! s:stack.push(e) abort dict "{{{
|
|
call add(self.data, a:e)
|
|
endfunction "}}}
|
|
|
|
function! s:stack.len() abort dict "{{{
|
|
return len(self.data)
|
|
endfunction "}}}
|
|
|
|
function! s:stack.empty() abort dict "{{{
|
|
return self.len() == 0
|
|
endfunction "}}}
|
|
|
|
function! s:stack.top() abort dict "{{{
|
|
if self.empty()
|
|
throw "stack is empty"
|
|
endif
|
|
return self.data[-1]
|
|
endfunction "}}}
|
|
|
|
function! s:stack.pop() abort dict "{{{
|
|
if self.empty()
|
|
throw "stack is empty"
|
|
endif
|
|
call remove(self.data, -1)
|
|
endfunction "}}}
|
|
|
|
function! s:stack.str() abort dict "{{{
|
|
let str = 'stack size:'.self.len()
|
|
for d in self.data
|
|
let str .= "\n"
|
|
let str .= 'stack elem:'.d
|
|
endfor
|
|
return str
|
|
endfunction "}}}
|
|
|
|
function! s:in_scope(content, pos, border, step, end) abort "{{{
|
|
" echom printf('content: %s, pos: %d, border: %s, step: %d, end: %d', a:content, a:pos, a:border, a:step, a:end)
|
|
let i = a:pos
|
|
while i != a:end
|
|
if a:content[i] =~# '\m['.a:border.']'
|
|
return 1
|
|
endif
|
|
let i += a:step
|
|
endwhile
|
|
return 0
|
|
endfunction "}}}
|
|
|
|
function! cmp#jumpable(forward) abort "{{{ can jump to next parameter or not
|
|
let filetype = &ft
|
|
try
|
|
let ftfunc = cmp#new_ftfunc(filetype)
|
|
catch
|
|
call <SID>debug_log('new ftfunc failed')
|
|
return 0
|
|
endtry
|
|
if !cmp#filetype_func_check(ftfunc)
|
|
call <SID>debug_log('func check failed')
|
|
return 0
|
|
endif
|
|
|
|
let delim = ftfunc.parameter_delim()
|
|
let border = a:forward > 0 ? ftfunc.parameter_begin() : ftfunc.parameter_end()
|
|
let step = a:forward > 0 ? -1 : 1
|
|
|
|
let lnum = line('.')
|
|
let content = getline(lnum)
|
|
let current_pos = col('.') - 1
|
|
|
|
let end = a:forward > 0 ? -1 : len(content)
|
|
return <SID>in_scope(content, current_pos, border, step, end)
|
|
endfunction "}}}
|
|
|
|
" content: string, the content to parse
|
|
" current_col: int, current col
|
|
" delim: string, split the paramter letter
|
|
" return: [int, int] begin_col, end_col
|
|
"
|
|
" col: base 1
|
|
" pos: base 0
|
|
function! cmp#parameter_position(content, current_col, delim, border_begin, border_end, step) abort "{{{
|
|
"{{{2
|
|
if empty(a:content) ||
|
|
\a:current_col==0 ||
|
|
\empty(a:delim) ||
|
|
\empty(a:border_begin) ||
|
|
\empty(a:border_end) ||
|
|
\len(a:border_begin) != len(a:border_end) ||
|
|
\a:step==0
|
|
call <SID>debug_log('parameter_position param error')
|
|
return [0, 0]
|
|
endif "}}}2
|
|
let step = a:step > 0 ? 1 : -1
|
|
let current_pos = a:current_col - 1
|
|
let content_len = len(a:content)
|
|
let end = a:step > 0 ? content_len : -1
|
|
if current_pos >= content_len
|
|
let current_pos = content_len-1
|
|
endif
|
|
|
|
" check current pos is in the scope or not
|
|
let scope_end = step > 0 ? -1 : content_len
|
|
if !<SID>in_scope(a:content, current_pos, a:border_begin, -step, scope_end)
|
|
call <SID>trace_log(printf("no in scope, content: %s, current_pos: %d, a:border_begin: %s, step: %d, scope_end: %d", a:content, current_pos, a:border_begin, -step, scope_end))
|
|
retur [0, 0]
|
|
endif
|
|
|
|
let stack = <SID>new_stack()
|
|
let pos = current_pos
|
|
|
|
let border_matcher = {}
|
|
let border_begin_chars = split(a:border_begin, '\zs')
|
|
let border_end_chars = split(a:border_end, '\zs')
|
|
let i = 0
|
|
while i < len(border_end_chars)
|
|
let border_matcher[border_begin_chars[i]] = '\m['.a:delim.border_end_chars[i].']'
|
|
let i += 1
|
|
endwhile
|
|
|
|
" let border_matcher[a:border_begin] = '\m['.a:delim.a:border_end.']'
|
|
let border_matcher[a:delim] = '\m['.a:delim.a:border_end.']'
|
|
let border_matcher['"'] = '"'
|
|
let border_matcher["'"] = "'"
|
|
let border_matcher["`"] = "`"
|
|
let begin_pos = 0
|
|
let end_pos = 0
|
|
|
|
" check has previous quote
|
|
let quote_test_content_pos = pos
|
|
if a:content[quote_test_content_pos] =~# '\m["''`]'
|
|
let quote_test_content_pos -= step
|
|
endif
|
|
let quote_test_content = a:content[:quote_test_content_pos]
|
|
let quote_test_content = substitute(quote_test_content, '\m\\.', '', 'g')
|
|
let quote_test_content = substitute(quote_test_content, '\m[^"''`]', '', 'g')
|
|
let quotes = split(quote_test_content, '\zs')
|
|
for quote in quotes
|
|
if stack.empty()
|
|
call stack.push(quote)
|
|
elseif border_matcher[stack.top()] ==# quote
|
|
call stack.pop()
|
|
endif
|
|
endfor
|
|
|
|
while pos != end "{{{2
|
|
if step < 0
|
|
if pos + step != end && a:content[pos+step] == '\'
|
|
let pos += 2*step
|
|
continue
|
|
endif
|
|
endif
|
|
|
|
" if top of stack is quote and current letter is not a quote
|
|
" the letter should be ignore
|
|
if !stack.empty() && stack.top() =~# '\m["`'']' && a:content[pos] !~# '\m["`''\\]'
|
|
let pos += step
|
|
continue
|
|
endif
|
|
|
|
if a:content[pos] ==# '"' || a:content[pos] ==# "'" || a:content[pos] ==# '`'
|
|
if stack.empty() || border_matcher[stack.top()] !=# a:content[pos]
|
|
call stack.push(a:content[pos])
|
|
else
|
|
call stack.pop()
|
|
endif
|
|
elseif a:content[pos] ==# '\'
|
|
let pos += step
|
|
elseif a:content[pos] ==# '='
|
|
if step > 0
|
|
if stack.len() > 1
|
|
" if stack more than 1, current maybe in the nest scope, ignore it
|
|
let pos += step
|
|
continue
|
|
endif
|
|
let pos += step
|
|
let pos = <SID>find_first_not_space(a:content, pos, end, step)
|
|
if pos == end
|
|
break
|
|
endif
|
|
let begin_pos = pos
|
|
if stack.len() == 0
|
|
" let = as a delim, it's the next begining
|
|
call stack.push(a:delim[0])
|
|
endif
|
|
continue
|
|
else
|
|
" backword
|
|
" if stack is empty, we need to find the first begin or delim
|
|
" if stack more than 1, current maybe in the nest scope, ignore it
|
|
if stack.len() != 1
|
|
let pos += step
|
|
continue
|
|
else
|
|
" if stack len is 1, and current pos must be want to select
|
|
break
|
|
endif
|
|
endif
|
|
elseif stridx(a:border_begin, a:content[pos]) != -1
|
|
if a:content[pos] ==# '>' && step < 0
|
|
" check if there are is a '<' or not
|
|
let tmppos = pos + step
|
|
while tmppos >= 0 && a:content[tmppos] !=# '<'
|
|
let tmppos += step
|
|
endwhile
|
|
if tmppos < 0
|
|
let pos += step
|
|
continue
|
|
endif
|
|
endif
|
|
|
|
call stack.push(a:content[pos])
|
|
if stack.len() == 1
|
|
" begin
|
|
let pos += step
|
|
let pos = <SID>find_first_not_space(a:content, pos, end, step)
|
|
if pos == end
|
|
break
|
|
endif
|
|
let begin_pos = pos
|
|
" no need to step forward
|
|
" goto the beginning of the loop
|
|
continue
|
|
endif
|
|
elseif a:content[pos] ==# a:delim
|
|
if stack.empty()
|
|
call stack.push(a:content[pos])
|
|
let pos += step
|
|
let pos = <SID>find_first_not_space(a:content, pos, end, step)
|
|
if pos == end
|
|
break
|
|
endif
|
|
let begin_pos = pos
|
|
" no need to step forward
|
|
" goto the beginning of the loop
|
|
continue
|
|
elseif stack.len() == 1 && a:content[pos] =~# border_matcher[stack.top()]
|
|
call stack.pop()
|
|
if stack.empty()
|
|
" match delim
|
|
break
|
|
endif
|
|
endif
|
|
elseif stridx(a:border_end, a:content[pos]) != -1
|
|
if a:content[pos] ==# '<' && step > 0
|
|
" check if there are is a '>' or not
|
|
let tmppos = pos + step
|
|
while tmppos < content_len && a:content[tmppos] !=# '>'
|
|
let tmppos += step
|
|
endwhile
|
|
if tmppos >= content_len
|
|
let pos += step
|
|
continue
|
|
endif
|
|
endif
|
|
|
|
if stack.empty()
|
|
let begin_pos = pos
|
|
let end_pos = pos
|
|
else
|
|
if a:content[pos] =~# border_matcher[stack.top()]
|
|
" border match, then pop
|
|
call stack.pop()
|
|
if stack.empty()
|
|
" match delim
|
|
break
|
|
endif
|
|
endif
|
|
endif
|
|
endif
|
|
let pos += step
|
|
endwhile "}}}2
|
|
if pos == end
|
|
if begin_pos != 0 && end_pos != 0
|
|
return [begin_pos+1,end_pos+1]
|
|
else
|
|
return [0, 0]
|
|
endif
|
|
endif
|
|
|
|
if begin_pos != pos
|
|
let pos -= step
|
|
" find previous no space
|
|
while pos != begin_pos && a:content[pos] =~# '\s'
|
|
let pos -= step
|
|
endwhile
|
|
endif
|
|
|
|
let end_pos = pos
|
|
if begin_pos == end_pos && stridx(a:border_end, a:content[end_pos]) != -1
|
|
return [begin_pos+1, end_pos+1]
|
|
endif
|
|
|
|
if end_pos < begin_pos
|
|
let [begin_pos, end_pos] = [end_pos, begin_pos]
|
|
endif
|
|
return [begin_pos+1, end_pos+2]
|
|
endfunction "}}}
|
|
|
|
function! s:find_first_not_space(content, pos, end, step) abort "{{{
|
|
let pos = a:pos
|
|
if pos == -1 ||
|
|
\pos==len(a:content)
|
|
return pos == a:end
|
|
endif
|
|
if a:step == 0
|
|
throw 'step is 0'
|
|
endif
|
|
while pos != a:end && a:content[pos] =~# '\s'
|
|
let pos += a:step
|
|
endwhile
|
|
return pos
|
|
endfunction "}}}
|
|
|
|
function! s:log(level, msg) abort "{{{
|
|
echom printf("[CompleteParameter][%s][%s][%d] %s", strftime("%T"), a:level, s:log_index, a:msg)
|
|
endfunction "}}}
|
|
|
|
function! s:error_log(msg) abort "{{{
|
|
if g:complete_parameter_log_level <= 4
|
|
echohl ErrorMsg
|
|
call <SID>log('ERROR', a:msg)
|
|
echohl None
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! s:debug_log(msg) abort "{{{
|
|
if g:complete_parameter_log_level <= 2
|
|
call <SID>log('DEBUG', a:msg)
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! s:trace_log(msg) abort "{{{
|
|
if g:complete_parameter_log_level <= 1
|
|
call <SID>log('TRACE', a:msg)
|
|
endif
|
|
endfunction "}}}
|
|
|
|
|