diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/diagnostic.lua | 104 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 112 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 5 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/uri.lua | 11 |
5 files changed, 170 insertions, 63 deletions
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index c7c8c1878e..326932d982 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -27,7 +27,10 @@ local global_diagnostic_options = { ---@private local function to_severity(severity) - return type(severity) == 'string' and M.severity[string.upper(severity)] or severity + if type(severity) == 'string' then + return assert(M.severity[string.upper(severity)], string.format("Invalid severity: %s", severity)) + end + return severity end ---@private @@ -90,28 +93,6 @@ local function reformat_diagnostics(format, diagnostics) return formatted end ----@private -local function resolve_optional_value(option, namespace, bufnr) - local enabled_val = {} - - if not option then - return false - elseif option == true then - return enabled_val - elseif type(option) == 'function' then - local val = option(namespace, bufnr) - if val == true then - return enabled_val - else - return val - end - elseif type(option) == 'table' then - return option - else - error("Unexpected option type: " .. vim.inspect(option)) - end -end - local all_namespaces = {} ---@private @@ -125,9 +106,7 @@ local function get_namespace(ns) end end - if not name then - return vim.notify("namespace does not exist or is anonymous", vim.log.levels.ERROR) - end + assert(name, "namespace does not exist or is anonymous") all_namespaces[ns] = { name = name, @@ -139,12 +118,46 @@ local function get_namespace(ns) end ---@private +local function enabled_value(option, namespace) + local ns = get_namespace(namespace) + if type(ns.opts[option]) == "table" then + return ns.opts[option] + end + + if type(global_diagnostic_options[option]) == "table" then + return global_diagnostic_options[option] + end + + return {} +end + +---@private +local function resolve_optional_value(option, value, namespace, bufnr) + if not value then + return false + elseif value == true then + return enabled_value(option, namespace) + elseif type(value) == 'function' then + local val = value(namespace, bufnr) + if val == true then + return enabled_value(option, namespace) + else + return val + end + elseif type(value) == 'table' then + return value + else + error("Unexpected option type: " .. vim.inspect(value)) + end +end + +---@private local function get_resolved_options(opts, namespace, bufnr) local ns = get_namespace(namespace) local resolved = vim.tbl_extend('keep', opts or {}, ns.opts, global_diagnostic_options) for k in pairs(global_diagnostic_options) do if resolved[k] ~= nil then - resolved[k] = resolve_optional_value(resolved[k], namespace, bufnr) + resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr) end end return resolved @@ -398,6 +411,17 @@ local function show_diagnostics(opts, diagnostics) diagnostics = prefix_source(opts.source, diagnostics) end + -- Use global setting for severity_sort since 'show_diagnostics' is namespace + -- independent + local severity_sort = global_diagnostic_options.severity_sort + if severity_sort then + if type(severity_sort) == "table" and severity_sort.reverse then + table.sort(diagnostics, function(a, b) return a.severity > b.severity end) + else + table.sort(diagnostics, function(a, b) return a.severity < b.severity end) + end + end + for i, diagnostic in ipairs(diagnostics) do local prefix = string.format("%d. ", i) local hiname = floating_highlight_map[diagnostic.severity] @@ -510,6 +534,9 @@ local function diagnostic_move_pos(opts, pos) return end + -- Save position in the window's jumplist + vim.api.nvim_win_call(win_id, function() vim.cmd("normal! m'") end) + vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) if enable_popup then @@ -527,10 +554,27 @@ end --- Configure diagnostic options globally or for a specific diagnostic --- namespace. --- +--- Configuration can be specified globally, per-namespace, or ephemerally +--- (i.e. only for a single call to |vim.diagnostic.set()| or +--- |vim.diagnostic.show()|). Ephemeral configuration has highest priority, +--- followed by namespace configuration, and finally global configuration. +--- +--- For example, if a user enables virtual text globally with +--- <pre> +--- vim.diagnostic.config({virt_text = true}) +--- </pre> +--- +--- and a diagnostic producer sets diagnostics with +--- <pre> +--- vim.diagnostic.set(ns, 0, diagnostics, {virt_text = false}) +--- </pre> +--- +--- then virtual text will not be enabled for those diagnostics. +--- ---@note Each of the configuration options below accepts one of the following: --- - `false`: Disable this feature --- - `true`: Enable this feature, use default settings. ---- - `table`: Enable this feature with overrides. +--- - `table`: Enable this feature with overrides. Use an empty table to use default values. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above. --- ---@param opts table Configuration table with the following keys: @@ -638,8 +682,6 @@ function M.set(namespace, bufnr, diagnostics, opts) if vim.api.nvim_buf_is_loaded(bufnr) then M.show(namespace, bufnr, diagnostics, opts) - elseif opts then - M.config(opts, namespace) end vim.api.nvim_command("doautocmd <nomodeline> User DiagnosticsChanged") @@ -1140,7 +1182,6 @@ function M.show_position_diagnostics(opts, bufnr, position) local diagnostics = M.get(bufnr, opts) clamp_line_numbers(bufnr, diagnostics) local position_diagnostics = vim.tbl_filter(match_position_predicate, diagnostics) - table.sort(position_diagnostics, function(a, b) return a.severity < b.severity end) return show_diagnostics(opts, position_diagnostics) end @@ -1263,6 +1304,7 @@ end --- <pre> --- WARNING filename:27:3: Variable 'foo' does not exist --- </pre> +--- --- This can be parsed into a diagnostic |diagnostic-structure| --- with: --- <pre> diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c7a88a0993..9c35351608 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -41,6 +41,7 @@ lsp._request_name_to_capability = { ['textDocument/documentSymbol'] = 'document_symbol'; ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; ['textDocument/rename'] = 'rename'; + ['textDocument/prepareRename'] = 'rename'; ['textDocument/codeAction'] = 'code_action'; ['textDocument/codeLens'] = 'code_lens'; ['codeLens/resolve'] = 'code_lens_resolve'; @@ -85,7 +86,7 @@ end function lsp._unsupported_method(method) local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method) log.warn(msg) - return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg) + return msg end ---@private @@ -130,15 +131,28 @@ local all_client_active_buffers = {} ---@param bufnr (Number) of buffer ---@param fn (function({client}, {client_id}, {bufnr}) Function to run on ---each client attached to that buffer. -local function for_each_buffer_client(bufnr, fn) +---@param restrict_client_ids table list of client ids on which to restrict function application. +local function for_each_buffer_client(bufnr, fn, restrict_client_ids) validate { fn = { fn, 'f' }; + restrict_client_ids = { restrict_client_ids, 't' , true}; } bufnr = resolve_bufnr(bufnr) local client_ids = all_buffer_active_clients[bufnr] if not client_ids or tbl_isempty(client_ids) then return end + + if restrict_client_ids and #restrict_client_ids > 0 then + local filtered_client_ids = {} + for client_id in pairs(client_ids) do + if vim.tbl_contains(restrict_client_ids, client_id) then + filtered_client_ids[client_id] = true + end + end + client_ids = filtered_client_ids + end + for client_id in pairs(client_ids) do local client = active_clients[client_id] if client then @@ -1254,33 +1268,33 @@ function lsp.buf_request(bufnr, method, params, handler) method = { method, 's' }; handler = { handler, 'f', true }; } - local client_request_ids = {} + local supported_clients = {} local method_supported = false - for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) + for_each_buffer_client(bufnr, function(client, client_id) if client.supports_method(method) then method_supported = true - local request_success, request_id = client.request(method, params, handler, resolved_bufnr) - - -- This could only fail if the client shut down in the time since we looked - -- it up and we did the request, which should be rare. - if request_success then - client_request_ids[client_id] = request_id - end + table.insert(supported_clients, client_id) end end) - -- if has client but no clients support the given method, call the callback with the proper - -- error message. + -- if has client but no clients support the given method, notify the user if not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported then - local unsupported_err = lsp._unsupported_method(method) - handler = handler or lsp.handlers[method] - if handler then - handler(unsupported_err, nil, {method=method, bufnr=bufnr}) - end + vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR) + vim.api.nvim_command("redraw") return end + local client_request_ids = {} + for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) + local request_success, request_id = client.request(method, params, handler, resolved_bufnr) + -- This could only fail if the client shut down in the time since we looked + -- it up and we did the request, which should be rare. + if request_success then + client_request_ids[client_id] = request_id + end + end, supported_clients) + local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do local client = active_clients[client_id] @@ -1308,12 +1322,13 @@ function lsp.buf_request_all(bufnr, method, params, callback) local request_results = {} local result_count = 0 local expected_result_count = 0 - local cancel, client_request_ids - local set_expected_result_count = once(function() - for _ in pairs(client_request_ids) do - expected_result_count = expected_result_count + 1 - end + local set_expected_result_count = once(function () + for_each_buffer_client(bufnr, function(client) + if client.supports_method(method) then + expected_result_count = expected_result_count + 1 + end + end) end) local function _sync_handler(err, result, ctx) @@ -1326,7 +1341,7 @@ function lsp.buf_request_all(bufnr, method, params, callback) end end - client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) + local _, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) return cancel end @@ -1383,6 +1398,29 @@ function lsp.buf_notify(bufnr, method, params) return resp end + +---@private +local function adjust_start_col(lnum, line, items, encoding) + local min_start_char = nil + for _, item in pairs(items) do + if item.textEdit and item.textEdit.range.start.line == lnum - 1 then + if min_start_char and min_start_char ~= item.textEdit.range.start.character then + return nil + end + min_start_char = item.textEdit.range.start.character + end + end + if min_start_char then + if encoding == 'utf-8' then + return min_start_char + else + return vim.str_byteindex(line, min_start_char, encoding == 'utf-16') + end + else + return nil + end +end + --- Implements 'omnifunc' compatible LSP completion. --- ---@see |complete-functions| @@ -1418,17 +1456,37 @@ function lsp.omnifunc(findstart, base) -- Get the start position of the current keyword local textMatch = vim.fn.match(line_to_cursor, '\\k*$') - local prefix = line_to_cursor:sub(textMatch+1) local params = util.make_position_params() local items = {} - lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result) + lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx) if err or not result or vim.fn.mode() ~= "i" then return end + + -- Completion response items may be relative to a position different than `textMatch`. + -- Concrete example, with sumneko/lua-language-server: + -- + -- require('plenary.asy| + -- ▲ ▲ ▲ + -- │ │ └── cursor_pos: 20 + -- │ └────── textMatch: 17 + -- └────────────── textEdit.range.start.character: 9 + -- .newText = 'plenary.async' + -- ^^^ + -- prefix (We'd remove everything not starting with `asy`, + -- so we'd eliminate the `plenary.async` result + -- + -- `adjust_start_col` is used to prefer the language server boundary. + -- + local client = lsp.get_client_by_id(ctx.client_id) + local encoding = client and client.offset_encoding or 'utf-16' + local candidates = util.extract_completion_items(result) + local startbyte = adjust_start_col(pos[1], line, candidates, encoding) or textMatch + local prefix = line:sub(startbyte + 1, pos[2]) local matches = util.text_document_completion_list_to_complete_items(result, prefix) -- TODO(ashkan): is this the best way to do this? vim.list_extend(items, matches) - vim.fn.complete(textMatch+1, items) + vim.fn.complete(startbyte + 1, items) end) -- Return -2 to signal that we should continue completion so that we can diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index b1bdb24def..d9a684a738 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -466,15 +466,12 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) -- We sent a number, so we expect a number. local result_id = tonumber(decoded.id) - -- Do not surface RequestCancelled or ContentModified to users, it is RPC-internal. + -- Do not surface RequestCancelled to users, it is RPC-internal. if decoded.error then local mute_error = false if decoded.error.code == protocol.ErrorCodes.RequestCancelled then local _ = log.debug() and log.debug("Received cancellation ack", decoded) mute_error = true - elseif decoded.error.code == protocol.ErrorCodes.ContentModified then - local _ = log.debug() and log.debug("Received content modified ack", decoded) - mute_error = true end if mute_error then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index fca956fb57..3751f94caf 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -193,6 +193,7 @@ function M.get_progress_messages() title = ctx.title or "empty title", message = ctx.message, percentage = ctx.percentage, + done = ctx.done, progress = true, } table.insert(new_messages, new_report) diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index a3e79a0f2b..5d8d4fa169 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -75,13 +75,22 @@ local function uri_from_fname(path) end local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):.*' +local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):[a-zA-Z]:.*' --- Get a URI from a bufnr ---@param bufnr (number): Buffer number ---@return URI local function uri_from_bufnr(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) - local scheme = fname:match(URI_SCHEME_PATTERN) + local volume_path = fname:match("^([a-zA-Z]:).*") + local is_windows = volume_path ~= nil + local scheme + if is_windows then + fname = fname:gsub("\\", "/") + scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN) + else + scheme = fname:match(URI_SCHEME_PATTERN) + end if scheme then return fname else |