neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit b0e8b0a35f3d38763c564f6d4924f7be2e2271fb
parent ea2d226df6f344451306274f3f99911d459fb9dd
Author: Justin M. Keyes <justinkz@gmail.com>
Date:   Tue, 22 Jul 2025 00:38:46 -0400

Merge #34853 from ribru17/diag_related_docs

feat(lsp): diagnostic related documents support
Diffstat:
Mruntime/doc/news.txt | 2++
Mruntime/lua/vim/lsp/diagnostic.lua | 26+++++++++++++++++++++++---
Mruntime/lua/vim/lsp/protocol.lua | 1+
Mtest/functional/plugin/lsp/diagnostic_spec.lua | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 106 insertions(+), 3 deletions(-)

diff --git 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 @@ -269,16 +269,36 @@ 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) + + 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 @@ -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 @@ -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,85 @@ 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) + + 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)