From 1d8e9b5d070ca43f9a12c865c598f7d7712078c6 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 8 Jul 2025 18:03:52 -0700 Subject: [PATCH 1/2] fix(lsp): store result id for unchanged diagnostic reports **Problem:** For unchanged document diagnostic reports, the `resultId` is ignored completely, even though it should still be saved for the request (in fact, the spec marks it as mandatory for unchanged reports, so it should be extra important). **Solution:** Always store the `resultId`. --- runtime/lua/vim/lsp/diagnostic.lua | 10 ++++--- .../functional/plugin/lsp/diagnostic_spec.lua | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 94cd3460c3..fcb3662adb 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -269,16 +269,20 @@ function M.on_diagnostic(error, result, ctx) return end - if result == nil or result.kind == 'unchanged' then + if result == nil then return end local client_id = ctx.client_id - handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true) - local bufnr = assert(ctx.bufnr) local bufstate = bufstates[bufnr] bufstate.client_result_id[client_id] = result.resultId + + if result.kind == 'unchanged' then + return + end + + handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true) end --- Clear push diagnostics and diagnostic cache. diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 0d9ed9bd2c..146575d0ca 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -470,6 +470,7 @@ describe('vim.lsp.diagnostic', function() end) it('requests with the `previousResultId`', function() + -- Full reports eq( 'dummy_server', exec_lua(function() @@ -497,6 +498,32 @@ describe('vim.lsp.diagnostic', function() return _G.params.previousResultId end) ) + + -- Unchanged reports + eq( + 'squidward', + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'unchanged', + resultId = 'squidward', + }, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + params = { + textDocument = { uri = fake_uri }, + }, + client_id = client_id, + bufnr = diagnostic_bufnr, + }) + vim.api.nvim_exec_autocmds('LspNotify', { + buffer = diagnostic_bufnr, + data = { + method = vim.lsp.protocol.Methods.textDocument_didChange, + client_id = client_id, + }, + }) + return _G.params.previousResultId + end) + ) end) end) end) From 371aa1c5663ccb73b12b105ed09e85813d1c985b Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 8 Jul 2025 18:41:50 -0700 Subject: [PATCH 2/2] feat(lsp): diagnostic related documents support --- runtime/doc/news.txt | 2 + runtime/lua/vim/lsp/diagnostic.lua | 16 ++++++ runtime/lua/vim/lsp/protocol.lua | 1 + .../functional/plugin/lsp/diagnostic_spec.lua | 53 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 220c5fcb38..a73b02fefa 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -214,6 +214,8 @@ LSP jump to the problematic location. • Support for `textDocument/linkedEditingRange`: |lsp-linked_editing_range| https://microsoft.github.io/language-server-protocol/specification/#textDocument_linkedEditingRange +• Support for related documents in pull diagnostics: + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#relatedFullDocumentDiagnosticReport LUA diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index fcb3662adb..4eae02c195 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -283,6 +283,22 @@ function M.on_diagnostic(error, result, ctx) end handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true) + + for uri, related_result in pairs(result.relatedDocuments or {}) do + if related_result.kind == 'full' then + handle_diagnostics(uri, client_id, related_result.items, true) + end + + local related_bufnr = vim.uri_to_bufnr(uri) + local related_bufstate = bufstates[related_bufnr] + -- Create a new bufstate if it doesn't exist for the related document. This will not enable + -- diagnostic pulling by itself, but will allow previous result IDs to be passed correctly the + -- next time this buffer's diagnostics are pulled. + or { pull_kind = 'document', client_result_id = {} } + bufstates[related_bufnr] = related_bufstate + + related_bufstate.client_result_id[client_id] = related_result.resultId + end end --- Clear push diagnostics and diagnostic cache. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 314717037a..372affecc4 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -350,6 +350,7 @@ function protocol.make_client_capabilities() }, dataSupport = true, relatedInformation = true, + relatedDocumentSupport = true, }, inlayHint = { dynamicRegistration = true, diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 146575d0ca..72d1dfebab 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -525,5 +525,58 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles relatedDocuments diagnostics', function() + local fake_uri_2 = 'file:///fake/uri2' + ---@type vim.Diagnostic[], vim.Diagnostic[], string? + local diagnostics, related_diagnostics, relatedPreviousResultId = exec_lua(function() + local second_buf = vim.uri_to_bufnr(fake_uri_2) + vim.fn.bufload(second_buf) + + -- Attach the client to both buffers. + vim.api.nvim_win_set_buf(0, second_buf) + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'full', + relatedDocuments = { + [fake_uri_2] = { + kind = 'full', + resultId = 'spongebob', + items = { + { + range = _G.make_range(4, 4, 4, 4), + message = 'related bad!', + }, + }, + }, + }, + items = {}, + }, { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + bufnr = diagnostic_bufnr, + }, {}) + + vim.api.nvim_exec_autocmds('LspNotify', { + buffer = second_buf, + data = { + method = vim.lsp.protocol.Methods.textDocument_didChange, + client_id = client_id, + }, + }) + + return vim.diagnostic.get(diagnostic_bufnr), + vim.diagnostic.get(second_buf), + _G.params.previousResultId + end) + eq(0, #diagnostics) + eq(1, #related_diagnostics) + eq('related bad!', related_diagnostics[1].message) + eq('spongebob', relatedPreviousResultId) + end) end) end)