Add textDocument/typeDefinition for LSP (#2226)

* Add textDocument/typeDefinition for LSP

Doc to spec https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition

This works like textDocument/definition but resolves a location of a
type of an expression under the cursor.

I'm not sure what to do with tsserver though.

* Fix passing column to LSP
* test_go_to_definition: wording
* Add tests for textDocument/typeDefinition
* Add docs for textDocument/typeDefinition
This commit is contained in:
Andrey Popp 2019-01-22 02:06:28 +03:00 committed by w0rp
parent a4932679b5
commit d0284f22ea
7 changed files with 201 additions and 16 deletions

View File

@ -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

View File

@ -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

View File

@ -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': {

View File

@ -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 `<Plug>(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 `<Plug>(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 `<Plug>(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 `<Plug>(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

View File

@ -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 <silent> <Plug>(ale_go_to_definition) :ALEGoToDefinition<Return>
nnoremap <silent> <Plug>(ale_go_to_definition_in_tab) :ALEGoToDefinitionInTab<Return>
nnoremap <silent> <Plug>(ale_go_to_definition_in_split) :ALEGoToDefinitionInSplit<Return>
nnoremap <silent> <Plug>(ale_go_to_definition_in_vsplit) :ALEGoToDefinitionInVSplit<Return>
nnoremap <silent> <Plug>(ale_go_to_type_definition) :ALEGoToTypeDefinition<Return>
nnoremap <silent> <Plug>(ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinitionInTab<Return>
nnoremap <silent> <Plug>(ale_go_to_type_definition_in_split) :ALEGoToTypeDefinitionInSplit<Return>
nnoremap <silent> <Plug>(ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinitionInVSplit<Return>
nnoremap <silent> <Plug>(ale_find_references) :ALEFindReferences<Return>
nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return>
nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return>

View File

@ -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
\ [

View File

@ -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()