aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp.lua')
-rw-r--r--runtime/lua/vim/lsp.lua172
1 files changed, 123 insertions, 49 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 90c5872f11..9a008ac965 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
@@ -121,24 +122,34 @@ local active_clients = {}
local all_buffer_active_clients = {}
local uninitialized_clients = {}
--- Tracks all buffers attached to a client.
-local all_client_active_buffers = {}
-
---@private
--- Invokes a function for each LSP client attached to the buffer {bufnr}.
---
---@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
@@ -728,7 +739,6 @@ function lsp.start_client(config)
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
changetracking.reset(client_id)
- all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
end
@@ -757,6 +767,7 @@ function lsp.start_client(config)
rpc = rpc;
offset_encoding = offset_encoding;
config = config;
+ attached_buffers = {};
handlers = handlers;
-- for $/progress report
@@ -830,7 +841,7 @@ function lsp.start_client(config)
rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err))
assert(result, "server sent empty result")
- rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary})
+ rpc.notify('initialized', vim.empty_dict())
client.initialized = true
uninitialized_clients[client_id] = nil
client.workspaceFolders = initialize_params.workspaceFolders
@@ -896,7 +907,7 @@ function lsp.start_client(config)
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
return rpc.request(method, params, function(err, result)
- handler(err, result, {method=method, client_id=client_id, bufnr=bufnr})
+ handler(err, result, {method=method, client_id=client_id, bufnr=bufnr, params=params})
end)
end
@@ -975,7 +986,6 @@ function lsp.start_client(config)
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
changetracking.reset(client_id)
- all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
end
@@ -1018,6 +1028,7 @@ function lsp.start_client(config)
-- TODO(ashkan) handle errors.
pcall(config.on_attach, client, bufnr)
end
+ client.attached_buffers[bufnr] = true
end
initialize()
@@ -1128,12 +1139,6 @@ function lsp.buf_attach_client(bufnr, client_id)
})
end
- if not all_client_active_buffers[client_id] then
- all_client_active_buffers[client_id] = {}
- end
-
- table.insert(all_client_active_buffers[client_id], bufnr)
-
if buffer_client_ids[client_id] then return end
-- This is our first time attaching this client to this buffer.
buffer_client_ids[client_id] = true
@@ -1158,7 +1163,7 @@ end
--- Gets a client by id, or nil if the id is invalid.
--- The returned client may not yet be fully initialized.
--
----@param client_id client id number
+---@param client_id number client id
---
---@returns |vim.lsp.client| object, or nil
function lsp.get_client_by_id(client_id)
@@ -1167,15 +1172,11 @@ end
--- Returns list of buffers attached to client_id.
--
----@param client_id client id
+---@param client_id number client id
---@returns list of buffer ids
function lsp.get_buffers_by_client_id(client_id)
- local active_client_buffers = all_client_active_buffers[client_id]
- if active_client_buffers then
- return active_client_buffers
- else
- return {}
- end
+ local client = lsp.get_client_by_id(client_id)
+ return client and vim.tbl_keys(client.attached_buffers) or {}
end
--- Stops a client(s).
@@ -1254,33 +1255,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 +1309,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 +1328,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 +1385,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|
@@ -1392,7 +1417,7 @@ end
---@param findstart 0 or 1, decides behavior
---@param base If findstart=0, text to match against
---
----@returns (number) Decided by `findstart`:
+---@returns (number) Decided by {findstart}:
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
@@ -1418,17 +1443,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
@@ -1534,5 +1579,34 @@ function lsp._with_extend(name, options, user_config)
return resulting_config
end
+
+--- Registry for client side commands.
+--- This is an extension point for plugins to handle custom commands which are
+--- not part of the core language server protocol specification.
+---
+--- The registry is a table where the key is a unique command name,
+--- and the value is a function which is called if any LSP action
+--- (code action, code lenses, ...) triggers the command.
+---
+--- If a LSP response contains a command for which no matching entry is
+--- available in this registry, the command will be executed via the LSP server
+--- using `workspace/executeCommand`.
+---
+--- The first argument to the function will be the `Command`:
+-- Command
+-- title: String
+-- command: String
+-- arguments?: any[]
+--
+--- The second argument is the `ctx` of |lsp-handler|
+lsp.commands = setmetatable({}, {
+ __newindex = function(tbl, key, value)
+ assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string")
+ assert(type(value) == 'function', "Command added to `vim.lsp.commands` must be a function")
+ rawset(tbl, key, value)
+ end;
+})
+
+
return lsp
-- vim:sw=2 ts=2 et