diff options
Diffstat (limited to 'runtime/lua/vim/lsp')
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 111 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 45 |
5 files changed, 144 insertions, 28 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 31116985e2..341a3e82fc 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -111,6 +111,39 @@ function M.completion(context) return request('textDocument/completion', params) end +--@private +--- If there is more than one client that supports the given method, +--- asks the user to select one. +-- +--@returns The client that the user selected or nil +local function select_client(method) + local clients = vim.tbl_values(vim.lsp.buf_get_clients()); + clients = vim.tbl_filter(function (client) + return client.supports_method(method) + end, clients) + -- better UX when choices are always in the same order (between restarts) + table.sort(clients, function (a, b) return a.name < b.name end) + + if #clients > 1 then + local choices = {} + for k,v in ipairs(clients) do + table.insert(choices, string.format("%d %s", k, v.name)) + end + local user_choice = vim.fn.confirm( + "Select a language server:", + table.concat(choices, "\n"), + 0, + "Question" + ) + if user_choice == 0 then return nil end + return clients[user_choice] + elseif #clients < 1 then + return nil + else + return clients[1] + end +end + --- Formats the current buffer. --- --@param options (optional, table) Can be used to specify FormattingOptions. @@ -119,8 +152,11 @@ end -- --@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting function M.formatting(options) + local client = select_client("textDocument/formatting") + if client == nil then return end + local params = util.make_formatting_params(options) - return request('textDocument/formatting', params) + return client.request("textDocument/formatting", params) end --- Performs |vim.lsp.buf.formatting()| synchronously. @@ -134,14 +170,62 @@ end --- --@param options Table with valid `FormattingOptions` entries --@param timeout_ms (number) Request timeout +--@see |vim.lsp.buf.formatting_seq_sync| function M.formatting_sync(options, timeout_ms) + local client = select_client("textDocument/formatting") + if client == nil then return end + local params = util.make_formatting_params(options) - local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms) - if not result or vim.tbl_isempty(result) then return end - local _, formatting_result = next(result) - result = formatting_result.result - if not result then return end - vim.lsp.util.apply_text_edits(result) + local result, err = client.request_sync("textDocument/formatting", params, timeout_ms) + if result and result.result then + util.apply_text_edits(result.result) + elseif err then + vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN) + end +end + +--- Formats the current buffer by sequentially requesting formatting from attached clients. +--- +--- Useful when multiple clients with formatting capability are attached. +--- +--- Since it's synchronous, can be used for running on save, to make sure buffer is formatted +--- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. +--- Example: +--- <pre> +--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]] +--- </pre> +--- +--@param options (optional, table) `FormattingOptions` entries +--@param timeout_ms (optional, number) Request timeout +--@param order (optional, table) List of client names. Formatting is requested from clients +---in the following order: first all clients that are not in the `order` list, then +---the remaining clients in the order as they occur in the `order` list. +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 + -- if the client exists, move to the end of the list + for i, client in ipairs(clients) do + if client.name == client_name then + table.insert(clients, table.remove(clients, i)) + break + end + end + end + + -- loop through the clients and make synchronous formatting requests + for _, client in ipairs(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) + if result and result.result then + util.apply_text_edits(result.result) + elseif err then + vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN) + end + end + end end --- Formats a given range. @@ -152,15 +236,12 @@ end --@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) - validate { options = {options, 't', true} } - local sts = vim.bo.softtabstop; - options = vim.tbl_extend('keep', options or {}, { - tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; - insertSpaces = vim.bo.expandtab; - }) + local client = select_client("textDocument/rangeFormatting") + if client == nil then return end + local params = util.make_given_range_params(start_pos, end_pos) - params.options = options - return request('textDocument/rangeFormatting', params) + params.options = util.make_formatting_params(options).options + return client.request("textDocument/rangeFormatting", params) end --- Renames all references to the symbol under the cursor. diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index e6132e78bf..6f2f846a3b 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -406,9 +406,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) end - if opts.severity_sort then - table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end) - end + table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end) return line_diagnostics end @@ -997,6 +995,8 @@ end --- - See |vim.lsp.diagnostic.set_signs()| --- - update_in_insert: (default=false) --- - Update diagnostics in InsertMode or wait until InsertLeave +--- - severity_sort: (default=false) +--- - Sort diagnostics (and thus signs and virtual text) function M.on_publish_diagnostics(_, _, params, client_id, _, config) local uri = params.uri local bufnr = vim.uri_to_bufnr(uri) @@ -1007,6 +1007,10 @@ function M.on_publish_diagnostics(_, _, params, client_id, _, config) local diagnostics = params.diagnostics + if config and if_nil(config.severity_sort, false) then + table.sort(diagnostics, function(a, b) return a.severity > b.severity end) + end + -- Always save the diagnostics, even if the buf is not loaded. -- Language servers may report compile or build errors via diagnostics -- Users should be able to find these, even if they're in files which @@ -1034,6 +1038,7 @@ function M.display(diagnostics, bufnr, client_id, config) underline = true, virtual_text = true, update_in_insert = false, + severity_sort = false, }, config) -- TODO(tjdevries): Consider how we can make this a "standardized" kind of thing for |lsp-handlers|. @@ -1116,7 +1121,6 @@ end ---@return table {popup_bufnr, win_id} function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) opts = opts or {} - opts.severity_sort = if_nil(opts.severity_sort, true) local show_header = if_nil(opts.show_header, true) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 331e980e67..471a311c16 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -10,7 +10,7 @@ local log = {} -- Can be used to lookup the number from the name or the name from the number. -- Levels by name: 'trace', 'debug', 'info', 'warn', 'error' -- Level numbers begin with 'trace' at 0 -log.levels = vim.log.levels +log.levels = vim.deepcopy(vim.log.levels) -- Default log level is warn. local current_log_level = log.levels.WARN diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1aa8326514..0cabd1a0d4 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -518,7 +518,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) send_response(decoded.id, err, result) end) -- This works because we are expecting vim.NIL here - elseif decoded.id and (decoded.result or decoded.error) then + elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then -- Server Result decoded.error = convert_NIL(decoded.error) decoded.result = convert_NIL(decoded.result) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 71ec85381b..ce8468aa8a 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -914,6 +914,23 @@ function M.make_floating_popup_options(width, height, opts) } end +local function _should_add_to_tagstack(new_item) + local stack = vim.fn.gettagstack() + + -- Check if we're at the bottom of the tagstack. + if stack.curidx <= 1 then return true end + + local top_item = stack.items[stack.curidx-1] + + -- Check if the item at the top of the tagstack is exactly the + -- same as the one we want to push. + if top_item.tagname ~= new_item.tagname then return true end + for i, v in ipairs(top_item.from) do + if v ~= new_item.from[i] then return true end + end + return false +end + --- Jumps to a location. --- --@param location (`Location`|`LocationLink`) @@ -922,22 +939,36 @@ function M.jump_to_location(location) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end - local bufnr = vim.uri_to_bufnr(uri) - -- Save position in jumplist - vim.cmd "normal! m'" - -- Push a new item into tagstack - local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0} - local items = {{tagname=vim.fn.expand('<cword>'), from=from}} - vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't') + local from_bufnr = vim.fn.bufnr('%') + local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0} + local item = {tagname=vim.fn.expand('<cword>'), from=from} + + -- Save position in jumplist + vim.cmd("mark '") --- Jump to new location (adjusting for UTF-16 encoding of characters) + local bufnr = vim.uri_to_bufnr(uri) api.nvim_set_current_buf(bufnr) api.nvim_buf_set_option(0, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line local col = get_line_byte_from_position(0, range.start) + -- This prevents the tagstack to be filled with items that provide + -- no motion when CTRL-T is pressed because they're both the source + -- and the destination. + local motionless = + bufnr == from_bufnr and + row+1 == from[2] and col+1 == from[3] + if not motionless and _should_add_to_tagstack(item) then + local winid = vim.fn.win_getid() + local items = {item} + vim.fn.settagstack(winid, {items=items}, 't') + end + + -- Jump to new location api.nvim_win_set_cursor(0, {row + 1, col}) + return true end |