local api = vim.api
local validate = vim.validate
local highlight = vim.highlight
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
local if_nil = vim.F.if_nil
--@class DiagnosticSeverity
local DiagnosticSeverity = protocol.DiagnosticSeverity
local to_severity = function(severity)
if not severity then return nil end
return type(severity) == 'string' and DiagnosticSeverity[severity] or severity
end
local to_position = function(position, bufnr)
vim.validate { position = {position, 't'} }
return {
position.line,
util._get_line_byte_from_position(bufnr, position)
}
end
---@brief lsp-diagnostic
---
--@class Diagnostic
--@field range Range
--@field message string
--@field severity DiagnosticSeverity|nil
--@field code number | string
--@field source string
--@field tags DiagnosticTag[]
--@field relatedInformation DiagnosticRelatedInformation[]
local M = {}
-- Diagnostic Highlights {{{
-- TODO(tjdevries): Determine how to generate documentation for these
-- and how to configure them to be easy for users.
--
-- For now, just use the following script. It should work pretty good.
--[[
local levels = {"Error", "Warning", "Information", "Hint" }
local all_info = {
{ "Default", "Used as the base highlight group, other highlight groups link to", },
{ "VirtualText", 'Used for "%s" diagnostic virtual text.\n See |vim.lsp.diagnostic.set_virtual_text()|', },
{ "Underline", 'Used to underline "%s" diagnostics.\n See |vim.lsp.diagnostic.set_underline()|', },
{ "Floating", 'Used to color "%s" diagnostic messages in diagnostics float.\n See |vim.lsp.diagnostic.show_line_diagnostics()|', },
{ "Sign", 'Used for "%s" signs in sing column.\n See |vim.lsp.diagnostic.set_signs()|', },
}
local results = {}
for _, info in ipairs(all_info) do
for _, level in ipairs(levels) do
local name = info[1]
local description = info[2]
local fullname = string.format("Lsp%s%s", name, level)
table.insert(results, string.format(
"%78s", string.format("*hl-%s*", fullname))
)
table.insert(results, fullname)
table.insert(results, string.format(" %s", description))
table.insert(results, "")
end
end
-- print(table.concat(results, '\n'))
vim.fn.setreg("*", table.concat(results, '\n'))
--]]
local diagnostic_severities = {
[DiagnosticSeverity.Error] = { guifg = "Red" };
[DiagnosticSeverity.Warning] = { guifg = "Orange" };
[DiagnosticSeverity.Information] = { guifg = "LightBlue" };
[DiagnosticSeverity.Hint] = { guifg = "LightGrey" };
}
-- Make a map from DiagnosticSeverity -> Highlight Name
local make_highlight_map = function(base_name)
local result = {}
for k, _ in pairs(diagnostic_severities) do
result[k] = "LspDiagnostics" .. base_name .. DiagnosticSeverity[k]
end
return result
end
local default_highlight_map = make_highlight_map("Default")
local virtual_text_highlight_map = make_highlight_map("VirtualText")
local underline_highlight_map = make_highlight_map("Underline")
local floating_highlight_map = make_highlight_map("Floating")
local sign_highlight_map = make_highlight_map("Sign")
-- }}}
-- Diagnostic Namespaces {{{
local DEFAULT_CLIENT_ID = -1
local get_client_id = function(client_id)
if client_id == nil then
client_id = DEFAULT_CLIENT_ID
end
return client_id
end
local get_bufnr = function(bufnr)
if not bufnr then
return api.nvim_get_current_buf()
elseif bufnr == 0 then
return api.nvim_get_current_buf()
end
return bufnr
end
--- Create a namespace table, used to track a client's buffer local items
local _make_namespace_table = function(namespace, api_namespace)
vim.validate { namespace = { namespace, 's' } }
return setmetatable({
[DEFAULT_CLIENT_ID] = api.nvim_create_namespace(namespace)
}, {
__index = function(t, client_id)
client_id = get_client_id(client_id)
if rawget(t, client_id) == nil then
local value = string.format("%s:%s", namespace, client_id)
if api_namespace then
value = api.nvim_create_namespace(value)
end
rawset(t, client_id, value)
end
return rawget(t, client_id)
end
})
end
local _diagnostic_namespaces = _make_namespace_table("vim_lsp_diagnostics", true)
local _sign_namespaces = _make_namespace_table("vim_lsp_signs", false)
--@private
function M._get_diagnostic_namespace(client_id)
return _diagnostic_namespaces[client_id]
end
--@private
function M._get_sign_namespace(client_id)
return _sign_namespaces[client_id]
end
-- }}}
-- Diagnostic Buffer & Client metatables {{{
local bufnr_and_client_cacher_mt = {
__index = function(t, bufnr)
if bufnr == 0 or bufnr == nil then
bufnr = vim.api.nvim_get_current_buf()
end
if rawget(t, bufnr) == nil then
rawset(t, bufnr, {})
end
return rawget(t, bufnr)
end,
__newindex = function(t, bufnr, v)
if bufnr == 0 or bufnr == nil then
bufnr = vim.api.nvim_get_current_buf()
end
rawset(t, bufnr, v)
end,
}
-- }}}
-- Diagnostic Saving & Caching {{{
local _diagnostic_cleanup = setmetatable({}, bufnr_and_client_cacher_mt)
local diagnostic_cache = setmetatable({}, bufnr_and_client_cacher_mt)
local diagnostic_cache_lines = setmetatable({}, bufnr_and_client_cacher_mt)
local diagnostic_cache_counts = setmetatable({}, bufnr_and_client_cacher_mt)
local _bufs_waiting_to_update = setmetatable({}, bufnr_and_client_cacher_mt)
--- Store Diagnostic[] by line
---
---@param diagnostics Diagnostic[]
---@return table<number, Diagnostic[]>
local _diagnostic_lines = function(diagnostics)
if not diagnostics then return end
local diagnostics_by_line = {}
for _, diagnostic in ipairs(diagnostics) do
local start = diagnostic.range.start
local line_diagnostics = diagnostics_by_line[start<
|