Files
neovim/test/functional/plugin/lsp/folding_range_spec.lua

624 lines
34 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local t_lsp = require('test.functional.plugin.lsp.testutil')
local eq = t.eq
local clear_notrace = t_lsp.clear_notrace
local create_server_definition = t_lsp.create_server_definition
local api = n.api
local exec_lua = n.exec_lua
local insert = n.insert
local command = n.command
local feed = n.feed
describe('vim.lsp.folding_range', function()
local text = [[// foldLevel() {{{2
/// @return fold level at line number "lnum" in the current window.
static int foldLevel(linenr_T lnum)
{
// While updating the folds lines between invalid_top and invalid_bot have
// an undefined fold level. Otherwise update the folds first.
if (invalid_top == 0) {
checkupdate(curwin);
} else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {
return prev_lnum_lvl;
} else if (lnum >= invalid_top && lnum <= invalid_bot) {
return -1;
}
// Return quickly when there is no folding at all in this window.
if (!hasAnyFolding(curwin)) {
return 0;
}
return foldLevelWin(curwin, lnum);
}]]
local result = {
{
endLine = 19,
kind = 'region',
startCharacter = 1,
startLine = 3,
},
{
endCharacter = 2,
endLine = 7,
kind = 'region',
startCharacter = 25,
startLine = 6,
},
{
endCharacter = 2,
endLine = 9,
kind = 'region',
startCharacter = 55,
startLine = 8,
},
{
endCharacter = 2,
endLine = 11,
kind = 'region',
startCharacter = 58,
startLine = 10,
},
{
endCharacter = 2,
endLine = 16,
kind = 'region',
startCharacter = 31,
startLine = 15,
},
{
endCharacter = 68,
endLine = 1,
kind = 'comment',
startCharacter = 2,
startLine = 0,
},
{
endCharacter = 64,
endLine = 5,
kind = 'comment',
startCharacter = 4,
startLine = 4,
},
}
local bufnr ---@type integer
local client_id ---@type integer
clear_notrace()
before_each(function()
clear_notrace()
exec_lua(create_server_definition)
bufnr = n.api.nvim_get_current_buf()
client_id = exec_lua(function()
_G.server = _G._create_server({
capabilities = {
foldingRangeProvider = true,
},
handlers = {
['textDocument/foldingRange'] = function(_, _, callback)
callback(nil, result)
end,
},
})
vim.api.nvim_win_set_buf(0, bufnr)
return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
end)
command('set foldmethod=expr foldcolumn=1 foldlevel=999')
insert(text)
end)
after_each(function()
api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
end)
describe('expr()', function()
--- @type test.functional.ui.screen
local screen
before_each(function()
screen = Screen.new(80, 45)
screen:set_default_attr_ids({
[1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
[2] = { bold = true, foreground = Screen.colors.Blue1 },
[3] = { bold = true, reverse = true },
[4] = { reverse = true },
})
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
command([[split]])
end)
it('controls the value of `b:_lsp_enable_folding_range`', function()
eq(
true,
exec_lua(function()
return vim.b._lsp_enable_folding_range
end)
)
command [[setlocal foldexpr=]]
eq(
nil,
exec_lua(function()
return vim.b._lsp_enable_folding_range
end)
)
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
eq(
true,
exec_lua(function()
return vim.b._lsp_enable_folding_range
end)
)
end)
it('can compute fold levels', function()
---@type table<integer, string>
local foldlevels = {}
for i = 1, 21 do
foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')')
end
eq({
[1] = '>1',
[2] = '<1',
[3] = '0',
[4] = '>1',
[5] = '>2',
[6] = '<2',
[7] = '>2',
[8] = '<2',
[9] = '>2',
[10] = '<2',
[11] = '>2',
[12] = '<2',
[13] = '1',
[14] = '1',
[15] = '1',
[16] = '>2',
[17] = '<2',
[18] = '1',
[19] = '1',
[20] = '<1',
[21] = '0',
}, foldlevels)
end)
it('updates folds in all windows', function()
screen:expect({
grid = [[
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:[No Name] [+] }|
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }} |
{4:[No Name] [+] }|
|
]],
})
end)
it('persists wherever foldexpr is set', function()
command([[setlocal foldexpr=]])
feed('<C-w><C-w>zx')
screen:expect({
grid = [[
{1: }// foldLevel() {{{2 |
{1: }/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1: }{ |
{1: } // While updating the folds lines between invalid_top and invalid_bot have |
{1: } // an undefined fold level. Otherwise update the folds first. |
{1: } if (invalid_top == 0) { |
{1: } checkupdate(curwin); |
{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1: } return prev_lnum_lvl; |
{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1: } return -1; |
{1: } } |
{1: } |
{1: } // Return quickly when there is no folding at all in this window. |
{1: } if (!hasAnyFolding(curwin)) { |
{1: } return 0; |
{1: } } |
{1: } |
{1: } return foldLevelWin(curwin, lnum); |
{1: }} |
{4:[No Name] [+] }|
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:[No Name] [+] }|
|
]],
})
end)
it('synchronizes changed rows with their previous foldlevels', function()
command('1,2d')
screen:expect({
grid = [[
{1: }^static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }} |
{2:~ }|*2
{3:[No Name] [+] }|
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }} |
{2:~ }|*2
{4:[No Name] [+] }|
|
]],
})
end)
it('clears folds when sole client detaches', function()
exec_lua(function()
vim.lsp.buf_detach_client(bufnr, client_id)
end)
screen:expect({
grid = [[
{1: }// foldLevel() {{{2 |
{1: }/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1: }{ |
{1: } // While updating the folds lines between invalid_top and invalid_bot have |
{1: } // an undefined fold level. Otherwise update the folds first. |
{1: } if (invalid_top == 0) { |
{1: } checkupdate(curwin); |
{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1: } return prev_lnum_lvl; |
{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1: } return -1; |
{1: } } |
{1: } |
{1: } // Return quickly when there is no folding at all in this window. |
{1: } if (!hasAnyFolding(curwin)) { |
{1: } return 0; |
{1: } } |
{1: } |
{1: } return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:[No Name] [+] }|
{1: }// foldLevel() {{{2 |
{1: }/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1: }{ |
{1: } // While updating the folds lines between invalid_top and invalid_bot have |
{1: } // an undefined fold level. Otherwise update the folds first. |
{1: } if (invalid_top == 0) { |
{1: } checkupdate(curwin); |
{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1: } return prev_lnum_lvl; |
{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1: } return -1; |
{1: } } |
{1: } |
{1: } // Return quickly when there is no folding at all in this window. |
{1: } if (!hasAnyFolding(curwin)) { |
{1: } return 0; |
{1: } } |
{1: } |
{1: } return foldLevelWin(curwin, lnum); |
{1: }} |
{4:[No Name] [+] }|
|
]],
})
end)
it('remains valid after the client re-attaches.', function()
exec_lua(function()
vim.lsp.buf_detach_client(bufnr, client_id)
vim.lsp.buf_attach_client(bufnr, client_id)
end)
screen:expect({
grid = [[
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:[No Name] [+] }|
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }} |
{4:[No Name] [+] }|
|
]],
})
end)
end)
describe('foldtext()', function()
--- @type test.functional.ui.screen
local screen
before_each(function()
screen = Screen.new(80, 23)
screen:set_default_attr_ids({
[1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
[2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey },
[3] = { bold = true, foreground = Screen.colors.Blue1 },
[4] = { bold = true, reverse = true },
[5] = { reverse = true },
})
command(
[[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]]
)
end)
it('shows the first folded line if `collapsedText` does not exist', function()
screen:expect({
grid = [[
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}|
{1:+}{2: if (invalid_top == 0) {······················································}|
{1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}|
{1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}|
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:+}{2: if (!hasAnyFolding(curwin)) {················································}|
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:~ }|*6
|
]],
})
end)
end)
describe('foldclose()', function()
--- @type test.functional.ui.screen
local screen
before_each(function()
screen = Screen.new(80, 23)
screen:set_default_attr_ids({
[1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue },
[2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey },
[3] = { bold = true, foreground = Screen.colors.Blue1 },
[4] = { bold = true, reverse = true },
[5] = { reverse = true },
})
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
end)
it('closes all folds of one kind immediately', function()
exec_lua(function()
vim.lsp.foldclose('comment')
end)
screen:expect({
grid = [[
{1:+}{2:+-- 2 lines: foldLevel()······················································}|
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}|
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:~ }|*3
|
]],
})
end)
it('closes the smallest fold first', function()
exec_lua(function()
vim.lsp.foldclose('region')
end)
screen:expect({
grid = [[
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:+}{2:+-- 17 lines: {································································}|
{1: }^} |
{3:~ }|*17
|
]],
})
command('4foldopen')
screen:expect({
grid = [[
{1:-}// foldLevel() {{{2 |
{1:│}/// @return fold level at line number "lnum" in the current window. |
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:-} // While updating the folds lines between invalid_top and invalid_bot have |
{1:2} // an undefined fold level. Otherwise update the folds first. |
{1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}|
{1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}|
{1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}|
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}|
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:~ }|*5
|
]],
})
end)
it('is deferred when the buffer is not up-to-date', function()
exec_lua(function()
vim.lsp.foldclose('comment')
vim.lsp.util.buf_versions[bufnr] = 0
end)
screen:expect({
grid = [[
{1:+}{2:+-- 2 lines: foldLevel()······················································}|
{1: }static int foldLevel(linenr_T lnum) |
{1:-}{ |
{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}|
{1:-} if (invalid_top == 0) { |
{1:2} checkupdate(curwin); |
{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { |
{1:2} return prev_lnum_lvl; |
{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { |
{1:2} return -1; |
{1:│} } |
{1:│} |
{1:│} // Return quickly when there is no folding at all in this window. |
{1:-} if (!hasAnyFolding(curwin)) { |
{1:2} return 0; |
{1:│} } |
{1:│} |
{1:│} return foldLevelWin(curwin, lnum); |
{1: }^} |
{3:~ }|*3
|
]],
})
end)
end)
end)