aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:31 +0000
commit9243becbedbb6a1592208051f8fa2b090dcc5e7d (patch)
tree607c2a862ec3f4399b8766383f6f8e04c4aa43b4 /runtime/lua/vim/lsp
parent9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 (diff)
parent3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff)
downloadrneovim-usermarks.tar.gz
rneovim-usermarks.tar.bz2
rneovim-usermarks.zip
Merge remote-tracking branch 'upstream/master' into usermarksusermarks
Diffstat (limited to 'runtime/lua/vim/lsp')
-rw-r--r--runtime/lua/vim/lsp/buf.lua316
-rw-r--r--runtime/lua/vim/lsp/codelens.lua27
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua2
-rw-r--r--runtime/lua/vim/lsp/handlers.lua145
-rw-r--r--runtime/lua/vim/lsp/health.lua16
-rw-r--r--runtime/lua/vim/lsp/log.lua17
-rw-r--r--runtime/lua/vim/lsp/protocol.lua97
-rw-r--r--runtime/lua/vim/lsp/rpc.lua741
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua702
-rw-r--r--runtime/lua/vim/lsp/sync.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua207
11 files changed, 1602 insertions, 670 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 6a070928d9..6ac885c78f 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -1,4 +1,3 @@
-local vim = vim
local api = vim.api
local validate = vim.validate
local util = require('vim.lsp.util')
@@ -111,7 +110,7 @@ end
--- about the context in which a completion was triggered (how it was triggered,
--- and by which trigger character, if applicable)
---
----@see |vim.lsp.protocol.constants.CompletionTriggerKind|
+---@see vim.lsp.protocol.constants.CompletionTriggerKind
function M.completion(context)
local params = util.make_position_params()
params.context = context
@@ -119,35 +118,30 @@ function M.completion(context)
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, on_choice)
- validate({
- on_choice = { on_choice, 'function', false },
- })
- 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
- vim.ui.select(clients, {
- prompt = 'Select a language server:',
- format_item = function(client)
- return client.name
- end,
- }, on_choice)
- elseif #clients < 1 then
- on_choice(nil)
- else
- on_choice(clients[1])
- end
+---@return table {start={row, col}, end={row, col}} using (1, 0) indexing
+local function range_from_selection()
+ -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896
+
+ -- [bufnum, lnum, col, off]; both row and column 1-indexed
+ local start = vim.fn.getpos('v')
+ local end_ = vim.fn.getpos('.')
+ local start_row = start[2]
+ local start_col = start[3]
+ local end_row = end_[2]
+ local end_col = end_[3]
+
+ -- A user can start visual selection at the end and move backwards
+ -- Normalize the range to start < end
+ if start_row == end_row and end_col < start_col then
+ end_col, start_col = start_col, end_col
+ elseif end_row < start_row then
+ start_row, end_row = end_row, start_row
+ start_col, end_col = end_col, start_col
+ end
+ return {
+ ['start'] = { start_row, start_col - 1 },
+ ['end'] = { end_row, end_col - 1 },
+ }
end
--- Formats a buffer using the attached (and optionally filtered) language
@@ -168,11 +162,11 @@ end
--- Predicate used to filter clients. Receives a client as argument and must return a
--- boolean. Clients matching the predicate are included. Example:
---
---- <pre>
---- -- Never request typescript-language-server for formatting
---- vim.lsp.buf.format {
---- filter = function(client) return client.name ~= "tsserver" end
---- }
+--- <pre>lua
+--- -- Never request typescript-language-server for formatting
+--- vim.lsp.buf.format {
+--- filter = function(client) return client.name ~= "tsserver" end
+--- }
--- </pre>
---
--- - async boolean|nil
@@ -184,7 +178,12 @@ end
--- Restrict formatting to the client with ID (client.id) matching this field.
--- - name (string|nil):
--- Restrict formatting to the client with name (client.name) matching this field.
-
+---
+--- - range (table|nil) Range to format.
+--- Table must contain `start` and `end` keys with {row, col} tuples using
+--- (1,0) indexing.
+--- Defaults to current selection in visual mode
+--- Defaults to `nil` in other modes, formatting the full buffer
function M.format(options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
@@ -198,24 +197,40 @@ function M.format(options)
clients = vim.tbl_filter(options.filter, clients)
end
+ local mode = api.nvim_get_mode().mode
+ local range = options.range
+ if not range and mode == 'v' or mode == 'V' then
+ range = range_from_selection()
+ end
+ local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting'
+
clients = vim.tbl_filter(function(client)
- return client.supports_method('textDocument/formatting')
+ return client.supports_method(method)
end, clients)
if #clients == 0 then
vim.notify('[LSP] Format request failed, no matching language servers.')
end
+ ---@private
+ local function set_range(client, params)
+ if range then
+ local range_params =
+ util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
+ params.range = range_params.range
+ end
+ return params
+ end
+
if options.async then
local do_format
do_format = function(idx, client)
if not client then
return
end
- local params = util.make_formatting_params(options.formatting_options)
- client.request('textDocument/formatting', params, function(...)
- local handler = client.handlers['textDocument/formatting']
- or vim.lsp.handlers['textDocument/formatting']
+ local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ client.request(method, params, function(...)
+ local handler = client.handlers[method] or vim.lsp.handlers[method]
handler(...)
do_format(next(clients, idx))
end, bufnr)
@@ -224,8 +239,8 @@ function M.format(options)
else
local timeout_ms = options.timeout_ms or 1000
for _, client in pairs(clients) do
- local params = util.make_formatting_params(options.formatting_options)
- local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr)
+ local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ local result, err = client.request_sync(method, params, timeout_ms, bufnr)
if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
@@ -235,138 +250,6 @@ function M.format(options)
end
end
---- Formats the current buffer.
----
----@param options (table|nil) Can be used to specify FormattingOptions.
---- Some unspecified options will be automatically derived from the current
---- Neovim options.
---
----@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
-function M.formatting(options)
- vim.notify_once(
- 'vim.lsp.buf.formatting is deprecated. Use vim.lsp.buf.format { async = true } instead',
- vim.log.levels.WARN
- )
- local params = util.make_formatting_params(options)
- local bufnr = api.nvim_get_current_buf()
- select_client('textDocument/formatting', function(client)
- if client == nil then
- return
- end
-
- return client.request('textDocument/formatting', params, nil, bufnr)
- end)
-end
-
---- Performs |vim.lsp.buf.formatting()| synchronously.
----
---- Useful for running on save, to make sure buffer is formatted prior to being
---- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example:
----
---- <pre>
---- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()
---- </pre>
----
----@param options table|nil with valid `FormattingOptions` entries
----@param timeout_ms (number) Request timeout
----@see |vim.lsp.buf.formatting_seq_sync|
-function M.formatting_sync(options, timeout_ms)
- vim.notify_once(
- 'vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead',
- vim.log.levels.WARN
- )
- local params = util.make_formatting_params(options)
- local bufnr = api.nvim_get_current_buf()
- select_client('textDocument/formatting', function(client)
- if client == nil then
- return
- end
-
- local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr)
- if result and result.result then
- util.apply_text_edits(result.result, bufnr, client.offset_encoding)
- elseif err then
- vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN)
- end
- 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 (table|nil) `FormattingOptions` entries
----@param timeout_ms (number|nil) Request timeout
----@param order (table|nil) 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)
- vim.notify_once(
- 'vim.lsp.buf.formatting_seq_sync is deprecated. Use vim.lsp.buf.format instead',
- vim.log.levels.WARN
- )
- local clients = vim.tbl_values(vim.lsp.buf_get_clients())
- local bufnr = api.nvim_get_current_buf()
-
- -- sort the clients according to `order`
- for _, client_name in pairs(order or {}) do
- -- if the client exists, move to the end of the list
- for i, client in pairs(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 pairs(clients) do
- if vim.tbl_get(client.server_capabilities, 'documentFormattingProvider') then
- local params = util.make_formatting_params(options)
- local result, err = client.request_sync(
- 'textDocument/formatting',
- params,
- timeout_ms,
- api.nvim_get_current_buf()
- )
- if result and result.result then
- util.apply_text_edits(result.result, bufnr, client.offset_encoding)
- 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.
----
----@param options Table with valid `FormattingOptions` entries.
----@param start_pos ({number, number}, optional) mark-indexed position.
----Defaults to the start of the last visual selection.
----@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)
- local params = util.make_given_range_params(start_pos, end_pos)
- params.options = util.make_formatting_params(options).options
- select_client('textDocument/rangeFormatting', function(client)
- if client == nil then
- return
- end
-
- return client.request('textDocument/rangeFormatting', params)
- end)
-end
-
--- Renames all references to the symbol under the cursor.
---
---@param new_name string|nil If not provided, the user will be prompted for a new
@@ -500,7 +383,7 @@ end
--- Lists all the references to the symbol under the cursor in the quickfix window.
---
----@param context (table) Context for the request
+---@param context (table|nil) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
---@param options table|nil additional options
--- - on_list: (function) handler for list results. See |lsp-on-list-handler|
@@ -565,14 +448,14 @@ end
--- Lists all the call sites of the symbol under the cursor in the
--- |quickfix| window. If the symbol can resolve to multiple
---- items, the user can pick one in the |inputlist|.
+--- items, the user can pick one in the |inputlist()|.
function M.incoming_calls()
call_hierarchy('callHierarchy/incomingCalls')
end
--- Lists all the items that are called by the symbol under the
--- cursor in the |quickfix| window. If the symbol can resolve to
---- multiple items, the user can pick one in the |inputlist|.
+--- multiple items, the user can pick one in the |inputlist()|.
function M.outgoing_calls()
call_hierarchy('callHierarchy/outgoingCalls')
end
@@ -581,7 +464,7 @@ end
---
function M.list_workspace_folders()
local workspace_folders = {}
- for _, client in pairs(vim.lsp.buf_get_clients()) do
+ for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
for _, folder in pairs(client.workspace_folders or {}) do
table.insert(workspace_folders, folder.name)
end
@@ -604,9 +487,9 @@ function M.add_workspace_folder(workspace_folder)
end
local params = util.make_workspace_params(
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } },
- { {} }
+ {}
)
- for _, client in pairs(vim.lsp.buf_get_clients()) do
+ for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
local found = false
for _, folder in pairs(client.workspace_folders or {}) do
if folder.name == workspace_folder then
@@ -639,7 +522,7 @@ function M.remove_workspace_folder(workspace_folder)
{ {} },
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }
)
- for _, client in pairs(vim.lsp.buf_get_clients()) do
+ for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
for idx, folder in pairs(client.workspace_folders) do
if folder.name == workspace_folder then
vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params)
@@ -672,18 +555,17 @@ end
--- Send request to the server to resolve document highlights for the current
--- text document position. This request can be triggered by a key mapping or
--- by events such as `CursorHold`, e.g.:
----
---- <pre>
---- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
---- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
---- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
+--- <pre>vim
+--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
+--- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
+--- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
--- </pre>
---
--- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups
--- to be defined or you won't be able to see the actual highlights.
---- |LspReferenceText|
---- |LspReferenceRead|
---- |LspReferenceWrite|
+--- |hl-LspReferenceText|
+--- |hl-LspReferenceRead|
+--- |hl-LspReferenceWrite|
function M.document_highlight()
local params = util.make_position_params()
request('textDocument/documentHighlight', params)
@@ -851,6 +733,7 @@ end
--- List of LSP `CodeActionKind`s used to filter the code actions.
--- Most language servers support values like `refactor`
--- or `quickfix`.
+--- - triggerKind (number|nil): The reason why code actions were requested.
--- - filter: (function|nil)
--- Predicate taking an `CodeAction` and returning a boolean.
--- - apply: (boolean|nil)
@@ -864,6 +747,7 @@ end
--- using mark-like indexing. See |api-indexing|
---
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
+---@see vim.lsp.protocol.constants.CodeActionTriggerKind
function M.code_action(options)
validate({ options = { options, 't', true } })
options = options or {}
@@ -873,6 +757,9 @@ function M.code_action(options)
options = { options = options }
end
local context = options.context or {}
+ if not context.triggerKind then
+ context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked
+ end
if not context.diagnostics then
local bufnr = api.nvim_get_current_buf()
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
@@ -885,23 +772,8 @@ function M.code_action(options)
local end_ = assert(options.range['end'], 'range must have a `end` property')
params = util.make_given_range_params(start, end_)
elseif mode == 'v' or mode == 'V' then
- -- [bufnum, lnum, col, off]; both row and column 1-indexed
- local start = vim.fn.getpos('v')
- local end_ = vim.fn.getpos('.')
- local start_row = start[2]
- local start_col = start[3]
- local end_row = end_[2]
- local end_col = end_[3]
-
- -- A user can start visual selection at the end and move backwards
- -- Normalize the range to start < end
- if start_row == end_row and end_col < start_col then
- end_col, start_col = start_col, end_col
- elseif end_row < start_row then
- start_row, end_row = end_row, start_row
- start_col, end_col = end_col, start_col
- end
- params = util.make_given_range_params({ start_row, start_col - 1 }, { end_row, end_col - 1 })
+ local range = range_from_selection()
+ params = util.make_given_range_params(range.start, range['end'])
else
params = util.make_range_params()
end
@@ -909,34 +781,6 @@ function M.code_action(options)
code_action_request(params, options)
end
---- Performs |vim.lsp.buf.code_action()| for a given range.
----
----
----@param context table|nil `CodeActionContext` of the LSP specification:
---- - diagnostics: (table|nil)
---- LSP `Diagnostic[]`. Inferred from the current
---- position if not provided.
---- - only: (table|nil)
---- List of LSP `CodeActionKind`s used to filter the code actions.
---- Most language servers support values like `refactor`
---- or `quickfix`.
----@param start_pos ({number, number}, optional) mark-indexed position.
----Defaults to the start of the last visual selection.
----@param end_pos ({number, number}, optional) mark-indexed position.
----Defaults to the end of the last visual selection.
-function M.range_code_action(context, start_pos, end_pos)
- vim.deprecate('vim.lsp.buf.range_code_action', 'vim.lsp.buf.code_action', '0.9.0')
- validate({ context = { context, 't', true } })
- context = context or {}
- if not context.diagnostics then
- local bufnr = api.nvim_get_current_buf()
- context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
- end
- local params = util.make_given_range_params(start_pos, end_pos)
- params.context = context
- code_action_request(params)
-end
-
--- Executes an LSP server command.
---
---@param command_params table A valid `ExecuteCommandParams` object
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 4fa02c8db2..17489ed84d 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -108,13 +108,36 @@ function M.run()
end
end
+---@private
+local function resolve_bufnr(bufnr)
+ return bufnr == 0 and api.nvim_get_current_buf() or bufnr
+end
+
+--- Clear the lenses
+---
+---@param client_id number|nil filter by client_id. All clients if nil
+---@param bufnr number|nil filter by buffer. All buffers if nil
+function M.clear(client_id, bufnr)
+ local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf)
+ for _, iter_bufnr in pairs(buffers) do
+ local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces)
+ for _, iter_client_id in pairs(client_ids) do
+ local ns = namespaces[iter_client_id]
+ lens_cache_by_buf[iter_bufnr][iter_client_id] = {}
+ api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1)
+ end
+ end
+end
+
--- Display the lenses using virtual text
---
---@param lenses table of lenses to display (`CodeLens[] | null`)
---@param bufnr number
---@param client_id number
function M.display(lenses, bufnr, client_id)
+ local ns = namespaces[client_id]
if not lenses or not next(lenses) then
+ api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
return
end
local lenses_by_lnum = {}
@@ -126,7 +149,6 @@ function M.display(lenses, bufnr, client_id)
end
table.insert(line_lenses, lens)
end
- 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] or {}
@@ -241,7 +263,8 @@ end
---
--- It is recommended to trigger this using an autocmd or via keymap.
---
---- <pre>
+--- Example:
+--- <pre>vim
--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
--- </pre>
---
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 1f9d084e2b..5e2bf75f1b 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -150,7 +150,7 @@ end
---
--- See |vim.diagnostic.config()| for configuration options. Handler-specific
--- configuration can be set using |vim.lsp.with()|:
---- <pre>
+--- <pre>lua
--- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
--- vim.lsp.diagnostic.on_publish_diagnostics, {
--- -- Enable underline, use default values
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 624436bc9b..5096100a60 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -1,7 +1,6 @@
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
-local vim = vim
local api = vim.api
local M = {}
@@ -82,22 +81,38 @@ M['window/workDoneProgress/create'] = function(_, result, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
+---@param result lsp.ShowMessageRequestParams
M['window/showMessageRequest'] = function(_, result)
- local actions = result.actions
- print(result.message)
- local option_strings = { result.message, '\nRequest Actions:' }
- for i, action in ipairs(actions) do
- local title = action.title:gsub('\r\n', '\\r\\n')
- title = title:gsub('\n', '\\n')
- table.insert(option_strings, string.format('%d. %s', i, title))
- end
-
- -- window/showMessageRequest can return either MessageActionItem[] or null.
- local choice = vim.fn.inputlist(option_strings)
- if choice < 1 or choice > #actions then
- return vim.NIL
+ local actions = result.actions or {}
+ local co, is_main = coroutine.running()
+ if co and not is_main then
+ local opts = {
+ prompt = result.message .. ': ',
+ format_item = function(action)
+ return (action.title:gsub('\r\n', '\\r\\n')):gsub('\n', '\\n')
+ end,
+ }
+ vim.ui.select(actions, opts, function(choice)
+ -- schedule to ensure resume doesn't happen _before_ yield with
+ -- default synchronous vim.ui.select
+ vim.schedule(function()
+ coroutine.resume(co, choice or vim.NIL)
+ end)
+ end)
+ return coroutine.yield()
else
- return actions[choice]
+ local option_strings = { result.message, '\nRequest Actions:' }
+ for i, action in ipairs(actions) do
+ local title = action.title:gsub('\r\n', '\\r\\n')
+ title = title:gsub('\n', '\\n')
+ table.insert(option_strings, string.format('%d. %s', i, title))
+ end
+ local choice = vim.fn.inputlist(option_strings)
+ if choice < 1 or choice > #actions then
+ return vim.NIL
+ else
+ return actions[choice]
+ end
end
end
@@ -116,9 +131,10 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
- if not workspace_edit then
- return
- end
+ assert(
+ workspace_edit,
+ 'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
+ )
-- TODO(ashkan) Do something more with label?
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
@@ -298,13 +314,15 @@ M['textDocument/completion'] = function(_, result, _, _)
end
--- |lsp-handler| for the method "textDocument/hover"
---- <pre>
---- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
---- vim.lsp.handlers.hover, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single"
---- }
---- )
+--- <pre>lua
+--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
+--- vim.lsp.handlers.hover, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single",
+--- -- add the title in hover float window
+--- title = "hover"
+--- }
+--- )
--- </pre>
---@param config table Configuration table.
--- - border: (default=nil)
@@ -313,8 +331,14 @@ end
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
+ if api.nvim_get_current_buf() ~= ctx.bufnr then
+ -- Ignore result since buffer changed. This happens for slow language servers.
+ return
+ end
if not (result and result.contents) then
- vim.notify('No information available')
+ if config.silent ~= true then
+ vim.notify('No information available')
+ end
return
end
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
@@ -378,21 +402,25 @@ 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, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single"
---- }
---- )
+--- <pre>lua
+--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
+--- vim.lsp.handlers.signature_help, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single"
+--- }
+--- )
--- </pre>
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |vim.api.nvim_open_win()|
+--- - See |nvim_open_win()|
function M.signature_help(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
+ if api.nvim_get_current_buf() ~= ctx.bufnr then
+ -- Ignore result since buffer changed. This happens for slow language servers.
+ return
+ end
-- 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
@@ -512,6 +540,55 @@ M['window/showMessage'] = function(_, result, ctx, _)
return result
end
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
+M['window/showDocument'] = function(_, result, ctx, _)
+ local uri = result.uri
+
+ if result.external then
+ -- TODO(lvimuser): ask the user for confirmation
+ local cmd
+ if vim.fn.has('win32') == 1 then
+ cmd = { 'cmd.exe', '/c', 'start', '""', uri }
+ elseif vim.fn.has('macunix') == 1 then
+ cmd = { 'open', uri }
+ else
+ cmd = { 'xdg-open', uri }
+ end
+
+ local ret = vim.fn.system(cmd)
+ if vim.v.shell_error ~= 0 then
+ return {
+ success = false,
+ error = {
+ code = protocol.ErrorCodes.UnknownErrorCode,
+ message = ret,
+ },
+ }
+ end
+
+ return { success = true }
+ end
+
+ local client_id = ctx.client_id
+ local client = vim.lsp.get_client_by_id(client_id)
+ local client_name = client and client.name or string.format('id=%d', client_id)
+ if not client then
+ err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method })
+ return vim.NIL
+ end
+
+ local location = {
+ uri = uri,
+ range = result.selection,
+ }
+
+ local success = util.show_document(location, client.offset_encoding, {
+ reuse_win = true,
+ focus = result.takeFocus,
+ })
+ return { success = success or false }
+end
+
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index ba730e3d6d..987707e661 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -2,8 +2,8 @@ local M = {}
--- Performs a healthcheck for LSP
function M.check()
- local report_info = vim.fn['health#report_info']
- local report_warn = vim.fn['health#report_warn']
+ local report_info = vim.health.report_info
+ local report_warn = vim.health.report_warn
local log = require('vim.lsp.log')
local current_log_level = log.get_level()
@@ -27,6 +27,18 @@ function M.check()
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
report_fn(string.format('Log size: %d KB', log_size / 1000))
+
+ local clients = vim.lsp.get_active_clients()
+ vim.health.report_start('vim.lsp: Active Clients')
+ if next(clients) then
+ for _, client in pairs(clients) do
+ report_info(
+ string.format('%s (id=%s, root_dir=%s)', client.name, client.id, client.config.root_dir)
+ )
+ end
+ else
+ report_info('No active clients')
+ end
end
return M
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 6c6ba0f206..d1a78572aa 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -20,6 +20,17 @@ local format_func = function(arg)
end
do
+ ---@private
+ local function notify(msg, level)
+ if vim.in_fast_event() then
+ vim.schedule(function()
+ vim.notify(msg, level)
+ end)
+ else
+ vim.notify(msg, level)
+ end
+ end
+
local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
---@private
local function path_join(...)
@@ -53,7 +64,7 @@ do
logfile, openerr = io.open(logfilename, 'a+')
if not logfile then
local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
- vim.notify(err_msg, vim.log.levels.ERROR)
+ notify(err_msg, vim.log.levels.ERROR)
return false
end
@@ -64,7 +75,7 @@ do
log_info.size / (1000 * 1000),
logfilename
)
- vim.notify(warn_msg)
+ notify(warn_msg)
end
-- Start message for logging
@@ -130,7 +141,7 @@ end
vim.tbl_add_reverse_lookup(log.levels)
--- Sets the current log level.
----@param level (string or number) One of `vim.lsp.log.levels`
+---@param level (string|number) One of `vim.lsp.log.levels`
function log.set_level(level)
if type(level) == 'string' then
current_log_level =
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 27da60b4ae..12345b6c8c 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -20,6 +20,14 @@ function transform_schema_to_table()
end
--]=]
+---@class lsp.ShowMessageRequestParams
+---@field type lsp.MessageType
+---@field message string
+---@field actions nil|lsp.MessageActionItem[]
+
+---@class lsp.MessageActionItem
+---@field title string
+
local constants = {
DiagnosticSeverity = {
-- Reports an error.
@@ -39,6 +47,7 @@ local constants = {
Deprecated = 2,
},
+ ---@enum lsp.MessageType
MessageType = {
-- An error message.
Error = 1,
@@ -142,6 +151,7 @@ local constants = {
},
-- Represents reasons why a text document is saved.
+ ---@enum lsp.TextDocumentSaveReason
TextDocumentSaveReason = {
-- Manually triggered, e.g. by the user pressing save, by starting debugging,
-- or by an API call.
@@ -294,6 +304,17 @@ local constants = {
-- Base kind for an organize imports source action
SourceOrganizeImports = 'source.organizeImports',
},
+ -- The reason why code actions were requested.
+ ---@enum lsp.CodeActionTriggerKind
+ CodeActionTriggerKind = {
+ -- Code actions were explicitly requested by the user or by an extension.
+ Invoked = 1,
+ -- Code actions were requested automatically.
+ --