diff --git a/README.md b/README.md index 235ab875..0d593360 100644 --- a/README.md +++ b/README.md @@ -505,6 +505,26 @@ vim.keymap.set( +
+ + Jump to related diagnostics + + + Sometimes, rust-analyzer provides related diagnostics in multiple locations. + Using the `relatedDiagnostics` subcommand, you can navigate between them. + If a diagnostic has more than one related diagnostic, this will populate the quickfix list. + + ```vim + :RustLsp relatedDiagnostics + ``` + ```lua + vim.cmd.RustLsp('relatedDiagnostics') + ``` + +![](https://github.com/user-attachments/assets/26695f41-2d9d-4250-82fa-fea867fd9432) + +
+
Open Cargo.toml diff --git a/doc/rustaceanvim.txt b/doc/rustaceanvim.txt index 690e2419..facba973 100644 --- a/doc/rustaceanvim.txt +++ b/doc/rustaceanvim.txt @@ -65,6 +65,9 @@ It accepts the following subcommands: - If called with |current|: Searches for diagnostics only in the current cursor line. + 'relatedDiagnostics' - Jump to diagnostics that are related to the one under the cursor. + If more than one diagnostic is found, this will populate and open + the quickfix list. 'openCargo' - Open the Cargo.toml file for the current package. 'openDocs' - Open docs.rs documentation for the symbol under the cursor. 'parentModule' - Open the current module's parent module. diff --git a/lua/rustaceanvim/commands/diagnostic.lua b/lua/rustaceanvim/commands/diagnostic.lua index 4902f722..1342aa3a 100644 --- a/lua/rustaceanvim/commands/diagnostic.lua +++ b/lua/rustaceanvim/commands/diagnostic.lua @@ -60,11 +60,15 @@ function M.explain_error() return end - local diagnostics = vim.tbl_filter(function(diagnostic) - return diagnostic.code ~= nil - and diagnostic.source == 'rustc' - and diagnostic.severity == vim.diagnostic.severity.ERROR - end, vim.diagnostic.get(0, {})) + local diagnostics = vim + .iter(vim.diagnostic.get(0, {})) + ---@param diagnostic vim.Diagnostic + :filter(function(diagnostic) + return diagnostic.code ~= nil + and diagnostic.source == 'rustc' + and diagnostic.severity == vim.diagnostic.severity.ERROR + end) + :totable() if #diagnostics == 0 then vim.notify('No explainable errors found.', vim.log.levels.INFO) return @@ -168,16 +172,16 @@ function M.explain_error_current_line() local cursor_position = vim.api.nvim_win_get_cursor(win_id) -- get matching diagnostics from current line - local diagnostics = vim.tbl_filter( - function(diagnostic) + local diagnostics = vim + .iter(vim.diagnostic.get(0, { + lnum = cursor_position[1] - 1, + })) + :filter(function(diagnostic) return diagnostic.code ~= nil and diagnostic.source == 'rustc' and diagnostic.severity == vim.diagnostic.severity.ERROR - end, - vim.diagnostic.get(0, { - lnum = cursor_position[1] - 1, - }) - ) + end) + :totable() -- no matching diagnostics on current line if #diagnostics == 0 then @@ -228,7 +232,7 @@ function M.explain_error_current_line() vim.system({ rustc, '--explain', tostring(diagnostic.code) }, nil, vim.schedule_wrap(handler)) end ----@param diagnostic table +---@param diagnostic vim.Diagnostic ---@return string | nil local function get_rendered_diagnostic(diagnostic) local result = vim.tbl_get(diagnostic, 'user_data', 'lsp', 'data', 'rendered') @@ -307,9 +311,13 @@ local function render_ansi_code_diagnostic(rendered_diagnostic) end function M.render_diagnostic() - local diagnostics = vim.tbl_filter(function(diagnostic) - return get_rendered_diagnostic(diagnostic) ~= nil - end, vim.diagnostic.get(0, {})) + local diagnostics = vim + .iter(vim.diagnostic.get(0, {})) + ---@param diagnostic vim.Diagnostic + :filter(function(diagnostic) + return get_rendered_diagnostic(diagnostic) ~= nil + end) + :totable() if #diagnostics == 0 then vim.notify('No renderable diagnostics found.', vim.log.levels.INFO) return @@ -364,22 +372,34 @@ function M.render_diagnostic() render_ansi_code_diagnostic(rendered_diagnostic) end -function M.render_diagnostic_current_line() +---@return vim.Diagnostic[] +local function get_diagnostics_current_line() local win_id = vim.api.nvim_get_current_win() local cursor_position = vim.api.nvim_win_get_cursor(win_id) + return vim.diagnostic.get(0, { + lnum = cursor_position[1] - 1, + }) +end + +---@return vim.Diagnostic[] +local function get_diagnostics_at_cursor() + local win_id = vim.api.nvim_get_current_win() + local cursor_position = vim.api.nvim_win_get_cursor(win_id) + return vim.diagnostic.get(0, { + pos = cursor_position, + }) +end +function M.render_diagnostic_current_line() -- get rendered diagnostics from current line - local rendered_diagnostics = vim.tbl_map( - function(diagnostic) + ---@type string[] + local rendered_diagnostics = vim + .iter(get_diagnostics_current_line()) + ---@param diagnostic vim.Diagnostic + :map(function(diagnostic) return get_rendered_diagnostic(diagnostic) - end, - vim.diagnostic.get(0, { - lnum = cursor_position[1] - 1, - }) - ) - rendered_diagnostics = vim.tbl_filter(function(diagnostic) - return diagnostic ~= nil - end, rendered_diagnostics) + end) + :totable() -- if no renderable diagnostics on current line if #rendered_diagnostics == 0 then @@ -391,4 +411,56 @@ function M.render_diagnostic_current_line() render_ansi_code_diagnostic(rendered_diagnostic) end +---@class rustaceanvim.diagnostic.RelatedInfo +---@field location lsp.Location +---@field message string + +function M.related_diagnostics() + local ra = require('rustaceanvim.rust_analyzer') + local clients = ra.get_active_rustaceanvim_clients(0) + if #clients == 0 then + return + end + ---@type lsp.Location[] + local locations = vim + .iter(get_diagnostics_at_cursor()) + ---@param diagnostic vim.Diagnostic + :map(function(diagnostic) + return vim.tbl_get(diagnostic, 'user_data', 'lsp', 'relatedInformation') + end) + :flatten() + ---@param related_info rustaceanvim.diagnostic.RelatedInfo + :map(function(related_info) + return related_info.location + end) + :totable() + if #locations == 0 then + vim.notify('No related diagnostics found.', vim.log.levels.INFO) + return + end + local quickfix_entries = vim.lsp.util.locations_to_items(locations, clients[1].offset_encoding) + if #quickfix_entries == 1 then + local item = quickfix_entries[1] + ---@diagnostic disable-next-line: undefined-field + local b = item.bufnr or vim.fn.bufadd(item.filename) + -- Save position in jumplist + vim.cmd.normal { "m'", bang = true } + -- Push a new item into tagstack + local tagstack = { { tagname = vim.fn.expand(''), from = vim.fn.getpos('.') } } + local current_window_id = vim.api.nvim_get_current_win() + vim.fn.settagstack(vim.fn.win_getid(current_window_id), { items = tagstack }, 't') + vim.bo[b].buflisted = true + local window_id = vim.fn.win_findbuf(b)[1] or current_window_id + vim.api.nvim_win_set_buf(window_id, b) + vim.api.nvim_win_set_cursor(window_id, { item.lnum, item.col - 1 }) + vim._with({ win = window_id }, function() + -- Open folds under the cursor + vim.cmd.normal { 'zv', bang = true } + end) + else + vim.fn.setqflist({}, ' ', { title = 'related diagnostics', items = quickfix_entries }) + vim.cmd.botright('copen') + end +end + return M diff --git a/lua/rustaceanvim/commands/init.lua b/lua/rustaceanvim/commands/init.lua index 93788964..36493d9a 100644 --- a/lua/rustaceanvim/commands/init.lua +++ b/lua/rustaceanvim/commands/init.lua @@ -77,6 +77,11 @@ local rustlsp_command_tbl = { return { 'cycle', 'current' } end, }, + relatedDiagnostics = { + impl = function() + require('rustaceanvim.commands.diagnostic').related_diagnostics() + end, + }, renderDiagnostic = { impl = function(args) local subcmd = args[1] or 'cycle' diff --git a/lua/rustaceanvim/init.lua b/lua/rustaceanvim/init.lua index bf7ee927..351c6a16 100644 --- a/lua/rustaceanvim/init.lua +++ b/lua/rustaceanvim/init.lua @@ -58,6 +58,9 @@ --- - If called with |current|: --- Searches for diagnostics only in the --- current cursor line. +--- 'relatedDiagnostics' - Jump to diagnostics that are related to the one under the cursor. +--- If more than one diagnostic is found, this will populate and open +--- the quickfix list. --- 'openCargo' - Open the Cargo.toml file for the current package. --- 'openDocs' - Open docs.rs documentation for the symbol under the cursor. --- 'parentModule' - Open the current module's parent module.