mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
feat(diagnostic): match(), tolist(), fromlist() #15704
* feat(diagnostic): add vim.diagnostic.match() Provide vim.diagnostic.match() to generate a diagnostic from a string and a Lua pattern. * feat(diagnostic): add tolist() and fromlist()
This commit is contained in:
parent
853346a94d
commit
e61ea7772e
@ -96,6 +96,7 @@ internally and are no longer exposed as part of the API. Instead, use
|
|||||||
|
|
||||||
LSP Utility Functions ~
|
LSP Utility Functions ~
|
||||||
|
|
||||||
|
*vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead.
|
||||||
*vim.lsp.util.set_qflist()* Use |setqflist()| instead.
|
*vim.lsp.util.set_qflist()* Use |setqflist()| instead.
|
||||||
*vim.lsp.util.set_loclist()* Use |setloclist()| instead.
|
*vim.lsp.util.set_loclist()* Use |setloclist()| instead.
|
||||||
|
|
||||||
|
@ -182,8 +182,8 @@ is the first letter of the severity name (for example, "E" for ERROR). Signs
|
|||||||
can be customized using the following: >
|
can be customized using the following: >
|
||||||
|
|
||||||
sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl=
|
sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl=
|
||||||
sign define DiagnosticSignWarning text=W texthl=DiagnosticSignWarning linehl= numhl=
|
sign define DiagnosticSignWarn text=W texthl=DiagnosticSignWarn linehl= numhl=
|
||||||
sign define DiagnosticSignInformation text=I texthl=DiagnosticSignInformation linehl= numhl=
|
sign define DiagnosticSignInfo text=I texthl=DiagnosticSignInfo linehl= numhl=
|
||||||
sign define DiagnosticSignHint text=H texthl=DiagnosticSignHint linehl= numhl=
|
sign define DiagnosticSignHint text=H texthl=DiagnosticSignHint linehl= numhl=
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
@ -265,6 +265,16 @@ enable({bufnr}, {namespace}) *vim.diagnostic.enable()*
|
|||||||
{namespace} number|nil Only enable diagnostics for the
|
{namespace} number|nil Only enable diagnostics for the
|
||||||
given namespace.
|
given namespace.
|
||||||
|
|
||||||
|
fromqflist({list}) *vim.diagnostic.fromqflist()*
|
||||||
|
Convert a list of quickfix items to a list of diagnostics.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{list} table A list of quickfix items from |getqflist()|
|
||||||
|
or |getloclist()|.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
array of diagnostics |diagnostic-structure|
|
||||||
|
|
||||||
get({bufnr}, {opts}) *vim.diagnostic.get()*
|
get({bufnr}, {opts}) *vim.diagnostic.get()*
|
||||||
Get current diagnostics.
|
Get current diagnostics.
|
||||||
|
|
||||||
@ -384,6 +394,41 @@ hide({namespace}, {bufnr}) *vim.diagnostic.hide()*
|
|||||||
{bufnr} number|nil Buffer number. Defaults to the
|
{bufnr} number|nil Buffer number. Defaults to the
|
||||||
current buffer.
|
current buffer.
|
||||||
|
|
||||||
|
*vim.diagnostic.match()*
|
||||||
|
match({str}, {pat}, {groups}, {severity_map}, {defaults})
|
||||||
|
Parse a diagnostic from a string.
|
||||||
|
|
||||||
|
For example, consider a line of output from a linter: >
|
||||||
|
|
||||||
|
WARNING filename:27:3: Variable 'foo' does not exist
|
||||||
|
|
||||||
|
< This can be parsed into a diagnostic |diagnostic-structure|
|
||||||
|
with: >
|
||||||
|
|
||||||
|
local s = "WARNING filename:27:3: Variable 'foo' does not exist"
|
||||||
|
local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
|
||||||
|
local groups = {"severity", "lnum", "col", "message"}
|
||||||
|
vim.diagnostic.match(s, pattern, groups, {WARNING = vim.diagnostic.WARN})
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{str} string String to parse diagnostics from.
|
||||||
|
{pat} string Lua pattern with capture groups.
|
||||||
|
{groups} table List of fields in a
|
||||||
|
|diagnostic-structure| to associate with
|
||||||
|
captures from {pat}.
|
||||||
|
{severity_map} table A table mapping the severity field
|
||||||
|
from {groups} with an item from
|
||||||
|
|vim.diagnostic.severity|.
|
||||||
|
{defaults} table|nil Table of default values for any
|
||||||
|
fields not listed in {groups}. When
|
||||||
|
omitted, numeric values default to 0 and
|
||||||
|
"severity" defaults to ERROR.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
diagnostic |diagnostic-structure| or `nil` if {pat} fails
|
||||||
|
to match {str}.
|
||||||
|
|
||||||
reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
|
reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
|
||||||
Remove all diagnostics from the given namespace.
|
Remove all diagnostics from the given namespace.
|
||||||
|
|
||||||
@ -495,4 +540,15 @@ show_position_diagnostics({opts}, {bufnr}, {position})
|
|||||||
Return: ~
|
Return: ~
|
||||||
tuple ({popup_bufnr}, {win_id})
|
tuple ({popup_bufnr}, {win_id})
|
||||||
|
|
||||||
|
toqflist({diagnostics}) *vim.diagnostic.toqflist()*
|
||||||
|
Convert a list of diagnostics to a list of quickfix items that
|
||||||
|
can be passed to |setqflist()| or |setloclist()|.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{diagnostics} table List of diagnostics
|
||||||
|
|diagnostic-structure|.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
array of quickfix list items |setqflist-what|
|
||||||
|
|
||||||
vim:tw=78:ts=8:ft=help:norl:
|
vim:tw=78:ts=8:ft=help:norl:
|
||||||
|
@ -9,6 +9,12 @@ M.severity = {
|
|||||||
|
|
||||||
vim.tbl_add_reverse_lookup(M.severity)
|
vim.tbl_add_reverse_lookup(M.severity)
|
||||||
|
|
||||||
|
-- Mappings from qflist/loclist error types to severities
|
||||||
|
M.severity.E = M.severity.ERROR
|
||||||
|
M.severity.W = M.severity.WARN
|
||||||
|
M.severity.I = M.severity.INFO
|
||||||
|
M.severity.N = M.severity.HINT
|
||||||
|
|
||||||
local global_diagnostic_options = {
|
local global_diagnostic_options = {
|
||||||
signs = true,
|
signs = true,
|
||||||
underline = true,
|
underline = true,
|
||||||
@ -375,35 +381,6 @@ local function show_diagnostics(opts, diagnostics)
|
|||||||
return popup_bufnr, winnr
|
return popup_bufnr, winnr
|
||||||
end
|
end
|
||||||
|
|
||||||
local errlist_type_map = {
|
|
||||||
[M.severity.ERROR] = 'E',
|
|
||||||
[M.severity.WARN] = 'W',
|
|
||||||
[M.severity.INFO] = 'I',
|
|
||||||
[M.severity.HINT] = 'I',
|
|
||||||
}
|
|
||||||
|
|
||||||
---@private
|
|
||||||
local function diagnostics_to_list_items(diagnostics)
|
|
||||||
local items = {}
|
|
||||||
for _, d in pairs(diagnostics) do
|
|
||||||
table.insert(items, {
|
|
||||||
bufnr = d.bufnr,
|
|
||||||
lnum = d.lnum + 1,
|
|
||||||
col = d.col + 1,
|
|
||||||
text = d.message,
|
|
||||||
type = errlist_type_map[d.severity or M.severity.ERROR] or 'E'
|
|
||||||
})
|
|
||||||
end
|
|
||||||
table.sort(items, function(a, b)
|
|
||||||
if a.bufnr == b.bufnr then
|
|
||||||
return a.lnum < b.lnum
|
|
||||||
else
|
|
||||||
return a.bufnr < b.bufnr
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
local function set_list(loclist, opts)
|
local function set_list(loclist, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
@ -415,7 +392,7 @@ local function set_list(loclist, opts)
|
|||||||
bufnr = vim.api.nvim_win_get_buf(winnr)
|
bufnr = vim.api.nvim_win_get_buf(winnr)
|
||||||
end
|
end
|
||||||
local diagnostics = M.get(bufnr, opts)
|
local diagnostics = M.get(bufnr, opts)
|
||||||
local items = diagnostics_to_list_items(diagnostics)
|
local items = M.toqflist(diagnostics)
|
||||||
if loclist then
|
if loclist then
|
||||||
vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items })
|
vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items })
|
||||||
else
|
else
|
||||||
@ -1168,7 +1145,134 @@ function M.enable(bufnr, namespace)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Parse a diagnostic from a string.
|
||||||
|
---
|
||||||
|
--- For example, consider a line of output from a linter:
|
||||||
|
--- <pre>
|
||||||
|
--- WARNING filename:27:3: Variable 'foo' does not exist
|
||||||
|
--- </pre>
|
||||||
|
--- This can be parsed into a diagnostic |diagnostic-structure|
|
||||||
|
--- with:
|
||||||
|
--- <pre>
|
||||||
|
--- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
|
||||||
|
--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
|
||||||
|
--- local groups = {"severity", "lnum", "col", "message"}
|
||||||
|
--- vim.diagnostic.match(s, pattern, groups, {WARNING = vim.diagnostic.WARN})
|
||||||
|
--- </pre>
|
||||||
|
---
|
||||||
|
---@param str string String to parse diagnostics from.
|
||||||
|
---@param pat string Lua pattern with capture groups.
|
||||||
|
---@param groups table List of fields in a |diagnostic-structure| to
|
||||||
|
--- associate with captures from {pat}.
|
||||||
|
---@param severity_map table A table mapping the severity field from {groups}
|
||||||
|
--- with an item from |vim.diagnostic.severity|.
|
||||||
|
---@param defaults table|nil Table of default values for any fields not listed in {groups}.
|
||||||
|
--- When omitted, numeric values default to 0 and "severity" defaults to
|
||||||
|
--- ERROR.
|
||||||
|
---@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}.
|
||||||
|
function M.match(str, pat, groups, severity_map, defaults)
|
||||||
|
vim.validate {
|
||||||
|
str = { str, 's' },
|
||||||
|
pat = { pat, 's' },
|
||||||
|
groups = { groups, 't' },
|
||||||
|
severity_map = { severity_map, 't', true },
|
||||||
|
defaults = { defaults, 't', true },
|
||||||
|
}
|
||||||
|
|
||||||
|
severity_map = severity_map or M.severity
|
||||||
|
|
||||||
|
local diagnostic = {}
|
||||||
|
local matches = {string.match(str, pat)}
|
||||||
|
if vim.tbl_isempty(matches) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, match in ipairs(matches) do
|
||||||
|
local field = groups[i]
|
||||||
|
if field == "severity" then
|
||||||
|
match = severity_map[match]
|
||||||
|
elseif field == "lnum" or field == "end_lnum" or field == "col" or field == "end_col" then
|
||||||
|
match = assert(tonumber(match)) - 1
|
||||||
|
end
|
||||||
|
diagnostic[field] = match
|
||||||
|
end
|
||||||
|
|
||||||
|
diagnostic = vim.tbl_extend("keep", diagnostic, defaults or {})
|
||||||
|
diagnostic.severity = diagnostic.severity or M.severity.ERROR
|
||||||
|
diagnostic.col = diagnostic.col or 0
|
||||||
|
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
|
||||||
|
diagnostic.end_col = diagnostic.end_col or diagnostic.col
|
||||||
|
return diagnostic
|
||||||
|
end
|
||||||
|
|
||||||
|
local errlist_type_map = {
|
||||||
|
[M.severity.ERROR] = 'E',
|
||||||
|
[M.severity.WARN] = 'W',
|
||||||
|
[M.severity.INFO] = 'I',
|
||||||
|
[M.severity.HINT] = 'N',
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Convert a list of diagnostics to a list of quickfix items that can be
|
||||||
|
--- passed to |setqflist()| or |setloclist()|.
|
||||||
|
---
|
||||||
|
---@param diagnostics table List of diagnostics |diagnostic-structure|.
|
||||||
|
---@return array of quickfix list items |setqflist-what|
|
||||||
|
function M.toqflist(diagnostics)
|
||||||
|
vim.validate { diagnostics = {diagnostics, 't'} }
|
||||||
|
|
||||||
|
local list = {}
|
||||||
|
for _, v in ipairs(diagnostics) do
|
||||||
|
local item = {
|
||||||
|
bufnr = v.bufnr,
|
||||||
|
lnum = v.lnum + 1,
|
||||||
|
col = v.col and (v.col + 1) or nil,
|
||||||
|
end_lnum = v.end_lnum and (v.end_lnum + 1) or nil,
|
||||||
|
end_col = v.end_col and (v.end_col + 1) or nil,
|
||||||
|
text = v.message,
|
||||||
|
type = errlist_type_map[v.severity] or 'E',
|
||||||
|
}
|
||||||
|
table.insert(list, item)
|
||||||
|
end
|
||||||
|
table.sort(list, function(a, b)
|
||||||
|
if a.bufnr == b.bufnr then
|
||||||
|
return a.lnum < b.lnum
|
||||||
|
else
|
||||||
|
return a.bufnr < b.bufnr
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a list of quickfix items to a list of diagnostics.
|
||||||
|
---
|
||||||
|
---@param list table A list of quickfix items from |getqflist()| or
|
||||||
|
--- |getloclist()|.
|
||||||
|
---@return array of diagnostics |diagnostic-structure|
|
||||||
|
function M.fromqflist(list)
|
||||||
|
vim.validate { list = {list, 't'} }
|
||||||
|
|
||||||
|
local diagnostics = {}
|
||||||
|
for _, item in ipairs(list) do
|
||||||
|
if item.valid == 1 then
|
||||||
|
local lnum = math.max(0, item.lnum - 1)
|
||||||
|
local col = item.col > 0 and (item.col - 1) or nil
|
||||||
|
local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum
|
||||||
|
local end_col = item.end_col > 0 and (item.end_col - 1) or col
|
||||||
|
local severity = item.type ~= "" and M.severity[item.type] or M.severity.ERROR
|
||||||
|
table.insert(diagnostics, {
|
||||||
|
bufnr = item.bufnr,
|
||||||
|
lnum = lnum,
|
||||||
|
col = col,
|
||||||
|
end_lnum = end_lnum,
|
||||||
|
end_col = end_col,
|
||||||
|
severity = severity,
|
||||||
|
message = item.text,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return diagnostics
|
||||||
|
end
|
||||||
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local NIL = helpers.NIL
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
@ -912,4 +913,84 @@ describe('vim.diagnostic', function()
|
|||||||
assert(loc_list[1].lnum < loc_list[2].lnum)
|
assert(loc_list[1].lnum < loc_list[2].lnum)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('match()', function()
|
||||||
|
it('matches a string', function()
|
||||||
|
local msg = "ERROR: george.txt:19:84:Two plus two equals five"
|
||||||
|
local diagnostic = {
|
||||||
|
severity = exec_lua [[return vim.diagnostic.severity.ERROR]],
|
||||||
|
lnum = 18,
|
||||||
|
col = 83,
|
||||||
|
end_lnum = 18,
|
||||||
|
end_col = 83,
|
||||||
|
message = "Two plus two equals five",
|
||||||
|
}
|
||||||
|
eq(diagnostic, exec_lua([[
|
||||||
|
return vim.diagnostic.match(..., "^(%w+): [^:]+:(%d+):(%d+):(.+)$", {"severity", "lnum", "col", "message"})
|
||||||
|
]], msg))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns nil if the pattern fails to match', function()
|
||||||
|
eq(NIL, exec_lua [[
|
||||||
|
local msg = "The answer to life, the universe, and everything is"
|
||||||
|
return vim.diagnostic.match(msg, "This definitely will not match", {})
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('respects default values', function()
|
||||||
|
local msg = "anna.txt:1:Happy families are all alike"
|
||||||
|
local diagnostic = {
|
||||||
|
severity = exec_lua [[return vim.diagnostic.severity.INFO]],
|
||||||
|
lnum = 0,
|
||||||
|
col = 0,
|
||||||
|
end_lnum = 0,
|
||||||
|
end_col = 0,
|
||||||
|
message = "Happy families are all alike",
|
||||||
|
}
|
||||||
|
eq(diagnostic, exec_lua([[
|
||||||
|
return vim.diagnostic.match(..., "^[^:]+:(%d+):(.+)$", {"lnum", "message"}, nil, {severity = vim.diagnostic.severity.INFO})
|
||||||
|
]], msg))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('accepts a severity map', function()
|
||||||
|
local msg = "46:FATAL:Et tu, Brute?"
|
||||||
|
local diagnostic = {
|
||||||
|
severity = exec_lua [[return vim.diagnostic.severity.ERROR]],
|
||||||
|
lnum = 45,
|
||||||
|
col = 0,
|
||||||
|
end_lnum = 45,
|
||||||
|
end_col = 0,
|
||||||
|
message = "Et tu, Brute?",
|
||||||
|
}
|
||||||
|
eq(diagnostic, exec_lua([[
|
||||||
|
return vim.diagnostic.match(..., "^(%d+):(%w+):(.+)$", {"lnum", "severity", "message"}, {FATAL = vim.diagnostic.severity.ERROR})
|
||||||
|
]], msg))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('toqflist() and fromqflist()', function()
|
||||||
|
it('works', function()
|
||||||
|
local result = exec_lua [[
|
||||||
|
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
|
||||||
|
make_error('Error 1', 0, 1, 0, 1),
|
||||||
|
make_error('Error 2', 1, 1, 1, 1),
|
||||||
|
make_warning('Warning', 2, 2, 2, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
local diagnostics = vim.diagnostic.get(diagnostic_bufnr)
|
||||||
|
vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics))
|
||||||
|
local list = vim.fn.getqflist()
|
||||||
|
local new_diagnostics = vim.diagnostic.fromqflist(list)
|
||||||
|
|
||||||
|
-- Remove namespace since it isn't present in the return value of
|
||||||
|
-- fromlist()
|
||||||
|
for _, v in ipairs(diagnostics) do
|
||||||
|
v.namespace = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return {diagnostics, new_diagnostics}
|
||||||
|
]]
|
||||||
|
eq(result[1], result[2])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user