mirror of
https://github.com/neovim/neovim.git
synced 2026-07-14 13:20:35 +00:00
fix(lps): separate namespaces for pull/push diagnostics #37938
Problem:
Regression from b99cdd0:
Pull diagnostics (from `textDocument/diagnostic`) and push diagnostics
(from `textDocument/publishDiagnostics`) use the same namespace, which
is a problem when using language servers that publish two different sets
of diagnostics on push vs pull, like rust-analyzer (see
https://github.com/rust-lang/rust-analyzer/issues/18709#issuecomment-2551394047).
Solution:
Rename `is_pull` to `pull_id` which accepts a pull namespace instead of
just a boolean.
This commit is contained in:
committed by
GitHub
parent
7852993f49
commit
ea5007b37f
@@ -2074,14 +2074,15 @@ from({diagnostics}) *vim.lsp.diagnostic.from()*
|
|||||||
(`lsp.Diagnostic[]`)
|
(`lsp.Diagnostic[]`)
|
||||||
|
|
||||||
*vim.lsp.diagnostic.get_namespace()*
|
*vim.lsp.diagnostic.get_namespace()*
|
||||||
get_namespace({client_id}, {is_pull})
|
get_namespace({client_id}, {pull_id})
|
||||||
Get the diagnostic namespace associated with an LSP client
|
Get the diagnostic namespace associated with an LSP client
|
||||||
|vim.diagnostic| for diagnostics
|
|vim.diagnostic| for diagnostics
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {client_id} (`integer`) The id of the LSP client
|
• {client_id} (`integer`) The id of the LSP client
|
||||||
• {is_pull} (`boolean?`) Whether the namespace is for a pull or push
|
• {pull_id} (`(boolean|string)?`) (default: nil) Pull diagnostics
|
||||||
client. Defaults to push
|
provider id (indicates "pull" client), or `nil` for a
|
||||||
|
"push" client.
|
||||||
|
|
||||||
*vim.lsp.diagnostic.on_diagnostic()*
|
*vim.lsp.diagnostic.on_diagnostic()*
|
||||||
on_diagnostic({error}, {result}, {ctx})
|
on_diagnostic({error}, {result}, {ctx})
|
||||||
|
|||||||
@@ -187,14 +187,25 @@ local client_pull_namespaces = {}
|
|||||||
--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics
|
--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics
|
||||||
---
|
---
|
||||||
---@param client_id integer The id of the LSP client
|
---@param client_id integer The id of the LSP client
|
||||||
---@param is_pull boolean? Whether the namespace is for a pull or push client. Defaults to push
|
---@param pull_id (boolean|string)? (default: nil) Pull diagnostics provider id
|
||||||
function M.get_namespace(client_id, is_pull)
|
--- (indicates "pull" client), or `nil` for a "push" client.
|
||||||
|
function M.get_namespace(client_id, pull_id)
|
||||||
vim.validate('client_id', client_id, 'number')
|
vim.validate('client_id', client_id, 'number')
|
||||||
|
vim.validate('pull_id', pull_id, { 'boolean', 'string' }, true)
|
||||||
|
|
||||||
|
if type(pull_id) == 'boolean' then
|
||||||
|
vim.deprecate('get_namespace(pull_id:boolean)', 'get_namespace(pull_id:string)', '0.14')
|
||||||
|
end
|
||||||
|
|
||||||
local client = lsp.get_client_by_id(client_id)
|
local client = lsp.get_client_by_id(client_id)
|
||||||
if is_pull then
|
if pull_id then
|
||||||
local key = ('%d'):format(client_id)
|
local provider_id = type(pull_id) == 'string' and pull_id or 'nil'
|
||||||
local name = ('nvim.lsp.%s.%d'):format(client and client.name or 'unknown', client_id)
|
local key = ('%d:%s'):format(client_id, provider_id)
|
||||||
|
local name = ('nvim.lsp.%s.%d.%s'):format(
|
||||||
|
client and client.name or 'unknown',
|
||||||
|
client_id,
|
||||||
|
provider_id
|
||||||
|
)
|
||||||
local ns = client_pull_namespaces[key]
|
local ns = client_pull_namespaces[key]
|
||||||
if not ns then
|
if not ns then
|
||||||
ns = api.nvim_create_namespace(name)
|
ns = api.nvim_create_namespace(name)
|
||||||
@@ -215,8 +226,8 @@ end
|
|||||||
--- @param uri string
|
--- @param uri string
|
||||||
--- @param client_id? integer
|
--- @param client_id? integer
|
||||||
--- @param diagnostics lsp.Diagnostic[]
|
--- @param diagnostics lsp.Diagnostic[]
|
||||||
--- @param is_pull boolean
|
--- @param pull_id boolean|string
|
||||||
local function handle_diagnostics(uri, client_id, diagnostics, is_pull)
|
local function handle_diagnostics(uri, client_id, diagnostics, pull_id)
|
||||||
local fname = vim.uri_to_fname(uri)
|
local fname = vim.uri_to_fname(uri)
|
||||||
|
|
||||||
if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
|
if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
|
||||||
@@ -230,7 +241,7 @@ local function handle_diagnostics(uri, client_id, diagnostics, is_pull)
|
|||||||
|
|
||||||
client_id = client_id or DEFAULT_CLIENT_ID
|
client_id = client_id or DEFAULT_CLIENT_ID
|
||||||
|
|
||||||
local namespace = M.get_namespace(client_id, is_pull)
|
local namespace = M.get_namespace(client_id, pull_id)
|
||||||
|
|
||||||
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
|
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
|
||||||
end
|
end
|
||||||
@@ -276,11 +287,13 @@ function M.on_diagnostic(error, result, ctx)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true)
|
---@type lsp.DocumentDiagnosticParams
|
||||||
|
local params = ctx.params
|
||||||
|
handle_diagnostics(params.textDocument.uri, client_id, result.items, params.identifier or true)
|
||||||
|
|
||||||
for uri, related_result in pairs(result.relatedDocuments or {}) do
|
for uri, related_result in pairs(result.relatedDocuments or {}) do
|
||||||
if related_result.kind == 'full' then
|
if related_result.kind == 'full' then
|
||||||
handle_diagnostics(uri, client_id, related_result.items, true)
|
handle_diagnostics(uri, client_id, related_result.items, params.identifier or true)
|
||||||
end
|
end
|
||||||
|
|
||||||
local related_bufnr = vim.uri_to_bufnr(uri)
|
local related_bufnr = vim.uri_to_bufnr(uri)
|
||||||
@@ -504,6 +517,8 @@ function M._workspace_diagnostics(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if error == nil and result ~= nil then
|
if error == nil and result ~= nil then
|
||||||
|
---@type lsp.WorkspaceDiagnosticParams
|
||||||
|
local params = ctx.params
|
||||||
for _, report in ipairs(result.items) do
|
for _, report in ipairs(result.items) do
|
||||||
local bufnr = vim.uri_to_bufnr(report.uri)
|
local bufnr = vim.uri_to_bufnr(report.uri)
|
||||||
|
|
||||||
@@ -515,7 +530,7 @@ function M._workspace_diagnostics(opts)
|
|||||||
-- We favor document pull requests over workspace results, so only update the buffer
|
-- We favor document pull requests over workspace results, so only update the buffer
|
||||||
-- state if we're not pulling document diagnostics for this buffer.
|
-- state if we're not pulling document diagnostics for this buffer.
|
||||||
if bufstates[bufnr].pull_kind == 'workspace' and report.kind == 'full' then
|
if bufstates[bufnr].pull_kind == 'workspace' and report.kind == 'full' then
|
||||||
handle_diagnostics(report.uri, ctx.client_id, report.items, true)
|
handle_diagnostics(report.uri, ctx.client_id, report.items, params.identifier or true)
|
||||||
bufstates[bufnr].client_result_id[ctx.client_id] = report.resultId
|
bufstates[bufnr].client_result_id[ctx.client_id] = report.resultId
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -351,6 +351,89 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
eq('Pull Diagnostic', diags[1].message)
|
eq('Pull Diagnostic', diags[1].message)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('preserves push diagnostics when pull diagnostics are empty', function()
|
||||||
|
local push_ns_count, pull_ns_count, all_diags_count, push_ns, pull_ns = exec_lua(function()
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
_G.make_error('Push Diagnostic', 0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
}, { client_id = client_id })
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_diagnostic(nil, {
|
||||||
|
kind = 'full',
|
||||||
|
items = {},
|
||||||
|
}, {
|
||||||
|
params = {
|
||||||
|
textDocument = { uri = fake_uri },
|
||||||
|
},
|
||||||
|
uri = fake_uri,
|
||||||
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
local push_ns = vim.lsp.diagnostic.get_namespace(client_id, false)
|
||||||
|
local pull_ns = vim.lsp.diagnostic.get_namespace(client_id, true)
|
||||||
|
|
||||||
|
return #vim.diagnostic.get(diagnostic_bufnr, { namespace = push_ns }),
|
||||||
|
#vim.diagnostic.get(diagnostic_bufnr, { namespace = pull_ns }),
|
||||||
|
#vim.diagnostic.get(diagnostic_bufnr),
|
||||||
|
push_ns,
|
||||||
|
pull_ns
|
||||||
|
end)
|
||||||
|
|
||||||
|
eq(1, push_ns_count)
|
||||||
|
eq(0, pull_ns_count)
|
||||||
|
eq(1, all_diags_count)
|
||||||
|
neq(push_ns, pull_ns)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('uses pull_id to isolate pull diagnostic namespaces', function()
|
||||||
|
local first_count, second_count, total_count, first_ns, second_ns = exec_lua(function()
|
||||||
|
vim.lsp.diagnostic.on_diagnostic(nil, {
|
||||||
|
kind = 'full',
|
||||||
|
items = {
|
||||||
|
_G.make_error('Pull Diagnostic A', 0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
params = {
|
||||||
|
identifier = 'provider-a',
|
||||||
|
textDocument = { uri = fake_uri },
|
||||||
|
},
|
||||||
|
uri = fake_uri,
|
||||||
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_diagnostic(nil, {
|
||||||
|
kind = 'full',
|
||||||
|
items = {},
|
||||||
|
}, {
|
||||||
|
params = {
|
||||||
|
identifier = 'provider-b',
|
||||||
|
textDocument = { uri = fake_uri },
|
||||||
|
},
|
||||||
|
uri = fake_uri,
|
||||||
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
local first_ns = vim.lsp.diagnostic.get_namespace(client_id, 'provider-a')
|
||||||
|
local second_ns = vim.lsp.diagnostic.get_namespace(client_id, 'provider-b')
|
||||||
|
|
||||||
|
return #vim.diagnostic.get(diagnostic_bufnr, { namespace = first_ns }),
|
||||||
|
#vim.diagnostic.get(diagnostic_bufnr, { namespace = second_ns }),
|
||||||
|
#vim.diagnostic.get(diagnostic_bufnr),
|
||||||
|
first_ns,
|
||||||
|
second_ns
|
||||||
|
end)
|
||||||
|
|
||||||
|
eq(1, first_count)
|
||||||
|
eq(0, second_count)
|
||||||
|
eq(1, total_count)
|
||||||
|
neq(first_ns, second_ns)
|
||||||
|
end)
|
||||||
|
|
||||||
it('handles multiline diagnostic ranges #33782', function()
|
it('handles multiline diagnostic ranges #33782', function()
|
||||||
local diags = exec_lua(function()
|
local diags = exec_lua(function()
|
||||||
vim.lsp.diagnostic.on_diagnostic(nil, {
|
vim.lsp.diagnostic.on_diagnostic(nil, {
|
||||||
|
|||||||
Reference in New Issue
Block a user