]> git.madduck.net Git - etc/vim.git/blob - autoload/lsp/internal/semantic.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/vim-lsp/' content from commit 04428c92
[etc/vim.git] / autoload / lsp / internal / semantic.vim
1 let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim')
2 let s:use_nvim_highlight = lsp#utils#_has_nvim_buf_highlight()
3 let s:textprop_cache = 'vim-lsp-semantic-cache'
4
5 if s:use_nvim_highlight
6     let s:namespace_id = nvim_create_namespace('vim-lsp-semantic')
7 endif
8
9 " Global functions {{{1
10 function! lsp#internal#semantic#is_enabled() abort
11     return g:lsp_semantic_enabled && (s:use_vim_textprops || s:use_nvim_highlight) ? v:true : v:false
12 endfunction
13
14 function! lsp#internal#semantic#_enable() abort
15     if !lsp#internal#semantic#is_enabled() | return | endif
16
17     augroup lsp#internal#semantic
18         autocmd!
19         au User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
20     augroup END
21
22     let l:events = [['User', 'lsp_buffer_enabled'], 'TextChanged', 'TextChangedI']
23     if exists('##TextChangedP')
24         call add(l:events, 'TextChangedP')
25     endif
26     let s:Dispose = lsp#callbag#pipe(
27         \     lsp#callbag#fromEvent(l:events),
28         \     lsp#callbag#filter({_->lsp#internal#semantic#is_enabled()}),
29         \     lsp#callbag#debounceTime(g:lsp_semantic_delay),
30         \     lsp#callbag#filter({_->index(['help', 'terminal', 'prompt', 'popup'], getbufvar(bufnr('%'), '&buftype')) ==# -1}),
31         \     lsp#callbag#filter({_->!lsp#utils#is_large_window(win_getid())}),
32         \     lsp#callbag#switchMap({_->
33         \         lsp#callbag#pipe(
34         \             s:semantic_request(),
35         \             lsp#callbag#materialize(),
36         \             lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
37         \             lsp#callbag#map({x->x['value']})
38         \         )
39         \     }),
40         \     lsp#callbag#subscribe({x->s:handle_semantic_request(x)})
41         \ )
42 endfunction
43
44 function! lsp#internal#semantic#_disable() abort
45     augroup lsp#internal#semantic
46         autocmd!
47     augroup END
48
49     if exists('s:Dispose')
50         call s:Dispose()
51         unlet s:Dispose
52     endif
53 endfunction
54
55 function! lsp#internal#semantic#get_legend(server) abort
56     if !lsp#capabilities#has_semantic_tokens(a:server)
57         return {'tokenTypes': [], 'tokenModifiers': []}
58     endif
59
60     let l:capabilities = lsp#get_server_capabilities(a:server)
61     return l:capabilities['semanticTokensProvider']['legend']
62 endfunction
63
64 function! lsp#internal#semantic#get_token_types() abort
65     let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)'
66     let l:servers = filter(lsp#get_allowed_servers(), l:capability)
67
68     if empty(l:servers)
69         return []
70     endif
71
72     let l:legend = lsp#internal#semantic#get_legend(l:servers[0])
73     let l:token_types = l:legend['tokenTypes']
74     call map(l:token_types, {_, type -> toupper(type[0]) . type[1:]})
75     return l:token_types
76 endfunction
77
78 function! lsp#internal#semantic#get_token_modifiers() abort
79     let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)'
80     let l:servers = filter(lsp#get_allowed_servers(), l:capability)
81
82     if empty(l:servers)
83         return []
84     endif
85
86     let l:legend = lsp#internal#semantic#get_legend(l:servers[0])
87     let l:token_modifiers = l:legend['tokenModifiers']
88     call map(l:token_modifiers, {_, modifier -> toupper(modifier[0]) . modifier[1:]})
89     return l:token_modifiers
90 endfunction
91
92 function! s:on_lsp_buffer_enabled() abort
93     augroup lsp#internal#semantic
94         if !exists('#BufUnload#<buffer>')
95             execute 'au BufUnload <buffer> call setbufvar(' . bufnr() . ', ''lsp_semantic_previous_result_id'', '''')'
96         endif
97     augroup END
98 endfunction
99
100 function! s:supports_full_semantic_request(server) abort
101     if !lsp#capabilities#has_semantic_tokens(a:server)
102         return v:false
103     endif
104
105     let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
106     if !has_key(l:capabilities, 'full')
107         return v:false
108     endif
109
110     if type(l:capabilities['full']) ==# v:t_dict
111         return v:true
112     endif
113
114     return l:capabilities['full']
115 endfunction
116
117 function! s:supports_delta_semantic_request(server) abort
118     if !lsp#capabilities#has_semantic_tokens(a:server)
119         return v:false
120     endif
121
122     let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
123     if !has_key(l:capabilities, 'full')
124         return v:false
125     endif
126
127     if type(l:capabilities['full']) !=# v:t_dict
128         return v:false
129     endif
130
131     if !has_key(l:capabilities['full'], 'delta')
132         return v:false
133     endif
134
135     return l:capabilities['full']['delta']
136 endfunction
137
138 function! s:get_server() abort
139     let l:capability = 's:supports_delta_semantic_request(v:val)'
140     let l:servers = filter(lsp#get_allowed_servers(), l:capability)
141     if empty(l:servers)
142         let l:capability = 's:supports_full_semantic_request(v:val)'
143         let l:servers = filter(lsp#get_allowed_servers(), l:capability)
144     endif
145     if empty(l:servers)
146         return ''
147     endif
148     return l:servers[0]
149 endfunction
150
151 function! s:semantic_request() abort
152     let l:server = s:get_server()
153     if l:server ==# ''
154         return lsp#callbag#empty()
155     endif
156
157     if (s:supports_delta_semantic_request(l:server)
158         \ && getbufvar(bufnr(), 'lsp_semantic_previous_result_id') !=# '')
159         return s:delta_semantic_request(l:server)
160     else
161         return s:full_semantic_request(l:server)
162     endif
163 endfunction
164
165 function! s:full_semantic_request(server) abort
166     return lsp#request(a:server, {
167         \ 'method': 'textDocument/semanticTokens/full',
168         \ 'params': {
169         \     'textDocument': lsp#get_text_document_identifier()
170         \ }})
171 endfunction
172
173 function! s:delta_semantic_request(server) abort
174     return lsp#request(a:server, {
175         \ 'method': 'textDocument/semanticTokens/full/delta',
176         \ 'params': {
177         \     'textDocument': lsp#get_text_document_identifier(),
178         \     'previousResultId': getbufvar(bufname(), 'lsp_semantic_previous_result_id', 0)
179         \ }})
180 endfunction
181
182 " Highlight helper functions {{{1
183 function! s:handle_semantic_request(data) abort
184     if lsp#client#is_error(a:data['response'])
185         call lsp#log('Skipping semantic highlight: response is invalid')
186         return
187     endif
188
189     let l:server = a:data['server_name']
190     let l:uri = a:data['request']['params']['textDocument']['uri']
191     let l:path = lsp#utils#uri_to_path(l:uri)
192     let l:bufnr = bufnr(l:path)
193
194     " Skip if the buffer doesn't exist. This might happen when a buffer is
195     " opened and quickly deleted.
196     if !bufloaded(l:bufnr) | return | endif
197
198     if has_key(a:data['response']['result'], 'data')
199         call s:handle_semantic_tokens_response(l:server, l:bufnr, a:data['response']['result'])
200     elseif has_key(a:data['response']['result'], 'edits')
201         call s:handle_semantic_tokens_delta_response(l:server, l:bufnr, a:data['response']['result'])
202     else
203         " Don't update previous result ID if we could not update local copy
204         call lsp#log('SemanticHighlight: unsupported semanticTokens method')
205         return
206     endif
207
208     if has_key(a:data['response']['result'], 'resultId')
209         call setbufvar(l:bufnr, 'lsp_semantic_previous_result_id', a:data['response']['result']['resultId'])
210     endif
211 endfunction
212
213 function! s:handle_semantic_tokens_response(server, buf, result) abort
214     let l:highlights = {}
215     let l:legend = lsp#internal#semantic#get_legend(a:server)
216     for l:token in s:decode_tokens(a:result['data'])
217         let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token)
218         let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value
219     endfor
220     call s:apply_highlights(a:server, a:buf, l:highlights)
221
222     call setbufvar(a:buf, 'lsp_semantic_local_data', a:result['data'])
223 endfunction
224
225 function! s:startpos_compare(edit1, edit2) abort
226     return a:edit1[0] == a:edit2[0] ? 0 : a:edit1[0] > a:edit2[0] ? -1 : 1
227 endfunction
228
229 function! s:handle_semantic_tokens_delta_response(server, buf, result) abort
230     " The locations given in the edit are all referenced to the state before
231     " any are applied and sorting is not required from the server,
232     " therefore the edits must be sorted before applying.
233     let l:edits = a:result['edits']
234     call sort(l:edits, function('s:startpos_compare'))
235
236     let l:localdata = getbufvar(a:buf, 'lsp_semantic_local_data')
237     for l:edit in l:edits
238         let l:insertdata = get(l:edit, 'data', [])
239         let l:localdata = l:localdata[:l:edit['start'] - 1]
240                       \ + l:insertdata
241                       \ + l:localdata[l:edit['start'] + l:edit['deleteCount']:]
242     endfor
243     call setbufvar(a:buf, 'lsp_semantic_local_data', l:localdata)
244
245     let l:highlights = {}
246     let l:legend = lsp#internal#semantic#get_legend(a:server)
247     for l:token in s:decode_tokens(l:localdata)
248         let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token)
249         let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value
250     endfor
251     call s:apply_highlights(a:server, a:buf, l:highlights)
252 endfunction
253
254 function! s:decode_tokens(data) abort
255     let l:tokens = []
256
257     let l:i = 0
258     let l:line = 0
259     let l:char = 0
260     while l:i < len(a:data)
261         let l:line = l:line + a:data[l:i]
262         if a:data[l:i] > 0
263             let l:char = 0
264         endif
265         let l:char = l:char + a:data[l:i + 1]
266
267         call add(l:tokens, {
268             \     'pos': {'line': l:line, 'character': l:char},
269             \     'length': a:data[l:i + 2],
270             \     'token_idx': a:data[l:i + 3],
271             \     'token_modifiers': a:data[l:i + 4]
272             \ })
273
274         let l:i = l:i + 5
275     endwhile
276
277     return l:tokens
278 endfunction
279
280 function! s:clear_highlights(server, buf) abort
281     if s:use_vim_textprops
282         let l:BeginsWith = {str, prefix -> str[0:len(prefix) - 1] ==# prefix}
283         let l:IsSemanticTextprop = {_, textprop -> l:BeginsWith(textprop, s:textprop_type_prefix)}
284         let l:textprop_types = prop_type_list()
285         call filter(l:textprop_types, l:IsSemanticTextprop)
286         for l:textprop_type in l:textprop_types
287             silent! call prop_remove({'type': l:textprop_type, 'bufnr': a:buf, 'all': v:true})
288         endfor
289     elseif s:use_nvim_highlight
290         call nvim_buf_clear_namespace(a:buf, s:namespace_id, 0, line('$'))
291     endif
292 endfunction
293
294 function! s:add_highlight(server, legend, buf, token) abort
295     let l:startpos = lsp#utils#position#lsp_to_vim(a:buf, a:token['pos'])
296     let l:endpos = a:token['pos']
297     let l:endpos['character'] = l:endpos['character'] + a:token['length']
298     let l:endpos = lsp#utils#position#lsp_to_vim(a:buf, l:endpos)
299
300     if s:use_vim_textprops
301         let l:textprop_name = s:get_textprop_type(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers'])
302          return [l:textprop_name, [[l:startpos[0], l:startpos[1], l:endpos[0], l:endpos[1]]]]
303     elseif s:use_nvim_highlight
304         let l:char = a:token['pos']['character']
305         let l:hl_name = s:get_hl_group(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers'])
306         return [l:hl_name, [[l:startpos[0] - 1, l:startpos[1] - 1, l:endpos[1] - 1]]]
307     endif
308 endfunction
309
310 function! s:apply_highlights(server, buf, highlights) abort
311     call s:clear_highlights(a:server, a:buf)
312
313     if s:use_vim_textprops
314         for [l:type, l:prop_list] in items(a:highlights)
315             call prop_add_list({'type': l:type, 'bufnr': a:buf}, l:prop_list)
316         endfor
317     elseif s:use_nvim_highlight
318         call lsp#log(a:highlights)
319         for [l:hl_name, l:instances] in items(a:highlights)
320             for l:instance in l:instances
321                 let [l:line, l:startcol, l:endcol] = l:instance
322                 try
323                     call nvim_buf_add_highlight(a:buf, s:namespace_id, l:hl_name, l:line, l:startcol, l:endcol)
324                 catch
325                     call lsp#log('SemanticHighlight: error while adding ' . l:hl_name . ' highlight on line ' . l:line)
326                 endtry
327             endfor
328         endfor
329     end
330 endfunction
331
332 let s:hl_group_prefix = 'LspSemantic'
333
334 let s:default_highlight_groups = {
335     \ s:hl_group_prefix . 'Type': 'Type',
336     \ s:hl_group_prefix . 'Class': 'Type',
337     \ s:hl_group_prefix . 'Enum': 'Type',
338     \ s:hl_group_prefix . 'Interface': 'TypeDef',
339     \ s:hl_group_prefix . 'Struct': 'Type',
340     \ s:hl_group_prefix . 'TypeParameter': 'Type',
341     \ s:hl_group_prefix . 'Parameter': 'Identifier',
342     \ s:hl_group_prefix . 'Variable': 'Identifier',
343     \ s:hl_group_prefix . 'Property': 'Identifier',
344     \ s:hl_group_prefix . 'EnumMember': 'Constant',
345     \ s:hl_group_prefix . 'Event': 'Identifier',
346     \ s:hl_group_prefix . 'Function': 'Function',
347     \ s:hl_group_prefix . 'Method': 'Function',
348     \ s:hl_group_prefix . 'Macro': 'Macro',
349     \ s:hl_group_prefix . 'Keyword': 'Keyword',
350     \ s:hl_group_prefix . 'Modifier': 'Type',
351     \ s:hl_group_prefix . 'Comment': 'Comment',
352     \ s:hl_group_prefix . 'String': 'String',
353     \ s:hl_group_prefix . 'Number': 'Number',
354     \ s:hl_group_prefix . 'Regexp': 'String',
355     \ s:hl_group_prefix . 'Operator': 'Operator',
356     \ s:hl_group_prefix . 'Decorator': 'Macro'
357 \ }
358
359 function! s:get_hl_group(server, legend, token_idx, token_modifiers) abort
360     " get highlight group name
361     let l:Capitalise = {str -> toupper(str[0]) . str[1:]}
362     let l:token_name = l:Capitalise(a:legend['tokenTypes'][a:token_idx])
363     let l:token_modifiers = []
364     for l:modifier_idx in range(len(a:legend['tokenModifiers']))
365         " float2nr(pow(2, a)) is 1 << a
366         if and(a:token_modifiers, float2nr(pow(2, l:modifier_idx)))
367             let l:modifier_name = a:legend['tokenModifiers'][l:modifier_idx]
368             call add(l:token_modifiers, l:Capitalise(l:modifier_name))
369         endif
370     endfor
371     call sort(l:token_modifiers)
372     let l:hl_group = s:hl_group_prefix
373                  \ . reduce(l:token_modifiers, {acc, val -> acc . val}, '')
374                  \ . l:token_name
375
376     " create the highlight group if it does not already exist
377     if !hlexists(l:hl_group)
378         if has_key(s:default_highlight_groups, l:hl_group)
379             exec 'highlight link' l:hl_group s:default_highlight_groups[l:hl_group]
380         else
381             if a:token_modifiers != 0
382                 let l:base_hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, 0)
383                 exec 'highlight link' l:hl_group l:base_hl_group
384             else
385                 exec 'highlight link' l:hl_group 'Normal'
386             endif
387         endif
388     endif
389
390     return l:hl_group
391 endfunction
392
393 let s:textprop_type_prefix = 'vim-lsp-semantic-'
394
395 function! s:get_textprop_type(server, legend, token_idx, token_modifiers) abort
396     " get textprop type name
397     let l:textprop_type = s:textprop_type_prefix . a:server . '-' . a:token_idx . '-' . a:token_modifiers
398
399     " create the textprop type if it does not already exist
400     if prop_type_get(l:textprop_type) ==# {}
401         let l:hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, a:token_modifiers)
402         silent! call prop_type_add(l:textprop_type, {
403                                 \ 'highlight': l:hl_group,
404                                 \ 'combine': v:true,
405                                 \ 'priority': lsp#internal#textprop#priority('semantic')})
406     endif
407
408     return l:textprop_type
409 endfunction
410
411 " vim: fdm=marker