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
This commit is contained in:
Mathias Fußenegger
2023-06-09 11:32:43 +02:00
committed by GitHub
parent f31dba93f9
commit e5e0bda41b
8 changed files with 173 additions and 65 deletions

View File

@@ -807,6 +807,9 @@ end
---
--- - {server_capabilities} (table): Response from the server sent on
--- `initialize` describing the server's capabilities.
---
--- - {progress} A ring buffer (|vim.ringbuf()|) containing progress messages
--- sent by the server.
function lsp.client()
error()
end
@@ -891,6 +894,50 @@ function lsp.start(config, opts)
return client_id
end
--- Consumes the latest progress messages from all clients and formats them as a string.
--- Empty if there are no clients or if no new messages
---
---@return string
function lsp.status()
local percentage = nil
local groups = {}
for _, client in ipairs(vim.lsp.get_active_clients()) do
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 = {}
groups[progress.token] = group
end
group.title = value.title or group.title
group.message = value.message or group.message
if value.percentage then
percentage = math.max(percentage or 0, value.percentage)
end
end
-- else: Doesn't look like work done progress and can be in any format
-- Just ignore it as there is no sensible way to display it
end
end
local messages = {}
for _, group in pairs(groups) do
if group.title then
table.insert(
messages,
group.message and (group.title .. ': ' .. group.message) or group.title
)
elseif group.message then
table.insert(messages, group.message)
end
end
local message = table.concat(messages, ', ')
if percentage then
return string.format('%03d: %s', percentage, message)
end
return message
end
---@private
-- Determines whether the given option can be set by `set_defaults`.
local function is_empty_or_default(bufnr, option)
@@ -1266,10 +1313,23 @@ function lsp.start_client(config)
--- @type table<integer,{ type: string, bufnr: integer, method: string}>
requests = {},
-- for $/progress report
--- Contains $/progress report messages.
--- They have the format {token: integer|string, value: any}
--- For "work done progress", value will be one of:
--- - lsp.WorkDoneProgressBegin,
--- - lsp.WorkDoneProgressReport (extended with title from Begin)
--- - lsp.WorkDoneProgressEnd (extended with title from Begin)
progress = vim.ringbuf(50),
---@deprecated use client.progress instead
messages = { name = name, messages = {}, progress = {}, status = {} },
dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
}
---@type table<string|integer, string> title of unfinished progress sequences by token
client.progress.pending = {}
--- @type lsp.ClientCapabilities
client.config.capabilities = config.capabilities or protocol.make_client_capabilities()