commit 7a051a4c389452b4955c22e3c29071433e01905a
parent 5151f635ca299dcb7aeb7e34d7c01fba07108b88
Author: Sergei Slipchenko <faergeek@gmail.com>
Date: Mon, 28 Jul 2025 09:24:10 +0400
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.
Diffstat:
2 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
@@ -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)
diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua
@@ -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()