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.