/ test / completion / test_lsp_completion_messages.vader
test_lsp_completion_messages.vader
  1  Before:
  2    Save g:ale_completion_delay
  3    Save g:ale_completion_max_suggestions
  4    Save g:ale_completion_info
  5    Save &l:omnifunc
  6    Save &l:completeopt
  7  
  8    let g:ale_completion_enabled = 1
  9  
 10    call ale#test#SetDirectory('/testplugin/test/completion')
 11    call ale#test#SetFilename('dummy.txt')
 12  
 13    runtime autoload/ale/lsp.vim
 14  
 15    let g:message_list = []
 16    let g:capability_checked = ''
 17    let g:conn_id = v:null
 18    let g:Callback = ''
 19    let g:init_callback_list = []
 20  
 21    function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
 22      let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {})
 23      call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
 24  
 25      let l:details = {
 26      \ 'command': 'foobar',
 27      \ 'buffer': a:buffer,
 28      \ 'connection_id': g:conn_id,
 29      \ 'project_root': '/foo/bar',
 30      \}
 31  
 32      call add(g:init_callback_list, {-> a:Callback(a:linter, l:details)})
 33    endfunction
 34  
 35    " Pretend we're in insert mode for most tests.
 36    function! ale#util#Mode(...) abort
 37      return 'i'
 38    endfunction
 39  
 40    function! ale#lsp#HasCapability(conn_id, capability) abort
 41      let g:capability_checked = a:capability
 42  
 43      return 1
 44    endfunction
 45  
 46    function! ale#lsp#RegisterCallback(conn_id, callback) abort
 47      let g:Callback = a:callback
 48    endfunction
 49  
 50    " Replace the Send function for LSP, so we can monitor calls to it.
 51    function! ale#lsp#Send(conn_id, message) abort
 52      call add(g:message_list, a:message)
 53  
 54      return 1
 55    endfunction
 56  
 57  After:
 58    Restore
 59  
 60    if g:conn_id isnot v:null
 61      call ale#lsp#RemoveConnectionWithID(g:conn_id)
 62    endif
 63  
 64    unlet! g:message_list
 65    unlet! g:capability_checked
 66    unlet! g:init_callback_list
 67    unlet! g:conn_id
 68    unlet! g:Callback
 69    unlet! b:ale_old_omnifunc
 70    unlet! b:ale_old_completeopt
 71    unlet! b:ale_completion_info
 72    unlet! b:ale_complete_done_time
 73    unlet! b:ale_linters
 74    unlet! b:ale_tsserver_completion_names
 75  
 76    " Reset the function.
 77    function! ale#util#Mode(...) abort
 78      return call('mode', a:000)
 79    endfunction
 80  
 81    call ale#test#RestoreDirectory()
 82    call ale#linter#Reset()
 83  
 84    " Stop any timers we left behind.
 85    " This stops the tests from failing randomly.
 86    call ale#completion#StopTimer()
 87  
 88    runtime autoload/ale/completion.vim
 89    runtime autoload/ale/lsp.vim
 90    runtime autoload/ale/lsp_linter.vim
 91  
 92  Given typescript(Some typescript file):
 93    foo
 94    somelongerline
 95    bazxyzxyzxyz
 96  
 97  Execute(The right message should be sent for the initial tsserver request):
 98    runtime ale_linters/typescript/tsserver.vim
 99    let b:ale_linters = ['tsserver']
100    " The cursor position needs to match what was saved before.
101    call setpos('.', [bufnr(''), 1, 3, 0])
102  
103    call ale#completion#GetCompletions('ale-automatic')
104  
105    " We shouldn't register the callback yet.
106    AssertEqual '''''', string(g:Callback)
107  
108    AssertEqual 1, len(g:init_callback_list)
109    call map(g:init_callback_list, 'v:val()')
110  
111    AssertEqual 'completion', g:capability_checked
112  
113    " We should send the right callback.
114    AssertEqual
115    \ 'function(''ale#completion#HandleTSServerResponse'')',
116    \ string(g:Callback)
117    " We should send the right message.
118    AssertEqual
119    \ [[0, 'ts@completions', {
120    \  'file': expand('%:p'),
121    \  'line': 1,
122    \  'offset': 3,
123    \  'prefix': 'fo',
124    \  'includeExternalModuleExports': g:ale_completion_autoimport,
125    \ }]],
126    \ g:message_list
127    " We should set up the completion info correctly.
128    AssertEqual
129    \ {
130    \   'line_length': 3,
131    \   'conn_id': g:conn_id,
132    \   'column': 3,
133    \   'request_id': 1,
134    \   'line': 1,
135    \   'prefix': 'fo',
136    \   'source': 'ale-automatic',
137    \ },
138    \ get(b:, 'ale_completion_info', {})
139  
140  Execute(The right message sent to the tsserver LSP when the first completion message is received):
141    " The cursor position needs to match what was saved before.
142    call setpos('.', [bufnr(''), 1, 1, 0])
143    let b:ale_completion_info = {
144    \ 'conn_id': 123,
145    \ 'prefix': 'f',
146    \ 'request_id': 4,
147    \ 'line': 1,
148    \ 'column': 1,
149    \}
150    " We should only show up to this many suggestions.
151    let g:ale_completion_max_suggestions = 3
152  
153    " Handle the response for completions.
154    call ale#completion#HandleTSServerResponse(123, {
155    \ 'request_seq': 4,
156    \ 'command': 'completions',
157    \ 'body': [
158    \   {'name': 'Baz'},
159    \   {'name': 'dingDong'},
160    \   {'name': 'Foo', 'source': '/path/to/foo.ts'},
161    \   {'name': 'FooBar'},
162    \   {'name': 'frazzle'},
163    \   {'name': 'FFS'},
164    \ ],
165    \})
166  
167    " We should save the names we got in the buffer, as TSServer doesn't return
168    " details for every name.
169    AssertEqual [{
170    \  'word': 'Foo',
171    \  'source': '/path/to/foo.ts',
172    \ }, {
173    \  'word': 'FooBar',
174    \  'source': '',
175    \ }, {
176    \  'word': 'frazzle',
177    \  'source': '',
178    \}],
179    \ get(b:, 'ale_tsserver_completion_names', [])
180  
181    " The entry details messages should have been sent.
182    AssertEqual
183    \ [[
184    \   0,
185    \   'ts@completionEntryDetails',
186    \   {
187    \     'file': expand('%:p'),
188    \     'entryNames': [{
189    \          'name': 'Foo',
190    \          'source': '/path/to/foo.ts',
191    \         }, {
192    \          'name': 'FooBar',
193    \         }, {
194    \          'name': 'frazzle',
195    \     }],
196    \     'offset': 1,
197    \     'line': 1,
198    \   },
199    \ ]],
200    \ g:message_list
201  
202  Given python(Some Python file):
203    foo
204    somelongerline
205    bazxyzxyzxyz
206  
207  Execute(The right message should be sent for the initial LSP request):
208    runtime ale_linters/python/pylsp.vim
209    let b:ale_linters = ['pylsp']
210    " The cursor position needs to match what was saved before.
211    call setpos('.', [bufnr(''), 1, 5, 0])
212  
213    call ale#completion#GetCompletions('ale-automatic')
214  
215    " We shouldn't register the callback yet.
216    AssertEqual '''''', string(g:Callback)
217  
218    AssertEqual 1, len(g:init_callback_list)
219    call map(g:init_callback_list, 'v:val()')
220  
221    AssertEqual 'completion', g:capability_checked
222  
223    " We should send the right callback.
224    AssertEqual
225    \ 'function(''ale#completion#HandleLSPResponse'')',
226    \ string(g:Callback)
227    " We should send the right message.
228    " The character index needs to be at most the index of the last character on
229    " the line, or integration with pylsp will be broken.
230    "
231    " We need to send the message for changing the document first.
232    AssertEqual
233    \ [
234    \   [1, 'textDocument/didChange', {
235    \     'textDocument': {
236    \         'uri': ale#path#ToFileURI(expand('%:p')),
237    \         'version': g:ale_lsp_next_version_id - 1,
238    \     },
239    \     'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}]
240    \   }],
241    \   [0, 'textDocument/completion', {
242    \   'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))},
243    \   'position': {'line': 0, 'character': 2},
244    \   }],
245    \ ],
246    \ g:message_list
247    " We should set up the completion info correctly.
248    AssertEqual
249    \ {
250    \   'line_length': 3,
251    \   'conn_id': g:conn_id,
252    \   'column': 3,
253    \   'request_id': 1,
254    \   'line': 1,
255    \   'prefix': 'fo',
256    \   'source': 'ale-automatic',
257    \   'completion_filter': 'ale#completion#python#CompletionItemFilter',
258    \ },
259    \ get(b:, 'ale_completion_info', {})
260  
261  Execute(Two completion requests shouldn't be sent in a row):
262    call ale#linter#PreventLoading('python')
263    call ale#linter#Define('python', {
264    \   'name': 'foo',
265    \   'lsp': 'stdio',
266    \   'executable': 'foo',
267    \   'command': 'foo',
268    \   'project_root': {-> '/foo/bar'},
269    \})
270    call ale#linter#Define('python', {
271    \   'name': 'bar',
272    \   'lsp': 'stdio',
273    \   'executable': 'foo',
274    \   'command': 'foo',
275    \   'project_root': {-> '/foo/bar'},
276    \})
277    let b:ale_linters = ['foo', 'bar']
278  
279    " The cursor position needs to match what was saved before.
280    call setpos('.', [bufnr(''), 1, 5, 0])
281  
282    call ale#completion#GetCompletions('ale-automatic')
283  
284    " We shouldn't register the callback yet.
285    AssertEqual '''''', string(g:Callback)
286  
287    AssertEqual 2, len(g:init_callback_list)
288    call map(g:init_callback_list, 'v:val()')
289  
290    AssertEqual 'completion', g:capability_checked
291  
292    " We should only send one completion message for two LSP servers.
293    AssertEqual
294    \ [
295    \   [1, 'textDocument/didChange', {
296    \     'textDocument': {
297    \         'uri': ale#path#ToFileURI(expand('%:p')),
298    \         'version': g:ale_lsp_next_version_id - 1,
299    \     },
300    \     'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}]
301    \   }],
302    \   [0, 'textDocument/completion', {
303    \   'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))},
304    \   'position': {'line': 0, 'character': 2},
305    \   }],
306    \ ],
307    \ g:message_list