mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
231 lines
7.6 KiB
VimL
231 lines
7.6 KiB
VimL
let s:use_vim_textprops = has('textprop') && !has('nvim')
|
|
let s:use_nvim_highlight = exists('*nvim_buf_add_highlight') && has('nvim')
|
|
let s:textprop_cache = 'vim-lsp-semantic-cache'
|
|
|
|
if s:use_nvim_highlight
|
|
let s:namespace_id = nvim_create_namespace('vim-lsp-semantic')
|
|
endif
|
|
|
|
if !hlexists('LspUnknownScope')
|
|
highlight LspUnknownScope gui=NONE cterm=NONE guifg=NONE ctermfg=NONE guibg=NONE ctermbg=NONE
|
|
endif
|
|
|
|
" Global functions {{{1
|
|
function! lsp#ui#vim#semantic#is_enabled() abort
|
|
return g:lsp_semantic_enabled && (s:use_vim_textprops || s:use_nvim_highlight) ? v:true : v:false
|
|
endfunction
|
|
|
|
function! lsp#ui#vim#semantic#get_scopes(server) abort
|
|
if !lsp#capabilities#has_semantic_highlight(a:server)
|
|
return []
|
|
endif
|
|
|
|
let l:capabilities = lsp#get_server_capabilities(a:server)
|
|
return l:capabilities['semanticHighlighting']['scopes']
|
|
endfunction
|
|
|
|
function! lsp#ui#vim#semantic#handle_semantic(server, data) abort
|
|
if !g:lsp_semantic_enabled | return | endif
|
|
|
|
if lsp#client#is_error(a:data['response'])
|
|
call lsp#log('Skipping semantic highlight: response is invalid')
|
|
return
|
|
endif
|
|
|
|
let l:uri = a:data['response']['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
|
|
|
|
call s:init_highlight(a:server, l:bufnr)
|
|
|
|
for l:info in a:data['response']['params']['lines']
|
|
let l:linenr = l:info['line']
|
|
let l:tokens = has_key(l:info, 'tokens') ? l:info['tokens'] : ''
|
|
call s:add_highlight(a:server, l:bufnr, l:linenr, l:tokens)
|
|
endfor
|
|
endfunction
|
|
|
|
" Highlight helper functions {{{1
|
|
function! s:init_highlight(server, buf) abort
|
|
if !empty(getbufvar(a:buf, 'lsp_did_semantic_setup'))
|
|
return
|
|
endif
|
|
|
|
if s:use_vim_textprops
|
|
let l:scopes = lsp#ui#vim#semantic#get_scopes(a:server)
|
|
for l:scope_idx in range(len(l:scopes))
|
|
let l:scope = l:scopes[l:scope_idx]
|
|
let l:hl = s:get_hl_name(a:server, l:scope)
|
|
|
|
silent! call prop_type_add(s:get_textprop_name(a:server, l:scope_idx), {'bufnr': a:buf, 'highlight': l:hl, 'combine': v:true})
|
|
endfor
|
|
|
|
silent! call prop_type_add(s:textprop_cache, {'bufnr': a:buf})
|
|
endif
|
|
|
|
call setbufvar(a:buf, 'lsp_did_semantic_setup', 1)
|
|
endfunction
|
|
|
|
function! s:hash(str) abort
|
|
let l:hash = 1
|
|
|
|
for l:char in split(a:str, '\zs')
|
|
let l:hash = (l:hash * 31 + char2nr(l:char)) % 2147483647
|
|
endfor
|
|
|
|
return l:hash
|
|
endfunction
|
|
|
|
function! s:add_highlight(server, buf, line, tokens) abort
|
|
" Return quickly if the tokens for this line are already set correctly,
|
|
" according to the cached tokens.
|
|
" This only works for Vim at the moment, for Neovim, we need extended
|
|
" marks.
|
|
if s:use_vim_textprops
|
|
let l:props = filter(prop_list(a:line + 1, {'bufnr': a:buf}), {idx, prop -> prop['type'] ==# s:textprop_cache})
|
|
let l:hash = s:hash(a:tokens)
|
|
|
|
if !empty(l:props) && l:props[0]['id'] == l:hash
|
|
" No changes for this line, so just return.
|
|
return
|
|
endif
|
|
endif
|
|
|
|
let l:scopes = lsp#ui#vim#semantic#get_scopes(a:server)
|
|
let l:highlights = s:tokens_to_hl_info(a:tokens)
|
|
|
|
if s:use_vim_textprops
|
|
" Clear text properties from the previous run
|
|
for l:scope_idx in range(len(l:scopes))
|
|
call prop_remove({'bufnr': a:buf, 'type': s:get_textprop_name(a:server, l:scope_idx), 'all': v:true}, a:line + 1)
|
|
endfor
|
|
|
|
" Clear cache from previous run
|
|
call prop_remove({'bufnr': a:buf, 'type': s:textprop_cache, 'all': v:true}, a:line + 1)
|
|
|
|
" Add textprop for cache
|
|
call prop_add(a:line + 1, 1, {'bufnr': a:buf, 'type': s:textprop_cache, 'id': l:hash})
|
|
|
|
for l:highlight in l:highlights
|
|
call prop_add(a:line + 1, l:highlight['char'] + 1, { 'length': l:highlight['length'], 'bufnr': a:buf, 'type': s:get_textprop_name(a:server, l:highlight['scope'])})
|
|
endfor
|
|
elseif s:use_nvim_highlight
|
|
" Clear text properties from the previous run
|
|
call nvim_buf_clear_namespace(a:buf, s:namespace_id, a:line, a:line + 1)
|
|
|
|
for l:highlight in l:highlights
|
|
call nvim_buf_add_highlight(a:buf, s:namespace_id, s:get_hl_name(a:server, l:scopes[l:highlight['scope']]), a:line, l:highlight['char'], l:highlight['char'] + l:highlight['length'])
|
|
endfor
|
|
endif
|
|
endfunction
|
|
|
|
function! s:get_hl_name(server, scope) abort
|
|
let l:hl = 'LspUnknownScope'
|
|
|
|
" Iterate over scopes in the order most general to most specific,
|
|
" returning the last scope encountered. This is accomplished by a try
|
|
" catch which ensures we always return the last scope even if an error is
|
|
" encountered midway.
|
|
try
|
|
let l:info = lsp#get_server_info(a:server)
|
|
let l:hl = l:info['semantic_highlight']
|
|
let l:i = 0
|
|
|
|
while (l:i < len(a:scope)) && has_key(l:hl, a:scope[l:i])
|
|
let l:hl = l:hl[a:scope[l:i]]
|
|
let l:i += 1
|
|
endwhile
|
|
catch
|
|
endtry
|
|
|
|
return type(l:hl) == type('') ? l:hl : 'LspUnknownScope'
|
|
endfunction
|
|
|
|
function! s:get_textprop_name(server, scope_index) abort
|
|
return 'vim-lsp-semantic-' . a:server . '-' . a:scope_index
|
|
endfunction
|
|
|
|
" Response parsing functions {{{1
|
|
|
|
" Converts a list of bytes (MSB first) to a Number.
|
|
function! s:octets_to_number(octets) abort
|
|
let l:ret = 0
|
|
|
|
for l:octet in a:octets
|
|
let l:ret *= 256
|
|
let l:ret += l:octet
|
|
endfor
|
|
|
|
return l:ret
|
|
endfunction
|
|
|
|
function! s:tokens_to_hl_info(token) abort
|
|
let l:ret = []
|
|
let l:octets = lsp#utils#base64_decode(a:token)
|
|
|
|
for l:i in range(0, len(l:octets) - 1, 8)
|
|
let l:char = s:octets_to_number(l:octets[l:i : l:i+3])
|
|
let l:length = s:octets_to_number(l:octets[l:i+4 : l:i+5])
|
|
let l:scope = s:octets_to_number(l:octets[l:i+6 : l:i+7])
|
|
|
|
call add(l:ret, { 'char': l:char, 'length': l:length, 'scope': l:scope })
|
|
endfor
|
|
|
|
return l:ret
|
|
endfunction
|
|
|
|
" Display scope tree {{{1
|
|
function! lsp#ui#vim#semantic#display_scope_tree(...) abort
|
|
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_semantic_highlight(v:val)')
|
|
|
|
if len(l:servers) == 0
|
|
call lsp#utils#error('Semantic highlighting not supported for ' . &filetype)
|
|
return
|
|
endif
|
|
|
|
let l:server = l:servers[0]
|
|
let l:info = lsp#get_server_info(l:server)
|
|
let l:hl_mapping = get(l:info, 'semantic_highlight', {})
|
|
let l:scopes = copy(lsp#ui#vim#semantic#get_scopes(l:server))
|
|
|
|
" Convert scope array to tree
|
|
let l:tree = {}
|
|
|
|
for l:scope in l:scopes
|
|
let l:cur = l:tree
|
|
|
|
for l:scope_part in l:scope
|
|
if !has_key(l:cur, l:scope_part)
|
|
let l:cur[l:scope_part] = {}
|
|
endif
|
|
let l:cur = l:cur[l:scope_part]
|
|
endfor
|
|
endfor
|
|
|
|
call s:display_tree(l:hl_mapping, l:tree, 0, a:0 > 0 ? a:1 - 1 : 20)
|
|
endfunction
|
|
|
|
function! s:display_tree(hl_tree, tree, indent, maxindent) abort
|
|
for [l:item, l:rest] in sort(items(a:tree))
|
|
if has_key(a:hl_tree, l:item) && type(a:hl_tree[l:item]) == type('')
|
|
execute 'echohl ' . a:hl_tree[l:item]
|
|
endif
|
|
echo repeat(' ', 4 * a:indent) . l:item
|
|
echohl None
|
|
|
|
if a:indent < a:maxindent
|
|
let l:new_hl_info = get(a:hl_tree, l:item, {})
|
|
if type(l:new_hl_info) != type({})
|
|
let l:new_hl_info = {}
|
|
endif
|
|
call s:display_tree(l:new_hl_info, l:rest, a:indent + 1, a:maxindent)
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
" vim: fdm=marker
|