From 8e0045f43ade397bbcf62c0d33aaf45e72e63610 Mon Sep 17 00:00:00 2001 From: Volodymyr Chernetskyi Date: Wed, 9 Sep 2026 14:45:10 +0200 Subject: [PATCH] feat(lsp): support `workspace/foldingRange/refresh` #41780 Problem: Servers cannot request refreshing stale LSP folding ranges after a project-wide change. Solution: Advertise `workspace.foldingRange.refreshSupport` and re-request folding ranges for active buffers when handling `workspace/foldingRange/refresh`. AI-assisted --- runtime/doc/news.txt | 2 ++ runtime/lua/vim/lsp/_folding_range.lua | 20 +++++++++++++ runtime/lua/vim/lsp/handlers.lua | 5 ++++ runtime/lua/vim/lsp/protocol.lua | 3 ++ .../plugin/lsp/folding_range_spec.lua | 28 ++++++++++++++++++- 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 8e8ae69b12..ee5392a563 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -375,6 +375,8 @@ LSP • |vim.lsp.formatexpr()| now falls back to `textDocument/formatting` if the whole buffer is being formatted and the language server doesn't support `textDocument/rangeFormatting`. +• Support for `workspace/foldingRange/refresh`: + https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#workspace_foldingRange_refresh LUA diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua index 63369764f9..9f85be9eae 100644 --- a/runtime/lua/vim/lsp/_folding_range.lua +++ b/runtime/lua/vim/lsp/_folding_range.lua @@ -289,6 +289,26 @@ function State:foldclose(kind, winid) end) end +--- |lsp-handler| for the method `workspace/foldingRange/refresh` +--- +--- Refresh requests are sent by the server to indicate a project-wide change +--- that requires all folding ranges to be re-requested by the client. +---@param ctx lsp.HandlerContext +---@internal +function M.on_refresh(err, _, ctx) + if err then + return vim.NIL + end + + for _, state in pairs(State.active) do + if state.client_state[ctx.client_id] then + state:refresh(ctx.client_id) + end + end + + return vim.NIL +end + ---@param kind lsp.FoldingRangeKind ---@param winid? integer function M.foldclose(kind, winid) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 8b75a8d3d8..5bdd8cc01f 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -673,6 +673,11 @@ RSC['workspace/diagnostic/refresh'] = function(err, result, ctx) return vim.lsp.diagnostic.on_refresh(err, result, ctx) end +---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_foldingRange_refresh +RSC['workspace/foldingRange/refresh'] = function(err, result, ctx) + return vim.lsp._folding_range.on_refresh(err, result, ctx) +end + ---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh RSC['workspace/inlayHint/refresh'] = function(err, result, ctx) return vim.lsp.inlay_hint.on_refresh(err, result, ctx) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 9df3327d59..fff076e307 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -639,6 +639,9 @@ function protocol.make_client_capabilities() diagnostics = { refreshSupport = true, }, + foldingRange = { + refreshSupport = true, + }, fileOperations = { dynamicRegistration = false, didCreate = false, diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua index 60e9cee6b8..ac6e427057 100644 --- a/test/functional/plugin/lsp/folding_range_spec.lua +++ b/test/functional/plugin/lsp/folding_range_spec.lua @@ -101,13 +101,14 @@ static int foldLevel(linenr_T lnum) exec_lua(create_server_definition) bufnr = n.api.nvim_get_current_buf() client_id = exec_lua(function() + _G.folding_ranges = result _G.server = _G._create_server({ capabilities = { foldingRangeProvider = true, }, handlers = { ['textDocument/foldingRange'] = function(_, _, callback) - callback(nil, result) + callback(nil, _G.folding_ranges) end, }, }) @@ -186,6 +187,31 @@ static int foldLevel(linenr_T lnum) }, foldlevels) end) + it('refreshes folding ranges on request', function() + local function foldlevels() + return exec_lua(function() + return { vim.lsp.foldexpr(1), vim.lsp.foldexpr(2), vim.lsp.foldexpr(3) } + end) + end + + retry(nil, nil, function() + eq({ '>1', '<1', '0' }, foldlevels()) + end) + + exec_lua(function() + _G.folding_ranges = { { startLine = 1, endLine = 2 } } + vim.lsp._folding_range.on_refresh( + nil, + nil, + { method = 'workspace/foldingRange/refresh', client_id = client_id } + ) + end) + + retry(nil, nil, function() + eq({ '0', '>1', '<1' }, foldlevels()) + end) + end) + it('updates folds in all windows', function() screen:expect({ grid = [[