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 endif
endfunction 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:buffer = a:lsp_details.buffer
let l:id = a:lsp_details.connection_id 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 " For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement " the line. python-language-server and perhaps others do not implement
" this correctly. " 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 endif
let l:request_id = ale#lsp#Send(l:id, l:message) 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 endfunction
function! s:GoToLSPDefinition(linter, options) abort function! s:GoToLSPDefinition(linter, options, capability) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2] let [l:line, l:column] = getcurpos()[1:2]
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) 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 let l:id = l:lsp_details.connection_id
call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [ call ale#lsp#WaitForCapability(l:id, a:capability, function('s:OnReady', [
\ a:linter, l:lsp_details, l:line, l:column, a:options \ a:linter, l:lsp_details, l:line, l:column, a:options, a:capability
\])) \]))
endfunction endfunction
function! ale#definition#GoTo(options) abort function! ale#definition#GoTo(options) abort
for l:linter in ale#linter#Get(&filetype) for l:linter in ale#linter#Get(&filetype)
if !empty(l:linter.lsp) 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 endif
endfor endfor
endfunction endfunction

View File

@ -43,6 +43,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'completion': 0, \ 'completion': 0,
\ 'completion_trigger_characters': [], \ 'completion_trigger_characters': [],
\ 'definition': 0, \ 'definition': 0,
\ 'typeDefinition': 0,
\ 'symbol_search': 0, \ 'symbol_search': 0,
\ }, \ },
\} \}
@ -207,6 +208,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.definition = 1 let a:conn.capabilities.definition = 1
endif endif
if get(a:capabilities, 'typeDefinitionProvider') is v:true
let a:conn.capabilities.typeDefinition = 1
endif
if get(a:capabilities, 'workspaceSymbolProvider') is v:true if get(a:capabilities, 'workspaceSymbolProvider') is v:true
let a:conn.capabilities.symbol_search = 1 let a:conn.capabilities.symbol_search = 1
endif endif

View File

@ -124,6 +124,15 @@ function! ale#lsp#message#Definition(buffer, line, column) abort
\}] \}]
endfunction 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 function! ale#lsp#message#References(buffer, line, column) abort
return [0, 'textDocument/references', { return [0, 'textDocument/references', {
\ 'textDocument': { \ 'textDocument': {

View File

@ -14,9 +14,10 @@ CONTENTS *ale-contents*
5. Language Server Protocol Support.....|ale-lsp| 5. Language Server Protocol Support.....|ale-lsp|
5.1 Completion........................|ale-completion| 5.1 Completion........................|ale-completion|
5.2 Go To Definition..................|ale-go-to-definition| 5.2 Go To Definition..................|ale-go-to-definition|
5.3 Find References...................|ale-find-references| 5.3 Go To Type Definition.............|ale-go-to-type-definition|
5.4 Hovering..........................|ale-hover| 5.4 Find References...................|ale-find-references|
5.5 Symbol Search.....................|ale-symbol-search| 5.5 Hovering..........................|ale-hover|
5.6 Symbol Search.....................|ale-symbol-search|
6. Global Options.......................|ale-options| 6. Global Options.......................|ale-options|
6.1 Highlights........................|ale-highlights| 6.1 Highlights........................|ale-highlights|
6.2 Options for write-good Linter.....|ale-write-good-options| 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. |ALEGoToDefinitionInSplit| - The same, but in a new split.
|ALEGoToDefinitionInVSplit| - The same, but in a new vertical 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 supports finding references for symbols though any enabled LSP linters.
ALE will display a preview window showing the places where a symbol is 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 ALE supports "hover" information for printing brief information about symbols
at the cursor taken from LSP linters. The following commands are supported: 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`. |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 ALE supports searching for workspace symbols via LSP linters. The following
commands are supported: commands are supported:
@ -2329,6 +2344,45 @@ ALEGoToDefinitionInVSplit *ALEGoToDefinitionInVSplit*
command. 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* ALEHover *ALEHover*
Print brief information about the symbol under the cursor, taken from any 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 ALEGoToDefinitionInSplit :call ale#definition#GoTo({'open_in': 'horizontal-split'})
command! -bar ALEGoToDefinitionInVSplit :call ale#definition#GoTo({'open_in': 'vertical-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 " Find references for tsserver and LSP
command! -bar ALEFindReferences :call ale#references#Find() 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_tab) :ALEGoToDefinitionInTab<Return>
nnoremap <silent> <Plug>(ale_go_to_definition_in_split) :ALEGoToDefinitionInSplit<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_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_find_references) :ALEFindReferences<Return>
nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return> nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return>
nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<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) \ 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): Execute(ale#lsp#message#References() should return correct messages):
AssertEqual 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 [3, 7], getpos('.')[1:2]
AssertEqual {}, ale#definition#GetMap() AssertEqual {}, ale#definition#GetMap()
Execute(tsserver completion requests should be sent): Execute(tsserver definition requests should be sent):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
call setpos('.', [bufnr(''), 2, 5, 0]) call setpos('.', [bufnr(''), 2, 5, 0])
@ -217,7 +217,7 @@ Execute(tsserver completion requests should be sent):
\ g:message_list \ g:message_list
AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() 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 runtime ale_linters/typescript/tsserver.vim
call setpos('.', [bufnr(''), 2, 5, 0]) call setpos('.', [bufnr(''), 2, 5, 0])
@ -348,7 +348,7 @@ Execute(Definition responses with null response should be handled):
AssertEqual [], g:expr_list AssertEqual [], g:expr_list
Execute(LSP completion requests should be sent): Execute(LSP definition requests should be sent):
runtime ale_linters/python/pyls.vim runtime ale_linters/python/pyls.vim
let b:ale_linters = ['pyls'] let b:ale_linters = ['pyls']
call setpos('.', [bufnr(''), 1, 5, 0]) 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() 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 runtime ale_linters/python/pyls.vim
let b:ale_linters = ['pyls'] let b:ale_linters = ['pyls']
call setpos('.', [bufnr(''), 1, 5, 0]) call setpos('.', [bufnr(''), 1, 5, 0])
@ -419,3 +455,39 @@ Execute(LSP tab completion requests should be sent):
\ g:message_list \ g:message_list
AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() 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()