diff options
| author | Gregory Anders <greg@gpanders.com> | 2021-09-06 20:21:18 -0600 |
|---|---|---|
| committer | Gregory Anders <greg@gpanders.com> | 2021-09-15 14:09:47 -0600 |
| commit | a5bbb932f9094098bd656d3f6be3c58344576709 (patch) | |
| tree | 80f35362c9e94853e9e8898416120a3ede623362 /runtime/lua/vim/lsp/diagnostic.lua | |
| parent | 6188926e00081ae4b1a33d5fd12692a6749a2144 (diff) | |
| download | rneovim-a5bbb932f9094098bd656d3f6be3c58344576709.tar.gz rneovim-a5bbb932f9094098bd656d3f6be3c58344576709.tar.bz2 rneovim-a5bbb932f9094098bd656d3f6be3c58344576709.zip | |
refactor: move vim.lsp.diagnostic to vim.diagnostic
This generalizes diagnostic handling outside of just the scope of LSP.
LSP clients are now a specific case of a diagnostic producer, but the
diagnostic subsystem is decoupled from the LSP subsystem (or will be,
eventually).
More discussion at [1].
[1]: https://github.com/neovim/neovim/pull/15585
Diffstat (limited to 'runtime/lua/vim/lsp/diagnostic.lua')
| -rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 1469 |
1 files changed, 368 insertions, 1101 deletions
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index ccd325b1ac..338491701f 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -1,48 +1,4 @@ -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 filter_to_severity_limit = function(severity, diagnostics) - local filter_level = to_severity(severity) - if not filter_level then - return diagnostics - end - - return vim.tbl_filter(function(t) return t.severity == filter_level end, diagnostics) -end - -local filter_by_severity_limit = function(severity_limit, diagnostics) - local filter_level = to_severity(severity_limit) - if not filter_level then - return diagnostics - end - - return vim.tbl_filter(function(t) return t.severity <= filter_level end, diagnostics) -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 --- @@ -57,70 +13,9 @@ end 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) +---@private +local function get_client_id(client_id) if client_id == nil then client_id = DEFAULT_CLIENT_ID end @@ -128,179 +23,112 @@ local get_client_id = function(client_id) return client_id end -local get_bufnr = function(bufnr) +---@private +local function get_bufnr(bufnr) if not bufnr then - return api.nvim_get_current_buf() + return vim.api.nvim_get_current_buf() elseif bufnr == 0 then - return api.nvim_get_current_buf() + return vim.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] +local function severity_lsp_to_vim(severity) + if type(severity) == 'string' then + severity = vim.lsp.protocol.DiagnosticSeverity[severity] + end + return severity 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_extmarks = 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 diagnostic_attached_buffers = {} - --- Disabled buffers and clients -local diagnostic_disabled = 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.line] - if not line_diagnostics then - line_diagnostics = {} - diagnostics_by_line[start.line] = line_diagnostics - end - table.insert(line_diagnostics, diagnostic) +local function severity_vim_to_lsp(severity) + if type(severity) == 'string' then + severity = vim.diagnostic.severity[severity] end - return diagnostics_by_line + return severity end ---- Get the count of M by Severity ---- ----@param diagnostics Diagnostic[] ----@return table<DiagnosticSeverity, number> -local _diagnostic_counts = function(diagnostics) - if not diagnostics then return end - - local counts = {} - for _, diagnostic in pairs(diagnostics) do - if diagnostic.severity then - local val = counts[diagnostic.severity] - if val == nil then - val = 0 - end +---@private +local function line_byte_from_position(lines, lnum, col, offset_encoding) + if offset_encoding == "utf-8" then + return col + end - counts[diagnostic.severity] = val + 1 - end + local line = lines[lnum + 1] + local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == "utf-16") + if ok then + return result end - return counts + return col end ---@private ---- Set the different diagnostic cache after `textDocument/publishDiagnostics` ----@param diagnostics Diagnostic[] ----@param bufnr number ----@param client_id number ----@return nil -local function set_diagnostic_cache(diagnostics, bufnr, client_id) - client_id = get_client_id(client_id) - - -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic - -- - -- The diagnostic's severity. Can be omitted. If omitted it is up to the - -- client to interpret diagnostics as error, warning, info or hint. - -- TODO: Replace this with server-specific heuristics to infer severity. - local buf_line_count = vim.api.nvim_buf_line_count(bufnr) - for _, diagnostic in ipairs(diagnostics) do - if diagnostic.severity == nil then - diagnostic.severity = DiagnosticSeverity.Error - end - -- Account for servers that place diagnostics on terminating newline - if buf_line_count > 0 then - diagnostic.range.start.line = math.max(math.min( - diagnostic.range.start.line, buf_line_count - 1 - ), 0) - diagnostic.range["end"].line = math.max(math.min( - diagnostic.range["end"].line, buf_line_count - 1 - ), 0) - end +local function get_buf_lines(bufnr) + if vim.api.nvim_buf_is_loaded(bufnr) then + return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) end - diagnostic_cache[bufnr][client_id] = diagnostics - diagnostic_cache_lines[bufnr][client_id] = _diagnostic_lines(diagnostics) - diagnostic_cache_counts[bufnr][client_id] = _diagnostic_counts(diagnostics) + local filename = vim.api.nvim_buf_get_name(bufnr) + local f = io.open(filename) + local lines = vim.split(f:read("*a"), "\n") + f:close() + return lines end +---@private +local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) + local buf_lines = get_buf_lines(bufnr) + local client = vim.lsp.get_client_by_id(client_id) + local offset_encoding = client and client.offset_encoding or "utf-16" + return vim.tbl_map(function(diagnostic) + local start = diagnostic.range.start + local _end = diagnostic.range["end"] + return { + lnum = start.line, + col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding), + end_lnum = _end.line, + end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding), + severity = severity_lsp_to_vim(diagnostic.severity), + message = diagnostic.message + } + end, diagnostics) +end ---@private ---- Clear the cached diagnostics ----@param bufnr number ----@param client_id number -local function clear_diagnostic_cache(bufnr, client_id) - client_id = get_client_id(client_id) +local function diagnostic_vim_to_lsp(diagnostics) + return vim.tbl_map(function(diagnostic) + return { + range = { + start = { + line = diagnostic.lnum, + character = diagnostic.col, + }, + ["end"] = { + line = diagnostic.end_lnum, + character = diagnostic.end_col, + }, + }, + severity = severity_vim_to_lsp(diagnostic.severity), + message = diagnostic.message, + } + end, diagnostics) +end - diagnostic_cache[bufnr][client_id] = nil - diagnostic_cache_lines[bufnr][client_id] = nil - diagnostic_cache_counts[bufnr][client_id] = nil +local _client_namespaces = {} + +--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|. +--- +---@param client_id number The id of the LSP client +function M.get_namespace(client_id) + vim.validate { client_id = { client_id, 'n' } } + if not _client_namespaces[client_id] then + local name = string.format("vim.lsp.client-%d", client_id) + _client_namespaces[client_id] = vim.api.nvim_create_namespace(name) + end + return _client_namespaces[client_id] end --- Save diagnostics to the current buffer. @@ -309,86 +137,146 @@ end ---@param diagnostics Diagnostic[] ---@param bufnr number ---@param client_id number +---@private function M.save(diagnostics, bufnr, client_id) - validate { - diagnostics = {diagnostics, 't'}, - bufnr = {bufnr, 'n'}, - client_id = {client_id, 'n', true}, - } + local namespace = M.get_namespace(client_id) + vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) +end +-- }}} + +--- |lsp-handler| for the method "textDocument/publishDiagnostics" +--- +--- See |vim.diagnostic.config()| for configuration options. Handler-specific +--- configuration can be set using |vim.lsp.with()|: +--- <pre> +--- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( +--- vim.lsp.diagnostic.on_publish_diagnostics, { +--- -- Enable underline, use default values +--- underline = true, +--- -- Enable virtual text, override spacing to 4 +--- virtual_text = { +--- spacing = 4, +--- }, +--- -- Use a function to dynamically turn signs off +--- -- and on, using buffer local variables +--- signs = function(bufnr, client_id) +--- return vim.bo[bufnr].show_signs == false +--- end, +--- -- Disable a feature +--- update_in_insert = false, +--- } +--- ) +--- </pre> +--- +---@param config table Configuration table (see |vim.diagnostic.config()|). +function M.on_publish_diagnostics(_, result, ctx, config) + local client_id = ctx.client_id + local uri = result.uri + local bufnr = vim.uri_to_bufnr(uri) - if not diagnostics then return end + if not bufnr then + return + end - bufnr = get_bufnr(bufnr) client_id = get_client_id(client_id) + local namespace = M.get_namespace(client_id) + local diagnostics = result.diagnostics - if not _diagnostic_cleanup[bufnr][client_id] then - _diagnostic_cleanup[bufnr][client_id] = true + if config then + if vim.F.if_nil(config.severity_sort, false) then + table.sort(diagnostics, function(a, b) return a.severity > b.severity end) + end - -- Clean up our data when the buffer unloads. - api.nvim_buf_attach(bufnr, false, { - on_detach = function(_, b) - clear_diagnostic_cache(b, client_id) - _diagnostic_cleanup[b][client_id] = nil + for _, opt in pairs(config) do + if type(opt) == 'table' then + if not opt.severity and opt.severity_limit then + opt.severity = {min=severity_lsp_to_vim(opt.severity_limit)} + end end - }) + end + + vim.diagnostic.config(config, namespace) end - set_diagnostic_cache(diagnostics, bufnr, client_id) + vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) + + -- Keep old autocmd for back compat. This should eventually be removed. + vim.api.nvim_command("doautocmd <nomodeline> User LspDiagnosticsChanged") +end + +--- Clear diagnotics and diagnostic cache. +--- +--- Diagnostic producers should prefer |vim.diagnostic.reset()|. However, +--- this method signature is still used internally in some parts of the LSP +--- implementation so it's simply marked @private rather than @deprecated. +--- +---@param client_id number +---@param buffer_client_map table map of buffers to active clients +---@private +function M.reset(client_id, buffer_client_map) + buffer_client_map = vim.deepcopy(buffer_client_map) + vim.schedule(function() + for bufnr, client_ids in pairs(buffer_client_map) do + if client_ids[client_id] then + local namespace = M.get_namespace(client_id) + vim.diagnostic.reset(namespace, bufnr) + end + end + end) end --- }}} --- Diagnostic Retrieval {{{ +-- Deprecated Functions {{{ --- Get all diagnostics for clients --- +---@deprecated Prefer |vim.diagnostic.get()| +--- ---@param client_id number Restrict included diagnostics to the client --- If nil, diagnostics of all clients are included. ---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[]) function M.get_all(client_id) - local diagnostics_by_bufnr = {} - for bufnr, buf_diagnostics in pairs(diagnostic_cache) do - diagnostics_by_bufnr[bufnr] = {} - for cid, client_diagnostics in pairs(buf_diagnostics) do - if client_id == nil or cid == client_id then - vim.list_extend(diagnostics_by_bufnr[bufnr], client_diagnostics) - end - end + local result = {} + local namespace + if client_id then + namespace = M.get_namespace(client_id) + end + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, {namespace = namespace})) + result[bufnr] = diagnostics end - return diagnostics_by_bufnr + return result end --- Return associated diagnostics for bufnr --- +---@deprecated Prefer |vim.diagnostic.get()| +--- ---@param bufnr number ---@param client_id number|nil If nil, then return all of the diagnostics. --- Else, return just the diagnostics associated with the client_id. ---@param predicate function|nil Optional function for filtering diagnostics function M.get(bufnr, client_id, predicate) + predicate = predicate or function() return true end if client_id == nil then local all_diagnostics = {} - for iter_client_id, _ in pairs(diagnostic_cache[bufnr]) do - local iter_diagnostics = M.get(bufnr, iter_client_id, predicate) - + vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _) + local iter_diagnostics = vim.tbl_filter(predicate, M.get(bufnr, iter_client_id)) for _, diagnostic in ipairs(iter_diagnostics) do table.insert(all_diagnostics, diagnostic) end - end - - return all_diagnostics + end) + return diagnostic_vim_to_lsp(all_diagnostics) end - 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 + local namespace = M.get_namespace(client_id) + return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, {namespace=namespace}))) end --- Get the diagnostics by line --- +--- Marked private as this is used internally by the LSP subsystem, but +--- most users should instead prefer |vim.diagnostic.get()|. +--- ---@param bufnr number|nil The buffer number ---@param line_nr number|nil The line number ---@param opts table|nil Configuration keys @@ -398,216 +286,134 @@ end --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. ---@param client_id|nil number the client id ---@return table Table with map of line number to list of diagnostics. --- Structured: { [1] = {...}, [5] = {.... } } +--- Structured: { [1] = {...}, [5] = {.... } } +---@private function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) opts = opts or {} - - bufnr = bufnr or vim.api.nvim_get_current_buf() - line_nr = line_nr or vim.api.nvim_win_get_cursor(0)[1] - 1 - - local client_get_diags = function(iter_client_id) - return (diagnostic_cache_lines[bufnr][iter_client_id] or {})[line_nr] or {} + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} end - local line_diagnostics - if client_id == nil then - line_diagnostics = {} - for iter_client_id, _ in pairs(diagnostic_cache_lines[bufnr]) do - for _, diagnostic in ipairs(client_get_diags(iter_client_id)) do - table.insert(line_diagnostics, diagnostic) - end - end - else - line_diagnostics = vim.deepcopy(client_get_diags(client_id)) + if client_id then + opts.namespace = M.get_namespace(client_id) end - if opts.severity then - line_diagnostics = filter_to_severity_limit(opts.severity, line_diagnostics) - elseif opts.severity_limit then - line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) + if not line_nr then + line_nr = vim.api.nvim_win_get_cursor(0)[1] - 1 end - table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end) + opts.lnum = line_nr - return line_diagnostics + return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts)) end --- Get the counts for a particular severity --- ---- Useful for showing diagnostic counts in statusline. eg: ---- ---- <pre> ---- function! LspStatus() abort ---- let sl = '' ---- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))') ---- let sl.='%#MyStatuslineLSP#E:' ---- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Error]])")}' ---- let sl.='%#MyStatuslineLSP# W:' ---- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.diagnostic.get_count(0, [[Warning]])")}' ---- else ---- let sl.='%#MyStatuslineLSPErrors#off' ---- endif ---- return sl ---- endfunction ---- autocmd BufWinEnter * let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() ---- </pre> +---@deprecated Prefer |vim.diagnostic.get_count()| --- ---@param bufnr number The buffer number ---@param severity DiagnosticSeverity ---@param client_id number the client id function M.get_count(bufnr, severity, client_id) - if client_id == nil then - local total = 0 - for iter_client_id, _ in pairs(diagnostic_cache_counts[bufnr]) do - total = total + M.get_count(bufnr, severity, iter_client_id) - end - - return total - end - - return (diagnostic_cache_counts[bufnr][client_id] or {})[DiagnosticSeverity[severity]] or 0 -end - - --- }}} --- Diagnostic Movements {{{ - ---- Helper function to find the next diagnostic relative to a position ----@return table the next diagnostic if found -local _next_diagnostic = function(position, search_forward, bufnr, opts, client_id) - position[1] = position[1] - 1 - bufnr = bufnr or vim.api.nvim_get_current_buf() - local wrap = if_nil(opts.wrap, true) - local line_count = vim.api.nvim_buf_line_count(bufnr) - for i = 0, line_count do - local offset = i * (search_forward and 1 or -1) - local line_nr = position[1] + offset - if line_nr < 0 or line_nr >= line_count then - 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 - ----@private ---- Helper function to return a position from a diagnostic ---- ----@return table {row, col} -local function _diagnostic_pos(opts, diagnostic) - opts = opts or {} - - local win_id = opts.win_id or vim.api.nvim_get_current_win() - local bufnr = vim.api.nvim_win_get_buf(win_id) - - if not diagnostic then return false end - - return to_position(diagnostic.range.start, bufnr) -end - ----@private --- Move to the diagnostic position -local function _diagnostic_move_pos(name, opts, pos) - opts = opts or {} - - local enable_popup = if_nil(opts.enable_popup, true) - local win_id = opts.win_id or vim.api.nvim_get_current_win() - - if not pos then - print(string.format("%s: No more valid diagnostics to move to.", name)) - return + severity = severity_lsp_to_vim(severity) + local opts = { severity = severity } + if client_id ~= nil then + opts.namespace = M.get_namespace(client_id) end - vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) - - if enable_popup then - -- This is a bit weird... I'm surprised that we need to wait til the next tick to do this. - vim.schedule(function() - M.show_position_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id)) - end) - end + return #vim.diagnostic.get(bufnr, opts) end --- Get the previous diagnostic closest to the cursor_position --- +---@deprecated Prefer |vim.diagnostic.get_prev()| +--- ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic function M.get_prev(opts) - opts = opts or {} - - local win_id = opts.win_id or vim.api.nvim_get_current_win() - 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) - - return _next_diagnostic(cursor_position, false, bufnr, opts, opts.client_id) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return diagnostic_vim_to_lsp({vim.diagnostic.get_prev(opts)})[1] end --- Return the pos, {row, col}, for the prev diagnostic in the current buffer. +--- +---@deprecated Prefer |vim.diagnostic.get_prev_pos()| +--- ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic position function M.get_prev_pos(opts) - return _diagnostic_pos( - opts, - M.get_prev(opts) - ) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return vim.diagnostic.get_prev_pos(opts) end --- Move to the previous diagnostic +--- +---@deprecated Prefer |vim.diagnostic.goto_prev()| +--- ---@param opts table See |vim.lsp.diagnostic.goto_next()| function M.goto_prev(opts) - return _diagnostic_move_pos( - "DiagnosticPrevious", - opts, - M.get_prev_pos(opts) - ) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return vim.diagnostic.goto_prev(opts) end --- Get the next diagnostic closest to the cursor_position +--- +---@deprecated Prefer |vim.diagnostic.get_next()| +--- ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic function M.get_next(opts) - opts = opts or {} - - local win_id = opts.win_id or vim.api.nvim_get_current_win() - 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) - - return _next_diagnostic(cursor_position, true, bufnr, opts, opts.client_id) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return diagnostic_vim_to_lsp({vim.diagnostic.get_next(opts)})[1] end --- Return the pos, {row, col}, for the next diagnostic in the current buffer. +--- +---@deprecated Prefer |vim.diagnostic.get_next_pos()| +--- ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic position function M.get_next_pos(opts) - return _diagnostic_pos( - opts, - M.get_next(opts) - ) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return vim.diagnostic.get_next_pos(opts) end --- Move to the next diagnostic +--- +---@deprecated Prefer |vim.diagnostic.goto_next()| +--- ---@param opts table|nil Configuration table. Keys: --- - {client_id}: (number) --- - If nil, will consider all clients attached to buffer. @@ -626,17 +432,20 @@ end --- - {win_id}: (number, default 0) --- - Window ID function M.goto_next(opts) - return _diagnostic_move_pos( - "DiagnosticNext", - opts, - M.get_next_pos(opts) - ) + if opts then + if opts.severity then + opts.severity = severity_lsp_to_vim(opts.severity) + elseif opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} + end + end + return vim.diagnostic.goto_next(opts) end --- }}} --- Diagnostic Setters {{{ --- Set signs for given diagnostics --- +---@deprecated Prefer |vim.diagnostic._set_signs()| +--- --- Sign characters can be customized with the following commands: --- --- <pre> @@ -654,35 +463,12 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) - opts = opts or {} - sign_ns = sign_ns or M._get_sign_namespace(client_id) - - if not diagnostics then - diagnostics = diagnostic_cache[bufnr][client_id] - end - - if not diagnostics then - return - end - - bufnr = get_bufnr(bufnr) - diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) - - local ok = true - for _, diagnostic in ipairs(diagnostics) do - - ok = ok and pcall(vim.fn.sign_place, - 0, - sign_ns, - sign_highlight_map[diagnostic.severity], - bufnr, - { - priority = opts.priority, - lnum = diagnostic.range.start.line + 1 - } - ) + local namespace = M.get_namespace(client_id) + if opts and not opts.severity and opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} end + local ok = vim.diagnostic._set_signs(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts) if not ok then log.debug("Failed to place signs:", diagnostics) end @@ -690,6 +476,8 @@ end --- Set underline for given diagnostics --- +---@deprecated Prefer |vim.diagnostic._set_underline()| +--- --- Underline highlights can be customized by changing the following |:highlight| groups. --- --- <pre> @@ -707,34 +495,17 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_underline(diagnostics, bufnr, client_id, diagnostic_ns, opts) - opts = opts or {} - - diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) - diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) - - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range["start"] - local finish = diagnostic.range["end"] - local higroup = underline_highlight_map[diagnostic.severity] - - if higroup == nil then - -- Default to error if we don't have a highlight associated - higroup = underline_highlight_map[DiagnosticSeverity.Error] - end - - highlight.range( - bufnr, - diagnostic_ns, - higroup, - to_position(start, bufnr), - to_position(finish, bufnr) - ) + local namespace = M.get_namespace(client_id) + if opts and not opts.severity and opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} end + return vim.diagnostic._set_underline(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts) end --- Virtual Text {{{ --- Set virtual text given diagnostics --- +---@deprecated Prefer |vim.diagnostic._set_virtual_text()| +--- --- Virtual text highlights can be customized by changing the following |:highlight| groups. --- --- <pre> @@ -754,35 +525,17 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) - opts = opts or {} - - client_id = get_client_id(client_id) - diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) - - local buffer_line_diagnostics - if diagnostics then - buffer_line_diagnostics = _diagnostic_lines(diagnostics) - else - buffer_line_diagnostics = diagnostic_cache_lines[bufnr][client_id] - end - - if not buffer_line_diagnostics then - return nil - end - - for line, line_diagnostics in pairs(buffer_line_diagnostics) do - line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) - local virt_texts = M.get_virtual_text_chunks_for_line(bufnr, line, line_diagnostics, opts) - - if virt_texts then - api.nvim_buf_set_extmark(bufnr, diagnostic_ns, line, 0, { - virt_text = virt_texts, - }) - end + local namespace = M.get_namespace(client_id) + if opts and not opts.severity and opts.severity_limit then + opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} end + return vim.diagnostic._set_virtual_text(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts) end --- Default function to get text chunks to display using |nvim_buf_set_extmark()|. +--- |