All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Go to definition support for LSP linters.
4 let s:go_to_definition_map = {}
6 " Enable automatic updates of the tagstack
7 let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', v:true)
8 let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer')
10 " Used to get the definition map in tests.
11 function! ale#definition#GetMap() abort
12 return deepcopy(s:go_to_definition_map)
15 " Used to set the definition map in tests.
16 function! ale#definition#SetMap(map) abort
17 let s:go_to_definition_map = a:map
20 function! ale#definition#ClearLSPData() abort
21 let s:go_to_definition_map = {}
24 function! ale#definition#UpdateTagStack() abort
25 let l:should_update_tagstack = exists('*gettagstack') && exists('*settagstack') && g:ale_update_tagstack
27 if l:should_update_tagstack
28 " Grab the old location (to jump back to) and the word under the
29 " cursor (as a label for the tagstack)
30 let l:old_location = [bufnr('%'), line('.'), col('.'), 0]
31 let l:tagname = expand('<cword>')
32 let l:winid = win_getid()
33 call settagstack(l:winid, {'items': [{'from': l:old_location, 'tagname': l:tagname}]}, 'a')
34 call settagstack(l:winid, {'curidx': len(gettagstack(l:winid)['items']) + 1})
38 function! ale#definition#FormatTSServerResponse(response_item, options) abort
39 if get(a:options, 'open_in') is# 'quickfix'
41 \ 'filename': a:response_item.file,
42 \ 'lnum': a:response_item.start.line,
43 \ 'col': a:response_item.start.offset,
47 \ 'filename': a:response_item.file,
48 \ 'line': a:response_item.start.line,
49 \ 'column': a:response_item.start.offset,
54 function! ale#definition#HandleTSServerResponse(conn_id, response) abort
55 if has_key(a:response, 'request_seq')
56 \&& has_key(s:go_to_definition_map, a:response.request_seq)
57 let l:options = remove(s:go_to_definition_map, a:response.request_seq)
59 if get(a:response, 'success', v:false) is v:true && !empty(a:response.body)
62 for l:response_item in a:response.body
65 \ ale#definition#FormatTSServerResponse(l:response_item, l:options)
70 call ale#util#Execute('echom ''No definitions found''')
71 elseif len(l:item_list) == 1
72 let l:filename = l:item_list[0].filename
74 if get(l:options, 'open_in') is# 'quickfix'
75 let l:line = l:item_list[0].lnum
76 let l:column = l:item_list[0].col
78 let l:line = l:item_list[0].line
79 let l:column = l:item_list[0].column
82 call ale#definition#UpdateTagStack()
83 call ale#util#Open(l:filename, l:line, l:column, l:options)
85 if get(l:options, 'open_in') is# 'quickfix'
86 call setqflist([], 'r')
87 call setqflist(l:item_list, 'a')
88 call ale#util#Execute('cc 1')
90 call ale#definition#UpdateTagStack()
91 call ale#preview#ShowSelection(l:item_list, l:options)
98 function! ale#definition#FormatLSPResponse(response_item, options) abort
99 if has_key(a:response_item, 'targetUri')
100 " LocationLink items use targetUri
101 let l:uri = a:response_item.targetUri
102 let l:line = a:response_item.targetRange.start.line + 1
103 let l:column = a:response_item.targetRange.start.character + 1
105 " LocationLink items use uri
106 let l:uri = a:response_item.uri
107 let l:line = a:response_item.range.start.line + 1
108 let l:column = a:response_item.range.start.character + 1
111 if get(a:options, 'open_in') is# 'quickfix'
113 \ 'filename': ale#util#ToResource(l:uri),
119 \ 'filename': ale#util#ToResource(l:uri),
121 \ 'column': l:column,
126 function! ale#definition#HandleLSPResponse(conn_id, response) abort
127 if has_key(a:response, 'id')
128 \&& has_key(s:go_to_definition_map, a:response.id)
129 let l:options = remove(s:go_to_definition_map, a:response.id)
131 " The result can be a Dictionary item, a List of the same, or null.
132 let l:result = get(a:response, 'result', v:null)
134 if type(l:result) is v:t_dict
135 let l:result = [l:result]
136 elseif type(l:result) isnot v:t_list
142 for l:response_item in l:result
143 call add(l:item_list,
144 \ ale#definition#FormatLSPResponse(l:response_item, l:options)
148 if empty(l:item_list)
149 call ale#util#Execute('echom ''No definitions found''')
150 elseif len(l:item_list) == 1
151 call ale#definition#UpdateTagStack()
153 let l:uri = ale#util#ToURI(l:item_list[0].filename)
155 if get(l:options, 'open_in') is# 'quickfix'
156 let l:line = l:item_list[0].lnum
157 let l:column = l:item_list[0].col
159 let l:line = l:item_list[0].line
160 let l:column = l:item_list[0].column
163 let l:uri_handler = ale#uri#GetURIHandler(l:uri)
165 if l:uri_handler is# v:null
166 let l:filename = ale#path#FromFileURI(l:uri)
167 call ale#util#Open(l:filename, l:line, l:column, l:options)
169 call l:uri_handler.OpenURILink(l:uri, l:line, l:column, l:options, a:conn_id)
172 if get(l:options, 'open_in') is# 'quickfix'
173 call setqflist([], 'r')
174 call setqflist(l:item_list, 'a')
175 call ale#util#Execute('cc 1')
177 call ale#definition#UpdateTagStack()
178 call ale#preview#ShowSelection(l:item_list, l:options)
184 function! s:OnReady(line, column, options, capability, linter, lsp_details) abort
185 let l:id = a:lsp_details.connection_id
187 if !ale#lsp#HasCapability(l:id, a:capability)
191 let l:buffer = a:lsp_details.buffer
193 let l:Callback = a:linter.lsp is# 'tsserver'
194 \ ? function('ale#definition#HandleTSServerResponse')
195 \ : function('ale#definition#HandleLSPResponse')
196 call ale#lsp#RegisterCallback(l:id, l:Callback)
198 if a:linter.lsp is# 'tsserver'
199 if a:capability is# 'definition'
200 let l:message = ale#lsp#tsserver_message#Definition(
205 elseif a:capability is# 'typeDefinition'
206 let l:message = ale#lsp#tsserver_message#TypeDefinition(
211 elseif a:capability is# 'implementation'
212 let l:message = ale#lsp#tsserver_message#Implementation(
219 " Send a message saying the buffer has changed first, or the
220 " definition position probably won't make sense.
221 call ale#lsp#NotifyForChanges(l:id, l:buffer)
223 " For LSP completions, we need to clamp the column to the length of
224 " the line. python-language-server and perhaps others do not implement
226 if a:capability is# 'definition'
227 let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column)
228 elseif a:capability is# 'typeDefinition'
229 let l:message = ale#lsp#message#TypeDefinition(l:buffer, a:line, a:column)
230 elseif a:capability is# 'implementation'
231 let l:message = ale#lsp#message#Implementation(l:buffer, a:line, a:column)
238 let l:request_id = ale#lsp#Send(l:id, l:message)
240 let s:go_to_definition_map[l:request_id] = {
241 \ 'open_in': get(a:options, 'open_in', 'current-buffer'),
245 function! s:GoToLSPDefinition(linter, options, capability) abort
246 let l:buffer = bufnr('')
247 let [l:line, l:column] = getpos('.')[1:2]
248 let l:column = min([l:column, len(getline(l:line))])
250 let l:Callback = function(
252 \ [l:line, l:column, a:options, a:capability]
254 call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
257 function! ale#definition#GoTo(options) abort
258 for l:linter in ale#lsp_linter#GetEnabled(bufnr(''))
259 call s:GoToLSPDefinition(l:linter, a:options, 'definition')
263 function! ale#definition#GoToType(options) abort
264 for l:linter in ale#lsp_linter#GetEnabled(bufnr(''))
265 call s:GoToLSPDefinition(l:linter, a:options, 'typeDefinition')
269 function! ale#definition#GoToImpl(options) abort
270 for l:linter in ale#lsp_linter#GetEnabled(bufnr(''))
271 call s:GoToLSPDefinition(l:linter, a:options, 'implementation')
275 function! ale#definition#GoToCommandHandler(command, ...) abort
279 for l:option in a:000
280 if l:option is? '-tab'
281 let l:options.open_in = 'tab'
282 elseif l:option is? '-split'
283 let l:options.open_in = 'split'
284 elseif l:option is? '-vsplit'
285 let l:options.open_in = 'vsplit'
290 if !has_key(l:options, 'open_in')
291 let l:default_navigation = ale#Var(bufnr(''), 'default_navigation')
293 if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0
294 let l:options.open_in = l:default_navigation
298 if a:command is# 'type'
299 call ale#definition#GoToType(l:options)
300 elseif a:command is# 'implementation'
301 call ale#definition#GoToImpl(l:options)
303 call ale#definition#GoTo(l:options)