fix(diagnostics): avoid jumping to diagnostics in deleted ranges #35088

Problem:
diagnostic extmark used for positioning continues to exist after
deleting a range containing it, so it's possible to jump to a
next/previous diagnositc, which isn't visible in any way, including not
being shown via `open_float`.

Solution:
enable `invalidate` flag when setting an extmark to be able to filter
out diagnostics based on `invalid` flag when looking for next/previous
diagnostic to jump to.
This commit is contained in:
Sergei Slipchenko
2025-07-28 09:24:10 +04:00
committed by GitHub
parent 5151f635ca
commit 7a051a4c38
2 changed files with 40 additions and 19 deletions

View File

@@ -648,6 +648,7 @@ local sign_highlight_map = make_highlight_map('Sign')
--- @return integer col
--- @return integer end_lnum
--- @return integer end_col
--- @return boolean valid
local function get_logical_pos(diagnostic)
local ns = M.get_namespace(diagnostic.namespace)
local extmark = api.nvim_buf_get_extmark_by_id(
@@ -657,7 +658,7 @@ local function get_logical_pos(diagnostic)
{ details = true }
)
return extmark[1], extmark[2], extmark[3].end_row, extmark[3].end_col
return extmark[1], extmark[2], extmark[3].end_row, extmark[3].end_col, not extmark[3].invalid
end
--- @param diagnostics vim.Diagnostic[]
@@ -669,13 +670,15 @@ local function diagnostic_lines(diagnostics)
local diagnostics_by_line = {} --- @type table<integer,vim.Diagnostic[]>
for _, diagnostic in ipairs(diagnostics) do
local lnum = get_logical_pos(diagnostic)
local line_diagnostics = diagnostics_by_line[lnum]
if not line_diagnostics then
line_diagnostics = {}
diagnostics_by_line[lnum] = line_diagnostics
local lnum, _, _, _, valid = get_logical_pos(diagnostic)
if valid then
local line_diagnostics = diagnostics_by_line[lnum]
if not line_diagnostics then
line_diagnostics = {}
diagnostics_by_line[lnum] = line_diagnostics
end
table.insert(line_diagnostics, diagnostic)
end
table.insert(line_diagnostics, diagnostic)
end
return diagnostics_by_line
end
@@ -1332,6 +1335,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
diagnostic._extmark_id = api.nvim_buf_set_extmark(bufnr, ns.user_data.location_ns, row, col, {
end_row = end_row,
end_col = end_col,
invalidate = true,
})
end
end)

View File

@@ -1433,15 +1433,6 @@ describe('vim.diagnostic', function()
describe('after inserting text before diagnostic position', function()
before_each(function()
exec_lua(function()
vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
_G.make_error('Diagnostic #1', 1, 4, 1, 7),
_G.make_error('Diagnostic #2', 3, 0, 3, 3),
})
end)
api.nvim_buf_set_text(0, 3, 0, 3, 0, { 'new line', 'new ' })
end)
@@ -1449,7 +1440,7 @@ describe('vim.diagnostic', function()
eq(
{ 5, 4 },
exec_lua(function()
vim.api.nvim_win_set_cursor(0, { 2, 4 })
vim.api.nvim_win_set_cursor(0, { 3, 1 })
vim.diagnostic.jump({ count = 1 })
return vim.api.nvim_win_get_cursor(0)
end)
@@ -1471,8 +1462,6 @@ describe('vim.diagnostic', function()
describe('if diagnostic is set after last character in line', function()
before_each(function()
exec_lua(function()
vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
_G.make_error('Diagnostic #1', 2, 3, 3, 4),
})
@@ -1501,6 +1490,34 @@ describe('vim.diagnostic', function()
)
end)
end)
describe('after entire text range with a diagnostic was deleted', function()
before_each(function()
api.nvim_buf_set_text(0, 1, 1, 1, 4, {})
end)
it('does not find next diagnostic inside the deleted range', function()
eq(
{ 3, 0 },
exec_lua(function()
vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.diagnostic.jump({ count = 1 })
return vim.api.nvim_win_get_cursor(0)
end)
)
end)
it('does not find previous diagnostic inside the deleted range', function()
eq(
{ 1, 0 },
exec_lua(function()
vim.api.nvim_win_set_cursor(0, { 3, 0 })
vim.diagnostic.jump({ count = -1 })
return vim.api.nvim_win_get_cursor(0)
end)
)
end)
end)
end)
describe('get()', function()