" 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 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