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:
Gregory Anders 2021-09-20 12:32:21 -06:00 committed by GitHub
parent f4ca3a29dd
commit 0216aed20c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 29 deletions

View File

@ -222,26 +222,14 @@ local function diagnostic_lines(diagnostics)
end
---@private
local function set_diagnostic_cache(namespace, diagnostics, bufnr)
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
if diagnostic.severity == nil then
diagnostic.severity = M.severity.ERROR
end
diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
diagnostic.end_col = diagnostic.end_col or diagnostic.col
diagnostic.namespace = namespace
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
diagnostic_cache[bufnr][namespace] = diagnostics
end
@ -403,13 +391,28 @@ local function set_list(loclist, opts)
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
local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
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 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
local offset = i * (search_forward and 1 or -1)
local lnum = position[1] + offset
@ -419,9 +422,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
end
lnum = (lnum + line_count) % line_count
end
opts.lnum = lnum
local line_diagnostics = M.get(bufnr, opts)
if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
local sort_diagnostics, is_next
if search_forward then
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
is_next = function(diagnostic) return diagnostic.col < position[2] end
end
table.sort(line_diagnostics, sort_diagnostics)
table.sort(line_diagnostics[lnum], sort_diagnostics)
if i == 0 then
for _, v in pairs(line_diagnostics) do
for _, v in pairs(line_diagnostics[lnum]) do
if is_next(v) then
return v
end
end
else
return line_diagnostics[1]
return line_diagnostics[lnum][1]
end
end
end
@ -466,7 +467,6 @@ local function diagnostic_move_pos(opts, pos)
end
end
-- }}}
-- Public API {{{
@ -566,7 +566,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
})
end
set_diagnostic_cache(namespace, diagnostics, bufnr)
set_diagnostic_cache(namespace, bufnr, diagnostics)
if vim.api.nvim_buf_is_loaded(bufnr) then
M.show(namespace, bufnr, diagnostics, opts)
@ -983,6 +983,8 @@ function M.show(namespace, bufnr, diagnostics, opts)
end
end
clamp_line_numbers(bufnr, diagnostics)
if opts.underline then
M._set_underline(namespace, bufnr, diagnostics, opts.underline)
end
@ -1029,7 +1031,9 @@ function M.show_position_diagnostics(opts, bufnr, position)
position[2] >= diag.col and
(position[2] <= diag.end_col or position[1] < diag.end_lnum)
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)
return show_diagnostics(opts, position_diagnostics)
end
@ -1049,9 +1053,11 @@ function M.show_line_diagnostics(opts, bufnr, lnum)
opts = opts or {}
opts.focus_id = "line_diagnostics"
opts.lnum = lnum or (vim.api.nvim_win_get_cursor(0)[1] - 1)
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)
end

View File

@ -842,6 +842,18 @@ describe('vim.diagnostic', function()
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
]])
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)
describe('set_signs()', function()