diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/lsp.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 11 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 22 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 188 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 2 |
5 files changed, 171 insertions, 54 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 84812b8c64..2fbc51481f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -507,6 +507,8 @@ function lsp.start_client(config) or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') + or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration') + or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition') or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') then diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 7c51fc2cc2..4b14f0132d 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -72,6 +72,17 @@ M['textDocument/publishDiagnostics'] = function(_, _, result) err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri) return end + + -- Unloaded buffers should not handle diagnostics. + -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen. + -- This should trigger another publish of the diagnostics. + -- + -- In particular, this stops a ton of spam when first starting a server for current + -- unloaded buffers. + if not api.nvim_buf_is_loaded(bufnr) then + return + end + util.buf_clear_diagnostics(bufnr) -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 7d5f8f5ef1..64911fe7bb 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -923,6 +923,28 @@ function protocol.resolve_capabilities(server_capabilities) error("The server sent invalid codeActionProvider") end + if server_capabilities.declarationProvider == nil then + general_properties.declaration = false + elseif type(server_capabilities.declarationProvider) == 'boolean' then + general_properties.declaration = server_capabilities.declarationProvider + elseif type(server_capabilities.declarationProvider) == 'table' then + -- TODO: support more detailed declarationProvider options. + general_properties.declaration = false + else + error("The server sent invalid declarationProvider") + end + + if server_capabilities.typeDefinitionProvider == nil then + general_properties.type_definition = false + elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then + general_properties.type_definition = server_capabilities.typeDefinitionProvider + elseif type(server_capabilities.typeDefinitionProvider) == 'table' then + -- TODO: support more detailed typeDefinitionProvider options. + general_properties.type_definition = false + else + error("The server sent invalid typeDefinitionProvider") + end + if server_capabilities.implementationProvider == nil then general_properties.implementation = false elseif type(server_capabilities.implementationProvider) == 'boolean' then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 79d428d12d..49e2557c16 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -95,17 +95,23 @@ local edit_sort_key = sort_by_key(function(e) return {e.A[1], e.A[2], e.i} end) -local function get_line_byte_from_line_character(bufnr, lnum, cnum) - -- Skip check when the byte and character position is the same - if cnum > 0 then - local lines = api.nvim_buf_get_lines(bufnr, lnum, lnum+1, false) - +--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position +-- Returns a zero-indexed column, since set_lines() does the conversion to +-- 1-indexed +local function get_line_byte_from_position(bufnr, position) + -- LSP's line and characters are 0-indexed + -- Vim's line and columns are 1-indexed + local col = position.character + -- When on the first character, we can ignore the difference between byte and + -- character + if col > 0 then + 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], cnum) + return vim.str_byteindex(lines[1], col) end end - - return cnum + return col end function M.apply_text_edits(text_edits, bufnr) @@ -118,15 +124,9 @@ function M.apply_text_edits(text_edits, bufnr) for i, e in ipairs(text_edits) do -- adjust start and end column for UTF-16 encoding of non-ASCII characters local start_row = e.range.start.line - local start_col = get_line_byte_from_line_character( - bufnr, - start_row, - e.range.start.character) + local start_col = get_line_byte_from_position(bufnr, e.range.start) local end_row = e.range["end"].line - local end_col = get_line_byte_from_line_character( - bufnr, - end_row, - e.range["end"].character) + local end_col = get_line_byte_from_position(bufnr, e.range['end']) start_line = math.min(e.range.start.line, start_line) finish_line = math.max(e.range["end"].line, finish_line) -- TODO(ashkan) sanity check ranges for overlap. @@ -289,7 +289,7 @@ local function get_completion_word(item) return item.label end --- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. +-- Some language 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) @@ -543,9 +543,7 @@ function M.jump_to_location(location) api.nvim_buf_set_option(0, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line - local col = range.start.character - local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] - col = vim.str_byteindex(line, col) + local col = get_line_byte_from_position(0, range.start) api.nvim_win_set_cursor(0, {row + 1, col}) return true end @@ -616,13 +614,53 @@ function M.focusable_preview(unique_name, fn) end) end --- Convert markdown into syntax highlighted regions by stripping the code --- blocks and converting them into highlighted code. --- This will by default insert a blank line separator after those code block --- regions to improve readability. +--- Trim 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 amount of columns to pad contents at left (default 1) +-- - pad_right amount of columns to pad contents at right (default 1) +--@return contents table of trimmed and padded lines +function M._trim_and_pad(contents, opts) + validate { + contents = { contents, 't' }; + opts = { opts, 't', true }; + } + opts = opts or {} + local left_padding = (" "):rep(opts.pad_left or 1) + local right_padding = (" "):rep(opts.pad_right or 1) + contents = M.trim_empty_lines(contents) + for i, line in ipairs(contents) do + contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding) + end + return contents +end + + + +--- Convert markdown into syntax highlighted regions by stripping the code +--- blocks and converting them into highlighted code. +--- This will by default insert a blank line separator after those code block +--- regions to improve readability. +--- The result is shown in a floating preview +--- TODO: refactor to separate stripping/converting and make use of open_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 +-- - pad_left amount of columns to pad contents at left +-- - pad_right amount of columns to pad contents at right +-- - separator insert separator after code block +--@return width,height size of float function M.fancy_floating_markdown(contents, opts) - local pad_left = opts and opts.pad_left - local pad_right = opts and opts.pad_right + validate { + contents = { contents, 't' }; + opts = { opts, 't', true }; + } + opts = opts or {} + local stripped = {} local highlights = {} do @@ -656,31 +694,27 @@ function M.fancy_floating_markdown(contents, opts) end end end - local width = 0 - for i, v in ipairs(stripped) do - v = v:gsub("\r", "") - if pad_left then v = (" "):rep(pad_left)..v end - if pad_right then v = v..(" "):rep(pad_right) end - stripped[i] = v - width = math.max(width, #v) - end - if opts and opts.max_width then - width = math.min(opts.max_width, width) - end - -- TODO(ashkan): decide how to make this customizable. - local insert_separator = true + -- Clean up and add padding + stripped = M._trim_and_pad(stripped, opts) + + -- Compute size of float needed to show (wrapped) lines + opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) + local width, height = M._make_floating_popup_size(stripped, opts) + + -- Insert blank line separator after code block + local insert_separator = opts.separator or true if insert_separator then for i, h in ipairs(highlights) do h.start = h.start + i - 1 h.finish = h.finish + i - 1 if h.finish + 1 <= #stripped then table.insert(stripped, h.finish + 1, string.rep("─", width)) + height = height + 1 end end end -- Make the floating window. - local height = #stripped local bufnr = api.nvim_create_buf(false, true) local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts)) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) @@ -721,33 +755,81 @@ function M.close_preview_autocmd(events, winnr) api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") end -function M.open_floating_preview(contents, filetype, opts) +--- Compute size of float needed to show contents (with optional wrapping) +--- +--@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 +--@return width,height size of float +function M._make_floating_popup_size(contents, opts) validate { contents = { contents, 't' }; - filetype = { filetype, 's', true }; opts = { opts, 't', true }; } opts = opts or {} - -- Trim empty lines from the end. - contents = M.trim_empty_lines(contents) - local width = opts.width - local height = opts.height or #contents + local height = opts.height + local line_widths = {} + if not width then width = 0 for i, line in ipairs(contents) do - -- Clean up the input and add left pad. - line = " "..line:gsub("\r", "") -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. - local line_width = vim.fn.strdisplaywidth(line) - width = math.max(line_width, width) - contents[i] = line + line_widths[i] = vim.fn.strdisplaywidth(line) + width = math.max(line_widths[i], width) + end + end + + if not height then + height = #contents + local wrap_at = opts.wrap_at + if wrap_at and width > wrap_at then + height = 0 + if vim.tbl_isempty(line_widths) then + for _, line in ipairs(contents) do + local line_width = vim.fn.strdisplaywidth(line) + height = height + math.ceil(line_width/wrap_at) + end + else + for i = 1, #contents do + height = height + math.ceil(line_widths[i]/wrap_at) + end + end end - -- Add right padding of 1 each. - width = width + 1 end + return width, height +end + +--- Show contents in a floating window +--- +--@param contents table of lines to show in window +--@param filetype string of filetype to set for opened buffer +--@param opts dictionary with optional fields +-- - height of floating window +-- - width of floating window +-- - wrap_at character to wrap at for computing height +-- - pad_left amount of columns to pad contents at left +-- - pad_right amount of columns to pad contents at right +--@return bufnr,winnr buffer and window number of floating window or nil +function M.open_floating_preview(contents, filetype, opts) + validate { + contents = { contents, 't' }; + filetype = { filetype, 's', true }; + opts = { opts, 't', true }; + } + opts = opts or {} + + -- Clean up input: trim empty lines from the end, pad + contents = M._trim_and_pad(contents, opts) + + -- Compute size of float needed to show (wrapped) lines + opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) + local width, height = M._make_floating_popup_size(contents, opts) + local floating_bufnr = api.nvim_create_buf(false, true) if filetype then api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 2135bfc837..384d22cb89 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -79,7 +79,7 @@ function vim.gsplit(s, sep, plain) end return function() - if done then + if done or (s == '' and sep == '') then return end if sep == '' then |