]> git.madduck.net Git - etc/vim.git/blob - autoload/ale/definition.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/ale/' content from commit 22185c4c
[etc/vim.git] / autoload / ale / definition.vim
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Go to definition support for LSP linters.
3
4 let s:go_to_definition_map = {}
5
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')
9
10 " Used to get the definition map in tests.
11 function! ale#definition#GetMap() abort
12     return deepcopy(s:go_to_definition_map)
13 endfunction
14
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
18 endfunction
19
20 function! ale#definition#ClearLSPData() abort
21     let s:go_to_definition_map = {}
22 endfunction
23
24 function! ale#definition#UpdateTagStack() abort
25     let l:should_update_tagstack = exists('*gettagstack') && exists('*settagstack') && g:ale_update_tagstack
26
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})
35     endif
36 endfunction
37
38 function! ale#definition#FormatTSServerResponse(response_item, options) abort
39     if get(a:options, 'open_in') is# 'quickfix'
40         return {
41         \ 'filename': a:response_item.file,
42         \ 'lnum': a:response_item.start.line,
43         \ 'col': a:response_item.start.offset,
44         \}
45     else
46         return {
47         \ 'filename': a:response_item.file,
48         \ 'line': a:response_item.start.line,
49         \ 'column': a:response_item.start.offset,
50         \}
51     endif
52 endfunction
53
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)
58
59         if get(a:response, 'success', v:false) is v:true && !empty(a:response.body)
60             let l:item_list = []
61
62             for l:response_item in a:response.body
63                 call add(
64                 \ l:item_list,
65                 \ ale#definition#FormatTSServerResponse(l:response_item, l:options)
66                 \)
67             endfor
68
69             if empty(l:item_list)
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
73
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
77                 else
78                     let l:line = l:item_list[0].line
79                     let l:column = l:item_list[0].column
80                 endif
81
82                 call ale#definition#UpdateTagStack()
83                 call ale#util#Open(l:filename, l:line, l:column, l:options)
84             else
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')
89                 else
90                     call ale#definition#UpdateTagStack()
91                     call ale#preview#ShowSelection(l:item_list, l:options)
92                 endif
93             endif
94         endif
95     endif
96 endfunction
97
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
104     else
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
109     endif
110
111     if get(a:options, 'open_in') is# 'quickfix'
112         return {
113         \ 'filename': ale#util#ToResource(l:uri),
114         \ 'lnum': l:line,
115         \ 'col': l:column,
116         \}
117     else
118         return {
119         \ 'filename': ale#util#ToResource(l:uri),
120         \ 'line': l:line,
121         \ 'column': l:column,
122         \}
123     endif
124 endfunction
125
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)
130
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)
133
134         if type(l:result) is v:t_dict
135             let l:result = [l:result]
136         elseif type(l:result) isnot v:t_list
137             let l:result = []
138         endif
139
140         let l:item_list = []
141
142         for l:response_item in l:result
143             call add(l:item_list,
144             \ ale#definition#FormatLSPResponse(l:response_item, l:options)
145             \)
146         endfor
147
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()
152
153             let l:uri = ale#util#ToURI(l:item_list[0].filename)
154
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
158             else
159                 let l:line = l:item_list[0].line
160                 let l:column = l:item_list[0].column
161             endif
162
163             let l:uri_handler = ale#uri#GetURIHandler(l:uri)
164
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)
168             else
169                 call l:uri_handler.OpenURILink(l:uri, l:line, l:column, l:options, a:conn_id)
170             endif
171         else
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')
176             else
177                 call ale#definition#UpdateTagStack()
178                 call ale#preview#ShowSelection(l:item_list, l:options)
179             endif
180         endif
181     endif
182 endfunction
183
184 function! s:OnReady(line, column, options, capability, linter, lsp_details) abort
185     let l:id = a:lsp_details.connection_id
186
187     if !ale#lsp#HasCapability(l:id, a:capability)
188         return
189     endif
190
191     let l:buffer = a:lsp_details.buffer
192
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)
197
198     if a:linter.lsp is# 'tsserver'
199         if a:capability is# 'definition'
200             let l:message = ale#lsp#tsserver_message#Definition(
201             \   l:buffer,
202             \   a:line,
203             \   a:column
204             \)
205         elseif a:capability is# 'typeDefinition'
206             let l:message = ale#lsp#tsserver_message#TypeDefinition(
207             \   l:buffer,
208             \   a:line,
209             \   a:column
210             \)
211         elseif a:capability is# 'implementation'
212             let l:message = ale#lsp#tsserver_message#Implementation(
213             \   l:buffer,
214             \   a:line,
215             \   a:column
216             \)
217         endif
218     else
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)
222
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
225         " this correctly.
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)
232         else
233             " XXX: log here?
234             return
235         endif
236     endif
237
238     let l:request_id = ale#lsp#Send(l:id, l:message)
239
240     let s:go_to_definition_map[l:request_id] = {
241     \   'open_in': get(a:options, 'open_in', 'current-buffer'),
242     \}
243 endfunction
244
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))])
249
250     let l:Callback = function(
251     \   's:OnReady',
252     \   [l:line, l:column, a:options, a:capability]
253     \)
254     call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
255 endfunction
256
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')
260     endfor
261 endfunction
262
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')
266     endfor
267 endfunction
268
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')
272     endfor
273 endfunction
274
275 function! ale#definition#GoToCommandHandler(command, ...) abort
276     let l:options = {}
277
278     if len(a:000) > 0
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'
286             endif
287         endfor
288     endif
289
290     if !has_key(l:options, 'open_in')
291         let l:default_navigation = ale#Var(bufnr(''), 'default_navigation')
292
293         if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0
294             let l:options.open_in = l:default_navigation
295         endif
296     endif
297
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)
302     else
303         call ale#definition#GoTo(l:options)
304     endif
305 endfunction