diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index 79d12596..a732e501 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -57,7 +57,7 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort endif endfunction -function! s:OnReady(linter, lsp_details, line, column, options, ...) abort +function! s:OnReady(linter, lsp_details, line, column, options, capability, ...) abort let l:buffer = a:lsp_details.buffer let l:id = a:lsp_details.connection_id @@ -80,7 +80,14 @@ function! s:OnReady(linter, lsp_details, line, column, options, ...) abort " For LSP completions, we need to clamp the column to the length of " the line. python-language-server and perhaps others do not implement " this correctly. - let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column) + if a:capability is# 'definition' + let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column) + elseif a:capability is# 'typeDefinition' + let l:message = ale#lsp#message#TypeDefinition(l:buffer, a:line, a:column) + else + " XXX: log here? + return + endif endif let l:request_id = ale#lsp#Send(l:id, l:message) @@ -90,7 +97,7 @@ function! s:OnReady(linter, lsp_details, line, column, options, ...) abort \} endfunction -function! s:GoToLSPDefinition(linter, options) abort +function! s:GoToLSPDefinition(linter, options, capability) abort let l:buffer = bufnr('') let [l:line, l:column] = getcurpos()[1:2] let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) @@ -105,15 +112,29 @@ function! s:GoToLSPDefinition(linter, options) abort let l:id = l:lsp_details.connection_id - call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [ - \ a:linter, l:lsp_details, l:line, l:column, a:options + call ale#lsp#WaitForCapability(l:id, a:capability, function('s:OnReady', [ + \ a:linter, l:lsp_details, l:line, l:column, a:options, a:capability \])) endfunction function! ale#definition#GoTo(options) abort for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) - call s:GoToLSPDefinition(l:linter, a:options) + call s:GoToLSPDefinition(l:linter, a:options, 'definition') + endif + endfor +endfunction + +function! ale#definition#GoToType(options) abort + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + " TODO: handle typeDefinition for tsserver if supported by the + " protocol + if l:linter.lsp is# 'tsserver' + continue + endif + + call s:GoToLSPDefinition(l:linter, a:options, 'typeDefinition') endif endfor endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index f55096c2..95ab83cf 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -43,6 +43,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort \ 'completion': 0, \ 'completion_trigger_characters': [], \ 'definition': 0, + \ 'typeDefinition': 0, \ 'symbol_search': 0, \ }, \} @@ -207,6 +208,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort let a:conn.capabilities.definition = 1 endif + if get(a:capabilities, 'typeDefinitionProvider') is v:true + let a:conn.capabilities.typeDefinition = 1 + endif + if get(a:capabilities, 'workspaceSymbolProvider') is v:true let a:conn.capabilities.symbol_search = 1 endif diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index a9921478..cc0b2227 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -124,6 +124,15 @@ function! ale#lsp#message#Definition(buffer, line, column) abort \}] endfunction +function! ale#lsp#message#TypeDefinition(buffer, line, column) abort + return [0, 'textDocument/typeDefinition', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] +endfunction + function! ale#lsp#message#References(buffer, line, column) abort return [0, 'textDocument/references', { \ 'textDocument': { diff --git a/doc/ale.txt b/doc/ale.txt index 2af77ac6..ac71ab83 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -14,9 +14,10 @@ CONTENTS *ale-contents* 5. Language Server Protocol Support.....|ale-lsp| 5.1 Completion........................|ale-completion| 5.2 Go To Definition..................|ale-go-to-definition| - 5.3 Find References...................|ale-find-references| - 5.4 Hovering..........................|ale-hover| - 5.5 Symbol Search.....................|ale-symbol-search| + 5.3 Go To Type Definition.............|ale-go-to-type-definition| + 5.4 Find References...................|ale-find-references| + 5.5 Hovering..........................|ale-hover| + 5.6 Symbol Search.....................|ale-symbol-search| 6. Global Options.......................|ale-options| 6.1 Highlights........................|ale-highlights| 6.2 Options for write-good Linter.....|ale-write-good-options| @@ -850,9 +851,23 @@ information returned by LSP servers. The following commands are supported: |ALEGoToDefinitionInSplit| - The same, but in a new split. |ALEGoToDefinitionInVSplit| - The same, but in a new vertical split. +------------------------------------------------------------------------------- +5.3 Go To Type Definition *ale-go-to-type-definition* + +ALE supports jumping to the files and locations where symbols' types are +defined through any enabled LSP linters. The locations ALE will jump to depend +on the information returned by LSP servers. The following commands are +supported: + +|ALEGoToTypeDefinition| - Open the definition of the symbol's type under + the cursor. +|ALEGoToTypeDefinitionInTab| - The same, but for opening the file in a new tab. +|ALEGoToTypeDefinitionInSplit| - The same, but in a new split. +|ALEGoToTypeDefinitionInVSplit| - The same, but in a new vertical split. + ------------------------------------------------------------------------------- -5.3 Find References *ale-find-references* +5.4 Find References *ale-find-references* ALE supports finding references for symbols though any enabled LSP linters. ALE will display a preview window showing the places where a symbol is @@ -863,7 +878,7 @@ supported: ------------------------------------------------------------------------------- -5.4 Hovering *ale-hover* +5.5 Hovering *ale-hover* ALE supports "hover" information for printing brief information about symbols at the cursor taken from LSP linters. The following commands are supported: @@ -894,7 +909,7 @@ Documentation for symbols at the cursor can be retrieved using the |ALEDocumentation| command. This command is only available for `tsserver`. ------------------------------------------------------------------------------- -5.5 Symbol Search *ale-symbol-search* +5.6 Symbol Search *ale-symbol-search* ALE supports searching for workspace symbols via LSP linters. The following commands are supported: @@ -2329,6 +2344,45 @@ ALEGoToDefinitionInVSplit *ALEGoToDefinitionInVSplit* command. +ALEGoToTypeDefinition *ALEGoToTypeDefinition* + + This works similar to |ALEGoToDefinition| but instead jumps to the + definition of a type of a symbol undert the cursor. ALE will jump to a + definition if an LSP server provides a location to jump to. Otherwise, ALE + will do nothing. + + You can jump back to the position you were at before going to the definition + of something with jump motions like CTRL-O. See |jump-motions|. + + A plug mapping `(ale_go_to_type_definition)` is defined for this + command. + + +ALEGoToTypeDefinitionInTab *ALEGoToTypeDefinitionInTab* + + The same as |ALEGoToTypeDefinition|, but opens results in a new tab. + + A plug mapping `(ale_go_to_type_definition_in_tab)` is defined for + this command. + + +ALEGoToTypeDefinitionInSplit *ALEGoToTypeDefinitionInSplit* + + The same as |ALEGoToTypeDefinition|, but opens results in a new split. + + A plug mapping `(ale_go_to_type_definition_in_split)` is defined for + this command. + + +ALEGoToTypeDefinitionInVSplit *ALEGoToTypeDefinitionInVSplit* + + The same as |ALEGoToTypeDefinition|, but opens results in a new vertical + split. + + A plug mapping `(ale_go_to_type_definition_in_vsplit)` is defined for + this command. + + ALEHover *ALEHover* Print brief information about the symbol under the cursor, taken from any diff --git a/plugin/ale.vim b/plugin/ale.vim index 15e50e9f..7d6a48f0 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -192,6 +192,12 @@ command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in': 'tab' command! -bar ALEGoToDefinitionInSplit :call ale#definition#GoTo({'open_in': 'horizontal-split'}) command! -bar ALEGoToDefinitionInVSplit :call ale#definition#GoTo({'open_in': 'vertical-split'}) +" Go to type definition for tsserver and LSP +command! -bar ALEGoToTypeDefinition :call ale#definition#GoToType({}) +command! -bar ALEGoToTypeDefinitionInTab :call ale#definition#GoToType({'open_in': 'tab'}) +command! -bar ALEGoToTypeDefinitionInSplit :call ale#definition#GoToType({'open_in': 'horizontal-split'}) +command! -bar ALEGoToTypeDefinitionInVSplit :call ale#definition#GoToType({'open_in': 'vertical-split'}) + " Find references for tsserver and LSP command! -bar ALEFindReferences :call ale#references#Find() @@ -228,6 +234,10 @@ nnoremap (ale_go_to_definition) :ALEGoToDefinition nnoremap (ale_go_to_definition_in_tab) :ALEGoToDefinitionInTab nnoremap (ale_go_to_definition_in_split) :ALEGoToDefinitionInSplit nnoremap (ale_go_to_definition_in_vsplit) :ALEGoToDefinitionInVSplit +nnoremap (ale_go_to_type_definition) :ALEGoToTypeDefinition +nnoremap (ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinitionInTab +nnoremap (ale_go_to_type_definition_in_split) :ALEGoToTypeDefinitionInSplit +nnoremap (ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinitionInVSplit nnoremap (ale_find_references) :ALEFindReferences nnoremap (ale_hover) :ALEHover nnoremap (ale_documentation) :ALEDocumentation diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader index 91efb41b..bdede460 100644 --- a/test/lsp/test_lsp_client_messages.vader +++ b/test/lsp/test_lsp_client_messages.vader @@ -146,6 +146,20 @@ Execute(ale#lsp#message#Definition() should return correct messages): \ ], \ ale#lsp#message#Definition(bufnr(''), 12, 34) +Execute(ale#lsp#message#TypeDefinition() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/typeDefinition', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#TypeDefinition(bufnr(''), 12, 34) + Execute(ale#lsp#message#References() should return correct messages): AssertEqual \ [ diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index 62dbc599..3cc2a58c 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -196,7 +196,7 @@ Execute(Other files should be jumped to for definition responses in vsplits too) AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() -Execute(tsserver completion requests should be sent): +Execute(tsserver definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) @@ -217,7 +217,7 @@ Execute(tsserver completion requests should be sent): \ g:message_list AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() -Execute(tsserver tab completion requests should be sent): +Execute(tsserver tab definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) @@ -348,7 +348,7 @@ Execute(Definition responses with null response should be handled): AssertEqual [], g:expr_list -Execute(LSP completion requests should be sent): +Execute(LSP definition requests should be sent): runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) @@ -384,7 +384,43 @@ Execute(LSP completion requests should be sent): AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() -Execute(LSP tab completion requests should be sent): +Execute(LSP type definition requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToTypeDefinition + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'typeDefinition', g:capability_checked + call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + + 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/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToURI(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/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) @@ -419,3 +455,39 @@ Execute(LSP tab completion requests should be sent): \ g:message_list AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Execute(LSP tab type definition requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToTypeDefinitionInTab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:WaitCallback) + AssertEqual 'typeDefinition', g:capability_checked + call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + + 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/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap()