mirror of
https://github.com/prabirshrestha/asyncomplete.vim.git
synced 2025-12-14 20:35:41 +01:00
Clear matches and enable closing popup menu (#230)
* Remove "skip popup" related logic * Fix matches not getting cleared Before this commit we don't clear the matches when the text under the cursor is no longer suitable for completion. This causes e.g. removing everything using <BS> does not close the completion menu. * Allow closing popup menu Closing popup menu calls completion itself, making it impossible to be successful. We used to have a "skip popup" logic to fix this. It was removed 2 commits ago for being too confusing. To fix this a check is adapted to make sure that completion is only called when the completion context has changed. Since closing popup menu does not change the completion context, it no longer calls completion, and therefore can succeed. Previously this check lets us not complete when we move to a different line we are in insert mode, through for example <CR> or <BS>, but actually we are not afraid of this. When we enter a newline, we don't complete anyway since the refresh pattern is not matched, and when we backspace to a previous line, and the line happens to end with something that will trigger a completion, why do we not want to trigger it? * Also compare completion base to determine context change It is possible that in some situation changing the text under the cursor does not change the position of the cursor, so we also compare the completion base to determine whether the context has changed.
This commit is contained in:
@@ -143,7 +143,6 @@ function! s:on_insert_enter() abort
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_leave() abort
|
||||
call s:disable_popup_skip()
|
||||
let s:matches = {}
|
||||
if exists('s:update_pum_timer')
|
||||
call timer_stop(s:update_pum_timer)
|
||||
@@ -235,22 +234,6 @@ function! s:update_trigger_characters() abort
|
||||
call asyncomplete#log('core', 'trigger characters for buffer', bufnr('%'), b:asyncomplete_triggers)
|
||||
endfunction
|
||||
|
||||
function! s:enable_popup_skip() abort
|
||||
let s:skip_popup = 1
|
||||
endfunction
|
||||
|
||||
function! s:disable_popup_skip() abort
|
||||
let s:skip_popup = 0
|
||||
endfunction
|
||||
|
||||
function! s:should_skip_popup() abort
|
||||
if get(s:, 'skip_popup', 0)
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:should_skip() abort
|
||||
if mode() isnot# 'i' || !get(b:, 'asyncomplete_enable', 0)
|
||||
return 1
|
||||
@@ -260,12 +243,10 @@ function! s:should_skip() abort
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#close_popup() abort
|
||||
call s:enable_popup_skip()
|
||||
return pumvisible() ? "\<C-y>" : ''
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#cancel_popup() abort
|
||||
call s:enable_popup_skip()
|
||||
return pumvisible() ? "\<C-e>" : ''
|
||||
endfunction
|
||||
|
||||
@@ -286,43 +267,36 @@ function! s:on_change() abort
|
||||
endif
|
||||
|
||||
let l:ctx = asyncomplete#context()
|
||||
let l:startcol = l:ctx['col']
|
||||
let l:last_char = l:ctx['typed'][l:startcol - 2] " col is 1-indexed, but str 0-indexed
|
||||
|
||||
let l:sources_to_notify = {}
|
||||
|
||||
" match sources based on the last character if it is a trigger character
|
||||
if has_key(b:asyncomplete_triggers, l:last_char)
|
||||
" TODO: also check for multiple chars instead of just last chars for
|
||||
" languages such as cpp which uses -> and ::
|
||||
for l:source_name in keys(b:asyncomplete_triggers[l:last_char])
|
||||
if !has_key(s:matches, l:source_name) || s:matches[l:source_name]['ctx']['lnum'] != l:ctx['lnum'] || s:matches[l:source_name]['startcol'] != l:startcol
|
||||
let l:sources_to_notify[l:source_name] = 1
|
||||
let s:matches[l:source_name] = { 'startcol': l:startcol, 'status': 'idle', 'items': [], 'refresh': 0, 'ctx': l:ctx }
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
" loop left and find the start of the word and set it as the startcol for the source instead of refresh_pattern
|
||||
let l:last_char = l:ctx['typed'][l:ctx['col'] - 2] " col is 1-indexed, but str 0-indexed
|
||||
let l:triggered_sources = get(b:asyncomplete_triggers, l:last_char, {})
|
||||
let l:refresh_pattern = get(b:, 'asyncomplete_refresh_pattern', '\(\k\+$\)')
|
||||
let [l:_, l:startidx, l:endidx] = asyncomplete#utils#matchstrpos(l:ctx['typed'], l:refresh_pattern)
|
||||
let l:startcol = l:startidx + 1
|
||||
let l:typed_len = l:endidx - l:startidx
|
||||
|
||||
if l:startidx > -1
|
||||
if s:should_skip_popup() | return | endif
|
||||
for l:source_name in b:asyncomplete_active_sources
|
||||
if l:typed_len >= s:get_min_chars(l:source_name) && !has_key(l:sources_to_notify, l:source_name)
|
||||
if has_key(s:matches, l:source_name) && s:matches[l:source_name]['ctx']['lnum'] ==# l:ctx['lnum'] && s:matches[l:source_name]['startcol'] ==# l:startcol
|
||||
continue
|
||||
endif
|
||||
let l:sources_to_notify[l:source_name] = 1
|
||||
for l:source_name in b:asyncomplete_active_sources
|
||||
" match sources based on the last character if it is a trigger character
|
||||
" TODO: also check for multiple chars instead of just last chars for
|
||||
" languages such as cpp which uses -> and ::
|
||||
if has_key(triggered_sources, l:source_name)
|
||||
let l:startcol = l:ctx['col']
|
||||
elseif l:startidx > -1 && l:endidx - l:startidx >= s:get_min_chars(l:source_name)
|
||||
let l:startcol = l:startidx + 1 " col is 1-indexed, but str 0-indexed
|
||||
endif
|
||||
" here we use the existence of `l:startcol` to determine whether to
|
||||
" use this completion source. If `l:startcol` exists, we use the
|
||||
" source. If it does not exist, it means that we cannot get a
|
||||
" meaningful starting point for the current source, and this implies
|
||||
" that we cannot use this source for completion. Therefore, we remove
|
||||
" the matches from the source.
|
||||
if exists('l:startcol')
|
||||
if !has_key(s:matches, l:source_name) || s:matches[l:source_name]['ctx']['lnum'] !=# l:ctx['lnum'] || s:matches[l:source_name]['startcol'] !=# l:startcol
|
||||
let s:matches[l:source_name] = { 'startcol': l:startcol, 'status': 'idle', 'items': [], 'refresh': 0, 'ctx': l:ctx }
|
||||
endif
|
||||
endfor
|
||||
else
|
||||
call s:disable_popup_skip()
|
||||
endif
|
||||
else
|
||||
if has_key(s:matches, l:source_name)
|
||||
unlet s:matches[l:source_name]
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
call s:trigger(l:ctx)
|
||||
call s:update_pum()
|
||||
@@ -384,7 +358,6 @@ function! asyncomplete#force_refresh() abort
|
||||
endfunction
|
||||
|
||||
function! asyncomplete#_force_refresh() abort
|
||||
call s:disable_popup_skip()
|
||||
if s:should_skip() | return | endif
|
||||
|
||||
let l:ctx = asyncomplete#context()
|
||||
@@ -419,7 +392,6 @@ endfunction
|
||||
|
||||
function! s:recompute_pum(...) abort
|
||||
if s:should_skip() | return | endif
|
||||
if s:should_skip_popup() | return | endif
|
||||
|
||||
" TODO: add support for remote recomputation of complete items,
|
||||
" Ex: heavy computation such as fuzzy search can happen in a python thread
|
||||
@@ -504,7 +476,6 @@ endfunction
|
||||
function! asyncomplete#preprocess_complete(ctx, items)
|
||||
" TODO: handle cases where this is called asynchronsouly. Currently not supported
|
||||
if s:should_skip() | return | endif
|
||||
if s:should_skip_popup() | return | endif
|
||||
|
||||
call asyncomplete#log('core', 'asyncomplete#preprocess_complete')
|
||||
|
||||
|
||||
@@ -32,20 +32,19 @@ function! s:unregister(obj, cb) abort
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_enter() abort
|
||||
let s:previous_position = getcurpos()
|
||||
let l:context = asyncomplete#context()
|
||||
let s:previous_context = {
|
||||
\ 'lnum': l:context['lnum'],
|
||||
\ 'col': l:context['col'],
|
||||
\ 'typed': l:context['typed'],
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_leave() abort
|
||||
unlet s:previous_position
|
||||
unlet s:previous_context
|
||||
endfunction
|
||||
|
||||
function! s:on_text_changed_i() abort
|
||||
let l:ctx = asyncomplete#context()
|
||||
let l:startcol = l:ctx['col']
|
||||
let l:last_char = l:ctx['typed'][l:startcol - 2] " col is 1-indexed, but str 0-indexed
|
||||
if exists('b:asyncomplete_triggers') && has_key(b:asyncomplete_triggers, l:last_char)
|
||||
let s:previous_position = getcurpos()
|
||||
endif
|
||||
call s:maybe_notify_on_change()
|
||||
endfunction
|
||||
|
||||
@@ -54,10 +53,24 @@ function! s:on_text_changed_p() abort
|
||||
endfunction
|
||||
|
||||
function! s:maybe_notify_on_change() abort
|
||||
" enter to new line or backspace to previous line shouldn't cause change trigger
|
||||
let l:previous_position = s:previous_position
|
||||
let s:previous_position = getcurpos()
|
||||
if l:previous_position[1] ==# getcurpos()[1]
|
||||
" We notify on_change callbacks only when the cursor position
|
||||
" has changed.
|
||||
" Unfortunatelly we need this check because in insert mode it
|
||||
" is possible to have TextChangedI triggered when the completion
|
||||
" context is not changed at all: When we close the completion
|
||||
" popup menu via <C-e> or <C-y>. If we still let on_change
|
||||
" do the completion in this case we never close the menu.
|
||||
" Vim doesn't allow programmatically changing buffer content
|
||||
" in insert mode, so by comparing the cursor's position and the
|
||||
" completion base we know whether the context has changed.
|
||||
let l:context = asyncomplete#context()
|
||||
let l:previous_context = s:previous_context
|
||||
let s:previous_context = {
|
||||
\ 'lnum': l:context['lnum'],
|
||||
\ 'col': l:context['col'],
|
||||
\ 'typed': l:context['typed'],
|
||||
\ }
|
||||
if l:previous_context !=# s:previous_context
|
||||
for l:Cb in s:callbacks
|
||||
call l:Cb()
|
||||
endfor
|
||||
|
||||
Reference in New Issue
Block a user