Close #1753 - Implement minimum viable integration with Deoplete

This commit is contained in:
w0rp 2019-04-23 21:26:16 +01:00
parent ce0b14979e
commit 01331266a8
No known key found for this signature in database
GPG key ID: 0FC1ECAA8C81CD83
10 changed files with 354 additions and 71 deletions

2
.gitignore vendored
View file

@ -3,6 +3,8 @@
# Ignore all hidden files everywhere. # Ignore all hidden files everywhere.
# Use `git add -f` to add hidden files. # Use `git add -f` to add hidden files.
.* .*
__pycache__
*.pyc
/doc/tags /doc/tags
/init.vim /init.vim
/test/ale-info-test-file /test/ale-info-test-file

View file

@ -26,7 +26,7 @@ features, including:
* Diagnostics (via Language Server Protocol linters) * Diagnostics (via Language Server Protocol linters)
* Go To Definition (`:ALEGoToDefinition`) * Go To Definition (`:ALEGoToDefinition`)
* Completion (`let g:ale_completion_enabled = 1` before ALE is loaded) * Completion (Built in completion support, or with Deoplete)
* Finding references (`:ALEFindReferences`) * Finding references (`:ALEFindReferences`)
* Hover information (`:ALEHover`) * Hover information (`:ALEHover`)
* Symbol search (`:ALESymbolSearch`) * Symbol search (`:ALESymbolSearch`)
@ -159,6 +159,18 @@ ALE offers some support for completion via hijacking of omnicompletion while you
type. All of ALE's completion information must come from Language Server type. All of ALE's completion information must come from Language Server
Protocol linters, or from `tsserver` for TypeScript. Protocol linters, or from `tsserver` for TypeScript.
ALE integrates with [Deoplete](https://github.com/Shougo/deoplete.nvim) as a
completion source, named `'ale'`. You can configure Deoplete to only use ALE as
the source of completion information, or mix it with other sources.
```vim
" Use ALE and also some plugin 'foobar' as completion sources for all code.
let g:deoplete#sources = {'_': ['ale', 'foobar']}
```
ALE also offers its own automatic completion support, which does not require any
other plugins, and can be enabled by changing a setting before ALE is loaded.
```vim ```vim
" Enable completion where available. " Enable completion where available.
" This setting must be set before ALE is loaded. " This setting must be set before ALE is loaded.

View file

@ -159,18 +159,20 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort
endfunction endfunction
function! s:ReplaceCompletionOptions() abort function! s:ReplaceCompletionOptions() abort
" Remember the old omnifunc value, if there is one. let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
" If we don't store an old one, we'll just never reset the option.
" This will stop some random exceptions from appearing. if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) " Remember the old omnifunc value, if there is one.
let b:ale_old_omnifunc = &l:omnifunc " If we don't store an old one, we'll just never reset the option.
" This will stop some random exceptions from appearing.
if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc)
let b:ale_old_omnifunc = &l:omnifunc
endif
let &l:omnifunc = 'ale#completion#OmniFunc'
endif endif
let &l:omnifunc = 'ale#completion#OmniFunc' if l:source is# 'ale-automatic'
let l:info = get(b:, 'ale_completion_info', {})
if !get(l:info, 'manual')
if !exists('b:ale_old_completeopt') if !exists('b:ale_old_completeopt')
let b:ale_old_completeopt = &l:completeopt let b:ale_old_completeopt = &l:completeopt
endif endif
@ -199,31 +201,49 @@ function! ale#completion#RestoreCompletionOptions() abort
endif endif
endfunction endfunction
function! ale#completion#GetCompletionPosition() abort
if !exists('b:ale_completion_info')
return 0
endif
let l:line = b:ale_completion_info.line
let l:column = b:ale_completion_info.column
let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype)
let l:up_to_column = getline(l:line)[: l:column - 2]
let l:match = matchstr(l:up_to_column, l:regex)
return l:column - len(l:match) - 1
endfunction
function! ale#completion#GetCompletionResult() abort
" Parse a new response if there is one.
if exists('b:ale_completion_response')
\&& exists('b:ale_completion_parser')
let l:response = b:ale_completion_response
let l:parser = b:ale_completion_parser
unlet b:ale_completion_response
unlet b:ale_completion_parser
let b:ale_completion_result = function(l:parser)(l:response)
endif
if exists('b:ale_completion_result')
return b:ale_completion_result
endif
return v:null
endfunction
function! ale#completion#OmniFunc(findstart, base) abort function! ale#completion#OmniFunc(findstart, base) abort
if a:findstart if a:findstart
let l:line = b:ale_completion_info.line return ale#completion#GetCompletionPosition()
let l:column = b:ale_completion_info.column
let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype)
let l:up_to_column = getline(l:line)[: l:column - 2]
let l:match = matchstr(l:up_to_column, l:regex)
return l:column - len(l:match) - 1
else else
" Parse a new response if there is one. let l:result = ale#completion#GetCompletionResult()
if exists('b:ale_completion_response')
\&& exists('b:ale_completion_parser')
let l:response = b:ale_completion_response
let l:parser = b:ale_completion_parser
unlet b:ale_completion_response
unlet b:ale_completion_parser
let b:ale_completion_result = function(l:parser)(l:response)
endif
call s:ReplaceCompletionOptions() call s:ReplaceCompletionOptions()
return get(b:, 'ale_completion_result', []) return l:result isnot v:null ? l:result : []
endif endif
endfunction endfunction
@ -239,7 +259,14 @@ function! ale#completion#Show(response, completion_parser) abort
" Replace completion options shortly before opening the menu. " Replace completion options shortly before opening the menu.
call s:ReplaceCompletionOptions() call s:ReplaceCompletionOptions()
call timer_start(0, {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}) let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
call timer_start(
\ 0,
\ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}
\)
endif
endfunction endfunction
function! s:CompletionStillValid(request_id) abort function! s:CompletionStillValid(request_id) abort
@ -249,7 +276,10 @@ function! s:CompletionStillValid(request_id) abort
\&& has_key(b:, 'ale_completion_info') \&& has_key(b:, 'ale_completion_info')
\&& b:ale_completion_info.request_id == a:request_id \&& b:ale_completion_info.request_id == a:request_id
\&& b:ale_completion_info.line == l:line \&& b:ale_completion_info.line == l:line
\&& b:ale_completion_info.column == l:column \&& (
\ b:ale_completion_info.column == l:column
\ || b:ale_completion_info.source is# 'deoplete'
\)
endfunction endfunction
function! ale#completion#ParseTSServerCompletions(response) abort function! ale#completion#ParseTSServerCompletions(response) abort
@ -519,12 +549,12 @@ endfunction
" This function can be used to manually trigger autocomplete, even when " This function can be used to manually trigger autocomplete, even when
" g:ale_completion_enabled is set to false " g:ale_completion_enabled is set to false
function! ale#completion#GetCompletions(manual) abort function! ale#completion#GetCompletions(source) abort
let [l:line, l:column] = getpos('.')[1:2] let [l:line, l:column] = getpos('.')[1:2]
let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
if !a:manual && empty(l:prefix) if a:source is# 'ale-automatic' && empty(l:prefix)
return return
endif endif
@ -537,8 +567,9 @@ function! ale#completion#GetCompletions(manual) abort
\ 'prefix': l:prefix, \ 'prefix': l:prefix,
\ 'conn_id': 0, \ 'conn_id': 0,
\ 'request_id': 0, \ 'request_id': 0,
\ 'manual': a:manual, \ 'source': a:source,
\} \}
unlet! b:ale_completion_result
let l:buffer = bufnr('') let l:buffer = bufnr('')
let l:Callback = function('s:OnReady') let l:Callback = function('s:OnReady')
@ -562,7 +593,7 @@ function! s:TimerHandler(...) abort
" When running the timer callback, we have to be sure that the cursor " When running the timer callback, we have to be sure that the cursor
" hasn't moved from where it was when we requested completions by typing. " hasn't moved from where it was when we requested completions by typing.
if s:timer_pos == [l:line, l:column] && ale#util#Mode() is# 'i' if s:timer_pos == [l:line, l:column] && ale#util#Mode() is# 'i'
call ale#completion#GetCompletions(0) call ale#completion#GetCompletions('ale-automatic')
endif endif
endfunction endfunction

View file

@ -321,40 +321,44 @@ servers. LSP linters can be used in combination with any other linter, and
will automatically connect to LSP servers when needed. ALE also supports will automatically connect to LSP servers when needed. ALE also supports
`tsserver` for TypeScript, which uses a different but very similar protocol. `tsserver` for TypeScript, which uses a different but very similar protocol.
ALE supports the following LSP/tsserver features:
1. Diagnostics/linting - Enabled via selecting linters as usual.
2. Completion
3. Go to definition
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
5.1 Completion *ale-completion* 5.1 Completion *ale-completion*
ALE offers limited support for automatic completion of code while you type. ALE offers support for automatic completion of code while you type.
Completion is only supported while at least one LSP linter is enabled. ALE Completion is only supported while at least one LSP linter is enabled. ALE
will only suggest symbols provided by the LSP servers. will only suggest symbols provided by the LSP servers.
Suggestions will be made while you type after completion is enabled. *ale-deoplete-integration*
Completion can be enabled by setting |g:ale_completion_enabled| to `1`. This
setting must be set to `1` before ALE is loaded. The delay for completion can ALE integrates with Deoplete for offering automatic completion data. ALE's
be configured with |g:ale_completion_delay|. ALE will only suggest so many completion source for Deoplete is named `'ale'`, and should enabled
possible matches for completion. The maximum number of items can be controlled automatically if Deoplete is enabled and configured correctly. Deoplete
with |g:ale_completion_max_suggestions|. integration should not be combined with ALE's own implementation.
ALE also offers its own completion implementation, which does not require any
other plugins. Suggestions will be made while you type after completion is
enabled. Completion can be enabled by setting |g:ale_completion_enabled| to
`1`. This setting must be set to `1` before ALE is loaded. The delay for
completion can be configured with |g:ale_completion_delay|.
ALE will only suggest so many possible matches for completion. The maximum
number of items can be controlled with |g:ale_completion_max_suggestions|.
If you don't like some of the suggestions you see, you can filter them out If you don't like some of the suggestions you see, you can filter them out
with |g:ale_completion_excluded_words| or |b:ale_completion_excluded_words|. with |g:ale_completion_excluded_words| or |b:ale_completion_excluded_words|.
The |ALEComplete| command can be used to show completion suggestions manually, The |ALEComplete| command can be used to show completion suggestions manually,
even when |g:ale_completion_enabled| is set to `0`. even when |g:ale_completion_enabled| is set to `0`. For manually requesting
completion information with Deoplete, consult Deoplete's documentation.
*ale-completion-completeopt-bug* *ale-completion-completeopt-bug*
Automatic completion replaces |completeopt| before opening the omnicomplete ALE Automatic completion implementation replaces |completeopt| before opening
menu with <C-x><C-o>. In some versions of Vim, the value set for the option the omnicomplete menu with <C-x><C-o>. In some versions of Vim, the value set
will not be respected. If you experience issues with Vim automatically for the option will not be respected. If you experience issues with Vim
inserting text while you type, set the following option in vimrc, and your automatically inserting text while you type, set the following option in
issues should go away. > vimrc, and your issues should go away. >
set completeopt=menu,menuone,preview,noselect,noinsert set completeopt=menu,menuone,preview,noselect,noinsert
< <

View file

@ -216,7 +216,7 @@ command! -bar ALEDocumentation :call ale#hover#ShowDocumentationAtCursor()
" Search for appearances of a symbol, such as a type name or function name. " Search for appearances of a symbol, such as a type name or function name.
command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>) command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>)
command! -bar ALEComplete :call ale#completion#GetCompletions(1) command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual')
" <Plug> mappings for commands " <Plug> mappings for commands
nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return>

View file

@ -0,0 +1,50 @@
"""
A Deoplete source for ALE completion via tsserver and LSP.
"""
__author__ = 'Joao Paulo, w0rp'
try:
from deoplete.source.base import Base
except ImportError:
# Mock the Base class if deoplete isn't available, as mock isn't available
# in the Docker image.
class Base(object):
def __init__(self, vim):
pass
# Make sure this code is valid in Python 2, used for running unit tests.
class Source(Base):
def __init__(self, vim):
super(Source, self).__init__(vim)
self.name = 'ale'
self.mark = '[L]'
self.rank = 100
self.is_bytepos = True
self.min_pattern_length = 1
# Returns an integer for the start position, as with omnifunc.
def get_completion_position(self):
return self.vim.call('ale#completion#GetCompletionPosition')
def gather_candidates(self, context):
if context.get('is_refresh'):
context['is_async'] = False
if context['is_async']:
# Result is the same as for omnifunc, or None.
result = self.vim.call('ale#completion#GetCompletionResult')
if result is not None:
context['is_async'] = False
return result
else:
context['is_async'] = True
# Request some completion results.
self.vim.call('ale#completion#GetCompletions', 'deoplete')
return []

View file

@ -27,7 +27,7 @@ Before:
let g:get_completions_called = 0 let g:get_completions_called = 0
" We just want to check if the function is called. " We just want to check if the function is called.
function! ale#completion#GetCompletions(manual) function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1 let g:get_completions_called = 1
endfunction endfunction
@ -57,6 +57,7 @@ After:
unlet! b:ale_completion_info unlet! b:ale_completion_info
unlet! b:ale_completion_response unlet! b:ale_completion_response
unlet! b:ale_completion_parser unlet! b:ale_completion_parser
unlet! b:ale_completion_result
unlet! b:ale_complete_done_time unlet! b:ale_complete_done_time
delfunction CheckCompletionCalled delfunction CheckCompletionCalled
@ -86,7 +87,7 @@ Execute(ale#completion#GetCompletions should not be called when the cursor posit
call setpos('.', [bufnr(''), 1, 2, 0]) call setpos('.', [bufnr(''), 1, 2, 0])
" We just want to check if the function is called. " We just want to check if the function is called.
function! ale#completion#GetCompletions(manual) function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1 let g:get_completions_called = 1
endfunction endfunction
@ -105,7 +106,7 @@ Execute(ale#completion#GetCompletions should not be called if you switch to norm
let g:fake_mode = 'n' let g:fake_mode = 'n'
" We just want to check if the function is called. " We just want to check if the function is called.
function! ale#completion#GetCompletions(manual) function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1 let g:get_completions_called = 1
endfunction endfunction
@ -124,6 +125,7 @@ Execute(Completion should not be done shortly after the CompleteDone function):
Execute(ale#completion#Show() should remember the omnifunc setting and replace it): Execute(ale#completion#Show() should remember the omnifunc setting and replace it):
let &l:omnifunc = 'FooBar' let &l:omnifunc = 'FooBar'
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser') call ale#completion#Show('Response', 'Parser')
AssertEqual 'FooBar', b:ale_old_omnifunc AssertEqual 'FooBar', b:ale_old_omnifunc
@ -136,6 +138,7 @@ Execute(ale#completion#Show() should remember the omnifunc setting and replace i
Execute(ale#completion#Show() should remember the completeopt setting and replace it): Execute(ale#completion#Show() should remember the completeopt setting and replace it):
let &l:completeopt = 'menu' let &l:completeopt = 'menu'
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser') call ale#completion#Show('Response', 'Parser')
AssertEqual 'menu', b:ale_old_completeopt AssertEqual 'menu', b:ale_old_completeopt
@ -148,6 +151,7 @@ Execute(ale#completion#Show() should remember the completeopt setting and replac
Execute(ale#completion#Show() should set the preview option if it's set): Execute(ale#completion#Show() should set the preview option if it's set):
let &l:completeopt = 'menu,preview' let &l:completeopt = 'menu,preview'
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser') call ale#completion#Show('Response', 'Parser')
AssertEqual 'menu,preview', b:ale_old_completeopt AssertEqual 'menu,preview', b:ale_old_completeopt
@ -158,7 +162,7 @@ Execute(ale#completion#Show() should set the preview option if it's set):
AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
Execute(ale#completion#Show() should not replace the completeopt setting for manual completion): Execute(ale#completion#Show() should not replace the completeopt setting for manual completion):
let b:ale_completion_info = {'manual': 1} let b:ale_completion_info = {'source': 'ale-manual'}
let &l:completeopt = 'menu,preview' let &l:completeopt = 'menu,preview'
@ -173,6 +177,7 @@ Execute(ale#completion#Show() should not replace the completeopt setting for man
Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it): Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it):
let &l:completeopt = 'menu' let &l:completeopt = 'menu'
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '') call ale#completion#OmniFunc(0, '')
AssertEqual 'menu', b:ale_old_completeopt AssertEqual 'menu', b:ale_old_completeopt
@ -181,18 +186,35 @@ Execute(ale#completion#OmniFunc() should also remember the completeopt setting a
Execute(ale#completion#OmniFunc() should set the preview option if it's set): Execute(ale#completion#OmniFunc() should set the preview option if it's set):
let &l:completeopt = 'menu,preview' let &l:completeopt = 'menu,preview'
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '') call ale#completion#OmniFunc(0, '')
AssertEqual 'menu,preview', b:ale_old_completeopt AssertEqual 'menu,preview', b:ale_old_completeopt
AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt
Execute(ale#completion#Show() should make the correct feedkeys() call): Execute(ale#completion#Show() should make the correct feedkeys() call for automatic completion):
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser') call ale#completion#Show('Response', 'Parser')
AssertEqual [], g:feedkeys_calls AssertEqual [], g:feedkeys_calls
sleep 1ms sleep 1ms
AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
Execute(ale#completion#Show() should make the correct feedkeys() call for manual completion):
let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
AssertEqual [], g:feedkeys_calls
sleep 1ms
AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
Execute(ale#completion#Show() should not call feedkeys() for other sources):
let b:ale_completion_info = {'source': 'deoplete'}
call ale#completion#Show('Response', 'Parser')
sleep 1ms
AssertEqual [], g:feedkeys_calls
Execute(ale#completion#Show() shouldn't do anything if you switch back to normal mode): Execute(ale#completion#Show() shouldn't do anything if you switch back to normal mode):
let &l:completeopt = 'menu,preview' let &l:completeopt = 'menu,preview'
let g:fake_mode = 'n' let g:fake_mode = 'n'
@ -247,9 +269,10 @@ Execute(The completion request_id should be reset when queuing again):
AssertEqual 0, b:ale_completion_info.request_id AssertEqual 0, b:ale_completion_info.request_id
Execute(b:ale_completion_info should be set up correctly when requesting completions): Execute(b:ale_completion_info should be set up correctly when requesting completions automatically):
let b:ale_completion_result = []
call setpos('.', [bufnr(''), 3, 14, 0]) call setpos('.', [bufnr(''), 3, 14, 0])
call ale#completion#GetCompletions(0) call ale#completion#GetCompletions('ale-automatic')
AssertEqual AssertEqual
\ { \ {
@ -259,11 +282,13 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14, \ 'line_length': 14,
\ 'line': 3, \ 'line': 3,
\ 'prefix': 'ab', \ 'prefix': 'ab',
\ 'manual': 0, \ 'source': 'ale-automatic',
\ }, \ },
\ b:ale_completion_info \ b:ale_completion_info
Assert !exists('b:ale_completion_result')
Execute(b:ale_completion_info should be set up correctly when requesting completions): Execute(b:ale_completion_info should be set up correctly when requesting completions manually):
let b:ale_completion_result = []
call setpos('.', [bufnr(''), 3, 14, 0]) call setpos('.', [bufnr(''), 3, 14, 0])
ALEComplete ALEComplete
@ -275,9 +300,28 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14, \ 'line_length': 14,
\ 'line': 3, \ 'line': 3,
\ 'prefix': 'ab', \ 'prefix': 'ab',
\ 'manual': 1, \ 'source': 'ale-manual',
\ }, \ },
\ b:ale_completion_info \ b:ale_completion_info
Assert !exists('b:ale_completion_result')
Execute(b:ale_completion_info should be set up correctly for other sources):
let b:ale_completion_result = []
call setpos('.', [bufnr(''), 3, 14, 0])
call ale#completion#GetCompletions('deoplete')
AssertEqual
\ {
\ 'request_id': 0,
\ 'conn_id': 0,
\ 'column': 14,
\ 'line_length': 14,
\ 'line': 3,
\ 'prefix': 'ab',
\ 'source': 'deoplete',
\ },
\ b:ale_completion_info
Assert !exists('b:ale_completion_result')
Execute(The correct keybinds should be configured): Execute(The correct keybinds should be configured):
redir => g:output redir => g:output

View file

@ -102,7 +102,7 @@ Execute(The right message should be sent for the initial tsserver request):
" The cursor position needs to match what was saved before. " The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 3, 0]) call setpos('.', [bufnr(''), 1, 3, 0])
call ale#completion#GetCompletions(0) call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet. " We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback) AssertEqual '''''', string(g:Callback)
@ -129,7 +129,7 @@ Execute(The right message should be sent for the initial tsserver request):
\ 'request_id': 1, \ 'request_id': 1,
\ 'line': 1, \ 'line': 1,
\ 'prefix': 'fo', \ 'prefix': 'fo',
\ 'manual': 0, \ 'source': 'ale-automatic',
\ }, \ },
\ get(b:, 'ale_completion_info', {}) \ get(b:, 'ale_completion_info', {})
@ -191,7 +191,7 @@ Execute(The right message should be sent for the initial LSP request):
" The cursor position needs to match what was saved before. " The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 5, 0]) call setpos('.', [bufnr(''), 1, 5, 0])
call ale#completion#GetCompletions(0) call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet. " We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback) AssertEqual '''''', string(g:Callback)
@ -234,7 +234,7 @@ Execute(The right message should be sent for the initial LSP request):
\ 'request_id': 1, \ 'request_id': 1,
\ 'line': 1, \ 'line': 1,
\ 'prefix': 'fo', \ 'prefix': 'fo',
\ 'manual': 0, \ 'source': 'ale-automatic',
\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \ 'completion_filter': 'ale#completion#python#CompletionItemFilter',
\ }, \ },
\ get(b:, 'ale_completion_info', {}) \ get(b:, 'ale_completion_info', {})
@ -260,7 +260,7 @@ Execute(Two completion requests shouldn't be sent in a row):
" The cursor position needs to match what was saved before. " The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 5, 0]) call setpos('.', [bufnr(''), 1, 5, 0])
call ale#completion#GetCompletions(0) call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet. " We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback) AssertEqual '''''', string(g:Callback)

View file

@ -0,0 +1,130 @@
import unittest
import imp
ale_module = imp.load_source(
'deoplete.sources.ale',
'/testplugin/rplugin/python3/deoplete/sources/ale.py',
)
class VimMock(object):
def __init__(self, call_list, call_results):
self.__call_list = call_list
self.__call_results = call_results
def call(self, function, *args):
self.__call_list.append((function, args))
return self.__call_results.get(function, 0)
class DeopleteSourceTest(unittest.TestCase):
def setUp(self):
super(DeopleteSourceTest, self).setUp()
self.call_list = []
self.call_results = {}
self.source = ale_module.Source('vim')
self.source.vim = VimMock(self.call_list, self.call_results)
def test_attributes(self):
"""
Check all of the attributes we set.
"""
attributes = dict(
(key, getattr(self.source, key))
for key in
dir(self.source)
if not key.startswith('__')
and key != 'vim'
and not hasattr(getattr(self.source, key), '__self__')
)
self.assertEqual(attributes, {
'is_bytepos': True,
'mark': '[L]',
'min_pattern_length': 1,
'name': 'ale',
'rank': 100,
})
def test_completion_position(self):
self.call_results['ale#completion#GetCompletionPosition'] = 2
self.assertEqual(self.source.get_completion_position(), 2)
self.assertEqual(self.call_list, [
('ale#completion#GetCompletionPosition', ()),
])
def test_request_completion_results(self):
context = {'is_async': False}
self.assertEqual(self.source.gather_candidates(context), [])
self.assertEqual(context, {'is_async': True})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletions', ('deoplete',)),
])
def test_refresh_completion_results(self):
context = {'is_async': False}
self.assertEqual(self.source.gather_candidates(context), [])
self.assertEqual(context, {'is_async': True})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletions', ('deoplete',)),
])
context = {'is_async': True, 'is_refresh': True}
self.assertEqual(self.source.gather_candidates(context), [])
self.assertEqual(context, {'is_async': True, 'is_refresh': True})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletions', ('deoplete',)),
('ale#completion#GetCompletions', ('deoplete',)),
])
def test_poll_no_result(self):
context = {'is_async': True}
self.call_results['ale#completion#GetCompletionResult'] = None
self.assertEqual(self.source.gather_candidates(context), [])
self.assertEqual(context, {'is_async': True})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletionResult', ()),
])
def test_poll_empty_result_ready(self):
context = {'is_async': True}
self.call_results['ale#completion#GetCompletionResult'] = []
self.assertEqual(self.source.gather_candidates(context), [])
self.assertEqual(context, {'is_async': False})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletionResult', ()),
])
def test_poll_non_empty_result_ready(self):
context = {'is_async': True}
self.call_results['ale#completion#GetCompletionResult'] = [
{
'word': 'foobar',
'kind': 'v',
'icase': 1,
'menu': '',
'info': '',
},
]
self.assertEqual(self.source.gather_candidates(context), [
{
'word': 'foobar',
'kind': 'v',
'icase': 1,
'menu': '',
'info': '',
},
])
self.assertEqual(context, {'is_async': False})
self.assertEqual(self.call_list, [
('ale#completion#GetCompletionResult', ()),
])

View file

@ -67,4 +67,14 @@ echo
test/script/check-toc || exit_code=$? test/script/check-toc || exit_code=$?
echo '========================================'
echo 'Check Python code'
echo '========================================'
echo
docker run --rm -v "$PWD:/testplugin" "$DOCKER_RUN_IMAGE" \
python -W ignore -m unittest discover /testplugin/test/python \
|| exit_code=$?
echo
exit $exit_code exit $exit_code