diff options
Diffstat (limited to 'runtime/lua/vim/lsp/util.lua')
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 157 |
1 files changed, 140 insertions, 17 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index df82e2d412..5dd010f2a4 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -88,6 +88,9 @@ function M.apply_text_edits(text_edits, bufnr) -- Reverse sort the orders so we can apply them without interfering with -- eachother. Also add i as a sort key to mimic a stable sort. table.sort(cleaned, edit_sort_key) + if not api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol') local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1 @@ -132,7 +135,7 @@ function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) -- TODO(ashkan) check this is correct. - if api.nvim_buf_get_changedtick(bufnr) > text_document.version then + if (M.buf_versions[bufnr] or 0) > text_document.version then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -145,15 +148,36 @@ function M.get_current_line_to_cursor() return line:sub(pos[2]+1) end +-- Sort by CompletionItem.sortText +-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +local function sort_completion_items(items) + if items[1] and items[1].sortText then + table.sort(items, function(a, b) return a.sortText < b.sortText + end) + end +end + +-- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. +-- So we exclude completion candidates whose prefix does not match. +local function remove_unmatch_completion_items(items, prefix) + return vim.tbl_filter(function(item) + local word = item.insertText or item.label + return vim.startswith(word, prefix) + end, items) +end + --- Getting vim complete-items with incomplete flag. -- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) -- @return { matches = complete-items table, incomplete = boolean } -function M.text_document_completion_list_to_complete_items(result) +function M.text_document_completion_list_to_complete_items(result, prefix) local items = M.extract_completion_items(result) if vim.tbl_isempty(items) then return {} end + items = remove_unmatch_completion_items(items, prefix) + sort_completion_items(items) + local matches = {} for _, completion_item in ipairs(items) do @@ -177,7 +201,7 @@ function M.text_document_completion_list_to_complete_items(result) menu = completion_item.detail or '', info = info, icase = 1, - dup = 0, + dup = 1, empty = 1, }) end @@ -245,7 +269,7 @@ function M.convert_input_to_markdown_lines(input, contents) end end end - if contents[1] == '' or contents[1] == nil then + if (contents[1] == '' or contents[1] == nil) and #contents == 1 then return {} end return contents @@ -545,7 +569,8 @@ do local all_buffer_diagnostics = {} 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 @@ -579,6 +604,11 @@ do function M.buf_clear_diagnostics(bufnr) validate { bufnr = {bufnr, 'n', true} } bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr + + -- clear sign group + vim.fn.sign_unplace(sign_ns, {buffer=bufnr}) + + -- clear virtual text namespace api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1) end @@ -659,7 +689,6 @@ do end end - function M.buf_diagnostics_underline(bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -681,6 +710,25 @@ do end end + function M.buf_clear_references(bufnr) + validate { bufnr = {bufnr, 'n', true} } + api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) + end + + function M.buf_highlight_references(bufnr, references) + validate { bufnr = {bufnr, 'n', true} } + for _, reference in ipairs(references) do + local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]} + local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]} + local document_highlight_kind = { + [protocol.DocumentHighlightKind.Text] = "LspReferenceText"; + [protocol.DocumentHighlightKind.Read] = "LspReferenceRead"; + [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite"; + } + highlight_range(bufnr, reference_ns, document_highlight_kind[reference["kind"]], start_pos, end_pos) + end + end + function M.buf_diagnostics_virtual_text(bufnr, diagnostics) local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] if not buffer_line_diagnostics then @@ -701,10 +749,38 @@ do api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {}) end end + function M.buf_diagnostics_count(kind) + local bufnr = vim.api.nvim_get_current_buf() + local buffer_line_diagnostics = all_buffer_diagnostics[bufnr] + if not buffer_line_diagnostics then return end + local count = 0 + for _, line_diags in pairs(buffer_line_diagnostics) do + for _, diag in ipairs(line_diags) do + if protocol.DiagnosticSeverity[kind] == diag.severity then count = count + 1 end + end + end + return count + end + function M.buf_diagnostics_signs(bufnr, diagnostics) + vim.fn.sign_define('LspDiagnosticsErrorSign', {text=vim.g['LspDiagnosticsErrorSign'] or 'E', texthl='LspDiagnosticsError', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsWarningSign', {text=vim.g['LspDiagnosticsWarningSign'] or 'W', texthl='LspDiagnosticsWarning', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsInformationSign', {text=vim.g['LspDiagnosticsInformationSign'] or 'I', texthl='LspDiagnosticsInformation', linehl='', numhl=''}) + vim.fn.sign_define('LspDiagnosticsHintSign', {text=vim.g['LspDiagnosticsHintSign'] or 'H', texthl='LspDiagnosticsHint', linehl='', numhl=''}) + + for _, diagnostic in ipairs(diagnostics) do + local diagnostic_severity_map = { + [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign"; + [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign"; + [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign"; + [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign"; + } + 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) - return {v.line, v.character} + return {v.start.line, v.start.character} end) -- Returns the items with the byte position calculated correctly and in sorted @@ -721,17 +797,21 @@ function M.locations_to_items(locations) for _, d in ipairs(locations) do local start = d.range.start local fname = assert(vim.uri_to_fname(d.uri)) - table.insert(grouped[fname], start) + table.insert(grouped[fname], {start = start}) end + + local keys = vim.tbl_keys(grouped) table.sort(keys) -- TODO(ashkan) I wish we could do this lazily. for _, fname in ipairs(keys) do local rows = grouped[fname] + table.sort(rows, position_sort) local i = 0 for line in io.lines(fname) do - for _, pos in ipairs(rows) do + for _, temp in ipairs(rows) do + local pos = temp.start local row = pos.line if i == row then local col @@ -754,23 +834,60 @@ function M.locations_to_items(locations) return items end --- locations is Location[] --- Only sets for the current window. -function M.set_loclist(locations) +function M.set_loclist(items) vim.fn.setloclist(0, {}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end --- locations is Location[] -function M.set_qflist(locations) +function M.set_qflist(items) vim.fn.setqflist({}, ' ', { title = 'Language Server'; - items = M.locations_to_items(locations); + items = items; }) end +--- Convert symbols to quickfix list items +--- +--@symbols DocumentSymbol[] or SymbolInformation[] +function M.symbols_to_items(symbols, bufnr) + local function _symbols_to_items(_symbols, _items, _bufnr) + for _, symbol in ipairs(_symbols) do + if symbol.location then -- SymbolInformation type + local range = symbol.location.range + local kind = protocol.SymbolKind[symbol.kind] + table.insert(_items, { + filename = vim.uri_to_fname(symbol.location.uri), + lnum = range.start.line + 1, + col = range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name, + }) + elseif symbol.range then -- DocumentSymbole type + local kind = protocol.SymbolKind[symbol.kind] + table.insert(_items, { + -- bufnr = _bufnr, + filename = vim.api.nvim_buf_get_name(_bufnr), + lnum = symbol.range.start.line + 1, + col = symbol.range.start.character + 1, + kind = kind, + text = '['..kind..'] '..symbol.name + }) + if symbol.children then + for _, child in ipairs(symbol) do + for _, v in ipairs(_symbols_to_items(child, _items, _bufnr)) do + vim.list_extend(_items, v) + end + end + end + end + end + return _items + end + return _symbols_to_items(symbols, {}, bufnr) +end + -- Remove empty lines from the beginning and end. function M.trim_empty_lines(lines) local start = 1 @@ -823,11 +940,15 @@ function M.make_position_params() local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] col = str_utfindex(line, col) return { - textDocument = { uri = vim.uri_from_bufnr(0) }; + textDocument = M.make_text_document_params(); position = { line = row; character = col; } } end +function M.make_text_document_params() + return { uri = vim.uri_from_bufnr(0) } +end + -- @param buf buffer handle or 0 for current. -- @param row 0-indexed line -- @param col 0-indexed byte offset in line @@ -840,5 +961,7 @@ function M.character_offset(buf, row, col) return str_utfindex(line, col) end +M.buf_versions = {} + return M -- vim:sw=2 ts=2 et |