From 68e316e3f9107f2bd9be934f089cf8203f9c4c3d Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 10 Jul 2025 11:24:17 -0700 Subject: [PATCH] feat(diagnostic): jump to related info location from `open_float` #34837 This commit allows users to jump to the location specified in a diagnostic's `relatedInformation`, using `gf` from within the `open_float` window. The cursor need only be on line that displays the related info. --- runtime/doc/lsp.txt | 3 +- runtime/doc/news.txt | 3 + runtime/lua/vim/diagnostic.lua | 24 +++++-- runtime/lua/vim/lsp/diagnostic.lua | 3 +- test/functional/lua/diagnostic_spec.lua | 86 +++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 6 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 13d50b1afa..713d4ee9e5 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1903,7 +1903,8 @@ Lua module: vim.lsp.diagnostic *lsp-diagnostic* This module provides functionality for requesting LSP diagnostics for a document/workspace and populating them using |vim.Diagnostic|s. `DiagnosticRelatedInformation` is supported: it is included in the window -shown by |vim.diagnostic.open_float()|. +shown by |vim.diagnostic.open_float()|. When the cursor is on a line with +related information, |gf| jumps to the problem location. from({diagnostics}) *vim.lsp.diagnostic.from()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 229d67b702..32f07e84ef 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -204,6 +204,9 @@ LSP • LSP `DiagnosticRelatedInformation` is now shown in |vim.diagnostic.open_float()|. It is read from the LSP diagnostic object stored in the `user_data` field. +• When inside the float created by |vim.diagnostic.open_float()| and the + cursor is on a line with `DiagnosticRelatedInformation`, |gf| can be used to + jump to the problematic location. LUA diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 480767dab0..a3ac09e35b 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -2341,6 +2341,8 @@ function M.open_float(opts, ...) end end + ---@type table + local related_info_locations = {} for i, diagnostic in ipairs(diagnostics) do if type(prefix_opt) == 'function' then --- @cast prefix_opt fun(...): string?, string? @@ -2378,15 +2380,16 @@ function M.open_float(opts, ...) -- Below the diagnostic, show its LSP related information (if any) in the form of file name and -- range, plus description. for _, info in ipairs(related_info) do - -- TODO: Somehow allow users to open the location when their cursor is over it? - local file_name = vim.fs.basename(vim.uri_to_fname(info.location.uri)) + local location = info.location + local file_name = vim.fs.basename(vim.uri_to_fname(location.uri)) local info_suffix = ': ' .. info.message + related_info_locations[#lines + 1] = info.location lines[#lines + 1] = string.format( '%s%s:%s:%s%s', default_pre, file_name, - info.location.range.start.line, - info.location.range.start.character, + location.range.start.line, + location.range.start.character, info_suffix ) highlights[#highlights + 1] = { @@ -2412,6 +2415,19 @@ function M.open_float(opts, ...) local float_bufnr, winnr = vim.lsp.util.open_floating_preview(lines, 'plaintext', opts) vim.bo[float_bufnr].path = vim.bo[bufnr].path + -- TODO: Handle this generally (like vim.ui.open()), rather than overriding gf. + vim.keymap.set('n', 'gf', function() + local cursor_row = api.nvim_win_get_cursor(0)[1] + local location = related_info_locations[cursor_row] + if location then + -- Split the window before calling `show_document` so the window doesn't disappear. + vim.cmd.split() + vim.lsp.util.show_document(location, 'utf-16', { focus = true }) + else + vim.cmd.normal({ 'gf', bang = true }) + end + end, { buffer = float_bufnr, remap = false }) + --- @diagnostic disable-next-line: deprecated local add_highlight = api.nvim_buf_add_highlight diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 7c8b7756e9..94cd3460c3 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -1,6 +1,7 @@ ---@brief This module provides functionality for requesting LSP diagnostics for a document/workspace ---and populating them using |vim.Diagnostic|s. `DiagnosticRelatedInformation` is supported: it is ----included in the window shown by |vim.diagnostic.open_float()|. +---included in the window shown by |vim.diagnostic.open_float()|. When the cursor is on a line with +---related information, |gf| jumps to the problem location. local lsp = vim.lsp local protocol = lsp.protocol diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 7f94d0ed54..497c6f1f97 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -3383,6 +3383,92 @@ describe('vim.diagnostic', function() ) end) + it('can add LSP related information to a diagnostic', function() + local related_info_uri = 'file:///fake/uri' + + -- Populate the related info buffer. + exec_lua(function() + local fake_bufnr = vim.uri_to_bufnr(related_info_uri) + vim.fn.bufload(fake_bufnr) + vim.api.nvim_buf_set_lines(fake_bufnr, 0, 1, false, { + 'O, the Pelican,', + 'so smoothly doth he crest.', + 'a wind god!', + }) + vim.api.nvim_win_set_buf(0, fake_bufnr) + end) + + -- Displays related info. + eq( + { + '1. Some warning', + ' uri:1:0: Some extra info', + ' uri:2:3: Some more extra info', + }, + exec_lua(function() + ---@type vim.Diagnostic + local diagnostic = _G.make_warning('Some warning', 1, 1, 1, 3) + diagnostic.user_data = { + -- Related information comes from LSP user_data + lsp = { + relatedInformation = { + { + message = 'Some extra info', + location = { + uri = related_info_uri, + range = { + start = { + line = 1, + character = 0, + }, + ['end'] = { + line = 1, + character = 1, + }, + }, + }, + }, + { + message = 'Some more extra info', + location = { + uri = related_info_uri, + range = { + start = { + line = 2, + character = 3, + }, + ['end'] = { + line = 4, + character = 5, + }, + }, + }, + }, + }, + }, + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { diagnostic }) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + -- Put the cursor on a line with related info + vim.api.nvim_tabpage_set_win(0, winnr) + vim.api.nvim_win_set_cursor(0, { 2, 0 }) + return lines + end) + ) + + -- Jumps to related info. + eq( + 'so smoothly doth he crest.', + exec_lua(function() + vim.cmd.norm('gf') + vim.wait(20, function() end) + return vim.api.nvim_get_current_line() + end) + ) + end) + it('works with the old signature', function() eq( { '1. Syntax error' },