Before: call ale#test#SetDirectory('/testplugin/test') call ale#test#SetFilename('dummy.txt') let g:old_filename = expand('%:p') let g:Callback = 0 let g:message_list = [] let g:expr_list = [] runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback let l:conn = ale#lsp#NewConnection({}) let l:conn.id = 347 let l:conn.open_documents = {a:buffer : -1} return { \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', \ 'language_id': 'python', \} endfunction function! ale#lsp#Send(conn_id, message, root) abort call add(g:message_list, a:message) return 42 endfunction function! ale#util#Execute(expr) abort call add(g:expr_list, a:expr) endfunction After: call ale#lsp#RemoveConnectionWithID(347) call ale#definition#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() unlet! g:old_filename unlet! g:Callback unlet! g:message_list unlet! g:expr_list unlet! b:ale_linters runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim Execute(Other messages for the tsserver handler should be ignored): call ale#definition#HandleTSServerResponse(1, {'command': 'foo'}) Execute(Failed definition responses should be handled correctly): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleTSServerResponse( \ 1, \ {'command': 'definition', 'request_seq': 3} \) AssertEqual {}, ale#definition#GetMap() Execute(Failed definition responses with no files should be handled correctly): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleTSServerResponse( \ 1, \ { \ 'command': 'definition', \ 'request_seq': 3, \ 'success': v:true, \ 'body': [], \ } \) AssertEqual {}, ale#definition#GetMap() Given typescript(Some typescript file): foo somelongerline bazxyzxyzxyz Execute(Other files should be jumped to for definition responses): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleTSServerResponse( \ 1, \ { \ 'command': 'definition', \ 'request_seq': 3, \ 'success': v:true, \ 'body': [ \ { \ 'file': ale#path#Simplify(g:dir . '/completion_dummy_file'), \ 'start': {'line': 3, 'offset': 7}, \ }, \ ], \ } \) AssertEqual \ [ \ 'edit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Other files should be jumped to for definition responses in tabs too): call ale#definition#SetMap({3: {'open_in_tab': 1}}) call ale#definition#HandleTSServerResponse( \ 1, \ { \ 'command': 'definition', \ 'request_seq': 3, \ 'success': v:true, \ 'body': [ \ { \ 'file': ale#path#Simplify(g:dir . '/completion_dummy_file'), \ 'start': {'line': 3, 'offset': 7}, \ }, \ ], \ } \) AssertEqual \ [ \ 'tabedit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(tsserver completion requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToDefinition AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], \ g:message_list AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() Execute(tsserver tab completion requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToDefinitionInTab AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], \ g:message_list AssertEqual {'42': {'open_in_tab': 1}}, ale#definition#GetMap() Given python(Some Python file): foo somelongerline bazxyzxyzxyz Execute(Other files should be jumped to for LSP definition responses): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ 'edit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Locations inside the same file should be jumped to without using :edit): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToURI(ale#path#Simplify(expand('%:p'))), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Other files should be jumped to in tabs for LSP definition responses): call ale#definition#SetMap({3: {'open_in_tab': 1}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ 'tabedit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with lists should be handled): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': [ \ { \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ { \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/other_file')), \ 'range': { \ 'start': {'line': 20, 'character': 3}, \ }, \ }, \ ], \ } \) AssertEqual \ [ \ 'edit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with null response should be handled): call ale#definition#SetMap({3: {'open_in_tab': 0}}) call ale#definition#HandleLSPResponse(1, {'id': 3, 'result': v:null}) AssertEqual [], g:expr_list Execute(LSP completion requests should be sent): runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToDefinition AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 3}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() Execute(LSP tab completion requests should be sent): runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToDefinitionInTab AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 3}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in_tab': 1}}, ale#definition#GetMap()