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 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'
5 if s:use_nvim_highlight
6 let s:namespace_id = nvim_create_namespace('vim-lsp-semantic')
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
14 function! lsp#internal#semantic#_enable() abort
15 if !lsp#internal#semantic#is_enabled() | return | endif
17 augroup lsp#internal#semantic
19 au User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
22 let l:events = [['User', 'lsp_buffer_enabled'], 'TextChanged', 'TextChangedI']
23 if exists('##TextChangedP')
24 call add(l:events, 'TextChangedP')
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({_->
34 \ s:semantic_request(),
35 \ lsp#callbag#materialize(),
36 \ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
37 \ lsp#callbag#map({x->x['value']})
40 \ lsp#callbag#subscribe({x->s:handle_semantic_request(x)})
44 function! lsp#internal#semantic#_disable() abort
45 augroup lsp#internal#semantic
49 if exists('s:Dispose')
55 function! lsp#internal#semantic#get_legend(server) abort
56 if !lsp#capabilities#has_semantic_tokens(a:server)
57 return {'tokenTypes': [], 'tokenModifiers': []}
60 let l:capabilities = lsp#get_server_capabilities(a:server)
61 return l:capabilities['semanticTokensProvider']['legend']
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)
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:]})
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)
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
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'', '''')'
100 function! s:supports_full_semantic_request(server) abort
101 if !lsp#capabilities#has_semantic_tokens(a:server)
105 let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
106 if !has_key(l:capabilities, 'full')
110 if type(l:capabilities['full']) ==# v:t_dict
114 return l:capabilities['full']
117 function! s:supports_delta_semantic_request(server) abort
118 if !lsp#capabilities#has_semantic_tokens(a:server)
122 let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
123 if !has_key(l:capabilities, 'full')
127 if type(l:capabilities['full']) !=# v:t_dict
131 if !has_key(l:capabilities['full'], 'delta')
135 return l:capabilities['full']['delta']
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)
142 let l:capability = 's:supports_full_semantic_request(v:val)'
143 let l:servers = filter(lsp#get_allowed_servers(), l:capability)
151 function! s:semantic_request() abort
152 let l:server = s:get_server()
154 return lsp#callbag#empty()
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)
161 return s:full_semantic_request(l:server)
165 function! s:full_semantic_request(server) abort
166 return lsp#request(a:server, {
167 \ 'method': 'textDocument/semanticTokens/full',
169 \ 'textDocument': lsp#get_text_document_identifier()
173 function! s:delta_semantic_request(server) abort
174 return lsp#request(a:server, {
175 \ 'method': 'textDocument/semanticTokens/full/delta',
177 \ 'textDocument': lsp#get_text_document_identifier(),
178 \ 'previousResultId': getbufvar(bufname(), 'lsp_semantic_previous_result_id', 0)
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')
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)
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
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'])
203 " Don't update previous result ID if we could not update local copy
204 call lsp#log('SemanticHighlight: unsupported semanticTokens method')
208 if has_key(a:data['response']['result'], 'resultId')
209 call setbufvar(l:bufnr, 'lsp_semantic_previous_result_id', a:data['response']['result']['resultId'])
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
220 call s:apply_highlights(a:server, a:buf, l:highlights)
222 call setbufvar(a:buf, 'lsp_semantic_local_data', a:result['data'])
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
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'))
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]
241 \ + l:localdata[l:edit['start'] + l:edit['deleteCount']:]
243 call setbufvar(a:buf, 'lsp_semantic_local_data', l:localdata)
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
251 call s:apply_highlights(a:server, a:buf, l:highlights)
254 function! s:decode_tokens(data) abort
260 while l:i < len(a:data)
261 let l:line = l:line + a:data[l:i]
265 let l:char = l:char + a:data[l:i + 1]
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]
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})
289 elseif s:use_nvim_highlight
290 call nvim_buf_clear_namespace(a:buf, s:namespace_id, 0, line('$'))
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)
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]]]
310 function! s:apply_highlights(server, buf, highlights) abort
311 call s:clear_highlights(a:server, a:buf)
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)
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
323 call nvim_buf_add_highlight(a:buf, s:namespace_id, l:hl_name, l:line, l:startcol, l:endcol)
325 call lsp#log('SemanticHighlight: error while adding ' . l:hl_name . ' highlight on line ' . l:line)
332 let s:hl_group_prefix = 'LspSemantic'
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'
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))
371 call sort(l:token_modifiers)
372 let l:hl_group = s:hl_group_prefix
373 \ . reduce(l:token_modifiers, {acc, val -> acc . val}, '')
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]
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
385 exec 'highlight link' l:hl_group 'Normal'
393 let s:textprop_type_prefix = 'vim-lsp-semantic-'
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
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,
405 \ 'priority': lsp#internal#textprop#priority('semantic')})
408 return l:textprop_type