Skip to content

Commit

Permalink
feat(diagnostics): :RustLsp relatedDiagnostics command (#601)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb authored Dec 3, 2024
1 parent 9032904 commit 0813d4d
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 27 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,26 @@ vim.keymap.set(

</details>

<details>
<summary>
<b>Jump to related diagnostics</b>
</summary>

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)

</details>

<details>
<summary>
<b>Open Cargo.toml</b>
Expand Down
3 changes: 3 additions & 0 deletions doc/rustaceanvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
126 changes: 99 additions & 27 deletions lua/rustaceanvim/commands/diagnostic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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('<cword>'), 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
5 changes: 5 additions & 0 deletions lua/rustaceanvim/commands/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
3 changes: 3 additions & 0 deletions lua/rustaceanvim/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 0813d4d

Please sign in to comment.