aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp
diff options
context:
space:
mode:
authorMathias Fußenegger <mfussenegger@users.noreply.github.com>2023-06-09 11:32:43 +0200
committerGitHub <noreply@github.com>2023-06-09 11:32:43 +0200
commite5e0bda41b640d324350c5147b956e37e9f8b32c (patch)
treed546e647fcde46494740852171593e78101d9997 /runtime/lua/vim/lsp
parentf31dba93f921891159eb707b185517648df00d6b (diff)
downloadrneovim-e5e0bda41b640d324350c5147b956e37e9f8b32c.tar.gz
rneovim-e5e0bda41b640d324350c5147b956e37e9f8b32c.tar.bz2
rneovim-e5e0bda41b640d324350c5147b956e37e9f8b32c.zip
feat(lsp)!: add vim.lsp.status, client.progress and promote LspProgressUpdate (#23958)
`client.messages` could grow unbounded because the default handler only added new messages, never removing them. A user either had to consume the messages by calling `vim.lsp.util.get_progress_messages` or by manually removing them from `client.messages.progress`. If they didn't do that, using LSP effectively leaked memory. To fix this, this deprecates the `messages` property and instead adds a `progress` ring buffer that only keeps at most 50 messages. In addition it deprecates `vim.lsp.util.get_progress_messages` in favour of a new `vim.lsp.status()` and also promotes the `LspProgressUpdate` user autocmd to a regular autocmd to allow users to pattern match on the progress kind. Also closes https://github.com/neovim/neovim/pull/20327
Diffstat (limited to 'runtime/lua/vim/lsp')
-rw-r--r--runtime/lua/vim/lsp/handlers.lua75
-rw-r--r--runtime/lua/vim/lsp/types.lua8
-rw-r--r--runtime/lua/vim/lsp/util.lua29
3 files changed, 68 insertions, 44 deletions
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 5346160871..19338ae8f0 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -9,7 +9,7 @@ local M = {}
---@private
--- Writes to error buffer.
----@param ... (table of strings) Will be concatenated before being written
+---@param ... string Will be concatenated before being written
local function err_message(...)
vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
api.nvim_command('redraw')
@@ -20,63 +20,52 @@ M['workspace/executeCommand'] = function(_, _, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
----@private
-local function progress_handler(_, result, ctx, _)
- 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)
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
+---@param result lsp.ProgressParams
+---@param ctx lsp.HandlerContext
+M['$/progress'] = function(_, result, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down during progress update')
+ err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- local val = result.value -- unspecified yet
- local token = result.token -- string or number
+ local kind = nil
+ local value = result.value
- if type(val) ~= 'table' then
- val = { content = val }
- end
- if val.kind then
- if val.kind == 'begin' then
- client.messages.progress[token] = {
- title = val.title,
- cancellable = val.cancellable,
- message = val.message,
- percentage = val.percentage,
- }
- elseif val.kind == 'report' then
- client.messages.progress[token].cancellable = val.cancellable
- client.messages.progress[token].message = val.message
- client.messages.progress[token].percentage = val.percentage
- elseif val.kind == 'end' then
- if client.messages.progress[token] == nil then
- err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`')
- else
- client.messages.progress[token].message = val.message
- client.messages.progress[token].done = true
+ if type(value) == 'table' then
+ kind = value.kind
+ -- Carry over title of `begin` messages to `report` and `end` messages
+ -- So that consumers always have it available, even if they consume a
+ -- subset of the full sequence
+ if kind == 'begin' then
+ client.progress.pending[result.token] = value.title
+ else
+ value.title = client.progress.pending[result.token]
+ if kind == 'end' then
+ client.progress.pending[result.token] = nil
end
end
- else
- client.messages.progress[token] = val
- client.messages.progress[token].done = true
end
- api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false })
-end
+ client.progress:push(result)
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
-M['$/progress'] = progress_handler
+ api.nvim_exec_autocmds('LspProgress', {
+ pattern = kind,
+ modeline = false,
+ data = { client_id = ctx.client_id, result = result },
+ })
+end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
+---@param result lsp.WorkDoneProgressCreateParams
+---@param ctx lsp.HandlerContext
M['window/workDoneProgress/create'] = function(_, result, ctx)
- local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
- local token = result.token -- string or number
- local client_name = client and client.name or string.format('id=%d', client_id)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down while creating progress report')
+ err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- client.messages.progress[token] = {}
+ client.progress:push(result)
return vim.NIL
end
diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua
index e77e1fb63a..ef85a0d10f 100644
--- a/runtime/lua/vim/lsp/types.lua
+++ b/runtime/lua/vim/lsp/types.lua
@@ -1,6 +1,12 @@
---@meta
----@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: table, config: table|nil)
+---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil)
+
+---@class lsp.HandlerContext
+---@field method string
+---@field client_id integer
+---@field bufnr integer
+---@field params any
---@class lsp.ResponseError
---@field code integer
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index e36014d07d..538e48c805 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -353,11 +353,40 @@ end
--- Process and return progress reports from lsp server
---@private
+---@deprecated Use vim.lsp.status() or access client.progress directly
function M.get_progress_messages()
+ vim.deprecate('vim.lsp.util.get_progress_messages', 'vim.lsp.status', '0.11.0')
local new_messages = {}
local progress_remove = {}
for _, client in ipairs(vim.lsp.get_active_clients()) do
+ local groups = {}
+ for progress in client.progress do
+ local value = progress.value
+ if type(value) == 'table' and value.kind then
+ local group = groups[progress.token]
+ if not group then
+ group = {
+ done = false,
+ progress = true,
+ title = 'empty title',
+ }
+ groups[progress.token] = group
+ end
+ group.title = value.title or group.title
+ group.cancellable = value.cancellable or group.cancellable
+ if value.kind == 'end' then
+ group.done = true
+ end
+ group.message = value.message or group.message
+ group.percentage = value.percentage or group.percentage
+ end
+ end
+
+ for _, group in pairs(groups) do
+ table.insert(new_messages, group)
+ end
+
local messages = client.messages
local data = messages
for token, ctx in pairs(data.progress) do