diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 22 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/codelens.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 15 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 86 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 7 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 42 |
7 files changed, 117 insertions, 65 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index b13d662ccb..ced1747ee0 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -126,7 +126,7 @@ local function select_client(method) if #clients > 1 then local choices = {} - for k,v in ipairs(clients) do + for k,v in pairs(clients) do table.insert(choices, string.format("%d %s", k, v.name)) end local user_choice = vim.fn.confirm( @@ -204,9 +204,9 @@ function M.formatting_seq_sync(options, timeout_ms, order) local clients = vim.tbl_values(vim.lsp.buf_get_clients()); -- sort the clients according to `order` - for _, client_name in ipairs(order or {}) do + for _, client_name in pairs(order or {}) do -- if the client exists, move to the end of the list - for i, client in ipairs(clients) do + for i, client in pairs(clients) do if client.name == client_name then table.insert(clients, table.remove(clients, i)) break @@ -215,7 +215,7 @@ function M.formatting_seq_sync(options, timeout_ms, order) end -- loop through the clients and make synchronous formatting requests - for _, client in ipairs(clients) do + for _, client in pairs(clients) do if client.resolved_capabilities.document_formatting then local params = util.make_formatting_params(options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf()) @@ -286,7 +286,7 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return call_hierarchy_items[1] end local items = {} - for i, item in ipairs(call_hierarchy_items) do + for i, item in pairs(call_hierarchy_items) do local entry = item.detail or item.name table.insert(items, string.format("%d. %s", i, entry)) end @@ -328,8 +328,8 @@ end --- function M.list_workspace_folders() local workspace_folders = {} - for _, client in ipairs(vim.lsp.buf_get_clients()) do - for _, folder in ipairs(client.workspaceFolders) do + for _, client in pairs(vim.lsp.buf_get_clients()) do + for _, folder in pairs(client.workspaceFolders) do table.insert(workspace_folders, folder.name) end end @@ -347,9 +347,9 @@ function M.add_workspace_folder(workspace_folder) return end local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}}) - for _, client in ipairs(vim.lsp.buf_get_clients()) do + for _, client in pairs(vim.lsp.buf_get_clients()) do local found = false - for _, folder in ipairs(client.workspaceFolders) do + for _, folder in pairs(client.workspaceFolders) do if folder.name == workspace_folder then found = true print(workspace_folder, "is already part of this workspace") @@ -371,8 +371,8 @@ function M.remove_workspace_folder(workspace_folder) vim.api.nvim_command("redraw") if not (workspace_folder and #workspace_folder > 0) then return end local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) - for _, client in ipairs(vim.lsp.buf_get_clients()) do - for idx, folder in ipairs(client.workspaceFolders) do + for _, client in pairs(vim.lsp.buf_get_clients()) do + for idx, folder in pairs(client.workspaceFolders) do if folder.name == workspace_folder then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) client.workspaceFolders[idx] = nil diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index fbd37e3830..46e2078507 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -111,12 +111,16 @@ function M.display(lenses, bufnr, client_id) local ns = namespaces[client_id] local num_lines = api.nvim_buf_line_count(bufnr) for i = 0, num_lines do - local line_lenses = lenses_by_lnum[i] + local line_lenses = lenses_by_lnum[i] or {} api.nvim_buf_clear_namespace(bufnr, ns, i, i + 1) local chunks = {} - for _, lens in pairs(line_lenses or {}) do + local num_line_lenses = #line_lenses + for j, lens in ipairs(line_lenses) do local text = lens.command and lens.command.title or 'Unresolved lens ...' table.insert(chunks, {text, 'LspCodeLens' }) + if j < num_line_lenses then + table.insert(chunks, {' | ', 'LspCodeLensSeparator' }) + end end if #chunks > 0 then api.nvim_buf_set_virtual_text(bufnr, ns, i, chunks, {}) diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index c67ea0c07a..1342df529f 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -1042,16 +1042,17 @@ function M.on_publish_diagnostics(_, _, params, client_id, _, config) end -- restores the extmarks set by M.display +--- @param last number last line that was changed -- @private -local function restore_extmarks(bufnr) - local lcount = api.nvim_buf_line_count(bufnr) +local function restore_extmarks(bufnr, last) for client_id, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do local ns = M._get_diagnostic_namespace(client_id) local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true}) local found = {} for _, extmark in ipairs(extmarks_current) do - -- HACK: the missing extmarks seem to still exist, but at the line after the last - if extmark[2] < lcount then + -- nvim_buf_set_lines will move any extmark to the line after the last + -- nvim_buf_set_text will move any extmark to the last line + if extmark[2] ~= last + 1 then found[extmark[1]] = true end end @@ -1076,8 +1077,8 @@ local function save_extmarks(bufnr, client_id) bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr if not diagnostic_attached_buffers[bufnr] then api.nvim_buf_attach(bufnr, false, { - on_lines = function() - restore_extmarks(bufnr) + on_lines = function(_, _, _, _, _, last) + restore_extmarks(bufnr, last - 1) end, on_detach = function() diagnostic_cache_extmarks[bufnr] = nil @@ -1210,7 +1211,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) table.insert(lines, prefix..message_lines[1]) table.insert(highlights, {#prefix, hiname}) for j = 2, #message_lines do - table.insert(lines, message_lines[j]) + table.insert(lines, string.rep(' ', #prefix) .. message_lines[j]) table.insert(highlights, {0, hiname}) end end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 41852b9d88..797ff762cb 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -18,10 +18,8 @@ local function err_message(...) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand -M['workspace/executeCommand'] = function(err, _) - if err then - error("Could not execute code action: "..err.message) - end +M['workspace/executeCommand'] = function() + -- Error handling is done implicitly by wrapping all handlers; see end of this file end -- @msg of type ProgressParams @@ -158,13 +156,12 @@ M['workspace/applyEdit'] = function(_, _, workspace_edit) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration -M['workspace/configuration'] = function(err, _, params, client_id) +M['workspace/configuration'] = function(_, _, params, client_id) local client = vim.lsp.get_client_by_id(client_id) if not client then err_message("LSP[id=", client_id, "] client has shut down after sending the message") return end - if err then error(vim.inspect(err)) end if not params.items then return {} end @@ -191,30 +188,33 @@ M['textDocument/codeLens'] = function(...) return require('vim.lsp.codelens').on_codelens(...) end ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references -M['textDocument/references'] = function(_, _, result) - if not result then return end - util.set_qflist(util.locations_to_items(result)) - api.nvim_command("copen") -end ---@private ---- Prints given list of symbols to the quickfix list. ---@param _ (not used) ---@param _ (not used) ---@param result (list of Symbols) LSP method name ---@param result (table) result of LSP method; a location or a list of locations. ----(`textDocument/definition` can return `Location` or `Location[]` -local symbol_handler = function(_, _, result, _, bufnr) - if not result or vim.tbl_isempty(result) then return end - util.set_qflist(util.symbols_to_items(result, bufnr)) - api.nvim_command("copen") +--@private +--- Return a function that converts LSP responses to quickfix items and opens the qflist +-- +--@param map_result function `((resp, bufnr) -> list)` to convert the response +--@param entity name of the resource used in a `not found` error message +local function response_to_qflist(map_result, entity) + return function(_, _, result, _, bufnr) + if not result or vim.tbl_isempty(result) then + vim.notify('No ' .. entity .. ' found') + else + util.set_qflist(map_result(result, bufnr)) + api.nvim_command("copen") + end + end end + + +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references +M['textDocument/references'] = response_to_qflist(util.locations_to_items, 'references') + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol -M['textDocument/documentSymbol'] = symbol_handler +M['textDocument/documentSymbol'] = response_to_qflist(util.symbols_to_items, 'document symbols') + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol -M['workspace/symbol'] = symbol_handler +M['workspace/symbol'] = response_to_qflist(util.symbols_to_items, 'symbols') --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename M['textDocument/rename'] = function(_, _, result) @@ -223,15 +223,15 @@ M['textDocument/rename'] = function(_, _, result) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting -M['textDocument/rangeFormatting'] = function(_, _, result) +M['textDocument/rangeFormatting'] = function(_, _, result, _, bufnr) if not result then return end - util.apply_text_edits(result) + util.apply_text_edits(result, bufnr) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting -M['textDocument/formatting'] = function(_, _, result) +M['textDocument/formatting'] = function(_, _, result, _, bufnr) if not result then return end - util.apply_text_edits(result) + util.apply_text_edits(result, bufnr) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -316,6 +316,7 @@ M['textDocument/typeDefinition'] = location_handler M['textDocument/implementation'] = location_handler --- |lsp-handler| for the method "textDocument/signatureHelp" +--- The active parameter is highlighted with |hl-LspSignatureActiveParameter| --- <pre> --- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( --- vim.lsp.handlers.signature_help, { @@ -328,23 +329,33 @@ M['textDocument/implementation'] = location_handler --- - border: (default=nil) --- - Add borders to the floating window --- - See |vim.api.nvim_open_win()| -function M.signature_help(_, method, result, _, bufnr, config) +function M.signature_help(_, method, result, client_id, bufnr, config) config = config or {} config.focus_id = method -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore if not (result and result.signatures and result.signatures[1]) then - print('No signature help available') + if config.silent ~= true then + print('No signature help available') + end return end + local client = vim.lsp.get_client_by_id(client_id) + local triggers = client.resolved_capabilities.signature_help_trigger_characters local ft = api.nvim_buf_get_option(bufnr, 'filetype') - local lines = util.convert_signature_help_to_markdown_lines(result, ft) + local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers) lines = util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then - print('No signature help available') + if config.silent ~= true then + print('No signature help available') + end return end - return util.open_floating_preview(lines, "markdown", config) + local fbuf, fwin = util.open_floating_preview(lines, "markdown", config) + if hl then + api.nvim_buf_add_highlight(fbuf, -1, "LspSignatureActiveParameter", 0, unpack(hl)) + end + return fbuf, fwin end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp @@ -436,7 +447,12 @@ for k, fn in pairs(M) do }) if err then - return err_message(tostring(err)) + -- LSP spec: + -- interface ResponseError: + -- code: integer; + -- message: string; + -- data?: string | number | boolean | array | object | null; + return err_message(tostring(err.code) .. ': ' .. err.message) end return fn(err, method, params, client_id, bufnr, config) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 471a311c16..73fafb9715 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -17,7 +17,7 @@ local current_log_level = log.levels.WARN local log_date_format = "%FT%H:%M:%S%z" do - local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/" + local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/" --@private local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 7e43eb84de..6d02b9ba74 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -691,10 +691,11 @@ function protocol.make_client_capabilities() signatureHelp = { dynamicRegistration = false; signatureInformation = { + activeParameterSupport = true; documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText }; - -- parameterInformation = { - -- labelOffsetSupport = false; - -- }; + parameterInformation = { + labelOffsetSupport = true; + }; }; }; references = { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 06afc2c5e2..f047a12dff 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -845,9 +845,10 @@ end --- --@param signature_help Response of `textDocument/SignatureHelp` --@param ft optional filetype that will be use as the `lang` for the label markdown code block +--@param triggers optional list of trigger characters from the lsp server. used to better determine parameter offsets --@returns list of lines of converted markdown. --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp -function M.convert_signature_help_to_markdown_lines(signature_help, ft) +function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers) if not signature_help.signatures then return end @@ -856,6 +857,7 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft) --=== 0`. Whenever possible implementors should make an active decision about --the active signature and shouldn't rely on a default value. local contents = {} + local active_hl local active_signature = signature_help.activeSignature or 0 -- If the activeSignature is not inside the valid range, then clip it. if active_signature >= #signature_help.signatures then @@ -875,11 +877,17 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft) M.convert_input_to_markdown_lines(signature.documentation, contents) end if signature.parameters and #signature.parameters > 0 then - local active_parameter = signature_help.activeParameter or 0 - -- If the activeParameter is not inside the valid range, then clip it. + local active_parameter = (signature.activeParameter or signature_help.activeParameter or 0) + if active_parameter < 0 + then active_parameter = 0 + end + + -- If the activeParameter is > #parameters, then set it to the last + -- NOTE: this is not fully according to the spec, but a client-side interpretation if active_parameter >= #signature.parameters then - active_parameter = 0 + active_parameter = #signature.parameters - 1 end + local parameter = signature.parameters[active_parameter + 1] if parameter then --[=[ @@ -900,13 +908,35 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft) documentation?: string | MarkupContent; } --]=] - -- TODO highlight parameter + if parameter.label then + if type(parameter.label) == "table" then + active_hl = parameter.label + else + local offset = 1 + -- try to set the initial offset to the first found trigger character + for _, t in ipairs(triggers or {}) do + local trigger_offset = signature.label:find(t, 1, true) + if trigger_offset and (offset == 1 or trigger_offset < offset) then + offset = trigger_offset + end + end + for p, param in pairs(signature.parameters) do + offset = signature.label:find(param.label, offset, true) + if not offset then break end + if p == active_parameter + 1 then + active_hl = {offset - 1, offset + #parameter.label - 1} + break + end + offset = offset + #param.label + 1 + end + end + end if parameter.documentation then M.convert_input_to_markdown_lines(parameter.documentation, contents) end end end - return contents + return contents, active_hl end --- Creates a table with sensible default options for a floating window. The |