]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-lsp/autoload/lsp/ui/vim.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:

Do not set EDITOR/VISUAL for shell
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / ui / vim.vim
1 function! s:not_supported(what) abort
2     return lsp#utils#error(printf("%s not supported for filetype '%s'", a:what, &filetype))
3 endfunction
4
5 function! lsp#ui#vim#implementation(in_preview, ...) abort
6     let l:ctx = { 'in_preview': a:in_preview }
7     if a:0
8         let l:ctx['mods'] = a:1
9     endif
10     call s:list_location('implementation', l:ctx)
11 endfunction
12
13 function! lsp#ui#vim#type_definition(in_preview, ...) abort
14     let l:ctx = { 'in_preview': a:in_preview }
15     if a:0
16         let l:ctx['mods'] = a:1
17     endif
18     call s:list_location('typeDefinition', l:ctx)
19 endfunction
20
21
22 function! lsp#ui#vim#declaration(in_preview, ...) abort
23     let l:ctx = { 'in_preview': a:in_preview }
24     if a:0
25         let l:ctx['mods'] = a:1
26     endif
27     call s:list_location('declaration', l:ctx)
28 endfunction
29
30 function! lsp#ui#vim#definition(in_preview, ...) abort
31     let l:ctx = { 'in_preview': a:in_preview }
32     if a:0
33         let l:ctx['mods'] = a:1
34     endif
35     call s:list_location('definition', l:ctx)
36 endfunction
37
38 function! lsp#ui#vim#references(ctx) abort
39     let l:ctx = extend({ 'jump_if_one': 0 }, a:ctx)
40     let l:request_params = { 'context': { 'includeDeclaration': v:true } }
41     call s:list_location('references', l:ctx, l:request_params)
42 endfunction
43
44 function! lsp#ui#vim#add_tree_references() abort
45     let l:ctx = { 'add_tree': v:true }
46     call lsp#ui#vim#references(l:ctx)
47 endfunction
48
49 function! s:list_location(method, ctx, ...) abort
50     " typeDefinition => type definition
51     let l:operation = substitute(a:method, '\u', ' \l\0', 'g')
52
53     let l:capabilities_func = printf('lsp#capabilities#has_%s_provider(v:val)', substitute(l:operation, ' ', '_', 'g'))
54     let l:servers = filter(lsp#get_allowed_servers(), l:capabilities_func)
55     let l:command_id = lsp#_new_command()
56
57
58     let l:ctx = extend({ 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 1, 'mods': '', 'in_preview': 0 }, a:ctx)
59     if len(l:servers) == 0
60         call s:not_supported('Retrieving ' . l:operation)
61         return
62     endif
63
64     let l:params = {
65         \   'textDocument': lsp#get_text_document_identifier(),
66         \   'position': lsp#get_position(),
67         \ }
68     if a:0
69         call extend(l:params, a:1)
70     endif
71     for l:server in l:servers
72         call lsp#send_request(l:server, {
73             \ 'method': 'textDocument/' . a:method,
74             \ 'params': l:params,
75             \ 'on_notification': function('s:handle_location', [l:ctx, l:server, l:operation]),
76             \ })
77     endfor
78
79     echo printf('Retrieving %s ...', l:operation)
80 endfunction
81
82 function! s:rename(server, new_name, pos) abort
83     if empty(a:new_name)
84         echo '... Renaming aborted ...'
85         return
86     endif
87
88     " needs to flush existing open buffers
89     call lsp#send_request(a:server, {
90         \ 'method': 'textDocument/rename',
91         \ 'params': {
92         \   'textDocument': lsp#get_text_document_identifier(),
93         \   'position': a:pos,
94         \   'newName': a:new_name,
95         \ },
96         \ 'on_notification': function('s:handle_workspace_edit', [a:server, lsp#_last_command(), 'rename']),
97         \ })
98
99     echo ' ... Renaming ...'
100 endfunction
101
102 function! lsp#ui#vim#rename() abort
103     let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_rename_prepare_provider(v:val)')
104     let l:prepare_support = 1
105     if len(l:servers) == 0
106         let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_rename_provider(v:val)')
107         let l:prepare_support = 0
108     endif
109
110     let l:command_id = lsp#_new_command()
111
112     if len(l:servers) == 0
113         call s:not_supported('Renaming')
114         return
115     endif
116
117     " TODO: ask the user which server it should use to rename if there are multiple
118     let l:server = l:servers[0]
119
120     if l:prepare_support
121         call lsp#send_request(l:server, {
122             \ 'method': 'textDocument/prepareRename',
123             \ 'params': {
124             \   'textDocument': lsp#get_text_document_identifier(),
125             \   'position': lsp#get_position(),
126             \ },
127             \ 'on_notification': function('s:handle_rename_prepare', [l:server, l:command_id, 'rename_prepare', expand('<cword>'), lsp#get_position()]),
128             \ })
129         return
130     endif
131
132     call s:rename(l:server, input('new name: ', expand('<cword>')), lsp#get_position())
133 endfunction
134
135 function! s:stop_all_servers() abort
136     for l:server in lsp#get_server_names()
137         if !lsp#is_server_running(l:server)
138             continue
139         endif
140
141         echo 'Stopping' l:server 'server ...'
142         call lsp#stop_server(l:server)
143     endfor
144 endfunction
145
146 function! s:stop_named_server(name) abort
147     if !lsp#is_valid_server_name(a:name)
148         call lsp#utils#warning('No LSP servers named "' . a:name . '"')
149         return
150     endif
151
152     if lsp#is_server_running(a:name)
153         echo 'Stopping "' . a:name . '" server...'
154         call lsp#stop_server(a:name)
155     else
156         call lsp#utils#warning(
157             \ 'Server "' . a:name . '" is not running: '
158             \ . lsp#get_server_status(a:name)
159             \ )
160     endif
161 endfunction
162
163 function! s:stop_buffer_servers() abort
164     let l:servers = lsp#get_allowed_servers()
165     let l:servers =
166         \ filter(l:servers, {idx, name -> lsp#is_server_running(name)})
167
168     if empty(l:servers)
169         call lsp#utils#warning('No active LSP servers for the current buffer')
170         return
171     endif
172
173     for l:server in l:servers
174         echo 'Stopping "' . l:server . '" server ...'
175         call lsp#stop_server(l:server)
176     endfor
177 endfunction
178
179 function! lsp#ui#vim#stop_server(stop_all, ...) abort
180     if a:0 != 0 && a:0 != 1
181         call lsp#utils#error(
182             \ 'lsp#ui#vim#stop_server(): expected 1 optional "name" argument.'
183             \ . ' Got: "' . join(a:000, '", "') . '".')
184         return
185     endif
186     let l:stop_all = a:stop_all ==# '!'
187     let l:name = get(a:000, 0, '')
188
189     if l:stop_all
190         if !empty(l:name)
191             call lsp#utils#error(
192                 \ '"!" stops all servers: name is ignored: "' . l:name . '"')
193         endif
194
195         call s:stop_all_servers()
196         return
197     endif
198
199     if !empty(l:name)
200         call s:stop_named_server(l:name)
201         return
202     endif
203
204     call s:stop_buffer_servers()
205 endfunction
206
207 function! lsp#ui#vim#workspace_symbol(query) abort
208     let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_workspace_symbol_provider(v:val)')
209     let l:command_id = lsp#_new_command()
210
211     if len(l:servers) == 0
212         call s:not_supported('Retrieving workspace symbols')
213         return
214     endif
215
216     if !empty(a:query)
217         let l:query = a:query
218     else
219         let l:query = inputdialog('query>', '', "\<ESC>")
220         if l:query ==# "\<ESC>"
221             return
222         endif
223     endif
224
225     for l:server in l:servers
226         call lsp#send_request(l:server, {
227             \ 'method': 'workspace/symbol',
228             \ 'params': {
229             \   'query': l:query,
230             \ },
231             \ 'on_notification': function('s:handle_symbol', [l:server, l:command_id, 'workspaceSymbol']),
232             \ })
233     endfor
234
235     redraw
236     echo 'Retrieving workspace symbols ...'
237 endfunction
238
239 function! lsp#ui#vim#document_symbol() abort
240     let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_document_symbol_provider(v:val)')
241     let l:command_id = lsp#_new_command()
242
243     if len(l:servers) == 0
244         call s:not_supported('Retrieving symbols')
245         return
246     endif
247
248     for l:server in l:servers
249         call lsp#send_request(l:server, {
250             \ 'method': 'textDocument/documentSymbol',
251             \ 'params': {
252             \   'textDocument': lsp#get_text_document_identifier(),
253             \ },
254             \ 'on_notification': function('s:handle_symbol', [l:server, l:command_id, 'documentSymbol']),
255             \ })
256     endfor
257
258     echo 'Retrieving document symbols ...'
259 endfunction
260
261 function! s:handle_symbol(server, last_command_id, type, data) abort
262     if a:last_command_id != lsp#_last_command()
263         return
264     endif
265
266     if lsp#client#is_error(a:data['response'])
267         call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
268         return
269     endif
270
271     let l:list = lsp#ui#vim#utils#symbols_to_loc_list(a:server, a:data)
272
273     call lsp#ui#vim#utils#setqflist(l:list, a:type)
274
275     if empty(l:list)
276         call lsp#utils#error('No ' . a:type .' found')
277     else
278         echo 'Retrieved ' . a:type
279         botright copen
280     endif
281 endfunction
282
283 function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list, last_command_id, jump_if_one, mods, in_preview}
284     if a:ctx['last_command_id'] != lsp#_last_command()
285         return
286     endif
287
288     let a:ctx['counter'] = a:ctx['counter'] - 1
289
290     if lsp#client#is_error(a:data['response']) || !has_key(a:data['response'], 'result')
291         call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
292     else
293         let a:ctx['list'] = a:ctx['list'] + lsp#utils#location#_lsp_to_vim_list(a:data['response']['result'])
294     endif
295
296     if a:ctx['counter'] == 0
297         if empty(a:ctx['list'])
298             call lsp#utils#error('No ' . a:type .' found')
299         else
300             call lsp#utils#tagstack#_update()
301
302             let l:loc = a:ctx['list'][0]
303
304             if len(a:ctx['list']) == 1 && a:ctx['jump_if_one'] && !a:ctx['in_preview']
305                 call lsp#utils#location#_open_vim_list_item(l:loc, a:ctx['mods'])
306                 echo 'Retrieved ' . a:type
307                 redraw
308             elseif !a:ctx['in_preview']
309                 if get(a:ctx, 'add_tree', v:false)
310                     let l:qf = getqflist({'idx' : 0, 'items': []})
311                     let l:pos = l:qf.idx
312                     let l:parent = l:qf.items
313                     let l:level = count(l:parent[l:pos-1].text, g:lsp_tree_incoming_prefix)
314                     let a:ctx['list'] = extend(l:parent, map(a:ctx['list'], 'extend(v:val, {"text": repeat("' . g:lsp_tree_incoming_prefix . '", l:level+1) . v:val.text})'), l:pos)
315                 endif
316                 call lsp#ui#vim#utils#setqflist(a:ctx['list'], a:type)
317                 echo 'Retrieved ' . a:type
318                 botright copen
319                 if get(a:ctx, 'add_tree', v:false)
320                     " move the cursor to the newly added item
321                     execute l:pos + 1
322                 endif
323             else
324                 let l:lines = readfile(l:loc['filename'])
325                 if has_key(l:loc,'viewstart') " showing a locationLink
326                     let l:view = l:lines[l:loc['viewstart'] : l:loc['viewend']]
327                     call lsp#ui#vim#output#preview(a:server, l:view, {
328                         \   'statusline': ' LSP Peek ' . a:type,
329                         \   'filetype': &filetype
330                         \ })
331                 else " showing a location
332                     call lsp#ui#vim#output#preview(a:server, l:lines, {
333                         \   'statusline': ' LSP Peek ' . a:type,
334                         \   'cursor': { 'line': l:loc['lnum'], 'col': l:loc['col'], 'align': g:lsp_peek_alignment },
335                         \   'filetype': &filetype
336                         \ })
337                 endif
338             endif
339         endif
340     endif
341 endfunction
342
343 function! s:handle_rename_prepare(server, last_command_id, type, cword, position, data) abort
344     if a:last_command_id != lsp#_last_command()
345         return
346     endif
347
348     if lsp#client#is_error(a:data['response'])
349         call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
350         return
351     endif
352     let l:result = a:data['response']['result']
353
354     " Check response: null.
355     if empty(l:result)
356         echo 'The ' . a:server . ' returns for ' . a:type . ' (The rename request may be invalid at the given position).'
357         return
358     endif
359
360     " Check response: { defaultBehavior: boolean }.
361     if has_key(l:result, 'defaultBehavior')
362         call timer_start(1, {x->s:rename(a:server, input('new name: ', a:cword), a:position)})
363         return
364     endif
365
366     " Check response: { placeholder: string }
367     if has_key(l:result, 'placeholder') && !empty(l:result['placeholder'])
368         call timer_start(1, {x->s:rename(a:server, input('new name: ', a:cword), a:position)})
369         return
370     endif
371
372     " Check response: { range: Range } | Range
373     let l:range = get(l:result, 'range', l:result)
374     let l:lines = getline(1, '$')
375     let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim('%', l:range['start'])
376     let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim('%', l:range['end'])
377     if l:start_line ==# l:end_line
378         let l:name = l:lines[l:start_line - 1][l:start_col - 1 : l:end_col - 2]
379     else
380         let l:name = l:lines[l:start_line - 1][l:start_col - 1 :]
381         for l:i in range(l:start_line, l:end_line - 2)
382             let l:name .= "\n" . l:lines[l:i]
383         endfor
384         if l:end_col - 2 < 0
385             let l:name .= "\n"
386         else
387             let l:name .= l:lines[l:end_line - 1][: l:end_col - 2]
388         endif
389     endif
390
391     call timer_start(1, {x->s:rename(a:server, input('new name: ', l:name), l:range['start'])})
392 endfunction
393
394 function! s:handle_workspace_edit(server, last_command_id, type, data) abort
395     if a:last_command_id != lsp#_last_command()
396         return
397     endif
398
399     if lsp#client#is_error(a:data['response'])
400         call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
401         return
402     endif
403
404     call lsp#utils#workspace_edit#apply_workspace_edit(a:data['response']['result'])
405
406     echo 'Renamed'
407 endfunction
408
409 function! s:handle_text_edit(server, last_command_id, type, data) abort
410     if a:last_command_id != lsp#_last_command()
411         return
412     endif
413
414     if lsp#client#is_error(a:data['response'])
415         call lsp#utils#error('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
416         return
417     endif
418
419     call lsp#utils#text_edit#apply_text_edits(a:data['request']['params']['textDocument']['uri'], a:data['response']['result'])
420
421     redraw | echo 'Document formatted'
422 endfunction
423
424 function! lsp#ui#vim#code_action(opts) abort
425     call lsp#ui#vim#code_action#do(extend({
426         \   'sync': v:false,
427         \   'selection': v:false,
428         \   'query': '',
429         \ }, a:opts))
430 endfunction
431
432 function! lsp#ui#vim#code_lens() abort
433     call lsp#ui#vim#code_lens#do({
434         \   'sync': v:false,
435         \ })
436 endfunction
437
438 function! lsp#ui#vim#add_tree_call_hierarchy_incoming() abort
439     let l:ctx = { 'add_tree': v:true }
440     call lsp#ui#vim#call_hierarchy_incoming(l:ctx)
441 endfunction
442
443 function! lsp#ui#vim#call_hierarchy_incoming(ctx) abort
444     let l:ctx = extend({ 'method': 'incomingCalls', 'key': 'from' }, a:ctx)
445     call s:prepare_call_hierarchy(l:ctx)
446 endfunction
447
448 function! lsp#ui#vim#call_hierarchy_outgoing() abort
449     let l:ctx = { 'method': 'outgoingCalls', 'key': 'to' }
450     call s:prepare_call_hierarchy(l:ctx)
451 endfunction
452
453 function! s:prepare_call_hierarchy(ctx) abort
454     let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_call_hierarchy_provider(v:val)')
455     let l:command_id = lsp#_new_command()
456
457     let l:ctx = extend({ 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id }, a:ctx)
458     if len(l:servers) == 0
459         call s:not_supported('Retrieving call hierarchy')
460         return
461     endif
462
463     for l:server in l:servers
464         call lsp#send_request(l:server, {
465             \ 'method': 'textDocument/prepareCallHierarchy',
466             \ 'params': {
467             \   'textDocument': lsp#get_text_document_identifier(),
468             \   'position': lsp#get_position(),
469             \ },
470             \ 'on_notification': function('s:handle_prepare_call_hierarchy', [l:ctx, l:server, 'prepare_call_hierarchy']),
471             \ })
472     endfor
473
474     echo 'Preparing call hierarchy ...'
475 endfunction
476
477 function! s:handle_prepare_call_hierarchy(ctx, server, type, data) abort
478     if a:ctx['last_command_id'] != lsp#_last_command()
479         return
480     endif
481
482     if lsp#client#is_error(a:data['response']) || !has_key(a:data['response'], 'result')
483         call lsp#utils#error('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
484         return
485     endif
486     if empty(a:data['response']['result'])
487         call lsp#utils#warning('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
488         return
489     endif
490
491     for l:item in a:data['response']['result']
492         call s:call_hierarchy(a:ctx, a:server, l:item)
493     endfor
494 endfunction
495
496 function! s:call_hierarchy(ctx, server, item) abort
497     call lsp#send_request(a:server, {
498         \ 'method': 'callHierarchy/' . a:ctx['method'],
499         \ 'params': {
500         \   'item': a:item,
501         \ },
502         \ 'on_notification': function('s:handle_call_hierarchy', [a:ctx, a:server, 'call_hierarchy']),
503         \ })
504 endfunction
505
506 function! s:handle_call_hierarchy(ctx, server, type, data) abort
507     if a:ctx['last_command_id'] != lsp#_last_command()
508         return
509     endif
510
511     let a:ctx['counter'] = a:ctx['counter'] - 1
512
513     if lsp#client#is_error(a:data['response']) || !has_key(a:data['response'], 'result')
514         call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
515     elseif a:data['response']['result'] isnot v:null
516         for l:item in a:data['response']['result']
517             let l:loc = s:hierarchy_item_to_vim(l:item[a:ctx['key']], a:server)
518             if l:loc isnot v:null
519                 let a:ctx['list'] += [l:loc]
520             endif
521         endfor
522     endif
523
524     if a:ctx['counter'] == 0
525         if empty(a:ctx['list'])
526             call lsp#utils#error('No ' . a:type .' found')
527         else
528             call lsp#utils#tagstack#_update()
529             if get(a:ctx, 'add_tree', v:false)
530                 let l:qf = getqflist({'idx' : 0, 'items': []})
531                 let l:pos = l:qf.idx
532                 let l:parent = l:qf.items
533                 let l:level = count(l:parent[l:pos-1].text, g:lsp_tree_incoming_prefix)
534                 let a:ctx['list'] = extend(l:parent, map(a:ctx['list'], 'extend(v:val, {"text": repeat("' . g:lsp_tree_incoming_prefix . '", l:level+1) . v:val.text})'), l:pos)
535             endif
536             call lsp#ui#vim#utils#setqflist(a:ctx['list'], a:type)
537             echo 'Retrieved ' . a:type
538             botright copen
539             if get(a:ctx, 'add_tree', v:false)
540                 " move the cursor to the newly added item
541                 execute l:pos + 1
542             endif
543         endif
544     endif
545 endfunction
546
547 function! s:hierarchy_item_to_vim(item, server) abort
548     let l:uri = a:item['uri']
549     if !lsp#utils#is_file_uri(l:uri)
550         return v:null
551     endif
552
553     let l:path = lsp#utils#uri_to_path(l:uri)
554     let [l:line, l:col] = lsp#utils#position#lsp_to_vim(l:path, a:item['range']['start'])
555     let l:text = '[' . lsp#ui#vim#utils#_get_symbol_text_from_kind(a:server, a:item['kind']) . '] ' . a:item['name']
556     if has_key(a:item, 'detail')
557         let l:text .= ": " . a:item['detail']
558     endif
559
560     return {
561         \ 'filename': l:path,
562         \ 'lnum': l:line,
563         \ 'col': l:col,
564         \ 'text': l:text,
565         \ }
566 endfunction