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
2 changed files with 47 additions and 29 deletions

View File

@@ -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

View File

@@ -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()