summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorw0rp <devw0rp@gmail.com>2019-04-23 21:26:16 +0100
committerw0rp <devw0rp@gmail.com>2019-04-23 21:26:25 +0100
commit01331266a84859d4b0935b81ae773ff0d7af7522 (patch)
treea3ea9871165efc259acd01d849d684bf3f1428de
parentce0b14979ea7429f07b6ca496333f72d93a8d013 (diff)
downloadale-01331266a84859d4b0935b81ae773ff0d7af7522.zip
Close #1753 - Implement minimum viable integration with Deoplete
-rw-r--r--.gitignore2
-rw-r--r--README.md14
-rw-r--r--autoload/ale/completion.vim101
-rw-r--r--doc/ale.txt42
-rw-r--r--plugin/ale.vim2
-rw-r--r--rplugin/python3/deoplete/sources/ale.py50
-rw-r--r--test/completion/test_completion_events.vader64
-rw-r--r--test/completion/test_lsp_completion_messages.vader10
-rw-r--r--test/python/test_deoplete_source.py130
-rwxr-xr-xtest/script/custom-checks10
10 files changed, 354 insertions, 71 deletions
diff --git a/.gitignore b/.gitignore
index 9b539d7f..ae9f65fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
# Ignore all hidden files everywhere.
# Use `git add -f` to add hidden files.
.*
+__pycache__
+*.pyc
/doc/tags
/init.vim
/test/ale-info-test-file
diff --git a/README.md b/README.md
index bc0902f7..a35a62ab 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ features, including:
* Diagnostics (via Language Server Protocol linters)
* 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`)
* Hover information (`:ALEHover`)
* 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
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
" Enable completion where available.
" This setting must be set before ALE is loaded.
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index 682f4c43..1d42c489 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -159,18 +159,20 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort
endfunction
function! s:ReplaceCompletionOptions() abort
- " Remember the old omnifunc value, if there is one.
- " 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'
+ let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
+
+ if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
+ " Remember the old omnifunc value, if there is one.
+ " 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:info = get(b:, 'ale_completion_info', {})
+ let &l:omnifunc = 'ale#completion#OmniFunc'
+ endif
- if !get(l:info, 'manual')
+ if l:source is# 'ale-automatic'
if !exists('b:ale_old_completeopt')
let b:ale_old_completeopt = &l:completeopt
endif
@@ -199,31 +201,49 @@ function! ale#completion#RestoreCompletionOptions() abort
endif
endfunction
-function! ale#completion#OmniFunc(findstart, base) abort
- if a:findstart
- 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)
+function! ale#completion#GetCompletionPosition() abort
+ if !exists('b:ale_completion_info')
+ return 0
+ endif
- return l:column - len(l:match) - 1
- else
- " 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
+ 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
- unlet b:ale_completion_response
- unlet b:ale_completion_parser
+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
- let b:ale_completion_result = function(l:parser)(l:response)
- endif
+ 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
+ if a:findstart
+ return ale#completion#GetCompletionPosition()
+ else
+ let l:result = ale#completion#GetCompletionResult()
call s:ReplaceCompletionOptions()
- return get(b:, 'ale_completion_result', [])
+ return l:result isnot v:null ? l:result : []
endif
endfunction
@@ -239,7 +259,14 @@ function! ale#completion#Show(response, completion_parser) abort
" Replace completion options shortly before opening the menu.
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
function! s:CompletionStillValid(request_id) abort
@@ -249,7 +276,10 @@ function! s:CompletionStillValid(request_id) abort
\&& has_key(b:, 'ale_completion_info')
\&& b:ale_completion_info.request_id == a:request_id
\&& 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
function! ale#completion#ParseTSServerCompletions(response) abort
@@ -519,12 +549,12 @@ endfunction
" This function can be used to manually trigger autocomplete, even when
" 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: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
endif
@@ -537,8 +567,9 @@ function! ale#completion#GetCompletions(manual) abort
\ 'prefix': l:prefix,
\ 'conn_id': 0,
\ 'request_id': 0,
- \ 'manual': a:manual,
+ \ 'source': a:source,
\}
+ unlet! b:ale_completion_result
let l:buffer = bufnr('')
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
" 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'
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
endif
endfunction
diff --git a/doc/ale.txt b/doc/ale.txt
index 6e48de27..95128187 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -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
`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*
-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
will only suggest symbols provided by the LSP servers.
-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|.
+ *ale-deoplete-integration*
+
+ALE integrates with Deoplete for offering automatic completion data. ALE's
+completion source for Deoplete is named `'ale'`, and should enabled
+automatically if Deoplete is enabled and configured correctly. Deoplete
+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
with |g:ale_completion_excluded_words| or |b:ale_completion_excluded_words|.
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*
-Automatic completion replaces |completeopt| before opening the omnicomplete
-menu with <C-x><C-o>. In some versions of Vim, the value set for the option
-will not be respected. If you experience issues with Vim automatically
-inserting text while you type, set the following option in vimrc, and your
-issues should go away. >
+ALE Automatic completion implementation replaces |completeopt| before opening
+the omnicomplete menu with <C-x><C-o>. In some versions of Vim, the value set
+for the option will not be respected. If you experience issues with Vim
+automatically inserting text while you type, set the following option in
+vimrc, and your issues should go away. >
set completeopt=menu,menuone,preview,noselect,noinsert
<
diff --git a/plugin/ale.vim b/plugin/ale.vim
index ad3d3e56..cf39d632 100644
--- a/plugin/ale.vim
+++ b/plugin/ale.vim
@@ -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.
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
nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return>
diff --git a/rplugin/python3/deoplete/sources/ale.py b/rplugin/python3/deoplete/sources/ale.py
new file mode 100644
index 00000000..1addfae3
--- /dev/null
+++ b/rplugin/python3/deoplete/sources/ale.py
@@ -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 []
diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader
index d04a8085..f79773cc 100644
--- a/test/completion/test_completion_events.vader
+++ b/test/completion/test_completion_events.vader
@@ -27,7 +27,7 @@ Before:
let g:get_completions_called = 0
" 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
endfunction
@@ -57,6 +57,7 @@ After:
unlet! b:ale_completion_info
unlet! b:ale_completion_response
unlet! b:ale_completion_parser
+ unlet! b:ale_completion_result
unlet! b:ale_complete_done_time
delfunction CheckCompletionCalled
@@ -86,7 +87,7 @@ Execute(ale#completion#GetCompletions should not be called when the cursor posit
call setpos('.', [bufnr(''), 1, 2, 0])
" 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
endfunction
@@ -105,7 +106,7 @@ Execute(ale#completion#GetCompletions should not be called if you switch to norm
let g:fake_mode = 'n'
" 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
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):
let &l:omnifunc = 'FooBar'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
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):
let &l:completeopt = 'menu'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
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):
let &l:completeopt = 'menu,preview'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
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
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'
@@ -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):
let &l:completeopt = 'menu'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '')
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):
let &l:completeopt = 'menu,preview'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '')
AssertEqual 'menu,preview', b:ale_old_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')
AssertEqual [], g:feedkeys_calls
sleep 1ms
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):
let &l:completeopt = 'menu,preview'
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
-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 ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
AssertEqual
\ {
@@ -259,11 +282,13 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14,
\ 'line': 3,
\ 'prefix': 'ab',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ },
\ 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])
ALEComplete
@@ -275,9 +300,28 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14,
\ 'line': 3,
\ 'prefix': 'ab',
- \ 'manual': 1,
+ \ 'source': 'ale-manual',
+ \ },
+ \ 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):
redir => g:output
diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader
index dce61e36..25536436 100644
--- a/test/completion/test_lsp_completion_messages.vader
+++ b/test/completion/test_lsp_completion_messages.vader
@@ -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.
call setpos('.', [bufnr(''), 1, 3, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
@@ -129,7 +129,7 @@ Execute(The right message should be sent for the initial tsserver request):
\ 'request_id': 1,
\ 'line': 1,
\ 'prefix': 'fo',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ },
\ 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.
call setpos('.', [bufnr(''), 1, 5, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
@@ -234,7 +234,7 @@ Execute(The right message should be sent for the initial LSP request):
\ 'request_id': 1,
\ 'line': 1,
\ 'prefix': 'fo',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ 'completion_filter': 'ale#completion#python#CompletionItemFilter',
\ },
\ 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.
call setpos('.', [bufnr(''), 1, 5, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
diff --git a/test/python/test_deoplete_source.py b/test/python/test_deoplete_source.py
new file mode 100644
index 00000000..960abe3a
--- /dev/null
+++ b/test/python/test_deoplete_source.py
@@ -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', ()),
+ ])
diff --git a/test/script/custom-checks b/test/script/custom-checks
index d4027fec..20dbfb80 100755
--- a/test/script/custom-checks
+++ b/test/script/custom-checks
@@ -67,4 +67,14 @@ echo
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