Files
mattn 516fd7ce95 diagnostics list can be v:null (#1190)
* diagnostics list can be v:null

Ref #1186

* ditto

* Small refactoring

* Use lsp#utils#iteratable for the list which possibly be v:null
2021-06-09 01:51:59 +09:00

174 lines
6.5 KiB
VimL

" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
"
" Refer to https://github.com/microsoft/language-server-protocol/pull/1019 on normalization of urls.
" {
" 'normalized_uri': {
" 'server_name': {
" 'method': 'textDocument/publishDiagnostics',
" 'params': {
" 'uri': 'uri', " this uri is not normalized and is exactly what server returns
" 'dignostics': [ " array can never be null but can be empty
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
" { range, message, severity?, code?, codeDesciption?, source?, tags?, relatedInformation?, data? }
" ]
" }
" }
" }
" Note: Do not remove when buffer unloads or doesn't exist since some server
" may send diagnsotics information regardless of textDocument/didOpen.
" buffer state is removed when server exits.
" TODO: reset buffer state when server initializes. ignoring for now for perf.
let s:diagnostics_state = {}
" internal state for whether it is enabled or not to avoid multiple subscriptions
let s:enabled = 0
let s:diagnostic_kinds = {
\ 1: 'error',
\ 2: 'warning',
\ 3: 'information',
\ 4: 'hint',
\ }
function! lsp#internal#diagnostics#state#_enable() abort
" don't even bother registering if the feature is disabled
if !g:lsp_diagnostics_enabled | return | endif
if s:enabled | return | endif
let s:enabled = 1
call lsp#internal#diagnostics#state#_reset()
let s:Dispose = lsp#callbag#pipe(
\ lsp#callbag#merge(
\ lsp#callbag#pipe(
\ lsp#stream(),
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
\ && get(x['response'], 'method', '') ==# 'textDocument/publishDiagnostics'}),
\ lsp#callbag#tap({x->s:on_text_documentation_publish_diagnostics(x['server'], x['response'])}),
\ ),
\ lsp#callbag#pipe(
\ lsp#stream(),
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
\ && get(x['response'], 'method', '') ==# '$/vimlsp/lsp_server_exit' }),
\ lsp#callbag#tap({x->s:on_exit(x['response'])}),
\ ),
\ ),
\ lsp#callbag#subscribe(),
\ )
call s:notify_diagnostics_update()
endfunction
function! lsp#internal#diagnostics#state#_disable() abort
if !s:enabled | return | endif
if exists('s:Dispose')
call s:Dispose()
unlet s:Dispose
endif
call lsp#internal#diagnostics#state#_reset()
call s:notify_diagnostics_update()
let s:enabled = 0
endfunction
function! lsp#internal#diagnostics#state#_reset() abort
let s:diagnostics_state = {}
let s:diagnostics_disabled_buffers = {}
endfunction
" callers should always treat the return value as immutable
" @return {
" 'servername': response
" }
function! lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(uri) abort
return get(s:diagnostics_state, lsp#utils#normalize_uri(a:uri), {})
endfunction
" callers should always treat the return value as immutable.
" callers should treat uri as normalized via lsp#utils#normalize_uri
" @return {
" 'normalized_uri': {
" 'servername': response
" }
" }
function! lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_uri_and_server() abort
return s:diagnostics_state
endfunction
function! s:on_text_documentation_publish_diagnostics(server, response) abort
if lsp#client#is_error(a:response) | return | endif
let l:normalized_uri = lsp#utils#normalize_uri(a:response['params']['uri'])
if !has_key(s:diagnostics_state, l:normalized_uri)
let s:diagnostics_state[l:normalized_uri] = {}
endif
let s:diagnostics_state[l:normalized_uri][a:server] = a:response
call s:notify_diagnostics_update(a:server, l:normalized_uri)
endfunction
function! s:on_exit(response) abort
let l:server = a:response['params']['server']
let l:notify = 0
for [l:key, l:value] in items(s:diagnostics_state)
if has_key(l:value, l:server)
let l:notify = 1
call remove(l:value, l:server)
endif
endfor
if l:notify | call s:notify_diagnostics_update(l:server) | endif
endfunction
function! lsp#internal#diagnostics#state#_force_notify_buffer(buffer) abort
" TODO: optimize buffer only
call s:notify_diagnostics_update()
endfunction
" call s:notify_diagnostics_update()
" call s:notify_diagnostics_update('server')
" call s:notify_diagnostics_update('server', 'uri')
function! s:notify_diagnostics_update(...) abort
let l:data = { 'server': '$vimlsp', 'response': { 'method': '$/vimlsp/lsp_diagnostics_updated', 'params': {} } }
" if a:0 > 0 | let l:data['response']['params']['server'] = a:1 | endif
" if a:0 > 1 | let l:data['response']['params']['uri'] = a:2 | endif
call lsp#stream(1, l:data)
doautocmd <nomodeline> User lsp_diagnostics_updated
endfunction
function! lsp#internal#diagnostics#state#_enable_for_buffer(bufnr) abort
if getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) == 0
call setbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1)
call s:notify_diagnostics_update()
endif
endfunction
function! lsp#internal#diagnostics#state#_disable_for_buffer(bufnr) abort
if getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) != 0
call setbufvar(a:bufnr, 'lsp_diagnostics_enabled', 0)
call s:notify_diagnostics_update()
endif
endfunction
function! lsp#internal#diagnostics#state#_is_enabled_for_buffer(bufnr) abort
return getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) == 1
endfunction
" Return dict with diagnostic counts for the specified buffer
" { 'error': 1, 'warning': 0, 'information': 0, 'hint': 0 }
function! lsp#internal#diagnostics#state#_get_diagnostics_count_for_buffer(bufnr) abort
let l:counts = {
\ 'error': 0,
\ 'warning': 0,
\ 'information': 0,
\ 'hint': 0,
\ }
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(a:bufnr)
let l:uri = lsp#utils#get_buffer_uri(a:bufnr)
for [l:_, l:response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
for l:diagnostic in lsp#utils#iteratable(l:response['params']['diagnostics'])
let l:key = get(s:diagnostic_kinds, get(l:diagnostic, 'severity', 1) , 'error')
let l:counts[l:key] += 1
endfor
endfor
end
return l:counts
endfunction