Close #3600 - Implement pull diagnostics in VimL

Implement pull diagnostics in the VimL implementation so ALE is able
to track when servers are busy checking files. Only servers that
support this feature will return diagnostics these ways.
This commit is contained in:
w0rp
2025-03-23 14:37:04 +00:00
parent 26ffb9dfa3
commit f90e72ae1f
9 changed files with 248 additions and 27 deletions

View File

@@ -47,6 +47,7 @@ function! ale#lsp#Register(executable_or_address, project, language, init_option
\ 'definition': 0,
\ 'typeDefinition': 0,
\ 'implementation': 0,
\ 'pull_model': 0,
\ 'symbol_search': 0,
\ 'code_actions': 0,
\ 'did_save': 0,
@@ -276,6 +277,13 @@ function! ale#lsp#UpdateCapabilities(conn_id, capabilities) abort
let l:conn.capabilities.implementation = 1
endif
" Check if the language server supports pull model diagnostics.
if type(get(a:capabilities, 'diagnosticProvider')) is v:t_dict
if type(get(a:capabilities.diagnosticProvider, 'interFileDependencies')) is v:t_bool
let l:conn.capabilities.pull_model = 1
endif
endif
if get(a:capabilities, 'workspaceSymbolProvider') is v:true
let l:conn.capabilities.symbol_search = 1
endif
@@ -486,6 +494,10 @@ function! s:SendInitMessage(conn) abort
\ 'dynamicRegistration': v:false,
\ 'linkSupport': v:false,
\ },
\ 'diagnostic': {
\ 'dynamicRegistration': v:true,
\ 'relatedDocumentSupport': v:true,
\ },
\ 'publishDiagnostics': {
\ 'relatedInformation': v:true,
\ },

View File

@@ -200,6 +200,14 @@ function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column,
\}]
endfunction
function! ale#lsp#message#Diagnostic(buffer) abort
return [0, 'textDocument/diagnostic', {
\ 'textDocument': {
\ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')),
\ },
\}]
endfunction
function! ale#lsp#message#ExecuteCommand(command, arguments) abort
return [0, 'workspace/executeCommand', {
\ 'command': a:command,

View File

@@ -21,11 +21,11 @@ let s:SEVERITY_WARNING = 2
let s:SEVERITY_INFORMATION = 3
let s:SEVERITY_HINT = 4
" Parse the message for textDocument/publishDiagnostics
function! ale#lsp#response#ReadDiagnostics(response) abort
" Convert Diagnostic[] data from a language server to an ALE loclist.
function! ale#lsp#response#ReadDiagnostics(diagnostics) abort
let l:loclist = []
for l:diagnostic in a:response.params.diagnostics
for l:diagnostic in a:diagnostics
let l:severity = get(l:diagnostic, 'severity', 0)
let l:loclist_item = {
\ 'text': substitute(l:diagnostic.message, '\(\r\n\|\n\|\r\)', ' ', 'g'),

View File

@@ -23,6 +23,26 @@ function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort
let s:lsp_linter_map = a:replacement_map
endfunction
" A map for tracking URIs for diagnostic request IDs
if !has_key(s:, 'diagnostic_uri_map')
let s:diagnostic_uri_map = {}
endif
" For internal use only.
function! ale#lsp_linter#ClearDiagnosticURIMap() abort
let s:diagnostic_uri_map = {}
endfunction
" For internal use only.
function! ale#lsp_linter#GetDiagnosticURIMap() abort
return s:diagnostic_uri_map
endfunction
" Just for tests.
function! ale#lsp_linter#SetDiagnosticURIMap(replacement_map) abort
let s:diagnostic_uri_map = a:replacement_map
endfunction
" Get all enabled LSP linters.
" This list still includes linters ignored with `ale_linters_ignore`.
"
@@ -77,14 +97,17 @@ function! s:ShouldIgnoreDiagnostics(buffer, linter) abort
return 0
endfunction
function! s:HandleLSPDiagnostics(conn_id, response) abort
" Handle LSP diagnostics for a given URI.
" The special value 'unchanged' can be used for diagnostics to indicate
" that diagnostics haven't changed since we last checked.
function! s:HandleLSPDiagnostics(conn_id, uri, diagnostics) abort
let l:linter = get(s:lsp_linter_map, a:conn_id)
if empty(l:linter)
return
endif
let l:filename = ale#util#ToResource(a:response.params.uri)
let l:filename = ale#util#ToResource(a:uri)
let l:escaped_name = escape(
\ fnameescape(l:filename),
\ has('win32') ? '^' : '^,}]'
@@ -100,9 +123,12 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort
return
endif
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
if a:diagnostics is# 'unchanged'
call ale#engine#MarkLinterInactive(l:info, l:linter)
else
let l:loclist = ale#lsp#response#ReadDiagnostics(a:diagnostics)
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
endif
endfunction
function! s:HandleTSServerDiagnostics(response, error_type) abort
@@ -204,7 +230,17 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
call s:HandleLSPErrorMessage(l:linter, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
let l:uri = a:response.params.uri
let l:diagnostics = a:response.params.diagnostics
call s:HandleLSPDiagnostics(a:conn_id, l:uri, l:diagnostics)
elseif has_key(s:diagnostic_uri_map, get(a:response, 'id'))
let l:uri = remove(s:diagnostic_uri_map, a:response.id)
let l:diagnostics = a:response.result.kind is# 'unchanged'
\ ? 'unchanged'
\ : a:response.result.items
call s:HandleLSPDiagnostics(a:conn_id, l:uri, l:diagnostics)
elseif l:method is# 'window/showMessage'
call ale#lsp_window#HandleShowMessage(
\ s:lsp_linter_map[a:conn_id].name,
@@ -530,6 +566,23 @@ function! s:CheckWithLSP(linter, details) abort
let l:save_message = ale#lsp#message#DidSave(l:buffer, l:include_text)
let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
endif
let l:diagnostic_request_id = 0
" If the document is updated and we can pull diagnostics, try to.
if ale#lsp#HasCapability(l:id, 'pull_model')
let l:diagnostic_message = ale#lsp#message#Diagnostic(l:buffer)
let l:diagnostic_request_id = ale#lsp#Send(l:id, l:diagnostic_message)
endif
" If we are going to pull diagnostics, then mark the linter as active,
" and remember the URI we sent the request for.
if l:diagnostic_request_id
call ale#engine#MarkLinterActive(l:info, a:linter)
let s:diagnostic_uri_map[l:diagnostic_request_id] =
\ l:diagnostic_message[2].textDocument.uri
endif
endif
endfunction