1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-03 04:00:04 +08:00
SpaceVim/bundle/CompleteParameter.vim/autoload/cmp.vim
2020-08-29 21:14:10 +08:00

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 "}}}