aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/deprecated.txt28
-rw-r--r--runtime/doc/lsp.txt25
-rw-r--r--runtime/doc/news.txt9
-rw-r--r--runtime/lua/vim/lsp.lua62
-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
-rw-r--r--src/nvim/auevents.lua2
8 files changed, 173 insertions, 65 deletions
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 6494c53059..73888a32cc 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -117,19 +117,21 @@ internally and are no longer exposed as part of the API. Instead, use
- *vim.lsp.diagnostic.set_virtual_text()*
LSP FUNCTIONS
-- *vim.lsp.buf.range_code_action()* Use |vim.lsp.buf.code_action()| with
- the `range` parameter.
-- *vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead.
-- *vim.lsp.util.set_qflist()* Use |setqflist()| instead.
-- *vim.lsp.util.set_loclist()* Use |setloclist()| instead.
-- *vim.lsp.buf_get_clients()* Use |vim.lsp.get_active_clients()| with
- {buffer = bufnr} instead.
-- *vim.lsp.buf.formatting()* Use |vim.lsp.buf.format()| with
- {async = true} instead.
-- *vim.lsp.buf.formatting_sync()* Use |vim.lsp.buf.format()| with
- {async = false} instead.
-- *vim.lsp.buf.range_formatting()* Use |vim.lsp.formatexpr()|
- or |vim.lsp.buf.format()| instead.
+- *vim.lsp.buf.range_code_action()* Use |vim.lsp.buf.code_action()| with
+ the `range` parameter.
+- *vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead.
+- *vim.lsp.util.set_qflist()* Use |setqflist()| instead.
+- *vim.lsp.util.set_loclist()* Use |setloclist()| instead.
+- *vim.lsp.buf_get_clients()* Use |vim.lsp.get_active_clients()| with
+ {buffer = bufnr} instead.
+- *vim.lsp.buf.formatting()* Use |vim.lsp.buf.format()| with
+ {async = true} instead.
+- *vim.lsp.buf.formatting_sync()* Use |vim.lsp.buf.format()| with
+ {async = false} instead.
+- *vim.lsp.buf.range_formatting()* Use |vim.lsp.formatexpr()|
+ or |vim.lsp.buf.format()| instead.
+- *vim.lsp.util.get_progress_messages()* Use |vim.lsp.status()| or access
+ `progress` of |vim.lsp.client|
TREESITTER FUNCTIONS
- *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()|
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 5b7c013c57..7248d03196 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -659,14 +659,20 @@ callbacks. Example: >lua
})
<
-Also the following |User| |autocommand| is provided:
+LspProgress *LspProgress*
+ Upon receipt of a progress notification from the server. Notifications can
+ be polled from a `progress` ring buffer of a |vim.lsp.client| or use
+ |vim.lsp.status()| to get an aggregate message
-LspProgressUpdate *LspProgressUpdate*
- Upon receipt of a progress notification from the server. See
- |vim.lsp.util.get_progress_messages()|.
+ If the server sends a "work done progress", the `pattern` is set to `kind`
+ (one of `begin`, `report` or `end`).
+
+ When used from Lua, the event contains a `data` table with `client_id` and
+ `result` properties. `result` will contain the request params sent by the
+ server.
Example: >vim
- autocmd User LspProgressUpdate redrawstatus
+ autocmd LspProgress * redrawstatus
<
==============================================================================
@@ -806,6 +812,8 @@ client() *vim.lsp.client*
|vim.lsp.start_client()|.
• {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.
client_is_stopped({client_id}) *vim.lsp.client_is_stopped()*
Checks whether a client is stopped.
@@ -1092,6 +1100,13 @@ start_client({config}) *vim.lsp.start_client()*
not be fully initialized. Use `on_init` to do any actions once the
client has been initialized.
+status() *vim.lsp.status()*
+ 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)
+
stop_client({client_id}, {force}) *vim.lsp.stop_client()*
Stops a client(s).
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index dbf5b131eb..87dfefcce8 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -33,8 +33,8 @@ The following changes may require adaptations in user config or plugins.
• When switching windows, |CursorMoved| autocommands trigger when Nvim is back
in the main loop rather than immediately. This is more compatible with Vim.
-• |LspRequest| autocmd was promoted from a |User| autocmd to a first class
- citizen.
+• |LspRequest| and LspProgressUpdate (renamed to |LspProgress|) autocmds were
+ promoted from a |User| autocmd to first class citizen.
• Renamed `vim.treesitter.playground` to `vim.treesitter.dev`.
@@ -43,6 +43,8 @@ ADDED FEATURES *news-added*
The following new APIs or features were added.
+• Added |vim.lsp.status()| to consume the last progress messages as a string.
+
• Neovim's LSP client now always saves and restores named buffer marks when
applying text edits.
@@ -142,6 +144,9 @@ release.
- |nvim_win_get_option()| Use |nvim_get_option_value()| instead.
- |nvim_win_set_option()| Use |nvim_set_option_value()| instead.
+• vim.lsp functions:
+ - |vim.lsp.util.get_progress_messages()| Use |vim.lsp.status()| instead.
+
• `vim.loop` has been renamed to `vim.uv`.
vim:tw=78:ts=8:sw=2:et:ft=help:norl:
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 532504a7db..6ddbfc6df7 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -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()
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
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 048b8d6631..41d7ee9b47 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -74,6 +74,7 @@ return {
'LspDetach', -- after an LSP client detaches from a buffer
'LspRequest', -- after an LSP request is started, canceled, or completed
'LspTokenUpdate', -- after a visible LSP token is updated
+ 'LspProgress', -- after a LSP progress update
'MenuPopup', -- just before popup menu is displayed
'ModeChanged', -- after changing the mode
'OptionSet', -- after setting any option
@@ -154,6 +155,7 @@ return {
LspAttach=true,
LspDetach=true,
LspRequest=true,
+ LspProgress=true,
LspTokenUpdate=true,
RecordingEnter=true,
RecordingLeave=true,