mirror of
https://github.com/prabirshrestha/asyncomplete.vim.git
synced 2026-05-31 11:18:47 +02:00
Merge pull request #49 from prabirshrestha/TextChangedP
[v2] add support for fuzzy search using lua and use TextChangedP
This commit is contained in:
+388
-273
@@ -2,20 +2,20 @@ if !has('timers')
|
||||
echohl ErrorMsg
|
||||
echomsg 'Vim/Neovim compiled with timers required for asyncomplete.vim.'
|
||||
echohl NONE
|
||||
if has('nvim')
|
||||
call asyncomplete#log('neovim compiled with timers required.')
|
||||
else
|
||||
call asyncomplete#log('vim compiled with timers required.')
|
||||
endif
|
||||
call asyncomplete#log('vim/neovim compiled with timers required.')
|
||||
finish
|
||||
endif
|
||||
|
||||
let s:sources = {}
|
||||
let s:matches = {}
|
||||
let s:already_setup = 0
|
||||
let s:change_timer = -1
|
||||
let s:last_tick = []
|
||||
let s:has_popped_up = 0
|
||||
let s:complete_timer_ctx = {}
|
||||
let s:already_setup = 0
|
||||
let s:startcol = -1
|
||||
let s:candidates = []
|
||||
let s:script_path = expand('<sfile>:p:h')
|
||||
let s:has_lua = has('lua') || has('neovim-0.2.2')
|
||||
let s:supports_smart_completion = s:has_lua && exists('##TextChangedP')
|
||||
|
||||
function! asyncomplete#log(...) abort
|
||||
if !empty(g:asyncomplete_log_file)
|
||||
@@ -36,17 +36,92 @@ function! asyncomplete#enable_for_buffer() abort
|
||||
endif
|
||||
|
||||
let b:asyncomplete_enable = 1
|
||||
augroup ayncomplete
|
||||
autocmd! * <buffer>
|
||||
autocmd InsertEnter <buffer> call s:python_cm_insert_enter()
|
||||
autocmd InsertEnter <buffer> call s:change_tick_start()
|
||||
autocmd InsertLeave <buffer> call s:change_tick_stop()
|
||||
" working together with timer, the timer is for detecting changes
|
||||
" popup menu is visible. TextChangedI will not be triggered when popup
|
||||
" menu is visible, but TextChangedI is more efficient and faster than
|
||||
" timer when popup menu is not visible.
|
||||
autocmd TextChangedI <buffer> call s:check_changes()
|
||||
augroup END
|
||||
if exists('##TextChangedP')
|
||||
augroup ayncomplete
|
||||
autocmd! * <buffer>
|
||||
autocmd InsertEnter <buffer> call s:on_insert_enter()
|
||||
autocmd InsertLeave <buffer> call s:on_insert_leave()
|
||||
autocmd TextChangedI <buffer> call s:on_text_changed()
|
||||
autocmd TextChangedP <buffer> call s:on_text_changed()
|
||||
augroup END
|
||||
else
|
||||
augroup ayncomplete
|
||||
autocmd! * <buffer>
|
||||
autocmd InsertEnter <buffer> call s:on_insert_enter()
|
||||
autocmd InsertLeave <buffer> call s:on_insert_leave()
|
||||
autocmd InsertEnter <buffer> call s:change_tick_start()
|
||||
autocmd InsertLeave <buffer> call s:change_tick_stop()
|
||||
" working together with timer, the timer is for detecting changes
|
||||
" popup menu is visible. TextChangedI will not be triggered when popup
|
||||
" menu is visible, but TextChangedI is more efficient and faster than
|
||||
" timer when popup menu is not visible.
|
||||
autocmd TextChangedI <buffer> call s:check_changes()
|
||||
augroup END
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_enter() abort
|
||||
call s:reset()
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_leave() abort
|
||||
call s:reset()
|
||||
endfunction
|
||||
|
||||
function! s:reset() abort
|
||||
let s:matches = {}
|
||||
let s:startcol = -1
|
||||
let s:candidates = []
|
||||
endfunction
|
||||
|
||||
function! s:on_text_changed() abort
|
||||
let l:ctx = asyncomplete#context()
|
||||
call s:notify_sources_to_refresh(l:ctx, 0)
|
||||
if s:supports_smart_completion() && pumvisible() && !empty(s:candidates)
|
||||
" TODO: delay s:update_pum() since it is expensive due to filtering candidates
|
||||
call s:update_pum(l:ctx, s:startcol, s:candidates)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:change_tick() abort
|
||||
return [b:changedtick, getcurpos()]
|
||||
endfunction
|
||||
|
||||
function! s:change_tick_start() abort
|
||||
if s:change_timer != -1
|
||||
return
|
||||
endif
|
||||
let s:last_tick = s:change_tick()
|
||||
" changes every 30ms, which is 0.03s, it should be fast enough
|
||||
let s:change_timer = timer_start(30, function('s:check_changes'), { 'repeat': -1 })
|
||||
call s:on_changed()
|
||||
endfunction
|
||||
|
||||
function! s:change_tick_stop() abort
|
||||
if s:change_timer == -1
|
||||
return
|
||||
endif
|
||||
call timer_stop(s:change_timer)
|
||||
let s:last_tick = []
|
||||
let s:change_timer = -1
|
||||
endfunction
|
||||
|
||||
function! s:check_changes(...) abort
|
||||
let l:tick = s:change_tick()
|
||||
if l:tick != s:last_tick
|
||||
let s:last_tick = l:tick
|
||||
call s:on_changed()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_changed() abort
|
||||
if exists('s:complete_timer')
|
||||
call timer_stop(s:complete_timer)
|
||||
unlet s:complete_timer
|
||||
endif
|
||||
|
||||
let l:ctx = asyncomplete#context()
|
||||
call s:notify_sources_to_refresh(l:ctx, 0)
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#register_source(info) abort
|
||||
@@ -57,7 +132,7 @@ function! asyncomplete#register_source(info) abort
|
||||
if has_key(a:info, 'events') && has_key(a:info, 'on_event')
|
||||
execute 'augroup asyncomplete_source_event_' . a:info['name']
|
||||
for l:event in a:info['events']
|
||||
let l:exec = 'if get(b:,"asyncomplete_enable",0) | call s:python_cm_event("' . a:info['name'] . '", "'.l:event.'",asyncomplete#context()) | endif'
|
||||
let l:exec = 'if get(b:,"asyncomplete_enable",0) | call s:notify_source_event("' . a:info['name'] . '", "'.l:event.'",asyncomplete#context()) | endif'
|
||||
if type(l:event) == type('')
|
||||
execute 'au ' . l:event . ' * ' . l:exec
|
||||
elseif type(l:event) == type([])
|
||||
@@ -80,18 +155,300 @@ function! asyncomplete#unregister_source(name) abort
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#complete(name, ctx, startcol, matches, ...) abort
|
||||
let l:refresh = a:0 > 0 ? a:1 : 0
|
||||
|
||||
" ignore the request if context has changed
|
||||
if asyncomplete#context_changed(a:ctx)
|
||||
if g:asyncomplete_force_refresh_on_context_changed
|
||||
call s:python_cm_complete(a:name, a:ctx, a:startcol, a:matches, l:refresh, 1)
|
||||
endif
|
||||
function! s:is_enabled() abort
|
||||
if !get(b:, 'asyncomplete_enable') || mode() isnot# 'i' || &paste
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
call s:python_cm_complete(a:name, a:ctx, a:startcol, a:matches, l:refresh, 0)
|
||||
function! s:notify_sources_to_refresh(ctx, force) abort
|
||||
if !s:is_enabled()
|
||||
return
|
||||
endif
|
||||
|
||||
if !pumvisible() && !g:asyncomplete_auto_popup
|
||||
if !a:force
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:typed = a:ctx['typed']
|
||||
|
||||
for l:source_name in s:get_active_sources_for_buffer()
|
||||
let l:refresh = a:force
|
||||
let l:source = s:sources[l:source_name]
|
||||
if !a:force
|
||||
if has_key(s:matches, l:source_name)
|
||||
if s:matches[l:source_name]['incomplete']
|
||||
let l:refresh = 1
|
||||
else
|
||||
let l:matchpos = s:get_matchpos(s:sources[l:source_name], a:ctx)
|
||||
let l:startpos = l:matchpos[1]
|
||||
let l:endpos = l:matchpos[2]
|
||||
let l:typed_len = l:endpos - l:startpos
|
||||
let l:startcol = len(l:typed[:len(l:typed) - l:typed_len])
|
||||
if s:matches[l:source_name]['startcol'] == l:startcol
|
||||
if s:matches[l:source_name]['pending']
|
||||
call s:queue_compute_candidates()
|
||||
else
|
||||
if s:supports_smart_completion()
|
||||
call s:queue_compute_candidates()
|
||||
endif
|
||||
endif
|
||||
else
|
||||
if s:matches[l:source_name]['pending']
|
||||
call s:queue_compute_candidates()
|
||||
else
|
||||
let l:typed_len = l:endpos - l:startpos
|
||||
let l:min_chars = get(l:source, 'min_chars', g:asyncomplete_min_chars)
|
||||
if l:typed_len >= l:min_chars
|
||||
let l:refresh = 1
|
||||
let s:matches[l:source_name] = {
|
||||
\ 'pending': 1,
|
||||
\ 'startcol': l:matchpos[1],
|
||||
\ 'incomplete': 0,
|
||||
\ 'candidates': [],
|
||||
\ }
|
||||
endif
|
||||
call s:queue_compute_candidates()
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
else
|
||||
let l:matchpos = s:get_matchpos(s:sources[l:source_name], a:ctx)
|
||||
let l:startpos = l:matchpos[1]
|
||||
let l:endpos = l:matchpos[2]
|
||||
let l:typed_len = l:endpos - l:startpos
|
||||
let l:min_chars = get(l:source, 'min_chars', g:asyncomplete_min_chars)
|
||||
if l:typed_len >= l:min_chars
|
||||
let l:refresh = 1
|
||||
let s:matches[l:source_name] = {
|
||||
\ 'pending': 1,
|
||||
\ 'startcol': len(l:typed[:len(l:typed) - l:typed_len -1]),
|
||||
\ 'typed': l:typed[:l:matchpos[1]],
|
||||
\ 'incomplete': 0,
|
||||
\ 'candidates': [],
|
||||
\ }
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
if l:refresh
|
||||
try
|
||||
call asyncomplete#log('core.s:notify_sources_to_refresh', 'completor()', l:source_name, a:ctx)
|
||||
call s:sources[l:source_name].completor(s:sources[l:source_name], a:ctx)
|
||||
catch
|
||||
call asyncomplete#log('core.s:notify_sources_to_refresh', 'completor()', 'error', v:exception)
|
||||
continue
|
||||
endtry
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:get_matchpos(source_info, ctx) abort
|
||||
if has_key(a:source_info, 'refresh_pattern')
|
||||
let l:refresh_pattern = a:source_info['refresh_pattern']
|
||||
if (type(l:refresh_pattern) != type(''))
|
||||
let l:refresh_pattern = l:refresh_pattern()
|
||||
endif
|
||||
else
|
||||
let l:refresh_pattern = g:asyncomplete_default_refresh_pattern
|
||||
endif
|
||||
|
||||
return s:matchstrpos(a:ctx['typed'], l:refresh_pattern)
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort
|
||||
let l:incomplete = a:0 > 0 ? a:1 : 0
|
||||
let l:current_context = asyncomplete#context()
|
||||
call asyncomplete#log('core#complete', a:name, a:startcol, len(a:candidates), l:incomplete, a:ctx, l:current_context)
|
||||
|
||||
" handle context_changed scenarios, add more scenarios
|
||||
if l:current_context['lnum'] != a:ctx['lnum'] || l:current_context['filetype'] != a:ctx['filetype']
|
||||
call asyncomplete#log('core#complete', a:name, 'ignoring since context changed', l:current_context, a:ctx)
|
||||
return
|
||||
endif
|
||||
|
||||
let s:matches[a:name] = {
|
||||
\ 'pending': 0,
|
||||
\ 'startcol': a:startcol,
|
||||
\ 'incomplete': l:incomplete,
|
||||
\ 'candidates': s:normalize_candidates(a:name, a:candidates),
|
||||
\ 'typed': '',
|
||||
\ 'ctx': a:ctx,
|
||||
\ }
|
||||
|
||||
call s:queue_compute_candidates()
|
||||
endfunction
|
||||
|
||||
function! s:queue_compute_candidates() abort
|
||||
" call s:compute_candidates() at the end of the event loop to avoid calling expensive compute multiple times
|
||||
if exists('s:compute_timer_candidate')
|
||||
call timer_stop(s:compute_timer_candidate)
|
||||
unlet s:compute_timer_candidate
|
||||
endif
|
||||
let s:compute_timer_candidate = timer_start(0, function('s:compute_candidates'))
|
||||
endfunction
|
||||
|
||||
function! s:normalize_candidates(name, candidates) abort
|
||||
let l:normalizedcurcandidates = []
|
||||
if get(s:sources[a:name], 'normalize_completion_items', g:asyncomplete_normalize_completion_items)
|
||||
call asyncomplete#log('s:normalize_candidates', 'normalizing all candidates', a:name)
|
||||
for l:item in a:candidates
|
||||
let l:e = {}
|
||||
if type(l:item) == type('')
|
||||
let l:e['word'] = l:item
|
||||
else
|
||||
let l:e = copy(l:item)
|
||||
endif
|
||||
call add(l:normalizedcurcandidates, l:e)
|
||||
endfor
|
||||
else
|
||||
if !empty(a:candidates)
|
||||
if type(a:candidates[0]) == type('')
|
||||
call asyncomplete#log('s:normalize_candidates', 'normalizing string candidates', a:name)
|
||||
for l:item in a:candidates
|
||||
call add(l:normalizedcurcandidates, { 'word': l:item })
|
||||
endfor
|
||||
else
|
||||
call asyncomplete#log('s:normalize_candidates', 'ignoring candidates normalization', a:name)
|
||||
let l:normalizedcurcandidates = a:candidates
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return l:normalizedcurcandidates
|
||||
endfunction
|
||||
|
||||
function! s:compute_candidates(...) abort
|
||||
if !s:is_enabled()
|
||||
return
|
||||
endif
|
||||
|
||||
call asyncomplete#log('core.s:compute_candidates()')
|
||||
|
||||
" find mimnimal startcol from all matches
|
||||
let l:startcols = []
|
||||
for l:item in values(s:matches)
|
||||
if !l:item['pending']
|
||||
let l:startcols += [l:item['startcol']]
|
||||
endif
|
||||
endfor
|
||||
let l:startcol = min(l:startcols)
|
||||
|
||||
let l:ctx = asyncomplete#context()
|
||||
let l:base = l:ctx['typed'][l:startcol-1:]
|
||||
|
||||
" sort sources by priority
|
||||
let l:sources = sort(keys(s:matches), function('s:sort_sources_by_priority'))
|
||||
|
||||
" remove duplicates if enabled
|
||||
if g:asyncomplete_remove_duplicates
|
||||
let l:sources = filter(copy(l:sources), 'index(l:sources, v:val, v:key+1) == -1')
|
||||
endif
|
||||
|
||||
let l:candidates = []
|
||||
|
||||
" normalize
|
||||
for l:name in l:sources
|
||||
let l:info = s:matches[l:name]
|
||||
let l:curstartcol = l:info['startcol']
|
||||
|
||||
if l:curstartcol > l:ctx['col']
|
||||
" wrong start col
|
||||
continue
|
||||
endif
|
||||
|
||||
let l:candidates += l:info['candidates']
|
||||
endfor
|
||||
|
||||
let s:startcol = l:startcol
|
||||
let s:candidates = l:candidates
|
||||
call s:update_pum(ctx, l:startcol, l:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:update_pum(ctx, startcol, candidates) abort
|
||||
if !s:is_enabled()
|
||||
return
|
||||
endif
|
||||
|
||||
if asyncomplete#menu_selected()
|
||||
return 0
|
||||
endif
|
||||
|
||||
setlocal completeopt-=longest
|
||||
setlocal completeopt+=menuone
|
||||
setlocal completeopt-=menu
|
||||
if &completeopt !~# 'noinsert\|noselect'
|
||||
setlocal completeopt+=noselect
|
||||
endif
|
||||
|
||||
let l:prefix = a:ctx['typed'][a:startcol-1 : col('.') - 1]
|
||||
|
||||
call asyncomplete#log('update pum')
|
||||
|
||||
" filter candidates
|
||||
let l:candidates = s:supports_smart_completion() ? s:filter_completion_items_lua(l:prefix, a:candidates) : a:candidates
|
||||
|
||||
call complete(a:startcol, l:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:supports_smart_completion() abort
|
||||
return s:supports_smart_completion && g:asyncomplete_smart_completion
|
||||
endfunction
|
||||
|
||||
function! s:filter_completion_items_lua(prefix, matches) abort
|
||||
let l:tmpmatches = []
|
||||
lua << EOF
|
||||
function spairs(t, order)
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
for k in pairs(t) do keys[#keys+1] = k end
|
||||
|
||||
-- if order function given, sort by it by passing the table and keys a, b,
|
||||
-- otherwise just sort the keys
|
||||
if order then
|
||||
table.sort(keys, function(a,b) return order(t, a, b) end)
|
||||
else
|
||||
table.sort(keys)
|
||||
end
|
||||
|
||||
-- return the iterator function
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if keys[i] then
|
||||
return keys[i], t[keys[i]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local prefix = vim.eval('a:prefix')
|
||||
local matches = vim.eval('a:matches')
|
||||
local tmpmatches = vim.eval('l:tmpmatches')
|
||||
if asyncomplete.fts == nil then
|
||||
local fts_fuzzy_match_script_path = vim.eval('s:script_path') .. '/fts_fuzzy_match.lua'
|
||||
asyncomplete.fts = dofile(fts_fuzzy_match_script_path)
|
||||
vim.eval("asyncomplete#log('fts_fuzzy_match loaded')")
|
||||
end
|
||||
local index = 0
|
||||
local unsorted_matches = {}
|
||||
for i = 0, #matches - 1 do
|
||||
local word = matches[i].word
|
||||
local matched, score, matchedIndices = asyncomplete.fts.fuzzy_match(prefix, word)
|
||||
if matched == true then
|
||||
table.insert(unsorted_matches, { score = score, match = matches[i] })
|
||||
end
|
||||
-- local matched = asyncomplete.fts.fuzzy_match_simple(prefix, word)
|
||||
-- if matched == true then
|
||||
-- tmpmatches:add(matches[i])
|
||||
-- end
|
||||
end
|
||||
for k,v in spairs(unsorted_matches, function(t,a,b) return t[b].score < t[a].score end) do
|
||||
tmpmatches:add(v.match)
|
||||
end
|
||||
EOF
|
||||
return l:tmpmatches
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#force_refresh() abort
|
||||
@@ -99,9 +456,7 @@ function! asyncomplete#force_refresh() abort
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#_force_refresh() abort
|
||||
if get(b:, 'asyncomplete_enable')
|
||||
call s:python_cm_refresh(asyncomplete#context(), 1)
|
||||
endif
|
||||
call s:notify_sources_to_refresh(asyncomplete#context(), 1)
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
@@ -122,90 +477,6 @@ function! asyncomplete#context_changed(ctx) abort
|
||||
return getcurpos() != a:ctx['curpos']
|
||||
endfunction
|
||||
|
||||
function! s:python_cm_insert_enter() abort
|
||||
call asyncomplete#log('core', 'python_cm_insert_enter')
|
||||
let s:matches = {}
|
||||
endfunction
|
||||
|
||||
" function! s:python_cm_insert_leave() abort
|
||||
" endfunction
|
||||
|
||||
function! s:change_tick_start() abort
|
||||
if s:change_timer != -1
|
||||
return
|
||||
endif
|
||||
let s:last_tick = s:change_tick()
|
||||
" changes every 30ms, which is 0.03s, it should be fast enough
|
||||
let s:change_timer = timer_start(30, function('s:check_changes'), { 'repeat': -1 })
|
||||
call s:on_changed()
|
||||
endfunction
|
||||
|
||||
function! s:change_tick_stop() abort
|
||||
if s:change_timer == -1
|
||||
return
|
||||
endif
|
||||
call timer_stop(s:change_timer)
|
||||
let s:last_tick = []
|
||||
let s:change_timer = -1
|
||||
endfunction
|
||||
|
||||
function! s:on_changed() abort
|
||||
if !get(b:, 'asyncomplete_enable') || mode() isnot# 'i' || &paste
|
||||
return
|
||||
endif
|
||||
|
||||
if exists('s:complete_timer')
|
||||
call timer_stop(s:complete_timer)
|
||||
unlet s:complete_timer
|
||||
endif
|
||||
|
||||
let l:ctx = asyncomplete#context()
|
||||
|
||||
call s:python_cm_refresh(l:ctx, 0)
|
||||
endfunction
|
||||
|
||||
function! s:check_changes(...) abort
|
||||
let l:tick = s:change_tick()
|
||||
if l:tick != s:last_tick
|
||||
let s:last_tick = l:tick
|
||||
call s:on_changed()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:change_tick() abort
|
||||
return [b:changedtick, getcurpos()]
|
||||
endfunction
|
||||
|
||||
function! s:python_cm_complete(name, ctx, startcol, matches, refresh, outdated) abort
|
||||
call asyncomplete#log('core', 's:python_cm_complete', a:name, a:ctx, a:startcol, a:refresh, a:outdated)
|
||||
if a:outdated
|
||||
call s:notify_sources_to_refresh([a:name], asyncomplete#context())
|
||||
return
|
||||
endif
|
||||
|
||||
if !has_key(s:matches, a:name)
|
||||
let s:matches[a:name] = {}
|
||||
endif
|
||||
if empty(a:matches)
|
||||
unlet s:matches[a:name]
|
||||
else
|
||||
let s:matches[a:name]['startcol'] = a:startcol
|
||||
let s:matches[a:name]['matches'] = a:matches
|
||||
let s:matches[a:name]['refresh'] = a:refresh
|
||||
endif
|
||||
|
||||
if s:has_popped_up
|
||||
call s:python_refresh_completions(asyncomplete#context())
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:python_cm_complete_timeout(srcs, ctx) abort
|
||||
if !s:has_popped_up
|
||||
call s:python_refresh_completions(a:ctx)
|
||||
let s:has_popped_up = 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_active_sources_for_buffer() abort
|
||||
" TODO: cache active sources per buffer
|
||||
let l:active_sources = []
|
||||
@@ -248,126 +519,12 @@ else
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:python_cm_refresh(ctx, force) abort
|
||||
let l:has_popped_up = 0
|
||||
if a:force
|
||||
call s:notify_sources_to_refresh(s:get_active_sources_for_buffer(), a:ctx)
|
||||
return
|
||||
endif
|
||||
|
||||
if !pumvisible() && !g:asyncomplete_auto_popup
|
||||
return
|
||||
endif
|
||||
|
||||
let l:typed = a:ctx['typed']
|
||||
let l:sources_to_notify = []
|
||||
|
||||
for l:name in s:get_active_sources_for_buffer()
|
||||
let l:source = s:sources[l:name]
|
||||
if has_key(l:source, 'refresh_pattern')
|
||||
let l:refresh_pattern = l:source['refresh_pattern']
|
||||
else
|
||||
let l:refresh_pattern = '\k\+$'
|
||||
endif
|
||||
let l:matchpos = s:matchstrpos(l:typed, l:refresh_pattern)
|
||||
let l:startpos = l:matchpos[1]
|
||||
let l:endpos = l:matchpos[2]
|
||||
|
||||
call asyncomplete#log('core', 's:python_cm_refresh', l:matchpos, a:ctx)
|
||||
|
||||
let l:typed_len = l:endpos - l:startpos
|
||||
if l:typed_len == 1
|
||||
call add(l:sources_to_notify, l:name)
|
||||
elseif has_key(s:matches, l:name) && s:matches[l:name]['refresh']
|
||||
call add(l:sources_to_notify, l:name)
|
||||
endif
|
||||
endfor
|
||||
|
||||
call s:notify_sources_to_refresh(l:sources_to_notify, a:ctx)
|
||||
endfunction
|
||||
|
||||
function! s:notify_sources_to_refresh(sources, ctx) abort
|
||||
if exists('s:complete_timer')
|
||||
call timer_stop(s:complete_timer)
|
||||
unlet s:complete_timer
|
||||
endif
|
||||
|
||||
let s:complete_timer = timer_start(g:asyncomplete_completion_delay, function('s:complete_timeout'))
|
||||
let s:complete_timer_ctx = a:ctx
|
||||
|
||||
for l:name in a:sources
|
||||
try
|
||||
call asyncomplete#log('core', 'completor()', l:name, a:ctx)
|
||||
call s:sources[l:name].completor(s:sources[l:name], a:ctx)
|
||||
catch
|
||||
call asyncomplete#log('core', 'notify_sources_to_refresh', 'error', v:exception)
|
||||
continue
|
||||
endtry
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:sort_sources_by_priority(source1, source2) abort
|
||||
let l:priority1 = get(get(s:sources, a:source1, {}), 'priority', 0)
|
||||
let l:priority2 = get(get(s:sources, a:source2, {}), 'priority', 0)
|
||||
return l:priority1 > l:priority2 ? -1 : (l:priority1 != l:priority2)
|
||||
endfunction
|
||||
|
||||
function! s:python_refresh_completions(ctx) abort
|
||||
let l:matches = []
|
||||
|
||||
let l:names = keys(s:matches)
|
||||
|
||||
if empty(l:names)
|
||||
call s:python_complete(a:ctx, a:ctx['col'], [])
|
||||
return
|
||||
endif
|
||||
|
||||
let l:startcols = []
|
||||
for l:item in values(s:matches)
|
||||
let l:startcols += [l:item['startcol']]
|
||||
endfor
|
||||
|
||||
let l:startcol = min(l:startcols)
|
||||
let l:base = a:ctx['typed'][l:startcol-1:]
|
||||
|
||||
let l:filtered_matches = []
|
||||
|
||||
let l:sources = sort(keys(s:matches), function('s:sort_sources_by_priority'))
|
||||
|
||||
if g:asyncomplete_remove_duplicates
|
||||
let l:sources = filter(copy(l:sources), 'index(l:sources, v:val, v:key+1) == -1')
|
||||
endif
|
||||
|
||||
for l:name in l:sources
|
||||
let l:info = s:matches[l:name]
|
||||
let l:curstartcol = l:info['startcol']
|
||||
let l:curmatches = l:info['matches']
|
||||
|
||||
if l:curstartcol > a:ctx['col']
|
||||
" wrong start col
|
||||
continue
|
||||
endif
|
||||
|
||||
let l:prefix = a:ctx['typed'][l:startcol-1 : col('.') -1]
|
||||
|
||||
let l:normalizedcurmatches = []
|
||||
for l:item in l:curmatches
|
||||
let l:e = {}
|
||||
if type(l:item) == type('')
|
||||
let l:e['word'] = l:item
|
||||
else
|
||||
let l:e = copy(l:item)
|
||||
let l:e['word'] = l:e['word']
|
||||
endif
|
||||
let l:normalizedcurmatches += [l:e]
|
||||
endfor
|
||||
|
||||
let l:filtered_matches += s:filter_completion_items(l:prefix, l:normalizedcurmatches)
|
||||
endfor
|
||||
|
||||
call s:core_complete(a:ctx, l:startcol, l:filtered_matches, s:matches)
|
||||
endfunction
|
||||
|
||||
function! s:filter_completion_items(prefix, matches) abort
|
||||
let l:tmpmatches = []
|
||||
for l:item in a:matches
|
||||
@@ -378,15 +535,7 @@ function! s:filter_completion_items(prefix, matches) abort
|
||||
return l:tmpmatches
|
||||
endfunction
|
||||
|
||||
function! s:python_complete(ctx, startcol, matches) abort
|
||||
if empty(a:matches)
|
||||
" no need to fire complete message
|
||||
return
|
||||
endif
|
||||
call s:core_complete(a:ctx, a:startcol, a:matches, s:matches)
|
||||
endfunction
|
||||
|
||||
function! s:python_cm_event(name, event, ctx) abort
|
||||
function! s:notify_source_event(name, event, ctx) abort
|
||||
try
|
||||
call s:sources[a:name].on_event(s:sources[a:name], a:ctx, a:event)
|
||||
catch
|
||||
@@ -394,40 +543,6 @@ function! s:python_cm_event(name, event, ctx) abort
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:core_complete(ctx, startcol, matches, allmatches) abort
|
||||
if !get(b:, 'asyncomplete_enable', 0)
|
||||
return 2
|
||||
endif
|
||||
|
||||
" ignore the request if context has changed
|
||||
if (a:ctx != asyncomplete#context()) || (mode() isnot# 'i')
|
||||
return 1
|
||||
endif
|
||||
|
||||
" something selected by user, do not refresh the menu
|
||||
if asyncomplete#menu_selected()
|
||||
return 0
|
||||
endif
|
||||
|
||||
setlocal completeopt-=longest
|
||||
setlocal completeopt+=menuone
|
||||
setlocal completeopt-=menu
|
||||
if &completeopt !~# 'noinsert\|noselect'
|
||||
setlocal completeopt+=noselect
|
||||
endif
|
||||
|
||||
call complete(a:startcol, a:matches)
|
||||
endfunction
|
||||
|
||||
function! s:complete_timeout(timer) abort
|
||||
" finished, clean variable
|
||||
unlet! s:complete_timer
|
||||
if s:complete_timer_ctx != asyncomplete#context()
|
||||
return
|
||||
endif
|
||||
call s:python_cm_complete_timeout(s:sources, s:complete_timer_ctx)
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#menu_selected() abort
|
||||
" when the popup menu is visible, v:completed_item will be the
|
||||
" current_selected item
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
-- LICENSE
|
||||
--
|
||||
-- This software is dual-licensed to the public domain and under the following
|
||||
-- license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
-- publish, and distribute this file as you see fit.
|
||||
|
||||
-- VERSION 0.1.0
|
||||
|
||||
-- Author: Forrest Smith (github.com/forrestthewoods/lib_fts)
|
||||
-- Translated to Lua by Blake Mealey (github.com/blake-mealey)
|
||||
|
||||
local module = {}
|
||||
|
||||
-- Returns true if each character in pattern is found sequentially within str
|
||||
function module.fuzzy_match_simple(pattern, str)
|
||||
local patternIdx = 1
|
||||
local strIdx = 1
|
||||
local patternLength = #pattern
|
||||
local strLength = #str
|
||||
|
||||
while (patternIdx <= patternLength and strIdx <= strLength) do
|
||||
local patternChar = pattern:sub(patternIdx, patternIdx):lower()
|
||||
local strChar = str:sub(strIdx, strIdx):lower()
|
||||
if patternChar == strChar then
|
||||
patternIdx = patternIdx + 1
|
||||
end
|
||||
strIdx = strIdx + 1
|
||||
end
|
||||
|
||||
return patternLength ~= 0 and strLength ~= 0 and (patternIdx - 1) == patternLength
|
||||
end
|
||||
|
||||
-- Returns [bool, score, matchedIndices]
|
||||
-- bool: true if each character in pattern is found sequentially within str
|
||||
-- score: integer; higher is better match. Value has no intrinsic meaning. Range localies with pattern.
|
||||
-- Can only compare scores with same search pattern.
|
||||
-- matchedIndices: the indices of characters that were matched in str
|
||||
function module.fuzzy_match(pattern, str)
|
||||
|
||||
-- Score consts
|
||||
local adjacency_bonus = 5 -- bonus for adjacent matches
|
||||
local separator_bonus = 10 -- bonus if match occurs after a separator
|
||||
local camel_bonus = 10 -- bonus if match is uppercase and prev is lower
|
||||
local leading_letter_penalty = -3 -- penalty applied for every letter in str before the first match
|
||||
local max_leading_letter_penalty = -9 -- maximum penalty for leading letters
|
||||
local unmatched_letter_penalty = -1 -- penalty for every letter that doesn't matter
|
||||
|
||||
-- Loop localiables
|
||||
local score = 0
|
||||
local patternIdx = 1
|
||||
local patternLength = #pattern
|
||||
local strIdx = 1
|
||||
local strLength = #str
|
||||
local prevMatched = false
|
||||
local prevLower = false
|
||||
local prevSeparator = true -- true so if first letter match gets separator bonus
|
||||
|
||||
-- Use "best" matched letter if multiple string letters match the pattern
|
||||
local bestLetter = nil
|
||||
local bestLower = nil
|
||||
local bestLetterIdx = nil
|
||||
local bestLetterScore = 0
|
||||
|
||||
local matchedIndices = {}
|
||||
|
||||
-- Loop over strings
|
||||
while (strIdx <= strLength) do
|
||||
local patternChar = patternIdx <= patternLength and pattern:sub(patternIdx, patternIdx) or nil
|
||||
local strChar = str:sub(strIdx, strIdx)
|
||||
|
||||
local patternLower = patternChar and patternChar:lower() or nil
|
||||
local strLower = strChar:lower()
|
||||
local strUpper = strChar:upper()
|
||||
|
||||
local nextMatch = patternChar and patternLower == strLower
|
||||
local rematch = bestLetter and bestLower == strLower
|
||||
|
||||
local advanced = nextMatch and bestLetter
|
||||
local patternRepeat = bestLetter and patternChar and bestLower == patternLower
|
||||
if advanced or patternRepeat then
|
||||
score = score + bestLetterScore
|
||||
table.insert(matchedIndices, bestLetterIdx)
|
||||
bestLetter = nil
|
||||
bestLower = nil
|
||||
bestLetterIdx = nil
|
||||
bestLetterScore = 0
|
||||
end
|
||||
|
||||
if nextMatch or rematch then
|
||||
local newScore = 0
|
||||
|
||||
-- Apply penalty for each letter before the first pattern match
|
||||
-- Note: std::max because penalties are negative values. So max is smallest penalty.
|
||||
if patternIdx == 0 then
|
||||
local penalty = math.max(strIdx * leading_letter_penalty, max_leading_letter_penalty)
|
||||
score = score + penalty
|
||||
end
|
||||
|
||||
-- Apply bonus for consecutive bonuses
|
||||
if prevMatched then
|
||||
newScore = newScore + adjacency_bonus
|
||||
end
|
||||
|
||||
-- Apply bonus for matches after a separator
|
||||
if prevSeparator then
|
||||
newScore = newScore + separator_bonus
|
||||
end
|
||||
|
||||
-- Apply bonus across camel case boundaries. Includes "clever" isLetter check.
|
||||
if prevLower and strChar == strUpper and strLower ~= strUpper then
|
||||
newScore = newScore + camel_bonus
|
||||
end
|
||||
|
||||
-- Update patter index IFF the next pattern letter was matched
|
||||
if nextMatch then
|
||||
patternIdx = patternIdx + 1
|
||||
end
|
||||
|
||||
-- Update best letter in str which may be for a "next" letter or a "rematch"
|
||||
if newScore >= bestLetterScore then
|
||||
|
||||
-- Apply penalty for now skipped letter
|
||||
if bestLetter then
|
||||
score = score + unmatched_letter_penalty
|
||||
end
|
||||
|
||||
bestLetter = strChar
|
||||
bestLower = bestLetter:lower()
|
||||
bestLetterIdx = strIdx
|
||||
bestLetterScore = newScore
|
||||
end
|
||||
|
||||
prevMatched = true
|
||||
else
|
||||
score = score + unmatched_letter_penalty
|
||||
prevMatched = false
|
||||
end
|
||||
|
||||
-- Includes "clever" isLetter check.
|
||||
prevLower = strChar == strLower and strLower ~= strUpper
|
||||
prevSeparator = strChar == '_' or strChar == ' '
|
||||
|
||||
strIdx = strIdx + 1
|
||||
end
|
||||
|
||||
-- Apply score for last match
|
||||
if bestLetter then
|
||||
score = score + bestLetterScore
|
||||
table.insert(matchedIndices, bestLetterIdx)
|
||||
end
|
||||
|
||||
local matched = patternIdx - 1 == patternLength
|
||||
return matched, score, matchedIndices
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -3,6 +3,8 @@ if exists('g:asyncomplete_loaded')
|
||||
endif
|
||||
let g:asyncomplete_loaded = 1
|
||||
|
||||
let s:has_lua = has('lua') || has('neovim-0.2.2')
|
||||
|
||||
if get(g:, 'asyncomplete_enable_for_all', 1)
|
||||
augroup asyncomplete_enable
|
||||
au!
|
||||
@@ -10,10 +12,14 @@ if get(g:, 'asyncomplete_enable_for_all', 1)
|
||||
augroup END
|
||||
endif
|
||||
|
||||
let g:asyncomplete_min_chars = get(g:, 'asyncomplete_min_chars', 1)
|
||||
let g:asyncomplete_auto_popup = get(g:, 'asyncomplete_auto_popup', 1)
|
||||
let g:asyncomplete_completion_delay = get(g:, 'asyncomplete_completion_delay', 100)
|
||||
let g:asyncomplete_log_file = get(g:, 'asyncomplete_log_file', '')
|
||||
let g:asyncomplete_remove_duplicates = get(g:, 'asyncomplete_remove_duplicates', 0)
|
||||
let g:asyncomplete_smart_completion = get(g:, 'asyncomplete_smart_completion', 0) " s:has_lua && exists('##TextChangedP')
|
||||
let g:asyncomplete_default_refresh_pattern = get(g:, 'asyncomplete_default_refresh_pattern', '\(\k\+$\|\.$\|:$\)')
|
||||
let g:asyncomplete_normalize_completion_items = get(g:, 'asyncomplete_normalize_completion_items', 0)
|
||||
|
||||
" Setting it to true may slow/hang vim especially on slow are sources such as asyncomplete-lsp.vim
|
||||
" use asyncomplete_force_refersh to retrive the latest autocomplete results instead.
|
||||
@@ -21,3 +27,13 @@ let g:asyncomplete_force_refresh_on_context_changed = get(g:, 'asyncomplete_forc
|
||||
|
||||
" imap <c-space> <Plug>(asyncomplete_force_refresh)
|
||||
inoremap <silent> <expr> <Plug>(asyncomplete_force_refresh) asyncomplete#force_refresh()
|
||||
|
||||
function! s:init_lua() abort
|
||||
lua << EOF
|
||||
asyncomplete = {}
|
||||
EOF
|
||||
endfunction
|
||||
|
||||
if s:has_lua
|
||||
call s:init_lua()
|
||||
endif
|
||||
|
||||
Reference in New Issue
Block a user