From 258ef8972c42d1581a0f674eb19d475af68ba453 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sun, 11 Feb 2018 21:50:09 -0800 Subject: [PATCH 01/16] add support for fuzzy search using lua and use TextChangedP --- autoload/asyncomplete.vim | 537 +++++++++++++++++------------------ autoload/fts_fuzzy_match.lua | 156 ++++++++++ plugin/asyncomplete.vim | 13 + 3 files changed, 432 insertions(+), 274 deletions(-) create mode 100644 autoload/fts_fuzzy_match.lua diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index f5c0218..4b61621 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -2,20 +2,25 @@ 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 + +if !exists('##TextChangedP') + echohl ErrorMsg + echomsg 'Vim/Neovim compiled with TextChangedP (vim-patch:8.0.1494) required for asyncomplete.vim.' + echohl NONE + call asyncomplete#log('vim/neovim compiled with TextChangedP (vim-patch:8.0.1494) required.') finish endif let s:sources = {} -let s:change_timer = -1 -let s:last_tick = [] -let s:has_popped_up = 0 -let s:complete_timer_ctx = {} +let s:matches = {} let s:already_setup = 0 +let s:startcol = -1 +let s:candidates = [] +let s:script_path = expand(':p:h') +let s:supports_smart_completion = has('lua') && exists('##TextChangedP') function! asyncomplete#log(...) abort if !empty(g:asyncomplete_log_file) @@ -36,17 +41,38 @@ function! asyncomplete#enable_for_buffer() abort endif let b:asyncomplete_enable = 1 - augroup ayncomplete - autocmd! * - autocmd InsertEnter call s:python_cm_insert_enter() - autocmd InsertEnter call s:change_tick_start() - autocmd InsertLeave 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 call s:check_changes() - augroup END + if exists('##TextChangedP') + augroup ayncomplete + autocmd! * + autocmd InsertEnter call s:on_insert_enter() + autocmd InsertLeave call s:on_insert_leave() + autocmd TextChangedI call s:on_text_changed() + autocmd TextChangedP call s:on_text_changed() + 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! asyncomplete#register_source(info) abort @@ -57,7 +83,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 +106,223 @@ 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 + + let l:typed = a:ctx['typed'] + + for l:source_name in s:get_active_sources_for_buffer() + let l:refresh = a:force + if !a:force + if has_key(s:matches, l:source_name) && s:matches[l:source_name]['incomplete'] + " force refresh since the results are incomplete + let l:refresh = 1 + else + " refresh only if the prefix changed + let l:source = s:sources[l:source_name] + if has_key(l:source, 'refresh_pattern') + let l:refresh_pattern = l:source['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 + + let l:matchpos = s:matchstrpos(l:typed, l:refresh_pattern) + 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 || (!pumvisible() && l:typed_len >= l:min_chars) + let l:refresh = 1 + 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! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort + let l:incomplete = a:0 > 0 ? a:1 : 0 + call asyncomplete#log('core#complete', a:name, a:startcol, len(a:candidates), l:incomplete) + + " handle context_changed scenarios + + let s:matches[a:name] = { + \ 'startcol': a:startcol, + \ 'incomplete': l:incomplete, + \ 'candidates': a:candidates, + \ 'ctx': a:ctx, + \ } + + " 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: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) + let l:startcols += [l:item['startcol']] + 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'] + let l:curcandidates = l:info['candidates'] + + if l:curstartcol > l:ctx['col'] + " wrong start col + continue + endif + + let l:normalizedcurcandidates = [] + for l:item in l:curcandidates + 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:normalizedcurcandidates += [l:e] + endfor + + let l:candidates += l:normalizedcurcandidates + 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', a:ctx['typed'], a:startcol, col('.'), l:prefix) + + " 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 +330,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 +351,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 +393,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 +409,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 +417,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 diff --git a/autoload/fts_fuzzy_match.lua b/autoload/fts_fuzzy_match.lua new file mode 100644 index 0000000..5ae86c8 --- /dev/null +++ b/autoload/fts_fuzzy_match.lua @@ -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 diff --git a/plugin/asyncomplete.vim b/plugin/asyncomplete.vim index d6ab234..49b4d01 100644 --- a/plugin/asyncomplete.vim +++ b/plugin/asyncomplete.vim @@ -10,10 +10,13 @@ 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', has('lua') && exists('##TextChangedP')) +let g:asyncomplete_default_refresh_pattern = get(g:, 'asyncomplete_default_refresh_pattern', '\(\k\+$\|\.$\|:$\)') " 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 +24,13 @@ let g:asyncomplete_force_refresh_on_context_changed = get(g:, 'asyncomplete_forc " imap (asyncomplete_force_refresh) inoremap (asyncomplete_force_refresh) asyncomplete#force_refresh() + +function! s:init_lua() abort + lua << EOF + asyncomplete = {} +EOF +endfunction + +if has('lua') + call s:init_lua() +endif From 2b21494987e21e118598cfa0eef63a1541ffcf00 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sun, 11 Feb 2018 23:16:23 -0800 Subject: [PATCH 02/16] add support for older vim and neovim when TextChangedP is not supported --- autoload/asyncomplete.vim | 55 +++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 4b61621..4bdc709 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -6,17 +6,11 @@ if !has('timers') finish endif -if !exists('##TextChangedP') - echohl ErrorMsg - echomsg 'Vim/Neovim compiled with TextChangedP (vim-patch:8.0.1494) required for asyncomplete.vim.' - echohl NONE - call asyncomplete#log('vim/neovim compiled with TextChangedP (vim-patch:8.0.1494) 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:startcol = -1 let s:candidates = [] let s:script_path = expand(':p:h') @@ -49,6 +43,19 @@ function! asyncomplete#enable_for_buffer() abort autocmd TextChangedI call s:on_text_changed() autocmd TextChangedP call s:on_text_changed() augroup END + else + augroup ayncomplete + autocmd! * + autocmd InsertEnter call s:on_insert_enter() + autocmd InsertLeave call s:on_insert_leave() + autocmd InsertEnter call s:change_tick_start() + autocmd InsertLeave 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 call s:check_changes() + augroup END endif endfunction @@ -75,6 +82,38 @@ function! s:on_text_changed() abort 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: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 if has_key(s:sources, a:info['name']) return From 1e1a56a03c5c3253307c67e1e6de454404bae9e4 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sun, 11 Feb 2018 23:23:47 -0800 Subject: [PATCH 03/16] add support for g:asyncomplete_auto_popup --- autoload/asyncomplete.vim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 4bdc709..8a6ea16 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -158,6 +158,12 @@ function! s:notify_sources_to_refresh(ctx, force) abort 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() From 17f933773635f8d07ee946efb114d89361bedf1b Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Fri, 16 Feb 2018 07:48:56 -0800 Subject: [PATCH 04/16] add missing s:change_tick_stop --- autoload/asyncomplete.vim | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 8a6ea16..56c8fb1 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -96,6 +96,15 @@ function! s:change_tick_start() abort 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 From 3ee5f249ea59d35cb86d084650b4fa121300e407 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Thu, 22 Feb 2018 23:19:20 -0800 Subject: [PATCH 05/16] optimize completion items normalizations * enabled by default * let g:asyncomplete_normalize_completion_items = 0 " to disable * also support per source normalization using normalize_completion_items --- autoload/asyncomplete.vim | 46 ++++++++++++++++++++++++++------------- plugin/asyncomplete.vim | 1 + 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 56c8fb1..19aa4d8 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -225,7 +225,7 @@ function! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort let s:matches[a:name] = { \ 'startcol': a:startcol, \ 'incomplete': l:incomplete, - \ 'candidates': a:candidates, + \ 'candidates': s:normalize_candidates(a:name, a:candidates), \ 'ctx': a:ctx, \ } @@ -237,6 +237,35 @@ function! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort 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 + let 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 + let 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 @@ -268,26 +297,13 @@ function! s:compute_candidates(...) abort for l:name in l:sources let l:info = s:matches[l:name] let l:curstartcol = l:info['startcol'] - let l:curcandidates = l:info['candidates'] if l:curstartcol > l:ctx['col'] " wrong start col continue endif - let l:normalizedcurcandidates = [] - for l:item in l:curcandidates - 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:normalizedcurcandidates += [l:e] - endfor - - let l:candidates += l:normalizedcurcandidates + let l:candidates += l:info['candidates'] endfor let s:startcol = l:startcol diff --git a/plugin/asyncomplete.vim b/plugin/asyncomplete.vim index 49b4d01..a0d75cf 100644 --- a/plugin/asyncomplete.vim +++ b/plugin/asyncomplete.vim @@ -17,6 +17,7 @@ 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', 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. From c8d2f7a3fc63badfc776fcb655a49d4062a533a4 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 24 Feb 2018 20:32:25 -0800 Subject: [PATCH 06/16] fixed normalize_candidates when string --- autoload/asyncomplete.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 19aa4d8..deea87d 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -248,14 +248,14 @@ function! s:normalize_candidates(name, candidates) abort else let l:e = copy(l:item) endif - let l:normalizedcurcandidates += [l:e] + 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 - let l:normalizedcurcandidates += { 'word': l:item } + call add(l:normalizedcurcandidates, { 'word': l:item }) endfor else call asyncomplete#log('s:normalize_candidates', 'ignoring candidates normalization', a:name) From 9a1137ba8d2adbf26b2cfbe174fec5794d8e7843 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 24 Feb 2018 23:39:41 -0800 Subject: [PATCH 07/16] fix fuzzy --- autoload/asyncomplete.vim | 90 ++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index deea87d..1775581 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -177,45 +177,87 @@ function! s:notify_sources_to_refresh(ctx, force) abort 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) && s:matches[l:source_name]['incomplete'] - " force refresh since the results are incomplete - let l:refresh = 1 - else - " refresh only if the prefix changed - let l:source = s:sources[l:source_name] - if has_key(l:source, 'refresh_pattern') - let l:refresh_pattern = l:source['refresh_pattern'] - if (type(l:refresh_pattern) != type('')) - let l:refresh_pattern = l:refresh_pattern() - endif + if has_key(s:matches, l:source_name) + if s:matches[l:source_name]['incomplete'] + let l:refresh = 1 else - let l:refresh_pattern = g:asyncomplete_default_refresh_pattern + 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 - - let l:matchpos = s:matchstrpos(l:typed, l:refresh_pattern) + 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 || (!pumvisible() && l:typed_len >= l: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 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) + 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 call asyncomplete#log('core#complete', a:name, a:startcol, len(a:candidates), l:incomplete) @@ -223,12 +265,18 @@ function! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort " handle context_changed scenarios 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) @@ -276,7 +324,9 @@ function! s:compute_candidates(...) abort " find mimnimal startcol from all matches let l:startcols = [] for l:item in values(s:matches) - let l:startcols += [l:item['startcol']] + if !l:item['pending'] + let l:startcols += [l:item['startcol']] + endif endfor let l:startcol = min(l:startcols) @@ -329,7 +379,7 @@ function! s:update_pum(ctx, startcol, candidates) abort let l:prefix = a:ctx['typed'][a:startcol-1 : col('.') - 1] - call asyncomplete#log('update pum', a:ctx['typed'], a:startcol, col('.'), l:prefix) + 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 From 6a1a8285a0eadd488821c0ecfae8d7d8bab6f4f6 Mon Sep 17 00:00:00 2001 From: Donnie West Date: Sat, 3 Mar 2018 12:13:55 -0600 Subject: [PATCH 08/16] Add base and startcol to ctx (#50) * Add base and startcol to ctx * Copy context, check if matchpos exists before setting --- autoload/asyncomplete.vim | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 1775581..31d7297 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -235,8 +235,20 @@ function! s:notify_sources_to_refresh(ctx, force) abort 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) + let l:ctx = copy(a:ctx) + let l:typed = l:ctx['typed'] + if !exits('l:matchpos') + 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 + endif + let l:startcol = len(l:typed[:len(l:typed) - l:typed_len -1]) + let l:base = l:ctx['typed'][l:startcol-1:] + let l:ctx['startcol'] = l:startcol + let l:ctx['base'] = l:base + call asyncomplete#log('core.s:notify_sources_to_refresh', 'completor()', l:source_name, l:ctx) + call s:sources[l:source_name].completor(s:sources[l:source_name], l:ctx) catch call asyncomplete#log('core.s:notify_sources_to_refresh', 'completor()', 'error', v:exception) continue From 5952002d7513609370383ac87e278ba982de5b7f Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 3 Mar 2018 10:39:03 -0800 Subject: [PATCH 09/16] fix spelling exists --- autoload/asyncomplete.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 31d7297..dc496a0 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -237,7 +237,7 @@ function! s:notify_sources_to_refresh(ctx, force) abort try let l:ctx = copy(a:ctx) let l:typed = l:ctx['typed'] - if !exits('l:matchpos') + if !exists('l:matchpos') 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] From cbe7fcc6fad0953fc05732d2f1203172eb7da51c Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 3 Mar 2018 10:42:13 -0800 Subject: [PATCH 10/16] update README.md to use au User asyncomplete_setup to regsiter sources fixes #7 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb51124..1570ee6 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ function s:completor(opt, ctx) call mylanguage#get_async_completions({candidates, startcol -> asyncomplete#complete(a:opt['name'], a:ctx, startcol, candidates) }) endfunction -call asyncomplete#register_source({ +au User asyncomplete_setup call asyncomplete#register_source({ \ 'name': 'mylanguage', \ 'whitelist': [*], \ 'completor': function('s:completor'), From 8074979532e8a25432bacf6864b37cc0589932e1 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 3 Mar 2018 13:31:21 -0800 Subject: [PATCH 11/16] handle partial context changed scenarios --- autoload/asyncomplete.vim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index dc496a0..5714849 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -272,9 +272,14 @@ endfunction function! asyncomplete#complete(name, ctx, startcol, candidates, ...) abort let l:incomplete = a:0 > 0 ? a:1 : 0 - call asyncomplete#log('core#complete', a:name, a:startcol, len(a:candidates), l:incomplete) + 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 + " 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, From 1c874955499e59703a20b5e73809589144d13fee Mon Sep 17 00:00:00 2001 From: prabirshrestha Date: Sat, 3 Mar 2018 19:04:59 -0800 Subject: [PATCH 12/16] detect neovim lua --- autoload/asyncomplete.vim | 3 ++- plugin/asyncomplete.vim | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 5714849..f94f651 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -14,7 +14,8 @@ let s:last_tick = [] let s:startcol = -1 let s:candidates = [] let s:script_path = expand(':p:h') -let s:supports_smart_completion = has('lua') && exists('##TextChangedP') +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) diff --git a/plugin/asyncomplete.vim b/plugin/asyncomplete.vim index a0d75cf..4480205 100644 --- a/plugin/asyncomplete.vim +++ b/plugin/asyncomplete.vim @@ -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! @@ -15,7 +17,7 @@ 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', has('lua') && exists('##TextChangedP')) +let g:asyncomplete_smart_completion = get(g:, 'asyncomplete_smart_completion', 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) @@ -32,6 +34,6 @@ function! s:init_lua() abort EOF endfunction -if has('lua') +if s:has_lua call s:init_lua() endif From 35b5e575f2f5d8da182f71ef2da90c9b7aeabd47 Mon Sep 17 00:00:00 2001 From: Kerem Goksel Date: Tue, 6 Mar 2018 18:09:11 -0800 Subject: [PATCH 13/16] Fixes typo in the initialization of force refresh variable (#51) --- plugin/asyncomplete.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/asyncomplete.vim b/plugin/asyncomplete.vim index 4480205..b517556 100644 --- a/plugin/asyncomplete.vim +++ b/plugin/asyncomplete.vim @@ -23,7 +23,7 @@ let g:asyncomplete_normalize_completion_items = get(g:, 'asyncomplete_normalize_ " 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. -let g:asyncomplete_force_refresh_on_context_changed = get(g:, 'asyncomplete_force_referesh_on_context_changed', 0) +let g:asyncomplete_force_refresh_on_context_changed = get(g:, 'asyncomplete_force_refresh_on_context_changed', 0) " imap (asyncomplete_force_refresh) inoremap (asyncomplete_force_refresh) asyncomplete#force_refresh() From b5bf763eb3464446dd63dbbf9a561d53c2ae4ac3 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Wed, 7 Mar 2018 20:12:15 -0800 Subject: [PATCH 14/16] Revert "fix spelling exists" This reverts commit 5952002d7513609370383ac87e278ba982de5b7f. --- autoload/asyncomplete.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index f94f651..08ed230 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -238,7 +238,7 @@ function! s:notify_sources_to_refresh(ctx, force) abort try let l:ctx = copy(a:ctx) let l:typed = l:ctx['typed'] - if !exists('l:matchpos') + if !exits('l:matchpos') 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] From 545d07bcf69495efda0de25fe57ab2a522469e06 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Wed, 7 Mar 2018 20:12:54 -0800 Subject: [PATCH 15/16] Revert "Add base and startcol to ctx (#50)" This reverts commit 6a1a8285a0eadd488821c0ecfae8d7d8bab6f4f6. --- autoload/asyncomplete.vim | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/autoload/asyncomplete.vim b/autoload/asyncomplete.vim index 08ed230..a0ebeb0 100644 --- a/autoload/asyncomplete.vim +++ b/autoload/asyncomplete.vim @@ -236,20 +236,8 @@ function! s:notify_sources_to_refresh(ctx, force) abort endif if l:refresh try - let l:ctx = copy(a:ctx) - let l:typed = l:ctx['typed'] - if !exits('l:matchpos') - 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 - endif - let l:startcol = len(l:typed[:len(l:typed) - l:typed_len -1]) - let l:base = l:ctx['typed'][l:startcol-1:] - let l:ctx['startcol'] = l:startcol - let l:ctx['base'] = l:base - call asyncomplete#log('core.s:notify_sources_to_refresh', 'completor()', l:source_name, l:ctx) - call s:sources[l:source_name].completor(s:sources[l:source_name], l:ctx) + 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 From c9b27157257e749bd269d14e8f7688cdc5bc7718 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Thu, 8 Mar 2018 08:11:08 -0800 Subject: [PATCH 16/16] disable asyncomplete_smart_completion by default --- plugin/asyncomplete.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/asyncomplete.vim b/plugin/asyncomplete.vim index b517556..7ed8bcb 100644 --- a/plugin/asyncomplete.vim +++ b/plugin/asyncomplete.vim @@ -17,7 +17,7 @@ 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', s:has_lua && exists('##TextChangedP')) +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)