X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5a4872f466ebd76ddd532bdf2798554421c53df4..fe3919e725e156d751069662d11e38f7b4791de1:/.vim/bundle/vim-lsp/autoload/lsp/internal/semantic.vim diff --git a/.vim/bundle/vim-lsp/autoload/lsp/internal/semantic.vim b/.vim/bundle/vim-lsp/autoload/lsp/internal/semantic.vim new file mode 100644 index 00000000..761b92a7 --- /dev/null +++ b/.vim/bundle/vim-lsp/autoload/lsp/internal/semantic.vim @@ -0,0 +1,411 @@ +let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim') +let s:use_nvim_highlight = lsp#utils#_has_nvim_buf_highlight() +let s:textprop_cache = 'vim-lsp-semantic-cache' + +if s:use_nvim_highlight + let s:namespace_id = nvim_create_namespace('vim-lsp-semantic') +endif + +" Global functions {{{1 +function! lsp#internal#semantic#is_enabled() abort + return g:lsp_semantic_enabled && (s:use_vim_textprops || s:use_nvim_highlight) ? v:true : v:false +endfunction + +function! lsp#internal#semantic#_enable() abort + if !lsp#internal#semantic#is_enabled() | return | endif + + augroup lsp#internal#semantic + autocmd! + au User lsp_buffer_enabled call s:on_lsp_buffer_enabled() + augroup END + + let l:events = [['User', 'lsp_buffer_enabled'], 'TextChanged', 'TextChangedI'] + if exists('##TextChangedP') + call add(l:events, 'TextChangedP') + endif + let s:Dispose = lsp#callbag#pipe( + \ lsp#callbag#fromEvent(l:events), + \ lsp#callbag#filter({_->lsp#internal#semantic#is_enabled()}), + \ lsp#callbag#debounceTime(g:lsp_semantic_delay), + \ lsp#callbag#filter({_->index(['help', 'terminal', 'prompt', 'popup'], getbufvar(bufnr('%'), '&buftype')) ==# -1}), + \ lsp#callbag#filter({_->!lsp#utils#is_large_window(win_getid())}), + \ lsp#callbag#switchMap({_-> + \ lsp#callbag#pipe( + \ s:semantic_request(), + \ lsp#callbag#materialize(), + \ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}), + \ lsp#callbag#map({x->x['value']}) + \ ) + \ }), + \ lsp#callbag#subscribe({x->s:handle_semantic_request(x)}) + \ ) +endfunction + +function! lsp#internal#semantic#_disable() abort + augroup lsp#internal#semantic + autocmd! + augroup END + + if exists('s:Dispose') + call s:Dispose() + unlet s:Dispose + endif +endfunction + +function! lsp#internal#semantic#get_legend(server) abort + if !lsp#capabilities#has_semantic_tokens(a:server) + return {'tokenTypes': [], 'tokenModifiers': []} + endif + + let l:capabilities = lsp#get_server_capabilities(a:server) + return l:capabilities['semanticTokensProvider']['legend'] +endfunction + +function! lsp#internal#semantic#get_token_types() abort + let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)' + let l:servers = filter(lsp#get_allowed_servers(), l:capability) + + if empty(l:servers) + return [] + endif + + let l:legend = lsp#internal#semantic#get_legend(l:servers[0]) + let l:token_types = l:legend['tokenTypes'] + call map(l:token_types, {_, type -> toupper(type[0]) . type[1:]}) + return l:token_types +endfunction + +function! lsp#internal#semantic#get_token_modifiers() abort + let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)' + let l:servers = filter(lsp#get_allowed_servers(), l:capability) + + if empty(l:servers) + return [] + endif + + let l:legend = lsp#internal#semantic#get_legend(l:servers[0]) + let l:token_modifiers = l:legend['tokenModifiers'] + call map(l:token_modifiers, {_, modifier -> toupper(modifier[0]) . modifier[1:]}) + return l:token_modifiers +endfunction + +function! s:on_lsp_buffer_enabled() abort + augroup lsp#internal#semantic + if !exists('#BufUnload#') + execute 'au BufUnload call setbufvar(' . bufnr() . ', ''lsp_semantic_previous_result_id'', '''')' + endif + augroup END +endfunction + +function! s:supports_full_semantic_request(server) abort + if !lsp#capabilities#has_semantic_tokens(a:server) + return v:false + endif + + let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider'] + if !has_key(l:capabilities, 'full') + return v:false + endif + + if type(l:capabilities['full']) ==# v:t_dict + return v:true + endif + + return l:capabilities['full'] +endfunction + +function! s:supports_delta_semantic_request(server) abort + if !lsp#capabilities#has_semantic_tokens(a:server) + return v:false + endif + + let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider'] + if !has_key(l:capabilities, 'full') + return v:false + endif + + if type(l:capabilities['full']) !=# v:t_dict + return v:false + endif + + if !has_key(l:capabilities['full'], 'delta') + return v:false + endif + + return l:capabilities['full']['delta'] +endfunction + +function! s:get_server() abort + let l:capability = 's:supports_delta_semantic_request(v:val)' + let l:servers = filter(lsp#get_allowed_servers(), l:capability) + if empty(l:servers) + let l:capability = 's:supports_full_semantic_request(v:val)' + let l:servers = filter(lsp#get_allowed_servers(), l:capability) + endif + if empty(l:servers) + return '' + endif + return l:servers[0] +endfunction + +function! s:semantic_request() abort + let l:server = s:get_server() + if l:server ==# '' + return lsp#callbag#empty() + endif + + if (s:supports_delta_semantic_request(l:server) + \ && getbufvar(bufnr(), 'lsp_semantic_previous_result_id') !=# '') + return s:delta_semantic_request(l:server) + else + return s:full_semantic_request(l:server) + endif +endfunction + +function! s:full_semantic_request(server) abort + return lsp#request(a:server, { + \ 'method': 'textDocument/semanticTokens/full', + \ 'params': { + \ 'textDocument': lsp#get_text_document_identifier() + \ }}) +endfunction + +function! s:delta_semantic_request(server) abort + return lsp#request(a:server, { + \ 'method': 'textDocument/semanticTokens/full/delta', + \ 'params': { + \ 'textDocument': lsp#get_text_document_identifier(), + \ 'previousResultId': getbufvar(bufname(), 'lsp_semantic_previous_result_id', 0) + \ }}) +endfunction + +" Highlight helper functions {{{1 +function! s:handle_semantic_request(data) abort + if lsp#client#is_error(a:data['response']) + call lsp#log('Skipping semantic highlight: response is invalid') + return + endif + + let l:server = a:data['server_name'] + let l:uri = a:data['request']['params']['textDocument']['uri'] + let l:path = lsp#utils#uri_to_path(l:uri) + let l:bufnr = bufnr(l:path) + + " Skip if the buffer doesn't exist. This might happen when a buffer is + " opened and quickly deleted. + if !bufloaded(l:bufnr) | return | endif + + if has_key(a:data['response']['result'], 'data') + call s:handle_semantic_tokens_response(l:server, l:bufnr, a:data['response']['result']) + elseif has_key(a:data['response']['result'], 'edits') + call s:handle_semantic_tokens_delta_response(l:server, l:bufnr, a:data['response']['result']) + else + " Don't update previous result ID if we could not update local copy + call lsp#log('SemanticHighlight: unsupported semanticTokens method') + return + endif + + if has_key(a:data['response']['result'], 'resultId') + call setbufvar(l:bufnr, 'lsp_semantic_previous_result_id', a:data['response']['result']['resultId']) + endif +endfunction + +function! s:handle_semantic_tokens_response(server, buf, result) abort + let l:highlights = {} + let l:legend = lsp#internal#semantic#get_legend(a:server) + for l:token in s:decode_tokens(a:result['data']) + let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token) + let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value + endfor + call s:apply_highlights(a:server, a:buf, l:highlights) + + call setbufvar(a:buf, 'lsp_semantic_local_data', a:result['data']) +endfunction + +function! s:startpos_compare(edit1, edit2) abort + return a:edit1[0] == a:edit2[0] ? 0 : a:edit1[0] > a:edit2[0] ? -1 : 1 +endfunction + +function! s:handle_semantic_tokens_delta_response(server, buf, result) abort + " The locations given in the edit are all referenced to the state before + " any are applied and sorting is not required from the server, + " therefore the edits must be sorted before applying. + let l:edits = a:result['edits'] + call sort(l:edits, function('s:startpos_compare')) + + let l:localdata = getbufvar(a:buf, 'lsp_semantic_local_data') + for l:edit in l:edits + let l:insertdata = get(l:edit, 'data', []) + let l:localdata = l:localdata[:l:edit['start'] - 1] + \ + l:insertdata + \ + l:localdata[l:edit['start'] + l:edit['deleteCount']:] + endfor + call setbufvar(a:buf, 'lsp_semantic_local_data', l:localdata) + + let l:highlights = {} + let l:legend = lsp#internal#semantic#get_legend(a:server) + for l:token in s:decode_tokens(l:localdata) + let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token) + let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value + endfor + call s:apply_highlights(a:server, a:buf, l:highlights) +endfunction + +function! s:decode_tokens(data) abort + let l:tokens = [] + + let l:i = 0 + let l:line = 0 + let l:char = 0 + while l:i < len(a:data) + let l:line = l:line + a:data[l:i] + if a:data[l:i] > 0 + let l:char = 0 + endif + let l:char = l:char + a:data[l:i + 1] + + call add(l:tokens, { + \ 'pos': {'line': l:line, 'character': l:char}, + \ 'length': a:data[l:i + 2], + \ 'token_idx': a:data[l:i + 3], + \ 'token_modifiers': a:data[l:i + 4] + \ }) + + let l:i = l:i + 5 + endwhile + + return l:tokens +endfunction + +function! s:clear_highlights(server, buf) abort + if s:use_vim_textprops + let l:BeginsWith = {str, prefix -> str[0:len(prefix) - 1] ==# prefix} + let l:IsSemanticTextprop = {_, textprop -> l:BeginsWith(textprop, s:textprop_type_prefix)} + let l:textprop_types = prop_type_list() + call filter(l:textprop_types, l:IsSemanticTextprop) + for l:textprop_type in l:textprop_types + silent! call prop_remove({'type': l:textprop_type, 'bufnr': a:buf, 'all': v:true}) + endfor + elseif s:use_nvim_highlight + call nvim_buf_clear_namespace(a:buf, s:namespace_id, 0, line('$')) + endif +endfunction + +function! s:add_highlight(server, legend, buf, token) abort + let l:startpos = lsp#utils#position#lsp_to_vim(a:buf, a:token['pos']) + let l:endpos = a:token['pos'] + let l:endpos['character'] = l:endpos['character'] + a:token['length'] + let l:endpos = lsp#utils#position#lsp_to_vim(a:buf, l:endpos) + + if s:use_vim_textprops + let l:textprop_name = s:get_textprop_type(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers']) + return [l:textprop_name, [[l:startpos[0], l:startpos[1], l:endpos[0], l:endpos[1]]]] + elseif s:use_nvim_highlight + let l:char = a:token['pos']['character'] + let l:hl_name = s:get_hl_group(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers']) + return [l:hl_name, [[l:startpos[0] - 1, l:startpos[1] - 1, l:endpos[1] - 1]]] + endif +endfunction + +function! s:apply_highlights(server, buf, highlights) abort + call s:clear_highlights(a:server, a:buf) + + if s:use_vim_textprops + for [l:type, l:prop_list] in items(a:highlights) + call prop_add_list({'type': l:type, 'bufnr': a:buf}, l:prop_list) + endfor + elseif s:use_nvim_highlight + call lsp#log(a:highlights) + for [l:hl_name, l:instances] in items(a:highlights) + for l:instance in l:instances + let [l:line, l:startcol, l:endcol] = l:instance + try + call nvim_buf_add_highlight(a:buf, s:namespace_id, l:hl_name, l:line, l:startcol, l:endcol) + catch + call lsp#log('SemanticHighlight: error while adding ' . l:hl_name . ' highlight on line ' . l:line) + endtry + endfor + endfor + end +endfunction + +let s:hl_group_prefix = 'LspSemantic' + +let s:default_highlight_groups = { + \ s:hl_group_prefix . 'Type': 'Type', + \ s:hl_group_prefix . 'Class': 'Type', + \ s:hl_group_prefix . 'Enum': 'Type', + \ s:hl_group_prefix . 'Interface': 'TypeDef', + \ s:hl_group_prefix . 'Struct': 'Type', + \ s:hl_group_prefix . 'TypeParameter': 'Type', + \ s:hl_group_prefix . 'Parameter': 'Identifier', + \ s:hl_group_prefix . 'Variable': 'Identifier', + \ s:hl_group_prefix . 'Property': 'Identifier', + \ s:hl_group_prefix . 'EnumMember': 'Constant', + \ s:hl_group_prefix . 'Event': 'Identifier', + \ s:hl_group_prefix . 'Function': 'Function', + \ s:hl_group_prefix . 'Method': 'Function', + \ s:hl_group_prefix . 'Macro': 'Macro', + \ s:hl_group_prefix . 'Keyword': 'Keyword', + \ s:hl_group_prefix . 'Modifier': 'Type', + \ s:hl_group_prefix . 'Comment': 'Comment', + \ s:hl_group_prefix . 'String': 'String', + \ s:hl_group_prefix . 'Number': 'Number', + \ s:hl_group_prefix . 'Regexp': 'String', + \ s:hl_group_prefix . 'Operator': 'Operator', + \ s:hl_group_prefix . 'Decorator': 'Macro' +\ } + +function! s:get_hl_group(server, legend, token_idx, token_modifiers) abort + " get highlight group name + let l:Capitalise = {str -> toupper(str[0]) . str[1:]} + let l:token_name = l:Capitalise(a:legend['tokenTypes'][a:token_idx]) + let l:token_modifiers = [] + for l:modifier_idx in range(len(a:legend['tokenModifiers'])) + " float2nr(pow(2, a)) is 1 << a + if and(a:token_modifiers, float2nr(pow(2, l:modifier_idx))) + let l:modifier_name = a:legend['tokenModifiers'][l:modifier_idx] + call add(l:token_modifiers, l:Capitalise(l:modifier_name)) + endif + endfor + call sort(l:token_modifiers) + let l:hl_group = s:hl_group_prefix + \ . reduce(l:token_modifiers, {acc, val -> acc . val}, '') + \ . l:token_name + + " create the highlight group if it does not already exist + if !hlexists(l:hl_group) + if has_key(s:default_highlight_groups, l:hl_group) + exec 'highlight link' l:hl_group s:default_highlight_groups[l:hl_group] + else + if a:token_modifiers != 0 + let l:base_hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, 0) + exec 'highlight link' l:hl_group l:base_hl_group + else + exec 'highlight link' l:hl_group 'Normal' + endif + endif + endif + + return l:hl_group +endfunction + +let s:textprop_type_prefix = 'vim-lsp-semantic-' + +function! s:get_textprop_type(server, legend, token_idx, token_modifiers) abort + " get textprop type name + let l:textprop_type = s:textprop_type_prefix . a:server . '-' . a:token_idx . '-' . a:token_modifiers + + " create the textprop type if it does not already exist + if prop_type_get(l:textprop_type) ==# {} + let l:hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, a:token_modifiers) + silent! call prop_type_add(l:textprop_type, { + \ 'highlight': l:hl_group, + \ 'combine': v:true, + \ 'priority': lsp#internal#textprop#priority('semantic')}) + endif + + return l:textprop_type +endfunction + +" vim: fdm=marker