mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
feat(lsp): jump to diagnostics by position (#14795)
This commit is contained in:
parent
d088066fa1
commit
ea39ff5732
@ -362,11 +362,12 @@ end
|
|||||||
---@param bufnr number
|
---@param bufnr number
|
||||||
---@param client_id number|nil If nil, then return all of the diagnostics.
|
---@param client_id number|nil If nil, then return all of the diagnostics.
|
||||||
--- Else, return just the diagnostics associated with the client_id.
|
--- Else, return just the diagnostics associated with the client_id.
|
||||||
function M.get(bufnr, client_id)
|
---@param predicate function|nil Optional function for filtering diagnostics
|
||||||
|
function M.get(bufnr, client_id, predicate)
|
||||||
if client_id == nil then
|
if client_id == nil then
|
||||||
local all_diagnostics = {}
|
local all_diagnostics = {}
|
||||||
for iter_client_id, _ in pairs(diagnostic_cache[bufnr]) do
|
for iter_client_id, _ in pairs(diagnostic_cache[bufnr]) do
|
||||||
local iter_diagnostics = M.get(bufnr, iter_client_id)
|
local iter_diagnostics = M.get(bufnr, iter_client_id, predicate)
|
||||||
|
|
||||||
for _, diagnostic in ipairs(iter_diagnostics) do
|
for _, diagnostic in ipairs(iter_diagnostics) do
|
||||||
table.insert(all_diagnostics, diagnostic)
|
table.insert(all_diagnostics, diagnostic)
|
||||||
@ -376,19 +377,26 @@ function M.get(bufnr, client_id)
|
|||||||
return all_diagnostics
|
return all_diagnostics
|
||||||
end
|
end
|
||||||
|
|
||||||
return diagnostic_cache[bufnr][client_id] or {}
|
predicate = predicate or function(_) return true end
|
||||||
|
local client_diagnostics = {}
|
||||||
|
for _, diagnostic in ipairs(diagnostic_cache[bufnr][client_id] or {}) do
|
||||||
|
if predicate(diagnostic) then
|
||||||
|
table.insert(client_diagnostics, diagnostic)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return client_diagnostics
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the diagnostics by line
|
--- Get the diagnostics by line
|
||||||
---
|
---
|
||||||
---@param bufnr number The buffer number
|
---@param bufnr number|nil The buffer number
|
||||||
---@param line_nr number The line number
|
---@param line_nr number|nil The line number
|
||||||
---@param opts table|nil Configuration keys
|
---@param opts table|nil Configuration keys
|
||||||
--- - severity: (DiagnosticSeverity, default nil)
|
--- - severity: (DiagnosticSeverity, default nil)
|
||||||
--- - Only return diagnostics with this severity. Overrides severity_limit
|
--- - Only return diagnostics with this severity. Overrides severity_limit
|
||||||
--- - severity_limit: (DiagnosticSeverity, default nil)
|
--- - severity_limit: (DiagnosticSeverity, default nil)
|
||||||
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
||||||
---@param client_id number the client id
|
---@param client_id|nil number the client id
|
||||||
---@return table Table with map of line number to list of diagnostics.
|
---@return table Table with map of line number to list of diagnostics.
|
||||||
-- Structured: { [1] = {...}, [5] = {.... } }
|
-- Structured: { [1] = {...}, [5] = {.... } }
|
||||||
function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
||||||
@ -464,63 +472,64 @@ end
|
|||||||
-- }}}
|
-- }}}
|
||||||
-- Diagnostic Movements {{{
|
-- Diagnostic Movements {{{
|
||||||
|
|
||||||
--- Helper function to iterate through all of the diagnostic lines
|
--- Helper function to find the next diagnostic relative to a position
|
||||||
---@return table list of diagnostics
|
---@return table the next diagnostic if found
|
||||||
local _iter_diagnostic_lines = function(start, finish, step, bufnr, opts, client_id)
|
local _next_diagnostic = function(position, search_forward, bufnr, opts, client_id)
|
||||||
if bufnr == nil then
|
position[1] = position[1] - 1
|
||||||
bufnr = vim.api.nvim_get_current_buf()
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||||
end
|
|
||||||
|
|
||||||
local wrap = if_nil(opts.wrap, true)
|
local wrap = if_nil(opts.wrap, true)
|
||||||
|
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||||
local search = function(search_start, search_finish, search_step)
|
for i = 0, line_count do
|
||||||
for line_nr = search_start, search_finish, search_step do
|
local offset = i * (search_forward and 1 or -1)
|
||||||
local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
local line_nr = position[1] + offset
|
||||||
if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
|
if line_nr < 0 or line_nr >= line_count then
|
||||||
return line_diagnostics
|
if not wrap then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
line_nr = (line_nr + line_count) % line_count
|
||||||
|
end
|
||||||
|
local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
||||||
|
if line_diagnostics and not vim.tbl_isempty(line_diagnostics) then
|
||||||
|
local sort_diagnostics, is_next
|
||||||
|
if search_forward then
|
||||||
|
sort_diagnostics = function(a, b) return a.range.start.character < b.range.start.character end
|
||||||
|
is_next = function(diagnostic) return diagnostic.range.start.character > position[2] end
|
||||||
|
else
|
||||||
|
sort_diagnostics = function(a, b) return a.range.start.character > b.range.start.character end
|
||||||
|
is_next = function(diagnostic) return diagnostic.range.start.character < position[2] end
|
||||||
|
end
|
||||||
|
table.sort(line_diagnostics, sort_diagnostics)
|
||||||
|
if i == 0 then
|
||||||
|
for _, v in pairs(line_diagnostics) do
|
||||||
|
if is_next(v) then
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return line_diagnostics[1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local result = search(start, finish, step)
|
|
||||||
|
|
||||||
if wrap then
|
|
||||||
local wrap_start, wrap_finish
|
|
||||||
if step == 1 then
|
|
||||||
wrap_start, wrap_finish = 1, start
|
|
||||||
else
|
|
||||||
wrap_start, wrap_finish = vim.api.nvim_buf_line_count(bufnr), start
|
|
||||||
end
|
|
||||||
|
|
||||||
if not result then
|
|
||||||
result = search(wrap_start, wrap_finish, step)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Helper function to ierate through diagnostic lines and return a position
|
--- Helper function to return a position from a diagnostic
|
||||||
---
|
---
|
||||||
---@return table {row, col}
|
---@return table {row, col}
|
||||||
local function _iter_diagnostic_lines_pos(opts, line_diagnostics)
|
local function _diagnostic_pos(opts, diagnostic)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
||||||
local win_id = opts.win_id or vim.api.nvim_get_current_win()
|
local win_id = opts.win_id or vim.api.nvim_get_current_win()
|
||||||
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
||||||
|
|
||||||
if line_diagnostics == nil or vim.tbl_isempty(line_diagnostics) then
|
if not diagnostic then return false end
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local iter_diagnostic = line_diagnostics[1]
|
return to_position(diagnostic.range.start, bufnr)
|
||||||
return to_position(iter_diagnostic.range.start, bufnr)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
-- Move to the diagnostic position
|
-- Move to the diagnostic position
|
||||||
local function _iter_diagnostic_move_pos(name, opts, pos)
|
local function _diagnostic_move_pos(name, opts, pos)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
||||||
local enable_popup = if_nil(opts.enable_popup, true)
|
local enable_popup = if_nil(opts.enable_popup, true)
|
||||||
@ -536,7 +545,7 @@ local function _iter_diagnostic_move_pos(name, opts, pos)
|
|||||||
if enable_popup then
|
if enable_popup then
|
||||||
-- This is a bit weird... I'm surprised that we need to wait til the next tick to do this.
|
-- This is a bit weird... I'm surprised that we need to wait til the next tick to do this.
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
M.show_line_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id))
|
M.show_position_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -552,14 +561,14 @@ function M.get_prev(opts)
|
|||||||
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
||||||
local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
|
local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
|
||||||
|
|
||||||
return _iter_diagnostic_lines(cursor_position[1] - 2, 0, -1, bufnr, opts, opts.client_id)
|
return _next_diagnostic(cursor_position, false, bufnr, opts, opts.client_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
|
--- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
|
||||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||||
---@return table Previous diagnostic position
|
---@return table Previous diagnostic position
|
||||||
function M.get_prev_pos(opts)
|
function M.get_prev_pos(opts)
|
||||||
return _iter_diagnostic_lines_pos(
|
return _diagnostic_pos(
|
||||||
opts,
|
opts,
|
||||||
M.get_prev(opts)
|
M.get_prev(opts)
|
||||||
)
|
)
|
||||||
@ -568,7 +577,7 @@ end
|
|||||||
--- Move to the previous diagnostic
|
--- Move to the previous diagnostic
|
||||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||||
function M.goto_prev(opts)
|
function M.goto_prev(opts)
|
||||||
return _iter_diagnostic_move_pos(
|
return _diagnostic_move_pos(
|
||||||
"DiagnosticPrevious",
|
"DiagnosticPrevious",
|
||||||
opts,
|
opts,
|
||||||
M.get_prev_pos(opts)
|
M.get_prev_pos(opts)
|
||||||
@ -585,14 +594,14 @@ function M.get_next(opts)
|
|||||||
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
||||||
local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
|
local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
|
||||||
|
|
||||||
return _iter_diagnostic_lines(cursor_position[1], vim.api.nvim_buf_line_count(bufnr), 1, bufnr, opts, opts.client_id)
|
return _next_diagnostic(cursor_position, true, bufnr, opts, opts.client_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Return the pos, {row, col}, for the next diagnostic in the current buffer.
|
--- Return the pos, {row, col}, for the next diagnostic in the current buffer.
|
||||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||||
---@return table Next diagnostic position
|
---@return table Next diagnostic position
|
||||||
function M.get_next_pos(opts)
|
function M.get_next_pos(opts)
|
||||||
return _iter_diagnostic_lines_pos(
|
return _diagnostic_pos(
|
||||||
opts,
|
opts,
|
||||||
M.get_next(opts)
|
M.get_next(opts)
|
||||||
)
|
)
|
||||||
@ -617,7 +626,7 @@ end
|
|||||||
--- - {win_id}: (number, default 0)
|
--- - {win_id}: (number, default 0)
|
||||||
--- - Window ID
|
--- - Window ID
|
||||||
function M.goto_next(opts)
|
function M.goto_next(opts)
|
||||||
return _iter_diagnostic_move_pos(
|
return _diagnostic_move_pos(
|
||||||
"DiagnosticNext",
|
"DiagnosticNext",
|
||||||
opts,
|
opts,
|
||||||
M.get_next_pos(opts)
|
M.get_next_pos(opts)
|
||||||
@ -1208,7 +1217,7 @@ end
|
|||||||
-- }}}
|
-- }}}
|
||||||
-- Diagnostic User Functions {{{
|
-- Diagnostic User Functions {{{
|
||||||
|
|
||||||
--- Open a floating window with the diagnostics from {line_nr}
|
--- Open a floating window with the provided diagnostics
|
||||||
---
|
---
|
||||||
--- The floating window can be customized with the following highlight groups:
|
--- The floating window can be customized with the following highlight groups:
|
||||||
--- <pre>
|
--- <pre>
|
||||||
@ -1218,32 +1227,21 @@ end
|
|||||||
--- LspDiagnosticsFloatingHint
|
--- LspDiagnosticsFloatingHint
|
||||||
--- </pre>
|
--- </pre>
|
||||||
---@param opts table Configuration table
|
---@param opts table Configuration table
|
||||||
--- - show_header (boolean, default true): Show "Diagnostics:" header.
|
--- - show_header (boolean, default true): Show "Diagnostics:" header
|
||||||
--- - Plus all the opts for |vim.lsp.diagnostic.get_line_diagnostics()|
|
--- - all opts for |vim.lsp.util.open_floating_preview()| can be used here
|
||||||
--- and |vim.lsp.util.open_floating_preview()| can be used here.
|
---@param diagnostics table: The diagnostics to display
|
||||||
---@param bufnr number The buffer number
|
|
||||||
---@param line_nr number The line number
|
|
||||||
---@param client_id number|nil the client id
|
|
||||||
---@return table {popup_bufnr, win_id}
|
---@return table {popup_bufnr, win_id}
|
||||||
function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
|
local function show_diagnostics(opts, diagnostics)
|
||||||
opts = opts or {}
|
if vim.tbl_isempty(diagnostics) then return end
|
||||||
|
|
||||||
local show_header = if_nil(opts.show_header, true)
|
|
||||||
|
|
||||||
bufnr = bufnr or 0
|
|
||||||
line_nr = line_nr or (vim.api.nvim_win_get_cursor(0)[1] - 1)
|
|
||||||
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
|
local show_header = if_nil(opts.show_header, true)
|
||||||
if show_header then
|
if show_header then
|
||||||
table.insert(lines, "Diagnostics:")
|
table.insert(lines, "Diagnostics:")
|
||||||
table.insert(highlights, {0, "Bold"})
|
table.insert(highlights, {0, "Bold"})
|
||||||
end
|
end
|
||||||
|
|
||||||
local line_diagnostics = M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
for i, diagnostic in ipairs(diagnostics) do
|
||||||
if vim.tbl_isempty(line_diagnostics) then return end
|
|
||||||
|
|
||||||
for i, diagnostic in ipairs(line_diagnostics) do
|
|
||||||
local prefix = string.format("%d. ", i)
|
local prefix = string.format("%d. ", i)
|
||||||
local hiname = M._get_floating_severity_highlight_name(diagnostic.severity)
|
local hiname = M._get_floating_severity_highlight_name(diagnostic.severity)
|
||||||
assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
|
assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
|
||||||
@ -1257,7 +1255,6 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.focus_id = "line_diagnostics"
|
|
||||||
local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
|
local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
|
||||||
for i, hi in ipairs(highlights) do
|
for i, hi in ipairs(highlights) do
|
||||||
local prefixlen, hiname = unpack(hi)
|
local prefixlen, hiname = unpack(hi)
|
||||||
@ -1268,6 +1265,57 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
|
|||||||
return popup_bufnr, winnr
|
return popup_bufnr, winnr
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Open a floating window with the diagnostics from {position}
|
||||||
|
|
||||||
|
---@param opts table|nil Configuration keys
|
||||||
|
--- - severity: (DiagnosticSeverity, default nil)
|
||||||
|
--- - Only return diagnostics with this severity. Overrides severity_limit
|
||||||
|
--- - severity_limit: (DiagnosticSeverity, default nil)
|
||||||
|
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
||||||
|
--- - all opts for |show_diagnostics()| can be used here
|
||||||
|
---@param buf_nr number|nil The buffer number
|
||||||
|
---@param position table|nil The (0,0)-indexed position
|
||||||
|
---@return table {popup_bufnr, win_id}
|
||||||
|
function M.show_position_diagnostics(opts, buf_nr, position)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.focus_id = "position_diagnostics"
|
||||||
|
buf_nr = buf_nr or vim.api.nvim_get_current_buf()
|
||||||
|
if not position then
|
||||||
|
local curr_position = vim.api.nvim_win_get_cursor(0)
|
||||||
|
curr_position[1] = curr_position[1] - 1
|
||||||
|
position = curr_position
|
||||||
|
end
|
||||||
|
local match_position_predicate = function(diag)
|
||||||
|
return position[1] == diag.range['start'].line and
|
||||||
|
position[2] >= diag.range['start'].character and
|
||||||
|
(position[2] <= diag.range['end'].character or position[1] < diag.range['end'].line)
|
||||||
|
end
|
||||||
|
local position_diagnostics = M.get(buf_nr, nil, match_position_predicate)
|
||||||
|
if opts.severity then
|
||||||
|
position_diagnostics = filter_to_severity_limit(opts.severity, position_diagnostics)
|
||||||
|
elseif opts.severity_limit then
|
||||||
|
position_diagnostics = filter_by_severity_limit(opts.severity_limit, position_diagnostics)
|
||||||
|
end
|
||||||
|
table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end)
|
||||||
|
return show_diagnostics(opts, position_diagnostics)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Open a floating window with the diagnostics from {line_nr}
|
||||||
|
|
||||||
|
---@param opts table Configuration table
|
||||||
|
--- - all opts for |vim.lsp.diagnostic.get_line_diagnostics()| and
|
||||||
|
--- |show_diagnostics()| can be used here
|
||||||
|
---@param buf_nr number|nil The buffer number
|
||||||
|
---@param line_nr number|nil The line number
|
||||||
|
---@param client_id number|nil the client id
|
||||||
|
---@return table {popup_bufnr, win_id}
|
||||||
|
function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.focus_id = "line_diagnostics"
|
||||||
|
line_nr = line_nr or (vim.api.nvim_win_get_cursor(0)[1] - 1)
|
||||||
|
local line_diagnostics = M.get_line_diagnostics(buf_nr, line_nr, opts, client_id)
|
||||||
|
return show_diagnostics(opts, line_diagnostics)
|
||||||
|
end
|
||||||
|
|
||||||
--- Clear diagnotics and diagnostic cache
|
--- Clear diagnotics and diagnostic cache
|
||||||
---
|
---
|
||||||
|
Loading…
Reference in New Issue
Block a user