Close #5112 - Support newer Pyright and other LSPs

Add support for dynamic capability registration for the diagnostic
pull model to support Pyright >= 1.1.407 and other language servers.

This is a rather complex and intricate change tested with Pyright and
gopls, and may need further tweaking if something breaks with another
server.
This commit is contained in:
w0rp
2026-05-15 23:38:34 +01:00
parent 7b5b854fc4
commit 307f2b99ff
4 changed files with 485 additions and 9 deletions
@@ -1,4 +1,6 @@
Before:
runtime autoload/ale/lsp.vim
Save g:ale_set_lists_synchronously
Save g:ale_buffer_info
Save g:ale_lsp_error_messages
@@ -40,6 +42,7 @@ After:
call ale#test#RestoreDirectory()
call ale#linter#Reset()
call ale#lsp_linter#ClearLSPData()
call ale#lsp_linter#ClearDiagnosticURIMap()
Given foobar(An empty file):
Execute(tsserver syntax error responses should be handled correctly):
@@ -534,6 +537,60 @@ Execute(LSP pull model diagnostic responses that are 'unchanged' should be handl
\ g:ale_buffer_info[bufnr('')].loclist
AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list
Execute(LSP pull model diagnostic errors should clear pending requests):
let b:ale_linters = ['eclipselsp']
runtime ale_linters/java/eclipselsp.vim
if has('win32')
call ale#test#SetFilename('filename,[]^$.ts')
else
call ale#test#SetFilename('filename*?,{}[]^$.java')
endif
call ale#engine#InitBufferInfo(bufnr(''))
let g:ale_buffer_info[bufnr('')].loclist = [
\ {
\ 'lnum': 1,
\ 'bufnr': bufnr(''),
\ 'col': 1,
\ 'pattern': '',
\ 'valid': 1,
\ 'vcol': 0,
\ 'nr': -1,
\ 'type': 'W',
\ 'text': 'Missing JRE 1-8'
\ },
\]
call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}})
call ale#lsp_linter#SetDiagnosticURIMap({'347': ale#util#ToURI(expand('%:p'))})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc': '2.0',
\ 'id': 347,
\ 'error': {
\ 'code': -32802,
\ 'message': 'server cancelled',
\ },
\})
AssertEqual {}, ale#lsp_linter#GetDiagnosticURIMap()
AssertEqual
\ [
\ {
\ 'lnum': 1,
\ 'bufnr': bufnr(''),
\ 'col': 1,
\ 'pattern': '',
\ 'valid': 1,
\ 'vcol': 0,
\ 'nr': -1,
\ 'type': 'W',
\ 'text': 'Missing JRE 1-8'
\ }
\ ],
\ g:ale_buffer_info[bufnr('')].loclist
Execute(workspace/configuration requests should be answered with the connection config):
let g:sent_responses = []
@@ -564,26 +621,134 @@ Execute(workspace/configuration requests should be answered with the connection
Execute(client/registerCapability requests should be acknowledged):
let g:sent_responses = []
let g:registered_capabilities = []
let g:sent_pull_diagnostics = []
function! ale#lsp#SendResponse(conn_id, id, result) abort
call add(g:sent_responses, [a:conn_id, a:id, a:result])
endfunction
function! ale#lsp#RegisterCapabilities(conn_id, registrations) abort
call add(g:registered_capabilities, [a:conn_id, a:registrations])
return 1
endfunction
function! ale#lsp#SendDiagnosticsForOpenDocuments(conn_id) abort
call add(g:sent_pull_diagnostics, a:conn_id)
return []
endfunction
call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'expert', 'aliases': [], 'lsp': 'stdio'}})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc': '2.0',
\ 'id': 12,
\ 'method': 'client/registerCapability',
\ 'params': {
\ 'registrations': [{'id': 'abc', 'method': 'textDocument/didSave'}],
\ 'registrations': [
\ {
\ 'id': 'abc',
\ 'method': 'textDocument/diagnostic',
\ 'registerOptions': {'interFileDependencies': v:true},
\ },
\ ],
\ },
\})
AssertEqual
\ [[1, 12, v:null]],
\ g:sent_responses
AssertEqual
\ [[1, [
\ {
\ 'id': 'abc',
\ 'method': 'textDocument/diagnostic',
\ 'registerOptions': {'interFileDependencies': v:true},
\ },
\ ]]],
\ g:registered_capabilities
AssertEqual [1], g:sent_pull_diagnostics
unlet! g:sent_responses
unlet! g:registered_capabilities
unlet! g:sent_pull_diagnostics
runtime autoload/ale/lsp.vim
Execute(workspace/diagnostic/refresh requests should pull diagnostics):
let g:sent_responses = []
let g:sent_pull_diagnostics = []
function! ale#lsp#SendResponse(conn_id, id, result) abort
call add(g:sent_responses, [a:conn_id, a:id, a:result])
endfunction
function! ale#lsp#SendDiagnosticsForOpenDocuments(conn_id) abort
call add(g:sent_pull_diagnostics, a:conn_id)
return []
endfunction
call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'expert', 'aliases': [], 'lsp': 'stdio'}})
call ale#lsp_linter#SetDiagnosticURIMap({'13': 'file://foo.py'})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc': '2.0',
\ 'id': 13,
\ 'method': 'workspace/diagnostic/refresh',
\})
AssertEqual
\ [[1, 13, v:null]],
\ g:sent_responses
AssertEqual [1], g:sent_pull_diagnostics
AssertEqual {'13': 'file://foo.py'}, ale#lsp_linter#GetDiagnosticURIMap()
unlet! g:sent_responses
unlet! g:sent_pull_diagnostics
runtime autoload/ale/lsp.vim
Execute(client/unregisterCapability requests should unregister and be acknowledged):
let g:sent_responses = []
let g:unregistered_capabilities = []
function! ale#lsp#SendResponse(conn_id, id, result) abort
call add(g:sent_responses, [a:conn_id, a:id, a:result])
endfunction
function! ale#lsp#UnregisterCapabilities(conn_id, unregisterations) abort
call add(g:unregistered_capabilities, [a:conn_id, a:unregisterations])
return 1
endfunction
call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'expert', 'aliases': [], 'lsp': 'stdio'}})
call ale#lsp_linter#SetDiagnosticURIMap({'14': 'file://foo.py'})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc': '2.0',
\ 'id': 14,
\ 'method': 'client/unregisterCapability',
\ 'params': {
\ 'unregisterations': [
\ {
\ 'id': 'abc',
\ 'method': 'textDocument/diagnostic',
\ },
\ ],
\ },
\})
AssertEqual
\ [[1, 14, v:null]],
\ g:sent_responses
AssertEqual
\ [[1, [{'id': 'abc', 'method': 'textDocument/diagnostic'}]]],
\ g:unregistered_capabilities
AssertEqual {'14': 'file://foo.py'}, ale#lsp_linter#GetDiagnosticURIMap()
unlet! g:sent_responses
unlet! g:unregistered_capabilities
runtime autoload/ale/lsp.vim
Execute(LSP errors should be logged in the history):