diff options
author | TJ DeVries <devries.timothyj@gmail.com> | 2020-11-12 22:21:34 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-12 22:21:34 -0500 |
commit | f75be5e9d510d5369c572cf98e78d9480df3b0bb (patch) | |
tree | e25baab19bcb47ca0d2edcf0baa18b71cfd03f9e /runtime/lua/vim/lsp/util.lua | |
parent | 4ae31c46f75aef7d7a80dd2a8d269c168806a1bd (diff) | |
download | rneovim-f75be5e9d510d5369c572cf98e78d9480df3b0bb.tar.gz rneovim-f75be5e9d510d5369c572cf98e78d9480df3b0bb.tar.bz2 rneovim-f75be5e9d510d5369c572cf98e78d9480df3b0bb.zip |
lsp: vim.lsp.diagnostic (#12655)
Breaking Changes:
- Deprecated all `vim.lsp.util.{*diagnostics*}()` functions.
- Instead, all functions must be found in vim.lsp.diagnostic
- For now, they issue a warning ONCE per neovim session. In a
"little while" we will remove them completely.
- `vim.lsp.callbacks` has moved to `vim.lsp.handlers`.
- For a "little while" we will just redirect `vim.lsp.callbacks` to
`vim.lsp.handlers`. However, we will remove this at some point, so
it is recommended that you change all of your references to
`callbacks` into `handlers`.
- This also means that for functions like |vim.lsp.start_client()|
and similar, keyword style arguments have moved from "callbacks"
to "handlers". Once again, these are currently being forward, but
will cease to be forwarded in a "little while".
- Changed the highlight groups for LspDiagnostic highlight as they were
inconsistently named.
- For more information, see |lsp-highlight-diagnostics|
- Changed the sign group names as well, to be consistent with
|lsp-highlight-diagnostics|
General Enhancements:
- Rewrote much of the getting started help document for lsp. It also
provides a much nicer configuration strategy, so as to not recommend
globally overwriting builtin neovim mappings.
LSP Enhancements:
- Introduced the concept of |lsp-handlers| which will allow much better
customization for users without having to copy & paste entire files /
functions / etc.
Diagnostic Enhancements:
- "goto next diagnostic" |vim.lsp.diagnostic.goto_next()|
- "goto prev diagnostic" |vim.lsp.diagnostic.goto_prev()|
- For each of the gotos, auto open diagnostics is available as a
configuration option
- Configurable diagnostic handling:
- See |vim.lsp.diagnostic.on_publish_diagnostics()|
- Delay display until after insert mode
- Configure signs
- Configure virtual text
- Configure underline
- Set the location list with the buffers diagnostics.
- See |vim.lsp.diagnostic.set_loclist()|
- Better performance for getting counts and line diagnostics
- They are now cached on save, to enhance lookups.
- Particularly useful for checking in statusline, etc.
- Actual testing :)
- See ./test/functional/plugin/lsp/diagnostic_spec.lua
- Added `guisp` for underline highlighting
NOTE: "a little while" means enough time to feel like most plugins and
plugin authors have had a chance to refactor their code to use the
updated calls. Then we will remove them completely. There is no need to
keep them, because we don't have any released version of neovim that
exposes these APIs. I'm trying to be nice to people following HEAD :)
Co-authored: [Twitch Chat 2020](https://twitch.tv/teej_dv)
Diffstat (limited to 'runtime/lua/vim/lsp/util.lua')
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 402 |
1 files changed, 101 insertions, 301 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 9ed19b938d..3deec6d74e 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -5,50 +5,32 @@ local api = vim.api local list_extend = vim.list_extend local highlight = require 'vim.highlight' +local npcall = vim.F.npcall +local split = vim.split + +local _warned = {} +local warn_once = function(message) + if not _warned[message] then + vim.api.nvim_err_writeln(message) + _warned[message] = true + end +end + local M = {} --- FIXME: DOC: Expose in vimdocs ---- Diagnostics received from the server via `textDocument/publishDiagnostics` --- by buffer. --- --- {<bufnr>: {diagnostics}} --- --- This contains only entries for active buffers. Entries for detached buffers --- are discarded. --- --- If you override the `textDocument/publishDiagnostic` callback, --- this will be empty unless you call `buf_diagnostics_save_positions`. --- --- --- Diagnostic is: --- --- { --- range: Range --- message: string --- severity?: DiagnosticSeverity --- code?: number | string --- source?: string --- tags?: DiagnosticTag[] --- relatedInformation?: DiagnosticRelatedInformation[] --- } -M.diagnostics_by_buf = {} +-- TODO(remove-callbacks) +M.diagnostics_by_buf = setmetatable({}, { + __index = function(_, bufnr) + warn_once("diagnostics_by_buf is deprecated. Use 'vim.lsp.diagnostic.get'") + return vim.lsp.diagnostic.get(bufnr) + end +}) -local split = vim.split --@private local function split_lines(value) return split(value, '\n', true) end ---@private -local function ok_or_nil(status, ...) - if not status then return end - return ... -end ---@private -local function npcall(fn, ...) - return ok_or_nil(pcall(fn, ...)) -end - --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! @@ -121,10 +103,18 @@ local function get_line_byte_from_position(bufnr, position) -- When on the first character, we can ignore the difference between byte and -- character if col > 0 then + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end + local line = position.line local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false) if #lines > 0 then - return vim.str_byteindex(lines[1], col) + local ok, result = pcall(vim.str_byteindex, lines[1], col) + + if ok then + return result + end end end return col @@ -700,13 +690,13 @@ end --- Trims empty lines from input and pad left and right with spaces --- ---@param contents table of lines to trim and pad ---@param opts dictionary with optional fields --- - pad_left number of columns to pad contents at left (default 1) --- - pad_right number of columns to pad contents at right (default 1) --- - pad_top number of lines to pad contents at top (default 0) --- - pad_bottom number of lines to pad contents at bottom (default 0) ---@returns contents table of trimmed and padded lines +---@param contents table of lines to trim and pad +---@param opts dictionary with optional fields +--- - pad_left number of columns to pad contents at left (default 1) +--- - pad_right number of columns to pad contents at right (default 1) +--- - pad_top number of lines to pad contents at top (default 0) +--- - pad_bottom number of lines to pad contents at bottom (default 0) +---@return contents table of trimmed and padded lines function M._trim_and_pad(contents, opts) validate { contents = { contents, 't' }; @@ -742,19 +732,19 @@ end --- regions to improve readability. --- The result is shown in a floating preview. --- ---@param contents table of lines to show in window ---@param opts dictionary with optional fields --- - height of floating window --- - width of floating window --- - wrap_at character to wrap at for computing height --- - max_width maximal width of floating window --- - max_height maximal height of floating window --- - pad_left number of columns to pad contents at left --- - pad_right number of columns to pad contents at right --- - pad_top number of lines to pad contents at top --- - pad_bottom number of lines to pad contents at bottom --- - separator insert separator after code block ---@returns width,height size of float +---@param contents table of lines to show in window +---@param opts dictionary with optional fields +--- - height of floating window +--- - width of floating window +--- - wrap_at character to wrap at for computing height +--- - max_width maximal width of floating window +--- - max_height maximal height of floating window +--- - pad_left number of columns to pad contents at left +--- - pad_right number of columns to pad contents at right +--- - pad_top number of lines to pad contents at top +--- - pad_bottom number of lines to pad contents at bottom +--- - separator insert separator after code block +---@returns width,height size of float function M.fancy_floating_markdown(contents, opts) validate { contents = { contents, 't' }; @@ -971,170 +961,80 @@ function M.open_floating_preview(contents, filetype, opts) return floating_bufnr, floating_winnr end +-- TODO(remove-callbacks) do - local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics") - local reference_ns = api.nvim_create_namespace("vim_lsp_references") - local sign_ns = 'vim_lsp_signs' - local underline_highlight_name = "LspDiagnosticsUnderline" - vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name)) - for kind, _ in pairs(protocol.DiagnosticSeverity) do - if type(kind) == 'string' then - vim.cmd(string.format("highlight default link %s%s %s", underline_highlight_name, kind, underline_highlight_name)) - end + --@deprecated + function M.get_severity_highlight_name(severity) + warn_once("vim.lsp.util.get_severity_highlight_name is deprecated.") + return vim.lsp.diagnostic._get_severity_highlight_name(severity) end - local severity_highlights = {} + --@deprecated + function M.buf_clear_diagnostics(bufnr, client_id) + warn_once("buf_clear_diagnostics is deprecated. Use vim.lsp.diagnostic.clear") + return vim.lsp.diagnostic.clear(bufnr, client_id) + end - local severity_floating_highlights = {} + --@deprecated + function M.get_line_diagnostics() + warn_once("get_line_diagnostics is deprecated. Use vim.lsp.diagnostic.get_line_diagnostics") - local default_severity_highlight = { - [protocol.DiagnosticSeverity.Error] = { guifg = "Red" }; - [protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" }; - [protocol.DiagnosticSeverity.Information] = { guifg = "LightBlue" }; - [protocol.DiagnosticSeverity.Hint] = { guifg = "LightGrey" }; - } + local bufnr = api.nvim_get_current_buf() + local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - -- Initialize default severity highlights - for severity, hi_info in pairs(default_severity_highlight) do - local severity_name = protocol.DiagnosticSeverity[severity] - local highlight_name = "LspDiagnostics"..severity_name - local floating_highlight_name = highlight_name.."Floating" - -- Try to fill in the foreground color with a sane default. - local cmd_parts = {"highlight", "default", highlight_name} - for k, v in pairs(hi_info) do - table.insert(cmd_parts, k.."="..v) - end - api.nvim_command(table.concat(cmd_parts, ' ')) - api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name) - api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name) - severity_highlights[severity] = highlight_name - severity_floating_highlights[severity] = floating_highlight_name + return vim.lsp.diagnostic.get_line_diagnostics(bufnr, line_nr) end - --- Clears diagnostics for a buffer. - --- - --@param bufnr (number) buffer id - function M.buf_clear_diagnostics(bufnr) - validate { bufnr = {bufnr, 'n', true} } - bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + --@deprecated + function M.show_line_diagnostics() + warn_once("show_line_diagnostics is deprecated. Use vim.lsp.diagnostic.show_line_diagnostics") - -- clear sign group - vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + local bufnr = api.nvim_get_current_buf() + local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - -- clear virtual text namespace - api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) + return vim.lsp.diagnostic.show_line_diagnostics(bufnr, line_nr) end - --- Gets the name of a severity's highlight group. - --- - --@param severity A member of `vim.lsp.protocol.DiagnosticSeverity` - --@returns (string) Highlight group name - function M.get_severity_highlight_name(severity) - return severity_highlights[severity] + --@deprecated + function M.buf_diagnostics_save_positions(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_save_positions is deprecated. Use vim.lsp.diagnostic.save") + return vim.lsp.diagnostic.save(diagnostics, bufnr, client_id) end - --- Gets list of diagnostics for the current line. - --- - --@returns (table) list of `Diagnostic` tables - --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic - function M.get_line_diagnostics() - local bufnr = api.nvim_get_current_buf() - local linenr = api.nvim_win_get_cursor(0)[1] - 1 - - local buffer_diagnostics = M.diagnostics_by_buf[bufnr] + --@deprecated + function M.buf_diagnostics_get_positions(bufnr, client_id) + warn_once("buf_diagnostics_get_positions is deprecated. Use vim.lsp.diagnostic.get") + return vim.lsp.diagnostic.get(bufnr, client_id) + end - if not buffer_diagnostics then - return {} - end + --@deprecated + function M.buf_diagnostics_underline(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_underline is deprecated. Use 'vim.lsp.diagnostic.set_underline'") + return vim.lsp.diagnostic.set_underline(diagnostics, bufnr, client_id) + end - local diagnostics_by_line = M.diagnostics_group_by_line(buffer_diagnostics) - return diagnostics_by_line[linenr] or {} + --@deprecated + function M.buf_diagnostics_virtual_text(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_virtual_text is deprecated. Use 'vim.lsp.diagnostic.set_virtual_text'") + return vim.lsp.diagnostic.set_virtual_text(diagnostics, bufnr, client_id) end - --- Displays the diagnostics for the current line in a floating hover - --- window. - function M.show_line_diagnostics() - -- local marks = api.nvim_buf_get_extmarks(bufnr, diagnostic_ns, {line, 0}, {line, -1}, {}) - -- if #marks == 0 then - -- return - -- end - local lines = {"Diagnostics:"} - local highlights = {{0, "Bold"}} - local line_diagnostics = M.get_line_diagnostics() - if vim.tbl_isempty(line_diagnostics) then return end - - for i, diagnostic in ipairs(line_diagnostics) do - -- for i, mark in ipairs(marks) do - -- local mark_id = mark[1] - -- local diagnostic = buffer_diagnostics[mark_id] - - -- TODO(ashkan) make format configurable? - local prefix = string.format("%d. ", i) - local hiname = severity_floating_highlights[diagnostic.severity] - assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity)) - local message_lines = split_lines(diagnostic.message) - table.insert(lines, prefix..message_lines[1]) - table.insert(highlights, {#prefix + 1, hiname}) - for j = 2, #message_lines do - table.insert(lines, message_lines[j]) - table.insert(highlights, {0, hiname}) - end - end - local popup_bufnr, winnr = M.open_floating_preview(lines, 'plaintext') - for i, hi in ipairs(highlights) do - local prefixlen, hiname = unpack(hi) - -- Start highlight after the prefix - api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1) - end - return popup_bufnr, winnr + --@deprecated + function M.buf_diagnostics_signs(bufnr, diagnostics, client_id) + warn_once("buf_diagnostics_signs is deprecated. Use 'vim.lsp.diagnostics.set_signs'") + return vim.lsp.diagnostic.set_signs(diagnostics, bufnr, client_id) end - --- Saves diagnostics into vim.lsp.util.diagnostics_by_buf[{bufnr}]. - --- - --@param bufnr (number) buffer id for which the diagnostics are for - --@param diagnostics list of `Diagnostic`s received from the LSP server - function M.buf_diagnostics_save_positions(bufnr, diagnostics) - validate { - bufnr = {bufnr, 'n', true}; - diagnostics = {diagnostics, 't', true}; - } - if not diagnostics then return end - bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr - - if not M.diagnostics_by_buf[bufnr] then - -- Clean up our data when the buffer unloads. - api.nvim_buf_attach(bufnr, false, { - on_detach = function(b) - M.diagnostics_by_buf[b] = nil - end - }) - end - M.diagnostics_by_buf[bufnr] = diagnostics + --@deprecated + function M.buf_diagnostics_count(kind, client_id) + warn_once("buf_diagnostics_count is deprecated. Use 'vim.lsp.diagnostic.get_count'") + return vim.lsp.diagnostic.get_count(vim.api.nvim_get_current_buf(), client_id, kind) end - --- Highlights a list of diagnostics in a buffer by underlining them. - --- - --@param bufnr (number) buffer id - --@param diagnostics (list of `Diagnostic`s) - function M.buf_diagnostics_underline(bufnr, diagnostics) - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range["start"] - local finish = diagnostic.range["end"] - - local hlmap = { - [protocol.DiagnosticSeverity.Error]='Error', - [protocol.DiagnosticSeverity.Warning]='Warning', - [protocol.DiagnosticSeverity.Information]='Information', - [protocol.DiagnosticSeverity.Hint]='Hint', - } +end - highlight.range(bufnr, diagnostic_ns, - underline_highlight_name..hlmap[diagnostic.severity], - {start.line, start.character}, - {finish.line, finish.character} - ) - end - end +do --[[ References ]] + local reference_ns = api.nvim_create_namespace("vim_lsp_references") --- Removes document highlights from a buffer. --- @@ -1162,109 +1062,6 @@ do highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos) end end - - --- Groups a list of diagnostics by line. - --- - --@param diagnostics (table) list of `Diagnostic`s - --@returns (table) dictionary mapping lines to lists of diagnostics valid on - ---those lines - --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic - function M.diagnostics_group_by_line(diagnostics) - if not diagnostics then return end - local diagnostics_by_line = {} - for _, diagnostic in ipairs(diagnostics) do - local start = diagnostic.range.start - -- TODO: Are diagnostics only valid for a single line? I don't understand - -- why this would be okay otherwise - 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) - end - return diagnostics_by_line - end - - --- Given a list of diagnostics, sets the corresponding virtual text for a - --- buffer. - --- - --@param bufnr buffer id - --@param diagnostics (table) list of `Diagnostic`s - function M.buf_diagnostics_virtual_text(bufnr, diagnostics) - if not diagnostics then - return - end - local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics) - for line, line_diags in pairs(buffer_line_diagnostics) do - local virt_texts = {} - for i = 1, #line_diags - 1 do - table.insert(virt_texts, {"■", severity_highlights[line_diags[i].severity]}) - end - local last = line_diags[#line_diags] - -- TODO(ashkan) use first line instead of subbing 2 spaces? - table.insert(virt_texts, {"■ "..last.message:gsub("\r", ""):gsub("\n", " "), severity_highlights[last.severity]}) - api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) - end - end - - --- Returns the number of diagnostics of given kind for current buffer. - --- - --- 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.util.buf_diagnostics_count([[Error]])")}' - --- let sl.='%#MyStatuslineLSP# W:' - --- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}' - --- else - --- let sl.='%#MyStatuslineLSPErrors#off' - --- endif - --- return sl - --- endfunction - --- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus() - --- </pre> - --- - --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity| - --@returns Count of diagnostics - function M.buf_diagnostics_count(kind) - local bufnr = vim.api.nvim_get_current_buf() - local diagnostics = M.diagnostics_by_buf[bufnr] - if not diagnostics then return end - local count = 0 - for _, diagnostic in pairs(diagnostics) do - if protocol.DiagnosticSeverity[kind] == diagnostic.severity then - count = count + 1 - end - end - return count - end - - local diagnostic_severity_map = { - [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; - [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; - [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; - [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; - } - - --- Places signs for each diagnostic in the sign column. - --- - --- Sign characters can be customized with the following commands: - --- - --- <pre> - --- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl= - --- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl= - --- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl= - --- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl= - --- </pre> - function M.buf_diagnostics_signs(bufnr, diagnostics) - for _, diagnostic in ipairs(diagnostics) do - vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)}) - end - end end local position_sort = sort_by_key(function(v) @@ -1561,6 +1358,9 @@ function M.character_offset(buf, row, col) return str_utfindex(line, col) end +M._get_line_byte_from_position = get_line_byte_from_position +M._warn_once = warn_once + M.buf_versions = {} return M |