folding_range_spec.lua (34962B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 local t_lsp = require('test.functional.plugin.lsp.testutil') 5 6 local eq = t.eq 7 8 local clear_notrace = t_lsp.clear_notrace 9 local create_server_definition = t_lsp.create_server_definition 10 11 local api = n.api 12 local exec_lua = n.exec_lua 13 local insert = n.insert 14 local command = n.command 15 local feed = n.feed 16 17 describe('vim.lsp.folding_range', function() 18 local text = [[// foldLevel() {{{2 19 /// @return fold level at line number "lnum" in the current window. 20 static int foldLevel(linenr_T lnum) 21 { 22 // While updating the folds lines between invalid_top and invalid_bot have 23 // an undefined fold level. Otherwise update the folds first. 24 if (invalid_top == 0) { 25 checkupdate(curwin); 26 } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { 27 return prev_lnum_lvl; 28 } else if (lnum >= invalid_top && lnum <= invalid_bot) { 29 return -1; 30 } 31 32 // Return quickly when there is no folding at all in this window. 33 if (!hasAnyFolding(curwin)) { 34 return 0; 35 } 36 37 return foldLevelWin(curwin, lnum); 38 }]] 39 40 local result = { 41 { 42 endLine = 19, 43 kind = 'region', 44 startCharacter = 1, 45 startLine = 3, 46 }, 47 { 48 endCharacter = 2, 49 endLine = 7, 50 kind = 'region', 51 startCharacter = 25, 52 startLine = 6, 53 }, 54 { 55 endCharacter = 2, 56 endLine = 9, 57 kind = 'region', 58 startCharacter = 55, 59 startLine = 8, 60 }, 61 { 62 endCharacter = 2, 63 endLine = 11, 64 kind = 'region', 65 startCharacter = 58, 66 startLine = 10, 67 }, 68 { 69 endCharacter = 2, 70 endLine = 16, 71 kind = 'region', 72 startCharacter = 31, 73 startLine = 15, 74 }, 75 { 76 endCharacter = 68, 77 endLine = 1, 78 kind = 'comment', 79 startCharacter = 2, 80 startLine = 0, 81 }, 82 { 83 endCharacter = 64, 84 endLine = 5, 85 kind = 'comment', 86 startCharacter = 4, 87 startLine = 4, 88 }, 89 } 90 91 local bufnr ---@type integer 92 local client_id ---@type integer 93 94 clear_notrace() 95 before_each(function() 96 clear_notrace() 97 98 exec_lua(create_server_definition) 99 bufnr = n.api.nvim_get_current_buf() 100 client_id = exec_lua(function() 101 _G.server = _G._create_server({ 102 capabilities = { 103 foldingRangeProvider = true, 104 }, 105 handlers = { 106 ['textDocument/foldingRange'] = function(_, _, callback) 107 callback(nil, result) 108 end, 109 }, 110 }) 111 112 vim.api.nvim_win_set_buf(0, bufnr) 113 114 return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 115 end) 116 command('set foldmethod=expr foldcolumn=1 foldlevel=999') 117 insert(text) 118 end) 119 after_each(function() 120 api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) 121 end) 122 123 describe('expr()', function() 124 --- @type test.functional.ui.screen 125 local screen 126 before_each(function() 127 screen = Screen.new(80, 45) 128 screen:set_default_attr_ids({ 129 [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, 130 [2] = { bold = true, foreground = Screen.colors.Blue1 }, 131 [3] = { bold = true, reverse = true }, 132 [4] = { reverse = true }, 133 }) 134 command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) 135 command([[split]]) 136 end) 137 138 it('controls whether folding range is enabled', function() 139 eq( 140 true, 141 exec_lua(function() 142 return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 }) 143 end) 144 ) 145 command [[setlocal foldexpr=]] 146 eq( 147 false, 148 exec_lua(function() 149 return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 }) 150 end) 151 ) 152 command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) 153 eq( 154 true, 155 exec_lua(function() 156 return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 }) 157 end) 158 ) 159 end) 160 161 it('can compute fold levels', function() 162 ---@type table<integer, string> 163 local foldlevels = {} 164 for i = 1, 21 do 165 foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')') 166 end 167 eq({ 168 [1] = '>1', 169 [2] = '<1', 170 [3] = '0', 171 [4] = '>1', 172 [5] = '>2', 173 [6] = '<2', 174 [7] = '>2', 175 [8] = '<2', 176 [9] = '>2', 177 [10] = '<2', 178 [11] = '>2', 179 [12] = '<2', 180 [13] = '1', 181 [14] = '1', 182 [15] = '1', 183 [16] = '>2', 184 [17] = '<2', 185 [18] = '1', 186 [19] = '1', 187 [20] = '<1', 188 [21] = '0', 189 }, foldlevels) 190 end) 191 192 it('updates folds in all windows', function() 193 screen:expect({ 194 grid = [[ 195 {1:-}// foldLevel() {{{2 | 196 {1:│}/// @return fold level at line number "lnum" in the current window. | 197 {1: }static int foldLevel(linenr_T lnum) | 198 {1:-}{ | 199 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 200 {1:2} // an undefined fold level. Otherwise update the folds first. | 201 {1:-} if (invalid_top == 0) { | 202 {1:2} checkupdate(curwin); | 203 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 204 {1:2} return prev_lnum_lvl; | 205 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 206 {1:2} return -1; | 207 {1:│} } | 208 {1:│} | 209 {1:│} // Return quickly when there is no folding at all in this window. | 210 {1:-} if (!hasAnyFolding(curwin)) { | 211 {1:2} return 0; | 212 {1:│} } | 213 {1:│} | 214 {1:│} return foldLevelWin(curwin, lnum); | 215 {1: }^} | 216 {3:[No Name] [+] }| 217 {1:-}// foldLevel() {{{2 | 218 {1:│}/// @return fold level at line number "lnum" in the current window. | 219 {1: }static int foldLevel(linenr_T lnum) | 220 {1:-}{ | 221 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 222 {1:2} // an undefined fold level. Otherwise update the folds first. | 223 {1:-} if (invalid_top == 0) { | 224 {1:2} checkupdate(curwin); | 225 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 226 {1:2} return prev_lnum_lvl; | 227 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 228 {1:2} return -1; | 229 {1:│} } | 230 {1:│} | 231 {1:│} // Return quickly when there is no folding at all in this window. | 232 {1:-} if (!hasAnyFolding(curwin)) { | 233 {1:2} return 0; | 234 {1:│} } | 235 {1:│} | 236 {1:│} return foldLevelWin(curwin, lnum); | 237 {1: }} | 238 {4:[No Name] [+] }| 239 | 240 ]], 241 }) 242 end) 243 244 it('persists wherever foldexpr is set', function() 245 command([[setlocal foldexpr=]]) 246 feed('<C-w><C-w>zx') 247 screen:expect({ 248 grid = [[ 249 {1: }// foldLevel() {{{2 | 250 {1: }/// @return fold level at line number "lnum" in the current window. | 251 {1: }static int foldLevel(linenr_T lnum) | 252 {1: }{ | 253 {1: } // While updating the folds lines between invalid_top and invalid_bot have | 254 {1: } // an undefined fold level. Otherwise update the folds first. | 255 {1: } if (invalid_top == 0) { | 256 {1: } checkupdate(curwin); | 257 {1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 258 {1: } return prev_lnum_lvl; | 259 {1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 260 {1: } return -1; | 261 {1: } } | 262 {1: } | 263 {1: } // Return quickly when there is no folding at all in this window. | 264 {1: } if (!hasAnyFolding(curwin)) { | 265 {1: } return 0; | 266 {1: } } | 267 {1: } | 268 {1: } return foldLevelWin(curwin, lnum); | 269 {1: }} | 270 {4:[No Name] [+] }| 271 {1:-}// foldLevel() {{{2 | 272 {1:│}/// @return fold level at line number "lnum" in the current window. | 273 {1: }static int foldLevel(linenr_T lnum) | 274 {1:-}{ | 275 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 276 {1:2} // an undefined fold level. Otherwise update the folds first. | 277 {1:-} if (invalid_top == 0) { | 278 {1:2} checkupdate(curwin); | 279 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 280 {1:2} return prev_lnum_lvl; | 281 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 282 {1:2} return -1; | 283 {1:│} } | 284 {1:│} | 285 {1:│} // Return quickly when there is no folding at all in this window. | 286 {1:-} if (!hasAnyFolding(curwin)) { | 287 {1:2} return 0; | 288 {1:│} } | 289 {1:│} | 290 {1:│} return foldLevelWin(curwin, lnum); | 291 {1: }^} | 292 {3:[No Name] [+] }| 293 | 294 ]], 295 }) 296 end) 297 298 it('synchronizes changed rows with their previous foldlevels', function() 299 command('1,2d') 300 screen:expect({ 301 grid = [[ 302 {1: }^static int foldLevel(linenr_T lnum) | 303 {1:-}{ | 304 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 305 {1:2} // an undefined fold level. Otherwise update the folds first. | 306 {1:-} if (invalid_top == 0) { | 307 {1:2} checkupdate(curwin); | 308 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 309 {1:2} return prev_lnum_lvl; | 310 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 311 {1:2} return -1; | 312 {1:│} } | 313 {1:│} | 314 {1:│} // Return quickly when there is no folding at all in this window. | 315 {1:-} if (!hasAnyFolding(curwin)) { | 316 {1:2} return 0; | 317 {1:│} } | 318 {1:│} | 319 {1:│} return foldLevelWin(curwin, lnum); | 320 {1: }} | 321 {2:~ }|*2 322 {3:[No Name] [+] }| 323 {1: }static int foldLevel(linenr_T lnum) | 324 {1:-}{ | 325 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 326 {1:2} // an undefined fold level. Otherwise update the folds first. | 327 {1:-} if (invalid_top == 0) { | 328 {1:2} checkupdate(curwin); | 329 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 330 {1:2} return prev_lnum_lvl; | 331 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 332 {1:2} return -1; | 333 {1:│} } | 334 {1:│} | 335 {1:│} // Return quickly when there is no folding at all in this window. | 336 {1:-} if (!hasAnyFolding(curwin)) { | 337 {1:2} return 0; | 338 {1:│} } | 339 {1:│} | 340 {1:│} return foldLevelWin(curwin, lnum); | 341 {1: }} | 342 {2:~ }|*2 343 {4:[No Name] [+] }| 344 | 345 ]], 346 }) 347 end) 348 349 it('clears folds when sole client detaches', function() 350 exec_lua(function() 351 vim.lsp.buf_detach_client(bufnr, client_id) 352 end) 353 screen:expect({ 354 grid = [[ 355 {1: }// foldLevel() {{{2 | 356 {1: }/// @return fold level at line number "lnum" in the current window. | 357 {1: }static int foldLevel(linenr_T lnum) | 358 {1: }{ | 359 {1: } // While updating the folds lines between invalid_top and invalid_bot have | 360 {1: } // an undefined fold level. Otherwise update the folds first. | 361 {1: } if (invalid_top == 0) { | 362 {1: } checkupdate(curwin); | 363 {1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 364 {1: } return prev_lnum_lvl; | 365 {1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 366 {1: } return -1; | 367 {1: } } | 368 {1: } | 369 {1: } // Return quickly when there is no folding at all in this window. | 370 {1: } if (!hasAnyFolding(curwin)) { | 371 {1: } return 0; | 372 {1: } } | 373 {1: } | 374 {1: } return foldLevelWin(curwin, lnum); | 375 {1: }^} | 376 {3:[No Name] [+] }| 377 {1: }// foldLevel() {{{2 | 378 {1: }/// @return fold level at line number "lnum" in the current window. | 379 {1: }static int foldLevel(linenr_T lnum) | 380 {1: }{ | 381 {1: } // While updating the folds lines between invalid_top and invalid_bot have | 382 {1: } // an undefined fold level. Otherwise update the folds first. | 383 {1: } if (invalid_top == 0) { | 384 {1: } checkupdate(curwin); | 385 {1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 386 {1: } return prev_lnum_lvl; | 387 {1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 388 {1: } return -1; | 389 {1: } } | 390 {1: } | 391 {1: } // Return quickly when there is no folding at all in this window. | 392 {1: } if (!hasAnyFolding(curwin)) { | 393 {1: } return 0; | 394 {1: } } | 395 {1: } | 396 {1: } return foldLevelWin(curwin, lnum); | 397 {1: }} | 398 {4:[No Name] [+] }| 399 | 400 ]], 401 }) 402 end) 403 404 it('remains valid after the client re-attaches.', function() 405 exec_lua(function() 406 vim.lsp.buf_detach_client(bufnr, client_id) 407 vim.lsp.buf_attach_client(bufnr, client_id) 408 end) 409 screen:expect({ 410 grid = [[ 411 {1:-}// foldLevel() {{{2 | 412 {1:│}/// @return fold level at line number "lnum" in the current window. | 413 {1: }static int foldLevel(linenr_T lnum) | 414 {1:-}{ | 415 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 416 {1:2} // an undefined fold level. Otherwise update the folds first. | 417 {1:-} if (invalid_top == 0) { | 418 {1:2} checkupdate(curwin); | 419 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 420 {1:2} return prev_lnum_lvl; | 421 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 422 {1:2} return -1; | 423 {1:│} } | 424 {1:│} | 425 {1:│} // Return quickly when there is no folding at all in this window. | 426 {1:-} if (!hasAnyFolding(curwin)) { | 427 {1:2} return 0; | 428 {1:│} } | 429 {1:│} | 430 {1:│} return foldLevelWin(curwin, lnum); | 431 {1: }^} | 432 {3:[No Name] [+] }| 433 {1:-}// foldLevel() {{{2 | 434 {1:│}/// @return fold level at line number "lnum" in the current window. | 435 {1: }static int foldLevel(linenr_T lnum) | 436 {1:-}{ | 437 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 438 {1:2} // an undefined fold level. Otherwise update the folds first. | 439 {1:-} if (invalid_top == 0) { | 440 {1:2} checkupdate(curwin); | 441 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 442 {1:2} return prev_lnum_lvl; | 443 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 444 {1:2} return -1; | 445 {1:│} } | 446 {1:│} | 447 {1:│} // Return quickly when there is no folding at all in this window. | 448 {1:-} if (!hasAnyFolding(curwin)) { | 449 {1:2} return 0; | 450 {1:│} } | 451 {1:│} | 452 {1:│} return foldLevelWin(curwin, lnum); | 453 {1: }} | 454 {4:[No Name] [+] }| 455 | 456 ]], 457 }) 458 end) 459 end) 460 461 describe('foldtext()', function() 462 --- @type test.functional.ui.screen 463 local screen 464 before_each(function() 465 screen = Screen.new(80, 23) 466 screen:set_default_attr_ids({ 467 [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, 468 [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, 469 [3] = { bold = true, foreground = Screen.colors.Blue1 }, 470 [4] = { bold = true, reverse = true }, 471 [5] = { reverse = true }, 472 }) 473 command( 474 [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]] 475 ) 476 end) 477 478 it('shows the first folded line if `collapsedText` does not exist', function() 479 screen:expect({ 480 grid = [[ 481 {1:-}// foldLevel() {{{2 | 482 {1:│}/// @return fold level at line number "lnum" in the current window. | 483 {1: }static int foldLevel(linenr_T lnum) | 484 {1:-}{ | 485 {1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}| 486 {1:+}{2: if (invalid_top == 0) {······················································}| 487 {1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}| 488 {1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}| 489 {1:│} } | 490 {1:│} | 491 {1:│} // Return quickly when there is no folding at all in this window. | 492 {1:+}{2: if (!hasAnyFolding(curwin)) {················································}| 493 {1:│} } | 494 {1:│} | 495 {1:│} return foldLevelWin(curwin, lnum); | 496 {1: }^} | 497 {3:~ }|*6 498 | 499 ]], 500 }) 501 end) 502 end) 503 504 describe('foldclose()', function() 505 --- @type test.functional.ui.screen 506 local screen 507 before_each(function() 508 screen = Screen.new(80, 23) 509 screen:set_default_attr_ids({ 510 [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, 511 [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, 512 [3] = { bold = true, foreground = Screen.colors.Blue1 }, 513 [4] = { bold = true, reverse = true }, 514 [5] = { reverse = true }, 515 }) 516 command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) 517 end) 518 519 it('closes all folds of one kind immediately', function() 520 exec_lua(function() 521 vim.lsp.foldclose('comment') 522 end) 523 screen:expect({ 524 grid = [[ 525 {1:+}{2:+-- 2 lines: foldLevel()······················································}| 526 {1: }static int foldLevel(linenr_T lnum) | 527 {1:-}{ | 528 {1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| 529 {1:-} if (invalid_top == 0) { | 530 {1:2} checkupdate(curwin); | 531 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 532 {1:2} return prev_lnum_lvl; | 533 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 534 {1:2} return -1; | 535 {1:│} } | 536 {1:│} | 537 {1:│} // Return quickly when there is no folding at all in this window. | 538 {1:-} if (!hasAnyFolding(curwin)) { | 539 {1:2} return 0; | 540 {1:│} } | 541 {1:│} | 542 {1:│} return foldLevelWin(curwin, lnum); | 543 {1: }^} | 544 {3:~ }|*3 545 | 546 ]], 547 }) 548 end) 549 550 it('closes the smallest fold first', function() 551 exec_lua(function() 552 vim.lsp.foldclose('region') 553 end) 554 screen:expect({ 555 grid = [[ 556 {1:-}// foldLevel() {{{2 | 557 {1:│}/// @return fold level at line number "lnum" in the current window. | 558 {1: }static int foldLevel(linenr_T lnum) | 559 {1:+}{2:+-- 17 lines: {································································}| 560 {1: }^} | 561 {3:~ }|*17 562 | 563 ]], 564 }) 565 command('4foldopen') 566 screen:expect({ 567 grid = [[ 568 {1:-}// foldLevel() {{{2 | 569 {1:│}/// @return fold level at line number "lnum" in the current window. | 570 {1: }static int foldLevel(linenr_T lnum) | 571 {1:-}{ | 572 {1:-} // While updating the folds lines between invalid_top and invalid_bot have | 573 {1:2} // an undefined fold level. Otherwise update the folds first. | 574 {1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}| 575 {1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}| 576 {1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}| 577 {1:│} } | 578 {1:│} | 579 {1:│} // Return quickly when there is no folding at all in this window. | 580 {1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}| 581 {1:│} } | 582 {1:│} | 583 {1:│} return foldLevelWin(curwin, lnum); | 584 {1: }^} | 585 {3:~ }|*5 586 | 587 ]], 588 }) 589 end) 590 591 it('is deferred when the buffer is not up-to-date', function() 592 exec_lua(function() 593 vim.lsp.foldclose('comment') 594 vim.lsp.util.buf_versions[bufnr] = 0 595 end) 596 screen:expect({ 597 grid = [[ 598 {1:+}{2:+-- 2 lines: foldLevel()······················································}| 599 {1: }static int foldLevel(linenr_T lnum) | 600 {1:-}{ | 601 {1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| 602 {1:-} if (invalid_top == 0) { | 603 {1:2} checkupdate(curwin); | 604 {1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | 605 {1:2} return prev_lnum_lvl; | 606 {1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | 607 {1:2} return -1; | 608 {1:│} } | 609 {1:│} | 610 {1:│} // Return quickly when there is no folding at all in this window. | 611 {1:-} if (!hasAnyFolding(curwin)) { | 612 {1:2} return 0; | 613 {1:│} } | 614 {1:│} | 615 {1:│} return foldLevelWin(curwin, lnum); | 616 {1: }^} | 617 {3:~ }|*3 618 | 619 ]], 620 }) 621 end) 622 end) 623 end)