Skip to content

Commit

Permalink
feat(lsp): redrawstatus on progress notifications
Browse files Browse the repository at this point in the history
Problem:

Currently users can include `vim.lsp.status()` in their statusline, but
they won't see status changes without also adding a `LspProgress`
autocmd.

The simple solution for that is:

    autocmd LspProgress * redrawstatus

But that has the problem that the last message remains stale until
something else triggers a redraw. To fix the stale messages, users would
have to add something like this:

    local timer = vim.loop.new_timer()
    api.nvim_create_autocmd("LspProgress", {
      group = lsp_group,
      callback = function()
        vim.cmd.redrawstatus()
        if timer then
          timer:stop()
          timer:start(500, 0, vim.schedule_wrap(function()
            timer:stop()
            vim.cmd.redrawstatus()
          end))
        end
      end
    })

That's quite complex.

Solution:

Call redrawstatus by default. To avoid `redrawstatus()` burst the calls
are debounced to 60 FPS.
  • Loading branch information
mfussenegger committed Apr 29, 2024
1 parent a1c9da2 commit 26dbb32
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 33 deletions.
5 changes: 2 additions & 3 deletions runtime/doc/lsp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,8 @@ LspProgress *LspProgress*
`result` properties. `result` will contain the request params sent by the
server.

Example: >vim
autocmd LspProgress * redrawstatus
<
|redrawstatus()| happens implicitly with LspProgress events.


LspRequest *LspRequest*
For each request sent to an LSP server, this event is triggered for
Expand Down
101 changes: 71 additions & 30 deletions runtime/lua/vim/lsp/handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,81 @@ M[ms.workspace_executeCommand] = function(_, _, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end

--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
---@param result lsp.ProgressParams
---@param ctx lsp.HandlerContext
M[ms.dollar_progress] = function(_, result, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
local kind = nil
local value = result.value

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
do
local timer1 = nil
local timer2 = nil

--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
---@param result lsp.ProgressParams
---@param ctx lsp.HandlerContext
M[ms.dollar_progress] = function(_, result, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message(
'LSP[id=',
tostring(ctx.client_id),
'] client has shut down during progress update'
)
return vim.NIL
end
local kind = nil
local value = result.value

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
end
end

client.progress:push(result)
client.progress:push(result)

-- Refresh status debounced to 60 FPS
-- The first timer ensures new messages are shown quickly
--
-- The second timer ensures the last message gets cleared in case the server stops sending
-- progress for some time. Otherwise there's a stale message until something else triggers a
-- redraw.
timer1 = timer1 or vim.uv.new_timer()
timer2 = timer2 or vim.uv.new_timer()
if timer1 then
timer1:stop()
timer1:start(
16.66,
0,
vim.schedule_wrap(function()
timer1:stop()
vim.cmd.redrawstatus()

if timer2 then
timer2:stop()
timer2:start(
500,
0,
vim.schedule_wrap(function()
timer2:stop()
vim.cmd.redrawstatus()
end)
)
end
end)
)
end

api.nvim_exec_autocmds('LspProgress', {
pattern = kind,
modeline = false,
data = { client_id = ctx.client_id, result = result },
})
api.nvim_exec_autocmds('LspProgress', {
pattern = kind,
modeline = false,
data = { client_id = ctx.client_id, result = result },
})
end
end

--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
Expand Down

0 comments on commit 26dbb32

Please sign in to comment.