mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
fix(diagnostic): clamp line numbers in display layer (#15729)
Some parts of LSP need to use cached diagnostics as sent from the LSP server unmodified. Rather than fixing invalid line numbers when diagnostics are first set, fix them when they are displayed to the user (e.g. in show() or one of the get_next/get_prev family of functions).
This commit is contained in:
@@ -222,26 +222,14 @@ local function diagnostic_lines(diagnostics)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
local function set_diagnostic_cache(namespace, diagnostics, bufnr)
|
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
|
||||||
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
|
|
||||||
for _, diagnostic in ipairs(diagnostics) do
|
for _, diagnostic in ipairs(diagnostics) do
|
||||||
if diagnostic.severity == nil then
|
diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
|
||||||
diagnostic.severity = M.severity.ERROR
|
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
|
||||||
end
|
diagnostic.end_col = diagnostic.end_col or diagnostic.col
|
||||||
|
|
||||||
diagnostic.namespace = namespace
|
diagnostic.namespace = namespace
|
||||||
diagnostic.bufnr = bufnr
|
diagnostic.bufnr = bufnr
|
||||||
|
|
||||||
if buf_line_count > 0 then
|
|
||||||
diagnostic.lnum = math.max(math.min(
|
|
||||||
diagnostic.lnum, buf_line_count - 1
|
|
||||||
), 0)
|
|
||||||
diagnostic.end_lnum = math.max(math.min(
|
|
||||||
diagnostic.end_lnum, buf_line_count - 1
|
|
||||||
), 0)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
diagnostic_cache[bufnr][namespace] = diagnostics
|
diagnostic_cache[bufnr][namespace] = diagnostics
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -403,13 +391,28 @@ local function set_list(loclist, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
local function clamp_line_numbers(bufnr, diagnostics)
|
||||||
|
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||||
|
if buf_line_count == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, diagnostic in ipairs(diagnostics) do
|
||||||
|
diagnostic.lnum = math.max(math.min(diagnostic.lnum, buf_line_count - 1), 0)
|
||||||
|
diagnostic.end_lnum = math.max(math.min(diagnostic.end_lnum, buf_line_count - 1), 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
||||||
position[1] = position[1] - 1
|
position[1] = position[1] - 1
|
||||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
bufnr = get_bufnr(bufnr)
|
||||||
local wrap = vim.F.if_nil(opts.wrap, true)
|
local wrap = vim.F.if_nil(opts.wrap, true)
|
||||||
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||||
opts.namespace = namespace
|
local diagnostics = M.get(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}))
|
||||||
|
clamp_line_numbers(bufnr, diagnostics)
|
||||||
|
local line_diagnostics = diagnostic_lines(diagnostics)
|
||||||
for i = 0, line_count do
|
for i = 0, line_count do
|
||||||
local offset = i * (search_forward and 1 or -1)
|
local offset = i * (search_forward and 1 or -1)
|
||||||
local lnum = position[1] + offset
|
local lnum = position[1] + offset
|
||||||
@@ -419,9 +422,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
|||||||
end
|
end
|
||||||
lnum = (lnum + line_count) % line_count
|
lnum = (lnum + line_count) % line_count
|
||||||
end
|
end
|
||||||
opts.lnum = lnum
|
if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
|
||||||
local line_diagnostics = M.get(bufnr, opts)
|
|
||||||
if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
|
|
||||||
local sort_diagnostics, is_next
|
local sort_diagnostics, is_next
|
||||||
if search_forward then
|
if search_forward then
|
||||||
sort_diagnostics = function(a, b) return a.col < b.col end
|
sort_diagnostics = function(a, b) return a.col < b.col end
|
||||||
@@ -430,15 +431,15 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
|||||||
sort_diagnostics = function(a, b) return a.col > b.col end
|
sort_diagnostics = function(a, b) return a.col > b.col end
|
||||||
is_next = function(diagnostic) return diagnostic.col < position[2] end
|
is_next = function(diagnostic) return diagnostic.col < position[2] end
|
||||||
end
|
end
|
||||||
table.sort(line_diagnostics, sort_diagnostics)
|
table.sort(line_diagnostics[lnum], sort_diagnostics)
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
for _, v in pairs(line_diagnostics) do
|
for _, v in pairs(line_diagnostics[lnum]) do
|
||||||
if is_next(v) then
|
if is_next(v) then
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return line_diagnostics[1]
|
return line_diagnostics[lnum][1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -466,7 +467,6 @@ local function diagnostic_move_pos(opts, pos)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- Public API {{{
|
-- Public API {{{
|
||||||
@@ -566,7 +566,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
set_diagnostic_cache(namespace, diagnostics, bufnr)
|
set_diagnostic_cache(namespace, bufnr, diagnostics)
|
||||||
|
|
||||||
if vim.api.nvim_buf_is_loaded(bufnr) then
|
if vim.api.nvim_buf_is_loaded(bufnr) then
|
||||||
M.show(namespace, bufnr, diagnostics, opts)
|
M.show(namespace, bufnr, diagnostics, opts)
|
||||||
@@ -983,6 +983,8 @@ function M.show(namespace, bufnr, diagnostics, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
clamp_line_numbers(bufnr, diagnostics)
|
||||||
|
|
||||||
if opts.underline then
|
if opts.underline then
|
||||||
M._set_underline(namespace, bufnr, diagnostics, opts.underline)
|
M._set_underline(namespace, bufnr, diagnostics, opts.underline)
|
||||||
end
|
end
|
||||||
@@ -1029,7 +1031,9 @@ function M.show_position_diagnostics(opts, bufnr, position)
|
|||||||
position[2] >= diag.col and
|
position[2] >= diag.col and
|
||||||
(position[2] <= diag.end_col or position[1] < diag.end_lnum)
|
(position[2] <= diag.end_col or position[1] < diag.end_lnum)
|
||||||
end
|
end
|
||||||
local position_diagnostics = vim.tbl_filter(match_position_predicate, M.get(bufnr, opts))
|
local diagnostics = M.get(bufnr, opts)
|
||||||
|
clamp_line_numbers(bufnr, diagnostics)
|
||||||
|
local position_diagnostics = vim.tbl_filter(match_position_predicate, diagnostics)
|
||||||
table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end)
|
table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end)
|
||||||
return show_diagnostics(opts, position_diagnostics)
|
return show_diagnostics(opts, position_diagnostics)
|
||||||
end
|
end
|
||||||
@@ -1049,9 +1053,11 @@ function M.show_line_diagnostics(opts, bufnr, lnum)
|
|||||||
|
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
opts.focus_id = "line_diagnostics"
|
opts.focus_id = "line_diagnostics"
|
||||||
opts.lnum = lnum or (vim.api.nvim_win_get_cursor(0)[1] - 1)
|
|
||||||
bufnr = get_bufnr(bufnr)
|
bufnr = get_bufnr(bufnr)
|
||||||
local line_diagnostics = M.get(bufnr, opts)
|
local diagnostics = M.get(bufnr, opts)
|
||||||
|
clamp_line_numbers(bufnr, diagnostics)
|
||||||
|
lnum = lnum or (vim.api.nvim_win_get_cursor(0)[1] - 1)
|
||||||
|
local line_diagnostics = diagnostic_lines(diagnostics)[lnum]
|
||||||
return show_diagnostics(opts, line_diagnostics)
|
return show_diagnostics(opts, line_diagnostics)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -842,6 +842,18 @@ describe('vim.diagnostic', function()
|
|||||||
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
|
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('clamps diagnostic line numbers within the valid range', function()
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
local diagnostics = {
|
||||||
|
make_error("Syntax error", 6, 0, 6, 0),
|
||||||
|
}
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
|
||||||
|
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({show_header = false}, diagnostic_bufnr, 5)
|
||||||
|
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('set_signs()', function()
|
describe('set_signs()', function()
|
||||||
|
Reference in New Issue
Block a user