Before: call ale#test#SetDirectory('/testplugin/test') call ale#test#SetFilename('dummy.txt') Save g:ale_default_navigation let g:old_filename = expand('%:p') let g:Callback = '' let g:message_list = [] let g:expr_list = [] let g:capability_checked = '' let g:conn_id = v:null let g:InitCallback = v:null let g:ale_default_navigation = 'buffer' runtime autoload/ale/linter.vim runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) if a:linter.lsp is# 'tsserver' call ale#lsp#MarkConnectionAsTsserver(g:conn_id) endif let l:details = { \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', \} let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} endfunction function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort let g:Callback = a:callback endfunction function! ale#lsp#Send(conn_id, message) 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: Restore if g:conn_id isnot v:null call ale#lsp#RemoveConnectionWithID(g:conn_id) endif call ale#definition#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() unlet! g:capability_checked unlet! g:InitCallback unlet! g:old_filename unlet! g:conn_id 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(Tagstack should be incremented if supported): if exists('*gettagstack') && exists('*settagstack') let original_stack_depth = gettagstack().length endif call ale#definition#UpdateTagStack() if exists('*gettagstack') && exists('*settagstack') AssertEqual original_stack_depth + 1, gettagstack().length endif Execute(Failed definition responses should be handled correctly): call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) 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': 'current-buffer'}}) 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': 'current-buffer'}}) 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 +3 ' . 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'}}) 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 +3 ' . 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 splits too): call ale#definition#SetMap({3: {'open_in': 'split'}}) 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 \ [ \ 'split +3 ' . 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 vsplits too): call ale#definition#SetMap({3: {'open_in': 'vsplit'}}) 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 \ [ \ 'vsplit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(tsserver definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(tsserver type definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToTypeDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'typeDefinition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@typeDefinition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(tsserver implementation requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToImplementation " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'implementation', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@implementation', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(tsserver tab definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALEGoToDefinition -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() Execute(The default navigation type should be used): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) let g:ale_default_navigation = 'tab' ALEGoToDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, 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': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ 'edit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 8], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Newer LocationLink items should be supported): call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'targetUri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'targetRange': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ 'edit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 8], 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': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToFileURI(ale#path#Simplify(expand('%:p'))), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ ], \ g:expr_list AssertEqual [3, 8], 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'}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': { \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ } \) AssertEqual \ [ \ 'tabedit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 8], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with lists should be handled): call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { \ 'id': 3, \ 'result': [ \ { \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ 'range': { \ 'start': {'line': 2, 'character': 7}, \ }, \ }, \ { \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/other_file')), \ 'range': { \ 'start': {'line': 20, 'character': 3}, \ }, \ }, \ ], \ } \) AssertEqual \ [ \ 'edit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), \ ], \ g:expr_list AssertEqual [3, 8], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with null response should be handled): call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse(1, {'id': 3, 'result': v:null}) AssertEqual [], g:expr_list Execute(LSP definition requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(LSP type definition requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToTypeDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'typeDefinition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/typeDefinition', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(LSP implementation requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToImplementation " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'implementation', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/implementation', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() Execute(LSP tab definition requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToDefinition -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() Execute(LSP tab type definition requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToTypeDefinition -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'typeDefinition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/typeDefinition', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() Execute(LSP tab implementation requests should be sent): runtime ale_linters/python/pylsp.vim let b:ale_linters = ['pylsp'] call setpos('.', [bufnr(''), 1, 5, 0]) ALEGoToImplementation -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'implementation', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToFileURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/implementation', { \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap()